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

Commit fa29f6f8 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Optimizing AppStandby.getIdleUidsForUser" into sc-dev

parents fb4b5801 8a41b3f7
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;