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

Commit 24640107 authored by Austin Borger's avatar Austin Borger
Browse files

Camera / UidObserver: Add the ability to subscribe to specific UIDs

UidObserver sends updates about the state of all packages installed on
the system. In the case of the cameraserver, we only care about a
handful of them. The current status quo is to filter out these callbacks
but there is a significant IPC cost that is not addressed by that
approach.

This patch adds new entrypoints to ActivityManagerService to listen only
to specified UIDs. This set of uids can be updated dynamically.

Change-Id: Ic699526588a3f9157ca7d38b905f898876d86847
Bug: 274486653
Test: -- on physical device:
      -- testCamera2AccessCallbackInSplitMode x10
      -- ActivityManagerServiceTest
      -- ActivityManagerProcessStateTest
      -- ActivityManagerFgsBgStartTest
      -- UidObserverControllerTest
      -- Alternate focus in split screen between Camera2 + GCA x20
parent 151d62e5
Loading
Loading
Loading
Loading
+37 −0
Original line number Diff line number Diff line
@@ -102,6 +102,43 @@ interface IActivityManager {
    void registerUidObserver(in IUidObserver observer, int which, int cutpoint,
            String callingPackage);
    void unregisterUidObserver(in IUidObserver observer);

    /**
     * Registers a UidObserver with a uid filter.
     *
     * @param observer The UidObserver implementation to register.
     * @param which    A bitmask of events to observe. See ActivityManager.UID_OBSERVER_*.
     * @param cutpoint The cutpoint for onUidStateChanged events. When the state crosses this
     *                 threshold in either direction, onUidStateChanged will be called.
     * @param callingPackage The name of the calling package.
     * @param uids     A list of uids to watch. If all uids are to be watched, use
     *                 registerUidObserver instead.
     * @throws RemoteException
     * @return Returns A binder token identifying the UidObserver registration.
     */
    IBinder registerUidObserverForUids(in IUidObserver observer, int which, int cutpoint,
            String callingPackage, in int[] uids);

    /**
     * Adds a uid to the list of uids that a UidObserver will receive updates about.
     *
     * @param observerToken  The binder token identifying the UidObserver registration.
     * @param callingPackage The name of the calling package.
     * @param uid            The uid to watch.
     * @throws RemoteException
     */
    void addUidToObserver(in IBinder observerToken, String callingPackage, int uid);

    /**
     * Removes a uid from the list of uids that a UidObserver will receive updates about.
     *
     * @param observerToken  The binder token identifying the UidObserver registration.
     * @param callingPackage The name of the calling package.
     * @param uid            The uid to stop watching.
     * @throws RemoteException
     */
    void removeUidFromObserver(in IBinder observerToken, String callingPackage, int uid);

    boolean isUidActive(int uid, String callingPackage);
    @JavaPassthrough(annotation=
            "@android.annotation.RequiresPermission(allOf = {android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional = true)")
+60 −2
Original line number Diff line number Diff line
@@ -7490,7 +7490,31 @@ public class ActivityManagerService extends IActivityManager.Stub
                    "registerUidObserver");
        }
        mUidObserverController.register(observer, which, cutpoint, callingPackage,
                Binder.getCallingUid());
                Binder.getCallingUid(), /*uids*/null);
    }
    /**
     * Registers a UidObserver with a uid filter.
     *
     * @param observer The UidObserver implementation to register.
     * @param which    A bitmask of events to observe. See ActivityManager.UID_OBSERVER_*.
     * @param cutpoint The cutpoint for onUidStateChanged events. When the state crosses this
     *                 threshold in either direction, onUidStateChanged will be called.
     * @param callingPackage The name of the calling package.
     * @param uids     A list of uids to watch. If all uids are to be watched, use
     *                 registerUidObserver instead.
     * @throws RemoteException
     * @return Returns A binder token identifying the UidObserver registration.
     */
    @Override
    public IBinder registerUidObserverForUids(IUidObserver observer, int which, int cutpoint,
            String callingPackage, int[] uids) {
        if (!hasUsageStatsPermission(callingPackage)) {
            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
                    "registerUidObserver");
        }
        return mUidObserverController.register(observer, which, cutpoint, callingPackage,
                Binder.getCallingUid(), uids);
    }
    @Override
