Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b8bb9fb5 authored by Kweku Adams's avatar Kweku Adams
Browse files

Don't disable GPS in Doze until the device is stationary.

GPS is normally disabled when the device is Dozing. This means that GPS
is turned off in Battery Saver when quick doze is enabled. This
interferes with navigation and possibly other use cases. This changes
the behavior so that GPS is only disabled during Dozing if the device is
stationary. This will not interfere with regular Doze since the device
has to be stationary before it will enter regular Doze, and also allows
GPS to continue to work when Quick Doze activates while the user is
still carrying the device around.

Bug: 140162457
Test: atest BatterySaverLocationTest
Test: atest com.android.server.DeviceIdleControllerTest
Test: [manual] Shorten motion timeout, step into idle, check gps
provider dump after short timeout to check mDisableGpsForPowerManager=true
Merged-In: I7bc68c8a5e6744aac3a160ac14fc3375f9374d82
Change-Id: I7bc68c8a5e6744aac3a160ac14fc3375f9374d82
(cherry picked from commit 810c77d5)
parent 06a28b06
Loading
Loading
Loading
Loading
+187 −9
Original line number Diff line number Diff line
@@ -291,6 +291,7 @@ public class DeviceIdleController extends SystemService
    private boolean mLightEnabled;
    private boolean mDeepEnabled;
    private boolean mQuickDozeActivated;
    private boolean mQuickDozeActivatedWhileIdling;
    private boolean mForceIdle;
    private boolean mNetworkConnected;
    private boolean mScreenOn;
@@ -302,6 +303,10 @@ public class DeviceIdleController extends SystemService
    private boolean mHasNetworkLocation;
    private Location mLastGenericLocation;
    private Location mLastGpsLocation;

    /** Time in the elapsed realtime timebase when this listener last received a motion event. */
    private long mLastMotionEventElapsed;

    // Current locked state of the screen
    private boolean mScreenLocked;
    private int mNumBlockingConstraints = 0;
@@ -547,6 +552,9 @@ public class DeviceIdleController extends SystemService
     */
    private ArrayMap<String, Integer> mRemovedFromSystemWhitelistApps = new ArrayMap<>();

    private final ArraySet<StationaryListener> mStationaryListeners =
            new ArraySet<>();

    private static final int EVENT_NULL = 0;
    private static final int EVENT_NORMAL = 1;
    private static final int EVENT_LIGHT_IDLE = 2;
@@ -605,6 +613,21 @@ public class DeviceIdleController extends SystemService
        }
    };

    private final AlarmManager.OnAlarmListener mMotionTimeoutAlarmListener = () -> {
        synchronized (DeviceIdleController.this) {
            if (!isStationaryLocked()) {
                // If the device keeps registering motion, then the alarm should be
                // rescheduled, so this shouldn't go off until the device is stationary.
                // This case may happen in a race condition (alarm goes off right before
                // motion is detected, but handleMotionDetectedLocked is called before
                // we enter this block).
                Slog.w(TAG, "motion timeout went off and device isn't stationary");
                return;
            }
        }
        postStationaryStatusUpdated();
    };

    private final AlarmManager.OnAlarmListener mSensingTimeoutAlarmListener
            = new AlarmManager.OnAlarmListener() {
        @Override
@@ -654,12 +677,70 @@ public class DeviceIdleController extends SystemService
        }
    };

    /** Post stationary status only to this listener. */
    private void postStationaryStatus(StationaryListener listener) {
        mHandler.obtainMessage(MSG_REPORT_STATIONARY_STATUS, listener).sendToTarget();
    }

    /** Post stationary status to all registered listeners. */
    private void postStationaryStatusUpdated() {
        mHandler.sendEmptyMessage(MSG_REPORT_STATIONARY_STATUS);
    }

    private boolean isStationaryLocked() {
        final long now = mInjector.getElapsedRealtime();
        return mMotionListener.active
                // Listening for motion for long enough and last motion was long enough ago.
                && now - Math.max(mMotionListener.activatedTimeElapsed, mLastMotionEventElapsed)
                >= mConstants.MOTION_INACTIVE_TIMEOUT;
    }

    @VisibleForTesting
    void registerStationaryListener(StationaryListener listener) {
        synchronized (this) {
            if (!mStationaryListeners.add(listener)) {
                // Listener already registered.
                return;
            }
            postStationaryStatus(listener);
            if (mMotionListener.active) {
                if (!isStationaryLocked() && mStationaryListeners.size() == 1) {
                    // First listener to be registered and the device isn't stationary, so we
                    // need to register the alarm to report the device is stationary.
                    scheduleMotionTimeoutAlarmLocked();
                }
            } else {
                startMonitoringMotionLocked();
                scheduleMotionTimeoutAlarmLocked();
            }
        }
    }

    private void unregisterStationaryListener(StationaryListener listener) {
        synchronized (this) {
            if (mStationaryListeners.remove(listener) && mStationaryListeners.size() == 0
                    // Motion detection is started when transitioning from INACTIVE to IDLE_PENDING
                    // and so doesn't need to be on for ACTIVE or INACTIVE states.
                    // Motion detection isn't needed when idling due to Quick Doze.
                    && (mState == STATE_ACTIVE || mState == STATE_INACTIVE
                    || mQuickDozeActivated)) {
                maybeStopMonitoringMotionLocked();
            }
        }
    }

    @VisibleForTesting
    final class MotionListener extends TriggerEventListener
            implements SensorEventListener {

        boolean active = false;

        /**
         * Time in the elapsed realtime timebase when this listener was activated. Only valid if
         * {@link #active} is true.
         */
        long activatedTimeElapsed;

        public boolean isActive() {
            return active;
        }
@@ -667,7 +748,6 @@ public class DeviceIdleController extends SystemService
        @Override
        public void onTrigger(TriggerEvent event) {
            synchronized (DeviceIdleController.this) {
                active = false;
                motionLocked();
            }
        }
@@ -675,8 +755,6 @@ public class DeviceIdleController extends SystemService
        @Override
        public void onSensorChanged(SensorEvent event) {
            synchronized (DeviceIdleController.this) {
                mSensorManager.unregisterListener(this, mMotionSensor);
                active = false;
                motionLocked();
            }
        }
@@ -694,6 +772,7 @@ public class DeviceIdleController extends SystemService
            }
            if (success) {
                active = true;
                activatedTimeElapsed = mInjector.getElapsedRealtime();
            } else {
                Slog.e(TAG, "Unable to register for " + mMotionSensor);
            }
