diff options
8 files changed, 183 insertions, 7 deletions
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index a5965bc7f85f..eb2a40f4a7dd 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -440,4 +440,11 @@ public abstract class ActivityManagerInternal { * @return true if exists, false otherwise. */ public abstract boolean isPendingTopUid(int uid); + + public abstract void tempAllowWhileInUsePermissionInFgs(int uid, long duration); + + public abstract boolean isTempAllowlistedForFgsWhileInUse(int uid); + + public abstract boolean canAllowWhileInUsePermissionInFgs(int pid, int uid, + @NonNull String packageName); } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index ca4b9c38b593..bfaf4e8f4b9a 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -4878,17 +4878,20 @@ public final class ActiveServices { return true; } - if (r.app != null) { + if (r != null && r.app != null) { ActiveInstrumentation instr = r.app.getActiveInstrumentation(); if (instr != null && instr.mHasBackgroundActivityStartsPermission) { return true; } } - final boolean hasAllowBackgroundActivityStartsToken = r.app != null - ? !r.app.mAllowBackgroundActivityStartsTokens.isEmpty() : false; - if (hasAllowBackgroundActivityStartsToken) { - return true; + for (int i = mAm.mProcessList.mLruProcesses.size() - 1; i >= 0; i--) { + final ProcessRecord pr = mAm.mProcessList.mLruProcesses.get(i); + if (pr.uid == callingUid) { + if (!pr.mAllowBackgroundActivityStartsTokens.isEmpty()) { + return true; + } + } } if (mAm.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid) @@ -4907,6 +4910,10 @@ public final class ActiveServices { return true; } + if (mAm.mInternal.isTempAllowlistedForFgsWhileInUse(callingUid)) { + return true; + } + final boolean isWhiteListedPackage = mWhiteListAllowWhileInUsePermissionInFgs.contains(callingPackage); if (isWhiteListedPackage) { @@ -4920,4 +4927,10 @@ public final class ActiveServices { } return false; } + + boolean canAllowWhileInUsePermissionInFgsLocked(int callingPid, int callingUid, + String callingPackage) { + return shouldAllowWhileInUsePermissionInFgsLocked( + callingPackage, callingPid, callingUid, null, null, false); + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index cb4881757c3e..4b3fbf0f124b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1266,6 +1266,13 @@ public class ActivityManagerService extends IActivityManager.Stub final PendingTempWhitelists mPendingTempWhitelist = new PendingTempWhitelists(this); /** + * List of uids that are allowed to have while-in-use permission when FGS is started from + * background. + */ + private final FgsWhileInUseTempAllowList mFgsWhileInUseTempAllowList = + new FgsWhileInUseTempAllowList(); + + /** * Information about and control over application operations */ final AppOpsService mAppOpsService; @@ -19945,6 +19952,24 @@ public class ActivityManagerService extends IActivityManager.Stub public boolean isPendingTopUid(int uid) { return mPendingStartActivityUids.isPendingTopUid(uid); } + + @Override + public void tempAllowWhileInUsePermissionInFgs(int uid, long duration) { + mFgsWhileInUseTempAllowList.add(uid, duration); + } + + @Override + public boolean isTempAllowlistedForFgsWhileInUse(int uid) { + return mFgsWhileInUseTempAllowList.isAllowed(uid); + } + + @Override + public boolean canAllowWhileInUsePermissionInFgs(int pid, int uid, + @NonNull String packageName) { + synchronized (ActivityManagerService.this) { + return mServices.canAllowWhileInUsePermissionInFgsLocked(pid, uid, packageName); + } + } } long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) { diff --git a/services/core/java/com/android/server/am/FgsWhileInUseTempAllowList.java b/services/core/java/com/android/server/am/FgsWhileInUseTempAllowList.java new file mode 100644 index 000000000000..ed0f264049ef --- /dev/null +++ b/services/core/java/com/android/server/am/FgsWhileInUseTempAllowList.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; + +import android.os.SystemClock; +import android.util.Slog; +import android.util.SparseLongArray; + +/** + * List of uids that are allowed to have while-in-use permission when FGS is started + * from background. + */ +final class FgsWhileInUseTempAllowList { + /** + * This list is supposed to have a small number of entries. If exceeds MAX_SIZE, log a warning + * message. + */ + private static final int MAX_SIZE = 100; + /** + * The key is the UID, the value is expiration elapse time in ms of this temp-allowed UID. + */ + private final SparseLongArray mTempAllowListFgs = new SparseLongArray(); + + private final Object mLock = new Object(); + + void add(int uid, long durationMs) { + synchronized (mLock) { + if (durationMs <= 0) { + Slog.e(TAG_AM, "FgsWhileInUseTempAllowList bad duration:" + durationMs + + " uid: " + uid); + return; + } + // The temp allowlist should be a short list with only a few entries in it. + final int size = mTempAllowListFgs.size(); + if (size > MAX_SIZE) { + Slog.w(TAG_AM, "FgsWhileInUseTempAllowList length:" + size + " exceeds " + + MAX_SIZE); + } + final long now = SystemClock.elapsedRealtime(); + for (int index = mTempAllowListFgs.size() - 1; index >= 0; index--) { + if (mTempAllowListFgs.valueAt(index) < now) { + mTempAllowListFgs.removeAt(index); + } + } + final long existingExpirationTime = mTempAllowListFgs.get(uid, -1); + final long expirationTime = now + durationMs; + if (existingExpirationTime == -1 || existingExpirationTime < expirationTime) { + mTempAllowListFgs.put(uid, expirationTime); + } + } + } + + boolean isAllowed(int uid) { + synchronized (mLock) { + final int index = mTempAllowListFgs.indexOfKey(uid); + if (index < 0) { + return false; + } else if (mTempAllowListFgs.valueAt(index) < SystemClock.elapsedRealtime()) { + mTempAllowListFgs.removeAt(index); + return false; + } else { + return true; + } + } + } +} diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index db8dc715214a..23f4611c1d38 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -583,6 +583,10 @@ public class AppOpsService extends IAppOpsService.Stub { if (mActivityManagerInternal != null && mActivityManagerInternal.isPendingTopUid(uid)) { return MODE_ALLOWED; + } else if (mActivityManagerInternal != null + && mActivityManagerInternal.isTempAllowlistedForFgsWhileInUse( + uid)) { + return MODE_ALLOWED; } else if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) { return MODE_ALLOWED; } else { @@ -592,6 +596,10 @@ public class AppOpsService extends IAppOpsService.Stub { if (mActivityManagerInternal != null && mActivityManagerInternal.isPendingTopUid(uid)) { return MODE_ALLOWED; + } else if (mActivityManagerInternal != null + && mActivityManagerInternal.isTempAllowlistedForFgsWhileInUse( + uid)) { + return MODE_ALLOWED; } else if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) { return MODE_ALLOWED; } else { diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java index 6e655eafa0e9..d1eaaf86d7ca 100644 --- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java +++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java @@ -18,6 +18,7 @@ package com.android.server.media; import android.annotation.IntDef; import android.annotation.NonNull; +import android.app.BroadcastOptions; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; @@ -196,6 +197,8 @@ final class MediaButtonReceiverHolder { // TODO: Find a way to also send PID/UID in secure way. mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callingPackageName); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setBackgroundActivityStartsAllowed(true); if (mPendingIntent != null) { if (DEBUG_KEY_EVENT) { Log.d(TAG, "Sending " + keyEvent + " to the last known PendingIntent " @@ -203,7 +206,8 @@ final class MediaButtonReceiverHolder { } try { mPendingIntent.send( - context, resultCode, mediaButtonIntent, onFinishedListener, handler); + context, resultCode, mediaButtonIntent, onFinishedListener, handler, + /* requiredPermission= */null, options.toBundle()); } catch (PendingIntent.CanceledException e) { Log.w(TAG, "Error sending key event to media button receiver " + mPendingIntent, e); return false; @@ -226,7 +230,8 @@ final class MediaButtonReceiverHolder { break; default: // Legacy behavior for other cases. - context.sendBroadcastAsUser(mediaButtonIntent, userHandle); + context.sendBroadcastAsUser(mediaButtonIntent, userHandle, + /* requiredPermission= */null, options.toBundle()); } } catch (Exception e) { Log.w(TAG, "Error sending media button to the restored intent " diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 02b7582a8637..348e9c122a9a 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -986,6 +986,12 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService, KeyEvent keyEvent, int sequenceId, ResultReceiver cb) { try { + if (KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())) { + final String reason = "action=" + KeyEvent.actionToString(keyEvent.getAction()) + + ";code=" + KeyEvent.keyCodeToString(keyEvent.getKeyCode()); + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); + } if (asSystemService) { mCb.onMediaButton(mContext.getPackageName(), Process.myPid(), Process.SYSTEM_UID, createMediaButtonIntent(keyEvent), sequenceId, cb); @@ -1003,6 +1009,12 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService, KeyEvent keyEvent) { try { + if (KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())) { + final String reason = "action=" + KeyEvent.actionToString(keyEvent.getAction()) + + ";code=" + KeyEvent.keyCodeToString(keyEvent.getKeyCode()); + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); + } if (asSystemService) { mCb.onMediaButton(mContext.getPackageName(), Process.myPid(), Process.SYSTEM_UID, createMediaButtonIntent(keyEvent), 0, null); diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 818b2b72ad0d..59e4e2c82bf8 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -25,6 +25,7 @@ import static com.android.server.media.MediaKeyDispatcher.isSingleTapOverridden; import static com.android.server.media.MediaKeyDispatcher.isTripleTapOverridden; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.INotificationManager; import android.app.KeyguardManager; import android.app.PendingIntent; @@ -113,6 +114,8 @@ public class MediaSessionService extends SystemService implements Monitor { + /* Buffer for delayed delivery of key event */ 50; private static final int MULTI_TAP_TIMEOUT = ViewConfiguration.getMultiPressTimeout(); + private static final int TEMP_ALLOW_WHILE_IN_USE_PERMISSION_IN_FGS_DURATION_MS = 10_000; + private final Context mContext; private final SessionManagerImpl mSessionManagerImpl; private final MessageHandler mHandler = new MessageHandler(); @@ -132,6 +135,7 @@ public class MediaSessionService extends SystemService implements Monitor { private final List<Session2TokensListenerRecord> mSession2TokensListenerRecords = new ArrayList<>(); + private ActivityManagerInternal mActivityManagerInternal; private KeyguardManager mKeyguardManager; private AudioManagerInternal mAudioManagerInternal; private ContentResolver mContentResolver; @@ -166,6 +170,7 @@ public class MediaSessionService extends SystemService implements Monitor { public void onStart() { publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl); Watchdog.getInstance().addMonitor(this); + mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class); mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance(mContext); @@ -491,6 +496,25 @@ public class MediaSessionService extends SystemService implements Monitor { throw new IllegalArgumentException("packageName is not owned by the calling process"); } + void tempAllowlistTargetPkgIfPossible(int targetUid, String targetPackage, + int callingPid, int callingUid, String callingPackage, String reason) { + final long token = Binder.clearCallingIdentity(); + try { + enforcePackageName(callingPackage, callingUid); + if (targetUid != callingUid + && mActivityManagerInternal.canAllowWhileInUsePermissionInFgs(callingPid, + callingUid, callingPackage)) { + Log.d(TAG, "tempAllowlistTargetPkgIfPossible callingPackage:" + + callingPackage + " targetPackage:" + targetPackage + + " reason:" + reason); + mActivityManagerInternal.tempAllowWhileInUsePermissionInFgs(targetUid, + TEMP_ALLOW_WHILE_IN_USE_PERMISSION_IN_FGS_DURATION_MS); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + /** * Checks a caller's authorization to register an IRemoteControlDisplay. * Authorization is granted if one of the following is true: |