@@ -7498,6 +7522,40 @@ public class ActivityManagerService extends IActivityManager.Stub
        mUidObserverController.unregister(observer);
    }
    /**
     * Adds a uid to the list of uids that a UidObserver will receive updates about.
     *
     * @param observerToken  The binder token identifying the UidObserver registration.
     * @param callingPackage The name of the calling package.
     * @param uid            The uid to watch.
     * @throws RemoteException
     */
    @Override
    public void addUidToObserver(IBinder observerToken, String callingPackage, int uid) {
        if (!hasUsageStatsPermission(callingPackage)) {
            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
                    "registerUidObserver");
        }
        mUidObserverController.addUidToObserver(observerToken, uid);
    }
    /**
     * Removes a uid from the list of uids that a UidObserver will receive updates about.
     *
     * @param observerToken  The binder token identifying the UidObserver registration.
     * @param callingPackage The name of the calling package.
     * @param uid            The uid to stop watching.
     * @throws RemoteException
     */
    @Override
    public void removeUidFromObserver(IBinder observerToken, String callingPackage, int uid) {
        if (!hasUsageStatsPermission(callingPackage)) {
            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
                    "registerUidObserver");
        }
        mUidObserverController.removeUidFromObserver(observerToken, uid);
    }
    @Override
    public boolean isUidActive(int uid, String callingPackage) {
        if (!hasUsageStatsPermission(callingPackage)) {
@@ -18560,7 +18618,7 @@ public class ActivityManagerService extends IActivityManager.Stub
                int which, int cutpoint, @NonNull String callingPackage) {
            mNetworkPolicyUidObserver = observer;
            mUidObserverController.register(observer, which, cutpoint, callingPackage,
                    Binder.getCallingUid());
                    Binder.getCallingUid(), /*uids*/null);
        }
        @Override
+133 −4
Original line number Diff line number Diff line
@@ -27,7 +27,9 @@ import android.app.ActivityManager;
import android.app.ActivityManagerProto;
import android.app.IUidObserver;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -43,6 +45,8 @@ import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserve

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.UUID;

public class UidObserverController {
    /** If a UID observer takes more than this long, send a WTF. */
@@ -79,14 +83,19 @@ public class UidObserverController {
        mValidateUids = new ActiveUids(null /* service */, false /* postChangesToAtm */);
    }

    void register(@NonNull IUidObserver observer, int which, int cutpoint,
            @NonNull String callingPackage, int callingUid) {
    IBinder register(@NonNull IUidObserver observer, int which, int cutpoint,
            @NonNull String callingPackage, int callingUid, @Nullable int[] uids) {
        IBinder token = new Binder("UidObserver-" + callingPackage + "-"
                + UUID.randomUUID().toString());

        synchronized (mLock) {
            mUidObservers.register(observer, new UidObserverRegistration(callingUid,
                    callingPackage, which, cutpoint,
                    ActivityManager.checkUidPermission(INTERACT_ACROSS_USERS_FULL, callingUid)
                    == PackageManager.PERMISSION_GRANTED));
                    == PackageManager.PERMISSION_GRANTED, uids, token));
        }

