From ebfad50cc1450843c169e52e9c9a869970d35e98 Mon Sep 17 00:00:00 2001 From: Hui Yu Date: Sun, 14 Mar 2021 12:37:29 -0700 Subject: [PATCH] Allow FGS started from MediaButtonReceiver to have while-in-use permissions. 1. Create FgsWhileInUseTempAllowList, the uid on this list is allowed to have while-in-use permission when the FGS is started from background for a duration of time. 2. When media button is pressed, the targetUid is added to FgsWhileInUseTempAllowList for the duration of 10 seconds, this way the targetUid can start FGS from background and have while-in-use permission. 3. When checking for ProcessRecord. mAllowBackgroundActivityStartsTokens, it is the caller app's ProcessRecord should be checked instead of the service's ProcessRecord. Bug: 182481312 Test: use b/167998084 reproduce steps, use Line app to make a voip call, the receiver side screen is unlocked, the receiver side uses bluetooth headset button to pick up the call, "dumpsys activity -a" shows the FGS "com.linecorp.voip.core.common.notification.VoipNotificationCommand" has "allowWhileInUsePermissionInFgs=true" and "isForeground=true". Change-Id: Ic80aefe4202fe2210881bb8eea24b6e34cb1e7d0 Merged-In: I32a14d675550cd262976bb8e16a48a070387d318 --- .../android/app/ActivityManagerInternal.java | 7 ++ .../com/android/server/am/ActiveServices.java | 23 ++++-- .../server/am/ActivityManagerService.java | 25 ++++++ .../server/am/FgsWhileInUseTempAllowList.java | 82 +++++++++++++++++++ .../android/server/appop/AppOpsService.java | 8 ++ .../media/MediaButtonReceiverHolder.java | 9 +- .../server/media/MediaSessionRecord.java | 12 +++ .../server/media/MediaSessionService.java | 24 ++++++ 8 files changed, 183 insertions(+), 7 deletions(-) create mode 100644 services/core/java/com/android/server/am/FgsWhileInUseTempAllowList.java diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index a5965bc7f85fd..eb2a40f4a7ddf 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 dd0e1f6458f99..2461a9129f82a 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 5134f498928b4..bff563b67c896 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1261,6 +1261,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 */ @@ -19770,6 +19777,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 0000000000000..ed0f264049ef0 --- /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 902b2e2228f88..63b5af59c3c1a 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 6e655eafa0e99..d1eaaf86d7ca2 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 02b7582a8637c..348e9c122a9a3 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 803eb862e0f11..1a2743f73b49e 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; @@ -114,6 +115,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(); @@ -133,6 +136,7 @@ public class MediaSessionService extends SystemService implements Monitor { private final List mSession2TokensListenerRecords = new ArrayList<>(); + private ActivityManagerInternal mActivityManagerInternal; private KeyguardManager mKeyguardManager; private AudioManagerInternal mAudioManagerInternal; private ContentResolver mContentResolver; @@ -168,6 +172,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); @@ -493,6 +498,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: