Require a foreground service of a specific type to start media projection.
The flow for apps targeting Q is :
- We check at the point of starting a projection whether the UID making
the request has a foreground service active with the type
FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
- The MediaProjectionManager registers a Process change observer to be
notified whenever the list of f/g service types associated with the
UID changes, and may stop the projection
Bug: 124753835
Test: Manual; CTS test in progress.
Change-Id: Iedcb9e944f5a7e2e59ba3b9831cdff619776e9e2
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0a80d45..f06b3e1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1270,11 +1270,13 @@
static final class ProcessChangeItem {
static final int CHANGE_ACTIVITIES = 1<<0;
+ static final int CHANGE_FOREGROUND_SERVICES = 1<<1;
int changes;
int uid;
int pid;
int processState;
boolean foregroundActivities;
+ int foregroundServiceTypes;
}
static final class UidObserverRegistration {
@@ -3076,6 +3078,13 @@
observer.onForegroundActivitiesChanged(item.pid, item.uid,
item.foregroundActivities);
}
+ if ((item.changes & ProcessChangeItem.CHANGE_FOREGROUND_SERVICES) != 0) {
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+ "FOREGROUND SERVICES CHANGED pid=" + item.pid + " uid="
+ + item.uid + ": " + item.foregroundServiceTypes);
+ observer.onForegroundServicesChanged(item.pid, item.uid,
+ item.foregroundServiceTypes);
+ }
}
} catch (RemoteException e) {
}
@@ -3090,6 +3099,47 @@
}
}
+ @GuardedBy("this")
+ ProcessChangeItem enqueueProcessChangeItemLocked(int uid, int pid) {
+ int i = mPendingProcessChanges.size()-1;
+ ActivityManagerService.ProcessChangeItem item = null;
+ while (i >= 0) {
+ item = mPendingProcessChanges.get(i);
+ if (item.pid == pid) {
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+ "Re-using existing item: " + item);
+ break;
+ }
+ i--;
+ }
+
+ if (i < 0) {
+ // No existing item in pending changes; need a new one.
+ final int NA = mAvailProcessChanges.size();
+ if (NA > 0) {
+ item = mAvailProcessChanges.remove(NA-1);
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+ "Retrieving available item: " + item);
+ } else {
+ item = new ActivityManagerService.ProcessChangeItem();
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+ "Allocating new item: " + item);
+ }
+ item.changes = 0;
+ item.pid = pid;
+ item.uid = uid;
+ if (mPendingProcessChanges.size() == 0) {
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+ "*** Enqueueing dispatch processes changed!");
+ mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG)
+ .sendToTarget();
+ }
+ mPendingProcessChanges.add(item);
+ }
+
+ return item;
+ }
+
private void dispatchProcessDied(int pid, int uid) {
int i = mProcessObservers.beginBroadcast();
while (i > 0) {
@@ -16256,11 +16306,12 @@
@GuardedBy("this")
final void updateProcessForegroundLocked(ProcessRecord proc, boolean isForeground,
int fgServiceTypes, boolean oomAdj) {
+ proc.setHasForegroundServices(isForeground, fgServiceTypes);
+
final boolean hasFgServiceLocationType =
(fgServiceTypes & ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION) != 0;
if (isForeground != proc.hasForegroundServices()
|| proc.hasLocationForegroundServices() != hasFgServiceLocationType) {
- proc.setHasForegroundServices(isForeground, fgServiceTypes);
ArrayList<ProcessRecord> curProcs = mForegroundPackages.get(proc.info.packageName,
proc.info.uid);
if (isForeground) {
@@ -16289,6 +16340,13 @@
updateOomAdjLocked();
}
}
+
+ if (proc.getForegroundServiceTypes() != fgServiceTypes) {
+ proc.setReportedForegroundServiceTypes(fgServiceTypes);
+ ProcessChangeItem item = enqueueProcessChangeItemLocked(proc.info.uid, proc.pid);
+ item.changes = ProcessChangeItem.CHANGE_FOREGROUND_SERVICES;
+ item.foregroundServiceTypes = fgServiceTypes;
+ }
}
// TODO(b/111541062): This method is only used for updating OOM adjustments. We need to update
@@ -18077,6 +18135,34 @@
public void prepareForPossibleShutdown() {
ActivityManagerService.this.prepareForPossibleShutdown();
}
+
+ @Override
+ public boolean hasRunningForegroundService(int uid, int foregroundServicetype) {
+ synchronized (ActivityManagerService.this) {
+ for (int i = 0; i < mProcessList.mLruProcesses.size(); i++) {
+ final ProcessRecord pr = mProcessList.mLruProcesses.get(i);
+ if (pr.uid != uid) {
+ continue;
+ }
+
+ if ((pr.getForegroundServiceTypes() & foregroundServicetype) != 0) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public void registerProcessObserver(IProcessObserver processObserver) {
+ ActivityManagerService.this.registerProcessObserver(processObserver);
+ }
+
+ @Override
+ public void unregisterProcessObserver(IProcessObserver processObserver) {
+ ActivityManagerService.this.unregisterProcessObserver(processObserver);
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 4e03b72..9296749 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -38,7 +38,6 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_USAGE_STATS;
import static com.android.server.am.ActivityManagerService.DISPATCH_OOM_ADJ_OBSERVER_MSG;
-import static com.android.server.am.ActivityManagerService.DISPATCH_PROCESSES_CHANGED_UI_MSG;
import static com.android.server.am.ActivityManagerService.IDLE_UIDS_MSG;
import static com.android.server.am.ActivityManagerService.TAG_BACKUP;
import static com.android.server.am.ActivityManagerService.TAG_LRU;
@@ -1916,41 +1915,9 @@
if (changes != 0) {
if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
"Changes in " + app + ": " + changes);
- int i = mService.mPendingProcessChanges.size()-1;
- ActivityManagerService.ProcessChangeItem item = null;
- while (i >= 0) {
- item = mService.mPendingProcessChanges.get(i);
- if (item.pid == app.pid) {
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
- "Re-using existing item: " + item);
- break;
- }
- i--;
- }
- if (i < 0) {
- // No existing item in pending changes; need a new one.
- final int NA = mService.mAvailProcessChanges.size();
- if (NA > 0) {
- item = mService.mAvailProcessChanges.remove(NA-1);
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
- "Retrieving available item: " + item);
- } else {
- item = new ActivityManagerService.ProcessChangeItem();
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
- "Allocating new item: " + item);
- }
- item.changes = 0;
- item.pid = app.pid;
- item.uid = app.info.uid;
- if (mService.mPendingProcessChanges.size() == 0) {
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
- "*** Enqueueing dispatch processes changed!");
- mService.mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG)
- .sendToTarget();
- }
- mService.mPendingProcessChanges.add(item);
- }
- item.changes |= changes;
+ ActivityManagerService.ProcessChangeItem item =
+ mService.enqueueProcessChangeItemLocked(app.pid, app.info.uid);
+ item.changes = changes;
item.foregroundActivities = app.repForegroundActivities;
if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
"Item " + Integer.toHexString(System.identityHashCode(item))
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 51b1ae1..17b244c 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -174,6 +174,7 @@
boolean hasStartedServices; // Are there any started services running in this process?
private boolean mHasForegroundServices; // Running any services that are foreground?
private int mFgServiceTypes; // Type of foreground service, if there is a foreground service.
+ private int mRepFgServiceTypes; // Last reported foreground service types.
private boolean mHasForegroundActivities; // Running any activities that are foreground?
boolean repForegroundActivities; // Last reported foreground activities.
boolean systemNoUi; // This is a system process, but not currently showing UI.
@@ -1079,6 +1080,18 @@
&& (mFgServiceTypes & ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION) != 0;
}
+ int getForegroundServiceTypes() {
+ return mHasForegroundServices ? mFgServiceTypes : 0;
+ }
+
+ int getReportedForegroundServiceTypes() {
+ return mRepFgServiceTypes;
+ }
+
+ void setReportedForegroundServiceTypes(int foregroundServiceTypes) {
+ mRepFgServiceTypes = foregroundServiceTypes;
+ }
+
void setHasForegroundActivities(boolean hasForegroundActivities) {
mHasForegroundActivities = hasForegroundActivities;
mWindowProcessController.setHasForegroundActivities(hasForegroundActivities);
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 270fbc6..3872523 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -17,9 +17,14 @@
package com.android.server.media.projection;
import android.Manifest;
+import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
+import android.app.IProcessObserver;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
import android.hardware.display.DisplayManager;
import android.media.MediaRouter;
import android.media.projection.IMediaProjection;
@@ -29,6 +34,8 @@
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
import android.os.Binder;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -38,6 +45,7 @@
import android.util.Slog;
import com.android.internal.util.DumpUtils;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.Watchdog;
@@ -63,6 +71,8 @@
private final Context mContext;
private final AppOpsManager mAppOps;
+ private final ActivityManagerInternal mActivityManagerInternal;
+ private final PackageManager mPackageManager;
private final MediaRouter mMediaRouter;
private final MediaRouterCallback mMediaRouterCallback;
@@ -77,6 +87,8 @@
mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>();
mCallbackDelegate = new CallbackDelegate();
mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+ mPackageManager = mContext.getPackageManager();
mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
mMediaRouterCallback = new MediaRouterCallback();
Watchdog.getInstance().addMonitor(this);
@@ -88,6 +100,21 @@
false /*allowIsolated*/);
mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mMediaRouterCallback,
MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
+ mActivityManagerInternal.registerProcessObserver(new IProcessObserver.Stub() {
+ @Override
+ public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) {
+ }
+
+ @Override
+ public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
+ MediaProjectionManagerService.this.handleForegroundServicesChanged(pid, uid,
+ serviceTypes);
+ }
+
+ @Override
+ public void onProcessDied(int pid, int uid) {
+ }
+ });
}
@Override
@@ -105,6 +132,29 @@
synchronized (mLock) { /* check for deadlock */ }
}
+ /**
+ * Called when the set of active foreground service types for a given {@code uid / pid} changes.
+ * We will stop the active projection grant if its owner targets {@code Q} or higher and has no
+ * started foreground services of type {@code FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION}.
+ */
+ private void handleForegroundServicesChanged(int pid, int uid, int serviceTypes) {
+ synchronized (mLock) {
+ if (mProjectionGrant == null || mProjectionGrant.uid != uid) {
+ return;
+ }
+
+ if (mProjectionGrant.targetSdkVersion < VERSION_CODES.Q) {
+ return;
+ }
+
+ if ((serviceTypes & ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION) != 0) {
+ return;
+ }
+
+ mProjectionGrant.stop();
+ }
+ }
+
private void startProjectionLocked(final MediaProjection projection) {
if (mProjectionGrant != null) {
mProjectionGrant.stop();
@@ -229,10 +279,19 @@
if (packageName == null || packageName.isEmpty()) {
throw new IllegalArgumentException("package name must not be empty");
}
+
long callingToken = Binder.clearCallingIdentity();
+
MediaProjection projection;
try {
- projection = new MediaProjection(type, uid, packageName);
+ ApplicationInfo ai;
+ try {
+ ai = mPackageManager.getApplicationInfo(packageName, 0);
+ } catch (NameNotFoundException e) {
+ throw new IllegalArgumentException("No package matching :" + packageName);
+ }
+
+ projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion);
if (isPermanentGrant) {
mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA,
projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED);
@@ -334,17 +393,19 @@
public final int uid;
public final String packageName;
public final UserHandle userHandle;
+ public final int targetSdkVersion;
private IMediaProjectionCallback mCallback;
private IBinder mToken;
private IBinder.DeathRecipient mDeathEater;
private int mType;
- public MediaProjection(int type, int uid, String packageName) {
+ MediaProjection(int type, int uid, String packageName, int targetSdkVersion) {
mType = type;
this.uid = uid;
this.packageName = packageName;
userHandle = new UserHandle(UserHandle.getUserId(uid));
+ this.targetSdkVersion = targetSdkVersion;
}
@Override // Binder call
@@ -400,6 +461,14 @@
+ " attempted to start already started MediaProjection");
return;
}
+
+ if (targetSdkVersion >= Build.VERSION_CODES.Q
+ && !mActivityManagerInternal.hasRunningForegroundService(
+ uid, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION)) {
+ throw new SecurityException("Media projections require a foreground service"
+ + " of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION");
+ }
+
mCallback = callback;
registerCallback(mCallback);
try {