summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/IActivityManager.aidl37
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java62
-rw-r--r--services/core/java/com/android/server/am/UidObserverController.java137
-rw-r--r--services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java3
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);
}