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

Commit 1c439ad4 authored by Robert Horvath's avatar Robert Horvath
Browse files

Add LowPowerStandby allowed reason: Ongoing call

When ALLOWED_REASON_ONGOING_CALL is set in the Low Power Standby policy,
apps with an active Foreground Service of type "phoneCall" are exempt
from Low Power Standby restrictions.

Bug: 234002812
Test: atest LowPowerStandbyControllerTest LowPowerStandbyTest
Change-Id: I9ad99130e31c5bbb259dc3a733d57cb413c23df2
parent 72c359a5
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -33437,6 +33437,7 @@ package android.os {
    field public static final int LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF = 1; // 0x1
    field public static final int LOCATION_MODE_NO_CHANGE = 0; // 0x0
    field public static final int LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF = 4; // 0x4
    field public static final int LOW_POWER_STANDBY_ALLOWED_REASON_ONGOING_CALL = 4; // 0x4
    field public static final int LOW_POWER_STANDBY_ALLOWED_REASON_TEMP_POWER_SAVE_ALLOWLIST = 2; // 0x2
    field public static final int LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION = 1; // 0x1
    field public static final String LOW_POWER_STANDBY_FEATURE_WAKE_ON_LAN = "com.android.lowpowerstandby.WAKE_ON_LAN";
+14 −0
Original line number Diff line number Diff line
@@ -3057,6 +3057,7 @@ public final class PowerManager {
    @IntDef(prefix = { "LOW_POWER_STANDBY_ALLOWED_REASON_" }, flag = true, value = {
            LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION,
            LOW_POWER_STANDBY_ALLOWED_REASON_TEMP_POWER_SAVE_ALLOWLIST,
            LOW_POWER_STANDBY_ALLOWED_REASON_ONGOING_CALL,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface LowPowerStandbyAllowedReason {
@@ -3076,6 +3077,15 @@ public final class PowerManager {
     */
    public static final int LOW_POWER_STANDBY_ALLOWED_REASON_TEMP_POWER_SAVE_ALLOWLIST = 1 << 1;

    /**
     * Exempts apps with ongoing calls.
     *
     * <p>This includes apps with foreground services of type "phoneCall".
     *
     * @see #isAllowedInLowPowerStandby(int)
     */
    public static final int LOW_POWER_STANDBY_ALLOWED_REASON_ONGOING_CALL = 1 << 2;

    /** @hide */
    public static String lowPowerStandbyAllowedReasonsToString(
            @LowPowerStandbyAllowedReason int allowedReasons) {
@@ -3088,6 +3098,10 @@ public final class PowerManager {
            allowedStrings.add("ALLOWED_REASON_TEMP_POWER_SAVE_ALLOWLIST");
            allowedReasons &= ~LOW_POWER_STANDBY_ALLOWED_REASON_TEMP_POWER_SAVE_ALLOWLIST;
        }
        if ((allowedReasons & LOW_POWER_STANDBY_ALLOWED_REASON_ONGOING_CALL) != 0) {
            allowedStrings.add("ALLOWED_REASON_ONGOING_CALL");
            allowedReasons &= ~LOW_POWER_STANDBY_ALLOWED_REASON_ONGOING_CALL;
        }
        if (allowedReasons != 0) {
            allowedStrings.add(String.valueOf(allowedReasons));
        }
+101 −3
Original line number Diff line number Diff line
@@ -16,19 +16,25 @@

package com.android.server.power;

import static android.os.PowerManager.LOW_POWER_STANDBY_ALLOWED_REASON_ONGOING_CALL;
import static android.os.PowerManager.LOW_POWER_STANDBY_ALLOWED_REASON_TEMP_POWER_SAVE_ALLOWLIST;
import static android.os.PowerManager.lowPowerStandbyAllowedReasonsToString;

import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
import android.app.IActivityManager;
import android.app.IForegroundServiceObserver;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.net.Uri;
@@ -53,6 +59,7 @@ import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
@@ -82,6 +89,7 @@ import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Supplier;

/**
 * Controls Low Power Standby state.
@@ -115,7 +123,8 @@ public class LowPowerStandbyController {
    private static final int MSG_NOTIFY_ACTIVE_CHANGED = 1;
    private static final int MSG_NOTIFY_ALLOWLIST_CHANGED = 2;
    private static final int MSG_NOTIFY_POLICY_CHANGED = 3;
    private static final int MSG_NOTIFY_STANDBY_PORTS_CHANGED = 4;
    private static final int MSG_FOREGROUND_SERVICE_STATE_CHANGED = 4;
    private static final int MSG_NOTIFY_STANDBY_PORTS_CHANGED = 5;

    private static final String TAG_ROOT = "low-power-standby-policy";
    private static final String TAG_IDENTIFIER = "identifier";
@@ -127,6 +136,7 @@ public class LowPowerStandbyController {
    private final Handler mHandler;
    private final SettingsObserver mSettingsObserver;
    private final DeviceConfigWrapper mDeviceConfig;
    private final Supplier<IActivityManager> mActivityManager;
    private final File mPolicyFile;
    private final Object mLock = new Object();

@@ -160,6 +170,7 @@ public class LowPowerStandbyController {
    };
    private final TempAllowlistChangeListener mTempAllowlistChangeListener =
            new TempAllowlistChangeListener();
    private final PhoneCallServiceTracker mPhoneCallServiceTracker = new PhoneCallServiceTracker();

    private final BroadcastReceiver mPackageBroadcastReceiver = new BroadcastReceiver() {
        @Override
@@ -243,6 +254,7 @@ public class LowPowerStandbyController {
    private AlarmManager mAlarmManager;
    @GuardedBy("mLock")
    private PowerManager mPowerManager;
    private ActivityManagerInternal mActivityManagerInternal;
    @GuardedBy("mLock")
    private boolean mSupportedConfig;
    @GuardedBy("mLock")
@@ -314,18 +326,20 @@ public class LowPowerStandbyController {

    public LowPowerStandbyController(Context context, Looper looper) {
        this(context, looper, SystemClock::elapsedRealtime,
                new DeviceConfigWrapper(),
                new DeviceConfigWrapper(), () -> ActivityManager.getService(),
                new File(Environment.getDataSystemDirectory(), "low_power_standby_policy.xml"));
    }

    @VisibleForTesting
    LowPowerStandbyController(Context context, Looper looper, Clock clock,
            DeviceConfigWrapper deviceConfig, File policyFile) {
            DeviceConfigWrapper deviceConfig, Supplier<IActivityManager> activityManager,
            File policyFile) {
        mContext = context;
        mHandler = new LowPowerStandbyHandler(looper);
        mClock = clock;
        mSettingsObserver = new SettingsObserver(mHandler);
        mDeviceConfig = deviceConfig;
        mActivityManager = activityManager;
        mPolicyFile = policyFile;
    }

@@ -343,6 +357,7 @@ public class LowPowerStandbyController {

            mAlarmManager = mContext.getSystemService(AlarmManager.class);
            mPowerManager = mContext.getSystemService(PowerManager.class);
            mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);

            mStandbyTimeoutConfig = resources.getInteger(
                    R.integer.config_lowPowerStandbyNonInteractiveTimeout);
@@ -707,6 +722,8 @@ public class LowPowerStandbyController {

        PowerAllowlistInternal pai = LocalServices.getService(PowerAllowlistInternal.class);
        pai.registerTempAllowlistChangeListener(mTempAllowlistChangeListener);

        mPhoneCallServiceTracker.register();
    }

    private void unregisterListeners() {
@@ -1142,6 +1159,10 @@ public class LowPowerStandbyController {
                case MSG_NOTIFY_POLICY_CHANGED:
                    notifyPolicyChanged((LowPowerStandbyPolicy) msg.obj);
                    break;
                case MSG_FOREGROUND_SERVICE_STATE_CHANGED:
                    final int uid = msg.arg1;
                    mPhoneCallServiceTracker.foregroundServiceStateChanged(uid);
                    break;
                case MSG_NOTIFY_STANDBY_PORTS_CHANGED:
                    notifyStandbyPortsChanged();
                    break;
@@ -1406,4 +1427,81 @@ public class LowPowerStandbyController {
                    LOW_POWER_STANDBY_ALLOWED_REASON_TEMP_POWER_SAVE_ALLOWLIST);
        }
    }

    final class PhoneCallServiceTracker extends IForegroundServiceObserver.Stub {
        private boolean mRegistered = false;
        private final SparseBooleanArray mUidsWithPhoneCallService = new SparseBooleanArray();

        public void register() {
            if (mRegistered) {
                return;
            }
            try {
                mActivityManager.get().registerForegroundServiceObserver(this);
                mRegistered = true;
            } catch (RemoteException e) {
                // call within system server
            }
        }

        @Override
        public void onForegroundStateChanged(IBinder serviceToken, String packageName,
                int userId, boolean isForeground) {
            try {
                final long now = mClock.elapsedRealtime();
                final int uid = mContext.getPackageManager()
                        .getPackageUidAsUser(packageName, userId);
                final Message message =
                        mHandler.obtainMessage(MSG_FOREGROUND_SERVICE_STATE_CHANGED, uid, 0);
                mHandler.sendMessageAtTime(message, now);
            } catch (PackageManager.NameNotFoundException e) {
                if (DEBUG) {
                    Slog.d(TAG, "onForegroundStateChanged: Unknown package: " + packageName
                            + ", userId=" + userId);
                }
            }
        }

        public void foregroundServiceStateChanged(int uid) {
            if (DEBUG) {
                Slog.d(TAG, "foregroundServiceStateChanged: uid=" + uid);
            }

            final boolean hadPhoneCallService = mUidsWithPhoneCallService.get(uid);
            final boolean hasPhoneCallService =
                    mActivityManagerInternal.hasRunningForegroundService(uid,
                            ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL);

            if (DEBUG) {
                Slog.d(TAG, "uid=" + uid + ", hasPhoneCallService=" + hasPhoneCallService
                        + ", hadPhoneCallService=" + hadPhoneCallService);
            }

            if (hasPhoneCallService == hadPhoneCallService) {
                return;
            }

            if (hasPhoneCallService) {
                mUidsWithPhoneCallService.append(uid, true);
                uidStartedPhoneCallService(uid);
            } else {
                mUidsWithPhoneCallService.delete(uid);
                uidStoppedPhoneCallService(uid);
            }
        }

        private void uidStartedPhoneCallService(int uid) {
            if (DEBUG) {
                Slog.d(TAG, "FGS of type phoneCall started: uid=" + uid);
            }
            addToAllowlistInternal(uid, LOW_POWER_STANDBY_ALLOWED_REASON_ONGOING_CALL);
        }

        private void uidStoppedPhoneCallService(int uid) {
            if (DEBUG) {
                Slog.d(TAG, "FGSs of type phoneCall stopped: uid=" + uid);
            }
            removeFromAllowlistInternal(uid, LOW_POWER_STANDBY_ALLOWED_REASON_ONGOING_CALL);
        }
    }
}
+55 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.power;

import static android.os.PowerManager.LOW_POWER_STANDBY_ALLOWED_REASON_ONGOING_CALL;
import static android.os.PowerManager.LOW_POWER_STANDBY_ALLOWED_REASON_TEMP_POWER_SAVE_ALLOWLIST;
import static android.os.PowerManager.LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION;
import static android.os.PowerManager.LOW_POWER_STANDBY_FEATURE_WAKE_ON_LAN;
@@ -44,10 +45,14 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
import android.app.IActivityManager;
import android.app.IForegroundServiceObserver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Binder;
@@ -118,6 +123,8 @@ public class LowPowerStandbyControllerTest {
    @Mock
    private DeviceConfigWrapper mDeviceConfigWrapperMock;
    @Mock
    private IActivityManager mIActivityManagerMock;
    @Mock
    private AlarmManager mAlarmManagerMock;
    @Mock
    private PackageManager mPackageManagerMock;
@@ -131,6 +138,8 @@ public class LowPowerStandbyControllerTest {
    private NetworkPolicyManagerInternal mNetworkPolicyManagerInternalMock;
    @Mock
    private PowerAllowlistInternal mPowerAllowlistInternalMock;
    @Mock
    private ActivityManagerInternal mActivityManagerInternalMock;

    @Before
    public void setUp() throws Exception {
@@ -145,6 +154,7 @@ public class LowPowerStandbyControllerTest {
        addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
        addLocalServiceMock(NetworkPolicyManagerInternal.class, mNetworkPolicyManagerInternalMock);
        addLocalServiceMock(PowerAllowlistInternal.class, mPowerAllowlistInternalMock);
        addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);

        when(mIPowerManagerMock.isInteractive()).thenReturn(true);

@@ -171,13 +181,18 @@ public class LowPowerStandbyControllerTest {
                UserHandle.of(USER_ID_1), UserHandle.of(USER_ID_2)));
        when(mPackageManagerMock.getPackageUid(eq(TEST_PKG1), any())).thenReturn(TEST_PKG1_APP_ID);
        when(mPackageManagerMock.getPackageUid(eq(TEST_PKG2), any())).thenReturn(TEST_PKG2_APP_ID);
        when(mPackageManagerMock.getPackageUidAsUser(eq(TEST_PKG1), eq(USER_ID_1)))
                .thenReturn(TEST_PKG1_APP_ID);
        when(mPackageManagerMock.getPackageUidAsUser(eq(TEST_PKG2), eq(USER_ID_1)))
                .thenReturn(TEST_PKG2_APP_ID);

        mClock = new OffsettableClock.Stopped();
        mTestLooper = new TestLooper(mClock::now);

        mTestPolicyFile = new File(mContextSpy.getCacheDir(), "lps_policy.xml");
        mController = new LowPowerStandbyController(mContextSpy, mTestLooper.getLooper(),
                () -> mClock.now(), mDeviceConfigWrapperMock, mTestPolicyFile);
                () -> mClock.now(), mDeviceConfigWrapperMock, () -> mIActivityManagerMock,
                mTestPolicyFile);
    }

    @After
@@ -186,6 +201,8 @@ public class LowPowerStandbyControllerTest {
        LocalServices.removeServiceForTest(LowPowerStandbyControllerInternal.class);
        LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
        LocalServices.removeServiceForTest(PowerAllowlistInternal.class);
        LocalServices.removeServiceForTest(ActivityManagerInternal.class);

        mTestPolicyFile.delete();
    }

@@ -713,6 +730,43 @@ public class LowPowerStandbyControllerTest {
        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[0]);
    }

    @Test
    public void testAllowReason_ongoingPhoneCallService() throws Exception {
        mController.systemReady();
        mController.setEnabled(true);
        mController.setPolicy(policyWithAllowedReasons(
                LOW_POWER_STANDBY_ALLOWED_REASON_ONGOING_CALL));
        mTestLooper.dispatchAll();

        ArgumentCaptor<IForegroundServiceObserver> fgsObserverCapt =
                ArgumentCaptor.forClass(IForegroundServiceObserver.class);
        verify(mIActivityManagerMock).registerForegroundServiceObserver(fgsObserverCapt.capture());
        IForegroundServiceObserver fgsObserver = fgsObserverCapt.getValue();

        when(mActivityManagerInternalMock.hasRunningForegroundService(eq(TEST_PKG1_APP_ID),
                eq(ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL))).thenReturn(true);
        fgsObserver.onForegroundStateChanged(null, TEST_PKG1, USER_ID_1, true);
        mTestLooper.dispatchAll();
        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{TEST_PKG1_APP_ID});

        when(mActivityManagerInternalMock.hasRunningForegroundService(eq(TEST_PKG2_APP_ID),
                eq(ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL))).thenReturn(true);
        fgsObserver.onForegroundStateChanged(null, TEST_PKG2, USER_ID_1, true);
        mTestLooper.dispatchAll();
        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(
                new int[]{TEST_PKG1_APP_ID, TEST_PKG2_APP_ID});

        when(mActivityManagerInternalMock.hasRunningForegroundService(eq(TEST_PKG1_APP_ID),
                eq(ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL))).thenReturn(false);
        fgsObserver.onForegroundStateChanged(null, TEST_PKG1, USER_ID_1, false);
        mTestLooper.dispatchAll();
        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{TEST_PKG2_APP_ID});

        mController.setPolicy(EMPTY_POLICY);
        mTestLooper.dispatchAll();
        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[0]);
    }

    @Test
    public void testStandbyPorts_broadcastChangedIfPackageIsExempt() throws Exception {
        mController.systemReady();