diff options
4 files changed, 232 insertions, 7 deletions
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 99ef315676c4..e9fbf6b178fb 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -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)") diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 8ce1829a4b16..5d3bb31bc31f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -7511,7 +7511,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 @@ -7519,6 +7543,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)) { @@ -18613,7 +18671,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 diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java index 790cc7b87f80..5e41dcd0009e 100644 --- a/services/core/java/com/android/server/am/UidObserverController.java +++ b/services/core/java/com/android/server/am/UidObserverController.java @@ -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); diff --git a/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java index 46974cf72381..37afc7f52f7e 100644 --- a/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java @@ -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); } |