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

Commit 8a41b3f7 authored by Suprabh Shukla's avatar Suprabh Shukla
Browse files

Optimizing AppStandby.getIdleUidsForUser

This is called by NPMS on boot for all users, and it makes a lot of
calls to getAppMinBucket. Optimizing the newly added call into alarm
manager internal, and some bookkeeping at the front.

Alarm manager now maintains a list of all apps that have requested the
permission. This saves any calls into permission manager in
hasScheduleExactAlarm.

Test: atest FrameworksMockingServicesTests:com.android.server.alarm
atest FrameworksServicesTests:AppStandbyControllerTests

atrace for boot time analysis.

Bug: 185081934
Change-Id: I4f0b80715acb37a777d28f8c41f666b20f26bd8d
parent a3e28dec
Loading
Loading
Loading
Loading
+53 −10
Original line number Diff line number Diff line
@@ -59,7 +59,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.net.Uri;
import android.os.BatteryManager;
@@ -123,6 +123,7 @@ import com.android.server.JobSchedulerBackgroundThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.usage.AppStandbyInternal;
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;

@@ -198,9 +199,13 @@ public class AlarmManagerService extends SystemService {
    private UsageStatsManagerInternal mUsageStatsManagerInternal;
    private ActivityManagerInternal mActivityManagerInternal;
    private PackageManagerInternal mPackageManagerInternal;
    private PermissionManagerServiceInternal mLocalPermissionManager;

    final Object mLock = new Object();

    /** Immutable set of app ids that have requested SCHEDULE_EXACT_ALARM permission.*/
    @VisibleForTesting
    volatile Set<Integer> mExactAlarmCandidates = Collections.emptySet();
    // List of alarms per uid deferred due to user applied background restrictions on the source app
    SparseArray<ArrayList<Alarm>> mPendingBackgroundAlarms = new SparseArray<>();
    private long mNextWakeup;
@@ -1540,6 +1545,21 @@ public class AlarmManagerService extends SystemService {
        publishBinderService(Context.ALARM_SERVICE, mService);
    }

    void refreshExactAlarmCandidates() {
        final String[] candidates = mLocalPermissionManager.getAppOpPermissionPackages(
                Manifest.permission.SCHEDULE_EXACT_ALARM);
        final Set<Integer> appIds = new ArraySet<>(candidates.length);
        for (final String candidate : candidates) {
            final int uid = mPackageManagerInternal.getPackageUid(candidate,
                    PackageManager.MATCH_ANY_USER, USER_SYSTEM);
            if (uid > 0) {
                appIds.add(UserHandle.getAppId(uid));
            }
        }
        // No need to lock. Assignment is always atomic.
        mExactAlarmCandidates = Collections.unmodifiableSet(appIds);
    }

    @Override
    public void onBootPhase(int phase) {
        if (phase == PHASE_SYSTEM_SERVICES_READY) {
@@ -1569,6 +1589,11 @@ public class AlarmManagerService extends SystemService {
                        LocalServices.getService(DeviceIdleInternal.class);
                mUsageStatsManagerInternal =
                        LocalServices.getService(UsageStatsManagerInternal.class);

                mLocalPermissionManager = LocalServices.getService(
                        PermissionManagerServiceInternal.class);
                refreshExactAlarmCandidates();

                AppStandbyInternal appStandbyInternal =
                        LocalServices.getService(AppStandbyInternal.class);
                appStandbyInternal.addListener(new AppStandbyTracker());
@@ -2097,17 +2122,21 @@ public class AlarmManagerService extends SystemService {

    boolean hasScheduleExactAlarmInternal(String packageName, int uid) {
        final long start = mStatLogger.getTime();
        // No locking needed as EXACT_ALARM_DENY_LIST is immutable.
        final boolean isOnDenyList = mConstants.EXACT_ALARM_DENY_LIST.contains(packageName);
        if (isOnDenyList && mAppOps.checkOpNoThrow(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, uid,
                packageName) != AppOpsManager.MODE_ALLOWED) {
            return false;
        final boolean hasPermission;
        // No locking needed as all internal containers being queried are immutable.
        if (!mExactAlarmCandidates.contains(UserHandle.getAppId(uid))) {
            hasPermission = false;
        } else {
            final int mode = mAppOps.checkOpNoThrow(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, uid,
                    packageName);
            if (mode == AppOpsManager.MODE_DEFAULT) {
                hasPermission = !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName);
            } else {
                hasPermission = (mode == AppOpsManager.MODE_ALLOWED);
            }
        }
        final boolean has = PermissionChecker.checkPermissionForPreflight(getContext(),
                Manifest.permission.SCHEDULE_EXACT_ALARM, -1, uid, packageName)
                == PermissionChecker.PERMISSION_GRANTED;
        mStatLogger.logDurationStat(Stats.HAS_SCHEDULE_EXACT_ALARM, start);
        return has;
        return hasPermission;
    }

    /**
@@ -2489,6 +2518,9 @@ public class AlarmManagerService extends SystemService {
            pw.print("Num time change events: ");
            pw.println(mNumTimeChanged);

            pw.println();
            pw.println("App ids requesting SCHEDULE_EXACT_ALARM: " + mExactAlarmCandidates);

            pw.println();
            pw.println("Next alarm clock information: ");
            pw.increaseIndent();
@@ -3924,6 +3956,7 @@ public class AlarmManagerService extends SystemService {
        public static final int REMOVE_FOR_CANCELED = 7;
        public static final int REMOVE_EXACT_ALARMS = 8;
        public static final int EXACT_ALARM_DENY_LIST_CHANGED = 9;
        public static final int REFRESH_EXACT_ALARM_CANDIDATES = 10;

        AlarmHandler() {
            super(Looper.myLooper());
@@ -4015,6 +4048,9 @@ public class AlarmManagerService extends SystemService {
                        handlePackagesAddedToExactAlarmsDenyListLocked((ArraySet<String>) msg.obj);
                    }
                    break;
                case REFRESH_EXACT_ALARM_CANDIDATES:
                    refreshExactAlarmCandidates();
                    break;
                default:
                    // nope, just ignore it
                    break;
@@ -4135,6 +4171,7 @@ public class AlarmManagerService extends SystemService {
        public UninstallReceiver() {
            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
            filter.addAction(Intent.ACTION_PACKAGE_ADDED);
            filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
            filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
            filter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
@@ -4179,8 +4216,11 @@ public class AlarmManagerService extends SystemService {
                    case Intent.ACTION_PACKAGE_REMOVED:
                        if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
                            // This package is being updated; don't kill its alarms.
                            // We will refresh the exact alarm candidates on subsequent receipt of
                            // PACKAGE_ADDED.
                            return;
                        }
                        mHandler.sendEmptyMessage(AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES);
                        // Intentional fall-through.
                    case Intent.ACTION_PACKAGE_RESTARTED:
                        final Uri data = intent.getData();
@@ -4191,6 +4231,9 @@ public class AlarmManagerService extends SystemService {
                            }
                        }
                        break;
                    case Intent.ACTION_PACKAGE_ADDED:
                        mHandler.sendEmptyMessage(AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES);
                        return;
                }
                if (pkgList != null && (pkgList.length > 0)) {
                    for (String pkg : pkgList) {
+31 −47
Original line number Diff line number Diff line
@@ -58,7 +58,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.usage.AppStandbyInfo;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManager.StandbyBuckets;
@@ -75,7 +74,6 @@ import android.content.pm.CrossProfileAppsInternal;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.database.ContentObserver;
import android.hardware.display.DisplayManager;
import android.net.NetworkScoreManager;
@@ -101,7 +99,7 @@ import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.SparseBooleanArray;
import android.util.TimeUtils;
import android.view.Display;
import android.widget.Toast;
@@ -118,6 +116,8 @@ import com.android.server.LocalServices;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.usage.AppIdleHistory.AppUsageHistory;

import libcore.util.EmptyArray;

import java.io.File;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -1249,71 +1249,55 @@ public class AppStandbyController
    @Override
    public int[] getIdleUidsForUser(int userId) {
        if (!mAppIdleEnabled) {
            return new int[0];
            return EmptyArray.INT;
        }

        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "getIdleUidsForUser");

        final long elapsedRealtime = mInjector.elapsedRealtime();

        List<ApplicationInfo> apps;
        try {
            ParceledListSlice<ApplicationInfo> slice = AppGlobals.getPackageManager()
                    .getInstalledApplications(/* flags= */ 0, userId);
            if (slice == null) {
                return new int[0];
            }
            apps = slice.getList();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        final PackageManagerInternal pmi = mInjector.getPackageManagerInternal();
        final List<ApplicationInfo> apps = pmi.getInstalledApplications(0, userId, Process.myUid());
        if (apps == null) {
            return EmptyArray.INT;
        }

        // State of each uid.  Key is the uid.  Value lower 16 bits is the number of apps
        // associated with that uid, upper 16 bits is the number of those apps that is idle.
        SparseIntArray uidStates = new SparseIntArray();

        // Now resolve all app state.  Iterating over all apps, keeping track of how many
        // we find for each uid and how many of those are idle.
        // State of each uid: Key is the uid, value is whether all the apps in that uid are idle.
        final SparseBooleanArray uidIdleStates = new SparseBooleanArray();
        int notIdleCount = 0;
        for (int i = apps.size() - 1; i >= 0; i--) {
            ApplicationInfo ai = apps.get(i);
            final ApplicationInfo ai = apps.get(i);
            final int index = uidIdleStates.indexOfKey(ai.uid);

            // Check whether this app is idle.
            boolean idle = isAppIdleFiltered(ai.packageName, UserHandle.getAppId(ai.uid),
                    userId, elapsedRealtime);
            final boolean currentIdle = (index < 0) ? true : uidIdleStates.valueAt(index);

            int index = uidStates.indexOfKey(ai.uid);
            final boolean newIdle = currentIdle && isAppIdleFiltered(ai.packageName,
                    UserHandle.getAppId(ai.uid), userId, elapsedRealtime);

            if (currentIdle && !newIdle) {
                // This transition from true to false can happen at most once per uid in this loop.
                notIdleCount++;
            }
            if (index < 0) {
                uidStates.put(ai.uid, 1 + (idle ? 1<<16 : 0));
                uidIdleStates.put(ai.uid, newIdle);
            } else {
                int value = uidStates.valueAt(index);
                uidStates.setValueAt(index, value + 1 + (idle ? 1<<16 : 0));
                uidIdleStates.setValueAt(index, newIdle);
            }
        }

        if (DEBUG) {
            Slog.d(TAG, "getIdleUids took " + (mInjector.elapsedRealtime() - elapsedRealtime));
        }
        int numIdle = 0;
        for (int i = uidStates.size() - 1; i >= 0; i--) {
            int value = uidStates.valueAt(i);
            if ((value&0x7fff) == (value>>16)) {
                numIdle++;
            }
        int numIdleUids = uidIdleStates.size() - notIdleCount;
        final int[] idleUids = new int[numIdleUids];
        for (int i = uidIdleStates.size() - 1; i >= 0; i--) {
            if (uidIdleStates.valueAt(i)) {
                idleUids[--numIdleUids] = uidIdleStates.keyAt(i);
            }

        int[] res = new int[numIdle];
        numIdle = 0;
        for (int i = uidStates.size() - 1; i >= 0; i--) {
            int value = uidStates.valueAt(i);
            if ((value&0x7fff) == (value>>16)) {
                res[numIdle] = uidStates.keyAt(i);
                numIdle++;
        }
        if (DEBUG) {
            Slog.d(TAG, "getIdleUids took " + (mInjector.elapsedRealtime() - elapsedRealtime));
        }

        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

        return res;
        return idleUids;
    }

    @Override
+148 −112

File changed.

Preview size limit exceeded, changes collapsed.

+46 −0
Original line number Diff line number Diff line
@@ -59,11 +59,14 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import static org.mockito.AdditionalMatchers.not;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -77,6 +80,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.Looper;
@@ -92,6 +96,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
@@ -101,6 +106,8 @@ import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.io.File;
import java.util.ArrayList;
@@ -194,6 +201,8 @@ public class AppStandbyControllerTests {
    }

    static class MyInjector extends AppStandbyController.Injector {
        @Mock
        private PackageManagerInternal mPackageManagerInternal;
        long mElapsedRealtime;
        boolean mIsAppIdleEnabled = true;
        boolean mIsCharging;
@@ -222,6 +231,7 @@ public class AppStandbyControllerTests {

        MyInjector(Context context, Looper looper) {
            super(context, looper);
            MockitoAnnotations.initMocks(this);
        }

        @Override
@@ -268,6 +278,11 @@ public class AppStandbyControllerTests {
            return mClockApps.contains(Pair.create(packageName, uid));
        }

        @Override
        PackageManagerInternal getPackageManagerInternal() {
            return mPackageManagerInternal;
        }

        @Override
        void updatePowerWhitelistCache() {
        }
@@ -491,6 +506,37 @@ public class AppStandbyControllerTests {
                        mInjector.mElapsedRealtime, false));
    }

    @Test
    public void testGetIdleUidsForUser() {
        final AppStandbyController controllerUnderTest = spy(mController);

        final int userIdForTest = 325;
        final int[] uids = new int[]{129, 23, 129, 129, 44, 23, 41, 751};
        final boolean[] idle = new boolean[]{true, true, false, true, false, true, false, true};
        // Based on uids[] and idle[], the only two uids that have all true's in idle[].
        final int[] expectedIdleUids = new int[]{23, 751};

        final List<ApplicationInfo> installedApps = new ArrayList<>();
        for (int i = 0; i < uids.length; i++) {
            final ApplicationInfo ai = mock(ApplicationInfo.class);
            ai.uid = uids[i];
            ai.packageName = "example.package.name." + i;
            installedApps.add(ai);
            when(controllerUnderTest.isAppIdleFiltered(eq(ai.packageName),
                    eq(UserHandle.getAppId(ai.uid)), eq(userIdForTest), anyLong()))
                    .thenReturn(idle[i]);
        }
        when(mInjector.mPackageManagerInternal.getInstalledApplications(anyInt(), eq(userIdForTest),
                anyInt())).thenReturn(installedApps);
        final int[] returnedIdleUids = controllerUnderTest.getIdleUidsForUser(userIdForTest);

        assertEquals(expectedIdleUids.length, returnedIdleUids.length);
        for (final int uid : expectedIdleUids) {
            assertTrue("Idle uid: " + uid + " not found in result: " + Arrays.toString(
                    returnedIdleUids), ArrayUtils.contains(returnedIdleUids, uid));
        }
    }

    private static class TestParoleListener extends AppIdleStateChangeListener {
        private boolean mIsParoleOn = false;
        private CountDownLatch mLatch;