        return token;
    }

    void unregister(@NonNull IUidObserver observer) {
@@ -95,6 +104,42 @@ public class UidObserverController {
        }
    }

    void addUidToObserver(@NonNull IBinder observerToken, int uid) {
        synchronized (mLock) {
            int i = mUidObservers.beginBroadcast();
            while (i-- > 0) {
                var reg = (UidObserverRegistration) mUidObservers.getBroadcastCookie(i);
                if (reg.getToken().equals(observerToken)) {
                    reg.addUid(uid);
                    break;
                }

                if (i == 0) {
                    Slog.e(TAG_UID_OBSERVERS, "Unable to find UidObserver by token");
                }
            }
            mUidObservers.finishBroadcast();
        }
    }

    void removeUidFromObserver(@NonNull IBinder observerToken, int uid) {
        synchronized (mLock) {
            int i = mUidObservers.beginBroadcast();
            while (i-- > 0) {
                var reg = (UidObserverRegistration) mUidObservers.getBroadcastCookie(i);
                if (reg.getToken().equals(observerToken)) {
                    reg.removeUid(uid);
                    break;
                }

                if (i == 0) {
                    Slog.e(TAG_UID_OBSERVERS, "Unable to find UidObserver by token");
                }
            }
            mUidObservers.finishBroadcast();
        }
    }

    int enqueueUidChange(@Nullable ChangeRecord currentRecord, int uid, int change, int procState,
            int procAdj, long procStateSeq, int capability, boolean ephemeral) {
        synchronized (mLock) {
@@ -257,6 +302,10 @@ public class UidObserverController {
                final ChangeRecord item = mActiveUidChanges[j];
                final long start = SystemClock.uptimeMillis();
                final int change = item.change;
                // Is the observer watching this uid?
                if (!reg.isWatchingUid(item.uid)) {
                    continue;
                }
                // Does the user have permission? Don't send a non user UID change otherwise
                if (UserHandle.getUserId(item.uid) != UserHandle.getUserId(reg.mUid)
                        && !reg.mCanInteractAcrossUsers) {
@@ -450,6 +499,8 @@ public class UidObserverController {
        private final int mWhich;
        private final int mCutpoint;
        private final boolean mCanInteractAcrossUsers;
        private final IBinder mToken;
        private int[] mUids;

        /**
         * Total # of callback calls that took more than {@link #SLOW_UID_OBSERVER_THRESHOLD_MS}.
@@ -481,16 +532,94 @@ public class UidObserverController {
        };

        UidObserverRegistration(int uid, @NonNull String pkg, int which, int cutpoint,
                boolean canInteractAcrossUsers) {
                boolean canInteractAcrossUsers, @Nullable int[] uids, @NonNull IBinder token) {
            this.mUid = uid;
            this.mPkg = pkg;
            this.mWhich = which;
            this.mCutpoint = cutpoint;
            this.mCanInteractAcrossUsers = canInteractAcrossUsers;

            if (uids != null) {
                this.mUids = uids.clone();
                Arrays.sort(this.mUids);
            } else {
                this.mUids = null;
            }

            this.mToken = token;

            mLastProcStates = cutpoint >= ActivityManager.MIN_PROCESS_STATE
                    ? new SparseIntArray() : null;
        }

        boolean isWatchingUid(int uid) {
            if (mUids == null) {
                return true;
            }

            return Arrays.binarySearch(mUids, uid) != -1;
        }

        void addUid(int uid) {
            if (mUids == null) {
                return;
            }

            int[] temp = mUids;
            mUids = new int[temp.length + 1];
            boolean inserted = false;
            for (int i = 0; i < temp.length; i++) {
                if (!inserted) {
                    if (temp[i] < uid) {
                        mUids[i] = temp[i];
                    } else if (temp[i] == uid) {
                        // Duplicate uid, no-op and fallback to the previous array
                        mUids = temp;
                        return;
                    } else {
                        mUids[i] = uid;
                        mUids[i + 1] = temp[i];
                        inserted = true;
                    }
                } else {
                    mUids[i + 1] = temp[i];
                }
            }

            if (!inserted) {
                mUids[temp.length] = uid;
            }
        }

        void removeUid(int uid) {
            if (mUids == null || mUids.length == 0) {
                return;
            }

            int[] temp = mUids;
            mUids = new int[temp.length - 1];
            boolean removed = false;
            for (int i = 0; i < temp.length; i++) {
                if (!removed) {
                    if (temp[i] == uid) {
                        removed = true;
                    } else if (i == temp.length - 1) {
                        // Uid not found, no-op and fallback to the previous array
                        mUids = temp;
                        return;
                    } else {
                        mUids[i] = temp[i];
                    }
                } else {
                    mUids[i - 1] = temp[i];
                }
            }
        }

        IBinder getToken() {
            return mToken;
        }

        void dump(@NonNull PrintWriter pw, @NonNull IUidObserver observer) {
            pw.print("    ");
            UserHandle.formatUid(pw, mUid);
+2 −1
Original line number Diff line number Diff line
@@ -217,7 +217,8 @@ public class UidObserverControllerTest {
    private void registerObserver(IUidObserver observer, int which, int cutpoint,
            String callingPackage, int callingUid) {
        when(observer.asBinder()).thenReturn((IBinder) observer);
        mUidObserverController.register(observer, which, cutpoint, callingPackage, callingUid);
        mUidObserverController.register(observer, which, cutpoint, callingPackage, callingUid,
                /*uids*/null);
        Mockito.reset(observer);
    }