@@ -1307,6 +1386,8 @@ public class DeviceIdleController extends SystemService
    private static final int MSG_SEND_CONSTRAINT_MONITORING = 10;
    private static final int MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR = 11;
    private static final int MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR = 12;
    @VisibleForTesting
    static final int MSG_REPORT_STATIONARY_STATUS = 13;

    final class MyHandler extends Handler {
        MyHandler(Looper looper) {
@@ -1443,6 +1524,30 @@ public class DeviceIdleController extends SystemService
                    updatePreIdleFactor();
                    maybeDoImmediateMaintenance();
                } break;
                case MSG_REPORT_STATIONARY_STATUS: {
                    final StationaryListener newListener = (StationaryListener) msg.obj;
                    final StationaryListener[] listeners;
                    final boolean isStationary;
                    synchronized (DeviceIdleController.this) {
                        isStationary = isStationaryLocked();
                        if (newListener == null) {
                            // Only notify all listeners if we aren't directing to one listener.
                            listeners = mStationaryListeners.toArray(
                                    new StationaryListener[mStationaryListeners.size()]);
                        } else {
                            listeners = null;
                        }
                    }
                    if (listeners != null) {
                        for (StationaryListener listener : listeners) {
                            listener.onDeviceStationaryChanged(isStationary);
                        }
                    }
                    if (newListener != null) {
                        newListener.onDeviceStationaryChanged(isStationary);
                    }
                }
                break;
            }
        }
    }
