/* * Copyright (C) 2014 AChep@xda <artemchep@gmail.com> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ package com.achep.acdisplay.services.activemode; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.hardware.Sensor; import android.hardware.SensorManager; import android.os.PowerManager; import android.os.SystemClock; import android.support.annotation.NonNull; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import com.achep.acdisplay.App; import com.achep.acdisplay.Atomic; import com.achep.acdisplay.Config; import com.achep.acdisplay.Presenter; import com.achep.acdisplay.R; import com.achep.acdisplay.notifications.NotificationPresenter; import com.achep.acdisplay.notifications.OpenNotification; import com.achep.acdisplay.services.BathService; import com.achep.acdisplay.services.Switch; import com.achep.acdisplay.services.SwitchService; import com.achep.acdisplay.services.activemode.sensors.AccelerometerSensor; import com.achep.acdisplay.services.activemode.sensors.GyroscopeSensor; import com.achep.acdisplay.services.activemode.sensors.ProximitySensor; import com.achep.acdisplay.services.switches.BatteryOutSwitch; import com.achep.acdisplay.services.switches.InactiveTimeSwitch; import com.achep.acdisplay.services.switches.NoNotifiesSwitch; import com.achep.acdisplay.services.switches.ScreenOffSwitch; import com.achep.base.AppHeap; import com.achep.base.content.ConfigBase; import com.achep.base.tests.Check; import com.achep.base.utils.power.PowerUtils; import static com.achep.base.Build.DEBUG; /** * Service that turns on AcDisplay exactly when it's needed. * * @author Artem Chepurnoy * @see com.achep.acdisplay.services.activemode.ActiveModeSensor */ public class ActiveModeService extends SwitchService implements NotificationPresenter.OnNotificationListChangedListener, ActiveModeSensor.Callback { private static final String TAG = "ActiveModeService"; private static final String WAKE_LOCK_TAG = "Consuming sensors"; private ActiveModeSensor[] mSensors; private long mConsumingPingTimestamp; private PowerManager.WakeLock mWakeLock; private final BroadcastReceiver mLocalReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { case App.ACTION_INTERNAL_PING_SENSORS: pingConsumingSensors(); break; } } }; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { case Intent.ACTION_BATTERY_CHANGED: mPluggedAtomic.react(PowerUtils.isPlugged(intent)); break; } } }; private final Atomic.Callback mPluggedAtomicCallback = new Atomic.Callback() { @Override public void onStart(Object... objects) { if (DEBUG) Log.d(TAG, "Plugged: Start the consuming sensors"); for (ActiveModeSensor ams : mSensors) { Check.getInstance().isTrue(ams.isAttached()); if (ams instanceof ActiveModeSensor.Consuming) { ActiveModeSensor.Consuming sensor = (ActiveModeSensor.Consuming) ams; sensor.start(); } } } @Override public void onStop(Object... objects) { if (DEBUG) Log.d(TAG, "Unplugged: Stop the consuming sensors"); for (ActiveModeSensor ams : mSensors) { Check.getInstance().isTrue(ams.isAttached()); if (ams instanceof ActiveModeSensor.Consuming) { ActiveModeSensor.Consuming sensor = (ActiveModeSensor.Consuming) ams; sensor.stop(); } } } }; private final Atomic mPluggedAtomic = new Atomic(mPluggedAtomicCallback, TAG + ":Plugged"); private boolean mActiveChargingEnabled; /** * Starts or stops this service as required by settings and device's state. */ public static void handleState(@NonNull Context context) { Config config = Config.getInstance(); boolean onlyWhileChangingOption = !config.isEnabledOnlyWhileCharging() || PowerUtils.isPlugged(context); if (config.isEnabled() && config.isActiveModeEnabled() && onlyWhileChangingOption) { BathService.startService(context, ActiveModeService.class); } else { BathService.stopService(context, ActiveModeService.class); } } public static boolean isSupported(@NonNull Context context) { SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); return sensorManager.getSensorList(Sensor.TYPE_PROXIMITY).size() > 0; } /** * Builds the array of supported {@link ActiveModeSensor sensors}. * * @return The array of supported {@link ActiveModeSensor sensors}. * @see ActiveModeSensor */ @NonNull public static ActiveModeSensor[] buildAvailableSensorsList(@NonNull Context context) { SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); ActiveModeSensor[] sensors = new ActiveModeSensor[]{ // all available sensors AccelerometerSensor.getInstance(), GyroscopeSensor.getInstance(), ProximitySensor.getInstance() }; // Count the number of supported sensors, and // mark unsupported. int count = sensors.length; boolean[] supportList = new boolean[sensors.length]; for (int i = 0; i < sensors.length; i++) { supportList[i] = sensors[i].isSupported(sensorManager); if (!supportList[i]) { count--; } } // Create the list of proven sensors. ActiveModeSensor[] sensorsSupported = new ActiveModeSensor[count]; for (int i = 0, j = 0; i < sensors.length; i++) { if (supportList[i]) { sensorsSupported[j++] = sensors[i]; } } return sensorsSupported; } @NonNull @Override public Switch[] onBuildSwitches() { Config config = Config.getInstance(); ConfigBase.Option noNotifies = config.getOption(Config.KEY_ACTIVE_MODE_WITHOUT_NOTIFICATIONS); ConfigBase.Option respectIt = config.getOption(Config.KEY_ACTIVE_MODE_RESPECT_INACTIVE_TIME); ConfigBase.Option batteryOut = config.getOption(Config.KEY_ACTIVE_MODE_DISABLE_ON_LOW_BATTERY); return new Switch[]{ new ScreenOffSwitch(getContext(), this), new InactiveTimeSwitch(getContext(), this, respectIt), new NoNotifiesSwitch(getContext(), this, noNotifies, true), new BatteryOutSwitch(getContext(), this, batteryOut, false), }; } @Override public void onCreate() { Context context = getContext(); mSensors = buildAvailableSensorsList(context); super.onCreate(); IntentFilter filter = new IntentFilter(); filter.addAction(App.ACTION_INTERNAL_PING_SENSORS); LocalBroadcastManager.getInstance(context).registerReceiver(mLocalReceiver, filter); NotificationPresenter.getInstance().registerListener(this); } @Override public void onDestroy() { LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mLocalReceiver); NotificationPresenter.getInstance().unregisterListener(this); super.onDestroy(); // Watch for the leaks AppHeap.getRefWatcher().watch(this); } @Override public String getLabel() { return getContext().getString(R.string.service_bath_active_mode); } @Override public void onNotificationListChanged(@NonNull NotificationPresenter np, OpenNotification osbn, int event, boolean isLastEventInSequence) { if (Config.getInstance().isNotifyWakingUp()) { // Notification will wake up device without // any sensors' callback. return; } switch (event) { case NotificationPresenter.EVENT_CHANGED: case NotificationPresenter.EVENT_POSTED: pingConsumingSensors(); break; } } @Override public void onStart(Object... objects) { if (DEBUG) Log.d(TAG, "Starting listening to sensors."); Context context = getContext(); SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); for (ActiveModeSensor sensor : mSensors) { sensor.registerCallback(this); sensor.onAttached(sensorManager, context); } mActiveChargingEnabled = Config.getInstance().isActiveModeActiveChargingEnabled(); if (mActiveChargingEnabled) { mPluggedAtomic.react(PowerUtils.isPlugged(context)); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); context.registerReceiver(mReceiver, intentFilter); } } @Override public void onStop(Object... objects) { if (DEBUG) Log.d(TAG, "Stopping listening to sensors."); if (mActiveChargingEnabled) { Context context = getContext(); context.unregisterReceiver(mReceiver); mPluggedAtomic.stop(); } for (ActiveModeSensor sensor : mSensors) { sensor.onDetached(); sensor.unregisterCallback(this); } releaseWakeLock(); } /** * {@inheritDoc} */ public void pingConsumingSensors() { mConsumingPingTimestamp = SystemClock.elapsedRealtime(); pingConsumingSensorsInternal(); } private void pingConsumingSensorsInternal() { // Find maximum remaining time. int remainingTime = -1; for (ActiveModeSensor ams : mSensors) { if (ams.isAttached() && ams instanceof ActiveModeSensor.Consuming) { ActiveModeSensor.Consuming sensor = (ActiveModeSensor.Consuming) ams; remainingTime = Math.max(remainingTime, sensor.getRemainingTime()); } } long now = SystemClock.elapsedRealtime(); int delta = (int) (now - mConsumingPingTimestamp); remainingTime -= delta; if (remainingTime < 0) { return; // Too late } // Acquire wake lock to be sure that sensors will be fine. releaseWakeLock(); PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG); mWakeLock.acquire(remainingTime); // Ping sensors for (ActiveModeSensor ams : mSensors) { if (ams.isAttached() && ams instanceof ActiveModeSensor.Consuming) { ActiveModeSensor.Consuming sensor = (ActiveModeSensor.Consuming) ams; int sensorRemainingTime = sensor.getRemainingTime() - delta; if (sensorRemainingTime > 0) { sensor.ping(sensorRemainingTime); } } } } private void releaseWakeLock() { if (mWakeLock != null && mWakeLock.isHeld()) { mWakeLock.release(); mWakeLock = null; } } @Override public void onWakeRequested(@NonNull ActiveModeSensor sensor) { Presenter.getInstance().tryStartGuiCauseSensor(getContext()); } }