@@ -1628,6 +1733,19 @@ public class DeviceIdleController extends SystemService
        }
    }

    /**
     * Listener to be notified when DeviceIdleController determines that the device has
     * moved or is stationary.
     */
    public interface StationaryListener {
        /**
         * Called when DeviceIdleController has determined that the device is stationary or moving.
         *
         * @param isStationary true if the device is stationary, false otherwise
         */
        void onDeviceStationaryChanged(boolean isStationary);
    }

    public class LocalService {
        public void onConstraintStateChanged(IDeviceIdleConstraint constraint, boolean active) {
            synchronized (DeviceIdleController.this) {
@@ -1693,6 +1811,24 @@ public class DeviceIdleController extends SystemService
        public int[] getPowerSaveTempWhitelistAppIds() {
            return DeviceIdleController.this.getAppIdTempWhitelistInternal();
        }

        /**
         * Registers a listener that will be notified when the system has detected that the device
         * is
         * stationary or in motion.
         */
        public void registerStationaryListener(StationaryListener listener) {
            DeviceIdleController.this.registerStationaryListener(listener);
        }

        /**
         * Unregisters a registered stationary listener from being notified when the system has
         * detected
         * that the device is stationary or in motion.
         */
        public void unregisterStationaryListener(StationaryListener listener) {
            DeviceIdleController.this.unregisterStationaryListener(listener);
        }
    }

    static class Injector {
@@ -1734,6 +1870,11 @@ public class DeviceIdleController extends SystemService
            return mConstants;
        }

        /** Returns the current elapsed realtime in milliseconds. */
        long getElapsedRealtime() {
            return SystemClock.elapsedRealtime();
        }

        LocationManager getLocationManager() {
            if (mLocationManager == null) {
                mLocationManager = mContext.getSystemService(LocationManager.class);
@@ -2601,6 +2742,8 @@ public class DeviceIdleController extends SystemService
    void updateQuickDozeFlagLocked(boolean enabled) {
        if (DEBUG) Slog.i(TAG, "updateQuickDozeFlagLocked: enabled=" + enabled);
        mQuickDozeActivated = enabled;
        mQuickDozeActivatedWhileIdling =
                mQuickDozeActivated && (mState == STATE_IDLE || mState == STATE_IDLE_MAINTENANCE);
        if (enabled) {
            // If Quick Doze is enabled, see if we should go straight into it.
            becomeInactiveIfAppropriateLocked();
@@ -2767,10 +2910,11 @@ public class DeviceIdleController extends SystemService
        mNextIdleDelay = 0;
        mNextLightIdleDelay = 0;
        mIdleStartTime = 0;
        mQuickDozeActivatedWhileIdling = false;
        cancelAlarmLocked();
        cancelSensingTimeoutAlarmLocked();
        cancelLocatingLocked();
        stopMonitoringMotionLocked();
        maybeStopMonitoringMotionLocked();
        mAnyMotionDetector.stop();
        updateActiveConstraintsLocked();
    }
@@ -3270,11 +3414,23 @@ public class DeviceIdleController extends SystemService

    void motionLocked() {
        if (DEBUG) Slog.d(TAG, "motionLocked()");
        // The motion sensor will have been disabled at this point
        mLastMotionEventElapsed = mInjector.getElapsedRealtime();
        handleMotionDetectedLocked(mConstants.MOTION_INACTIVE_TIMEOUT, "motion");
    }

    void handleMotionDetectedLocked(long timeout, String type) {
        if (mStationaryListeners.size() > 0) {
            postStationaryStatusUpdated();
            scheduleMotionTimeoutAlarmLocked();
        }
        if (mQuickDozeActivated && !mQuickDozeActivatedWhileIdling) {
            // Don't exit idle due to motion if quick doze is enabled.
            // However, if the device started idling due to the normal progression (going through
            // all the states) and then had quick doze activated, come out briefly on motion so the
            // user can get slightly fresher content.
            return;
        }
        maybeStopMonitoringMotionLocked();
        // The device is not yet active, so we want to go back to the pending idle
        // state to wait again for no motion.  Note that we only monitor for motion
        // after moving out of the inactive state, so no need to worry about that.
@@ -3326,10 +3482,15 @@ public class DeviceIdleController extends SystemService
        }
    }

    void stopMonitoringMotionLocked() {
        if (DEBUG) Slog.d(TAG, "stopMonitoringMotionLocked()");
        if (mMotionSensor != null && mMotionListener.active) {
    /**
     * Stops motion monitoring. Will not stop monitoring if there are registered stationary
     * listeners.
     */
    private void maybeStopMonitoringMotionLocked() {
        if (DEBUG) Slog.d(TAG, "maybeStopMonitoringMotionLocked()");
        if (mMotionSensor != null && mMotionListener.active && mStationaryListeners.size() == 0) {
            mMotionListener.unregisterLocked();
            cancelMotionTimeoutAlarmLocked();
        }
    }

@@ -3356,6 +3517,10 @@ public class DeviceIdleController extends SystemService
        }
    }

    private void cancelMotionTimeoutAlarmLocked() {
        mAlarmManager.cancel(mMotionTimeoutAlarmListener);
    }

    void cancelSensingTimeoutAlarmLocked() {
        if (mNextSensingTimeoutAlarmTime != 0) {
            mNextSensingTimeoutAlarmTime = 0;
@@ -3402,6 +3567,14 @@ public class DeviceIdleController extends SystemService
                mNextLightAlarmTime, "DeviceIdleController.light", mLightAlarmListener, mHandler);
    }

    private void scheduleMotionTimeoutAlarmLocked() {
        if (DEBUG) Slog.d(TAG, "scheduleMotionAlarmLocked");
        long nextMotionTimeoutAlarmTime =
                mInjector.getElapsedRealtime() + mConstants.MOTION_INACTIVE_TIMEOUT;
        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextMotionTimeoutAlarmTime,
                "DeviceIdleController.motion", mMotionTimeoutAlarmListener, mHandler);
    }

    void scheduleSensingTimeoutAlarmLocked(long delay) {
        if (DEBUG) Slog.d(TAG, "scheduleSensingAlarmLocked(" + delay + ")");
        mNextSensingTimeoutAlarmTime = SystemClock.elapsedRealtime() + delay;
@@ -4322,9 +4495,14 @@ public class DeviceIdleController extends SystemService
                }
                pw.println("  }");
            }
            if (mUseMotionSensor) {
            if (mUseMotionSensor || mStationaryListeners.size() > 0) {
                pw.print("  mMotionActive="); pw.println(mMotionListener.active);
                pw.print("  mNotMoving="); pw.println(mNotMoving);
                pw.print("  mMotionListener.activatedTimeElapsed=");
                pw.println(mMotionListener.activatedTimeElapsed);
                pw.print("  mLastMotionEventElapsed="); pw.println(mLastMotionEventElapsed);
                pw.print("  "); pw.print(mStationaryListeners.size());
                pw.println(" stationary listeners registered");
            }
            pw.print("  mLocating="); pw.print(mLocating); pw.print(" mHasGps=");
                    pw.print(mHasGps); pw.print(" mHasNetwork=");
+37 −6
Original line number Diff line number Diff line
@@ -75,6 +75,8 @@ import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
import com.android.internal.location.gnssmetrics.GnssMetrics;
import com.android.internal.telephony.TelephonyIntents;
import com.android.server.DeviceIdleController;
import com.android.server.LocalServices;
import com.android.server.location.GnssSatelliteBlacklistHelper.GnssSatelliteBlacklistCallback;
import com.android.server.location.NtpTimeHelper.InjectNtpTimeCallback;

@@ -178,6 +180,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
    private static final int AGPS_SUPL_MODE_MSA = 0x02;
    private static final int AGPS_SUPL_MODE_MSB = 0x01;

    private static final int UPDATE_LOW_POWER_MODE = 1;
    private static final int SET_REQUEST = 3;
    private static final int INJECT_NTP_TIME = 5;
    // PSDS stands for Predicted Satellite Data Service
@@ -370,6 +373,12 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
    // True if gps should be disabled because of PowerManager controls
    private boolean mDisableGpsForPowerManager = false;

    /**
     * True if the device idle controller has determined that the device is stationary. This is only
     * updated when the device enters idle mode.
     */
    private volatile boolean mIsDeviceStationary = false;

    /**
     * Properties loaded from PROPERTIES_FILE.
     * It must be accessed only inside {@link #mHandler}.
@@ -462,6 +471,15 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
    public GnssNavigationMessageProvider getGnssNavigationMessageProvider() {
        return mGnssNavigationMessageProvider;
    }

    private final DeviceIdleController.StationaryListener mDeviceIdleStationaryListener =
            isStationary -> {
                mIsDeviceStationary = isStationary;
                // Call updateLowPowerMode on handler thread so it's always called from the same
                // thread.
                mHandler.sendEmptyMessage(UPDATE_LOW_POWER_MODE);
            };

    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
@@ -478,11 +496,22 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
                case ALARM_TIMEOUT:
                    hibernate();
                    break;
                case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED:
                case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
                    DeviceIdleController.LocalService deviceIdleService = LocalServices.getService(
                            DeviceIdleController.LocalService.class);
                    if (mPowerManager.isDeviceIdleMode()) {
                        deviceIdleService.registerStationaryListener(mDeviceIdleStationaryListener);
                    } else {
                        deviceIdleService.unregisterStationaryListener(
                                mDeviceIdleStationaryListener);
                    }
                    // Intentional fall-through.
                case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED:
                case Intent.ACTION_SCREEN_OFF:
                case Intent.ACTION_SCREEN_ON:
                    updateLowPowerMode();
                    // Call updateLowPowerMode on handler thread so it's always called from the
                    // same thread.
                    mHandler.sendEmptyMessage(UPDATE_LOW_POWER_MODE);
                    break;
                case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
                case TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
@@ -540,10 +569,9 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
    }

    private void updateLowPowerMode() {
        // Disable GPS if we are in device idle mode.
        boolean disableGpsForPowerManager = mPowerManager.isDeviceIdleMode();
        final PowerSaveState result =
                mPowerManager.getPowerSaveState(ServiceType.LOCATION);
        // Disable GPS if we are in device idle mode and the device is stationary.
        boolean disableGpsForPowerManager = mPowerManager.isDeviceIdleMode() && mIsDeviceStationary;
        final PowerSaveState result = mPowerManager.getPowerSaveState(ServiceType.LOCATION);
        switch (result.locationMode) {
            case PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
            case PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
@@ -2048,6 +2076,9 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
                case REPORT_SV_STATUS:
                    handleReportSvStatus((SvStatusInfo) msg.obj);
                    break;
                case UPDATE_LOW_POWER_MODE:
                    updateLowPowerMode();
                    break;
            }
            if (msg.arg2 == 1) {
                // wakelock was taken for this message, release it
+125 −5

File changed.

Preview size limit exceeded, changes collapsed.