diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java index d9a49aa523654..35159763332ef 100644 --- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java +++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java @@ -29,6 +29,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.UserHandleAware; import android.content.Context; import java.lang.annotation.Retention; @@ -295,6 +296,11 @@ public class PowerExemptionManager { * @hide */ public static final int REASON_SHELL = 316; + /** + * Media session callbacks. + * @hide + */ + public static final int REASON_MEDIA_SESSION_CALLBACK = 317; /** * The list of BG-FGS-Launch and temp-allow-list reason code. @@ -354,6 +360,7 @@ public class PowerExemptionManager { REASON_EVENT_SMS, REASON_EVENT_MMS, REASON_SHELL, + REASON_MEDIA_SESSION_CALLBACK, }) @Retention(RetentionPolicy.SOURCE) public @interface ReasonCode {} @@ -451,6 +458,7 @@ public class PowerExemptionManager { * @param reasonCode one of {@link ReasonCode}, use {@link #REASON_UNKNOWN} if not sure. * @param reason a optional human readable reason string, could be null or empty string. */ + @UserHandleAware @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void addToTemporaryAllowList(@NonNull String packageName, long durationMs, @ReasonCode int reasonCode, @Nullable String reason) { @@ -474,6 +482,7 @@ public class PowerExemptionManager { * used for logging purposes. Could be null or empty string. * @return The duration (in milliseconds) that the app is allow-listed for */ + @UserHandleAware @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long addToTemporaryAllowListForEvent(@NonNull String packageName, @AllowListEvent int event, @ReasonCode int reasonCode, @Nullable String reason) { @@ -626,6 +635,8 @@ public class PowerExemptionManager { return "EVENT_MMS"; case REASON_SHELL: return "SHELL"; + case REASON_MEDIA_SESSION_CALLBACK: + return "MEDIA_SESSION_CALLBACK"; default: return "(unknown:" + reasonCode + ")"; } diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 119dcb63770d3..667fc60fb20b4 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -2692,7 +2692,7 @@ public class DeviceIdleController extends SystemService void addPowerSaveTempAllowlistAppChecked(String packageName, long duration, int userId, @ReasonCode int reasonCode, @Nullable String reason) throws RemoteException { - getContext().enforceCallingPermission( + getContext().enforceCallingOrSelfPermission( Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, "No permission to change device idle whitelist"); final int callingUid = Binder.getCallingUid(); @@ -2715,7 +2715,7 @@ public class DeviceIdleController extends SystemService void removePowerSaveTempAllowlistAppChecked(String packageName, int userId) throws RemoteException { - getContext().enforceCallingPermission( + getContext().enforceCallingOrSelfPermission( Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, "No permission to change device idle whitelist"); final int callingUid = Binder.getCallingUid(); diff --git a/services/api/current.txt b/services/api/current.txt index 7e8f7a20bd647..a3e6715207536 100644 --- a/services/api/current.txt +++ b/services/api/current.txt @@ -88,6 +88,14 @@ package com.android.server { } +package com.android.server.am { + + public interface ActivityManagerLocal { + method public boolean canStartForegroundService(int, int, @NonNull String); + } + +} + package com.android.server.role { public interface RoleServicePlatformHelper { diff --git a/services/api/non-updatable-current.txt b/services/api/non-updatable-current.txt index 3c72d38927bce..f01c1824116cb 100644 --- a/services/api/non-updatable-current.txt +++ b/services/api/non-updatable-current.txt @@ -35,6 +35,14 @@ package com.android.server { } +package com.android.server.am { + + public interface ActivityManagerLocal { + method public boolean canStartForegroundService(int, int, @NonNull String); + } + +} + package com.android.server.role { public interface RoleServicePlatformHelper { diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 673749c083182..14f4d02cbd487 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -5409,16 +5409,28 @@ public final class ActiveServices { } } + boolean canStartForegroundServiceLocked(int callingPid, int callingUid, String callingPackage) { + if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) { + return true; + } + final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked( + callingPackage, callingPid, callingUid, null /* serviceRecord */, + false /* allowBackgroundActivityStarts */); + final @ReasonCode int allowStartFgs = shouldAllowFgsStartForegroundLocked( + allowWhileInUse, callingPid, callingUid, callingPackage, null /* targetService */); + return allowStartFgs != REASON_DENIED; + } + /** * Should allow while-in-use permissions in FGS or not. * A typical BG started FGS is not allowed to have while-in-use permissions. * @param callingPackage caller app's package name. * @param callingUid caller app's uid. - * @param r the service to start. + * @param targetService the service to start. * @return {@link ReasonCode} */ private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage, - int callingPid, int callingUid, ServiceRecord r, + int callingPid, int callingUid, @Nullable ServiceRecord targetService, boolean allowBackgroundActivityStarts) { int ret = REASON_DENIED; @@ -5480,8 +5492,8 @@ public final class ActiveServices { } if (ret == REASON_DENIED) { - if (r.app != null) { - ActiveInstrumentation instr = r.app.getActiveInstrumentation(); + if (targetService != null && targetService.app != null) { + ActiveInstrumentation instr = targetService.app.getActiveInstrumentation(); if (instr != null && instr.mHasBackgroundActivityStartsPermission) { ret = REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION; } @@ -5527,16 +5539,44 @@ public final class ActiveServices { private @ReasonCode int shouldAllowFgsStartForegroundLocked( @ReasonCode int allowWhileInUse, String callingPackage, int callingPid, int callingUid, Intent intent, ServiceRecord r, boolean allowBackgroundActivityStarts) { - int ret = allowWhileInUse; FgsStartTempAllowList.TempFgsAllowListEntry tempAllowListReason = r.mInfoTempFgsAllowListReason = mAm.isAllowlistedForFgsStartLOSP(callingUid); + int ret = shouldAllowFgsStartForegroundLocked(allowWhileInUse, callingPid, callingUid, + callingPackage, r); - final StringBuilder sb = new StringBuilder(64); final int uidState = mAm.getUidStateLocked(callingUid); + final String debugInfo = + "[callingPackage: " + callingPackage + + "; callingUid: " + callingUid + + "; uidState: " + ProcessList.makeProcStateString(uidState) + + "; intent: " + intent + + "; code:" + reasonCodeToString(ret) + + "; tempAllowListReason:<" + + (tempAllowListReason == null ? null : + (tempAllowListReason.mReason + + ",reasonCode:" + + reasonCodeToString(tempAllowListReason.mReasonCode) + + ",duration:" + tempAllowListReason.mDuration + + ",callingUid:" + tempAllowListReason.mCallingUid)) + + ">" + + "; targetSdkVersion:" + r.appInfo.targetSdkVersion + + "]"; + if (!debugInfo.equals(r.mInfoAllowStartForeground)) { + r.mLoggedInfoAllowStartForeground = false; + r.mInfoAllowStartForeground = debugInfo; + } + return ret; + } + + private @ReasonCode int shouldAllowFgsStartForegroundLocked(@ReasonCode int allowWhileInUse, + int callingPid, int callingUid, String callingPackage, + @Nullable ServiceRecord targetService) { + int ret = allowWhileInUse; + if (ret == REASON_DENIED) { + final int uidState = mAm.getUidStateLocked(callingUid); // Is the calling UID at PROCESS_STATE_TOP or above? if (uidState <= PROCESS_STATE_TOP) { - sb.append("uidState=").append(uidState); ret = getReasonCodeFromProcState(uidState); } } @@ -5608,8 +5648,10 @@ public final class ActiveServices { // NOTE this should always be the last check. if (ret == REASON_DENIED) { - if (isPackageExemptedFromFgsRestriction(r.appInfo.packageName, r.appInfo.uid) - || isPackageExemptedFromFgsRestriction(callingPackage, callingUid)) { + if (isPackageExemptedFromFgsRestriction(callingPackage, callingUid)) { + ret = REASON_EXEMPTED_PACKAGE; + } else if (targetService != null && isPackageExemptedFromFgsRestriction( + targetService.appInfo.packageName, targetService.appInfo.uid)) { ret = REASON_EXEMPTED_PACKAGE; } } @@ -5622,28 +5664,6 @@ public final class ActiveServices { } } - final String debugInfo = - "[callingPackage: " + callingPackage - + "; callingUid: " + callingUid - + "; uidState: " + ProcessList.makeProcStateString(uidState) - + "; intent: " + intent - + "; code:" + reasonCodeToString(ret) - + "; tempAllowListReason:<" + - (tempAllowListReason == null ? null : - (tempAllowListReason.mReason - + ",reasonCode:" - + reasonCodeToString(tempAllowListReason.mReasonCode) - + ",duration:" + tempAllowListReason.mDuration - + ",callingUid:" + tempAllowListReason.mCallingUid)) - + ">" - + "; extra:" + sb.toString() - + "; targetSdkVersion:" + r.appInfo.targetSdkVersion - + "]"; - if (!debugInfo.equals(r.mInfoAllowStartForeground)) { - r.mLoggedInfoAllowStartForeground = false; - r.mInfoAllowStartForeground = debugInfo; - } - return ret; } diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java new file mode 100644 index 0000000000000..cd4180e464282 --- /dev/null +++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java @@ -0,0 +1,39 @@ +/* + * 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 android.annotation.NonNull; +import android.annotation.SystemApi; + +/** + * Interface for in-process calls into + * {@link android.content.Context#ACTIVITY_SERVICE ActivityManager system service}. + * + * @hide + */ +@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) +public interface ActivityManagerLocal { + /** + * Checks whether an app will be able to start a foreground service or not. + * + * @param pid The process id belonging to the app to be checked. + * @param uid The UID of the app to be checked. + * @param packageName The package name of the app to be checked. + * @return whether the app will be able to start a foreground service or not. + */ + boolean canStartForegroundService(int pid, int uid, @NonNull String packageName); +} diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 06a1abb726076..83cbf66ecaea4 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -345,6 +345,7 @@ import com.android.server.DeviceIdleInternal; import com.android.server.DisplayThread; import com.android.server.IntentResolver; import com.android.server.IoThread; +import com.android.server.LocalManagerRegistry; import com.android.server.LocalServices; import com.android.server.LockGuard; import com.android.server.NetworkManagementInternal; @@ -2327,6 +2328,8 @@ public class ActivityManagerService extends IActivityManager.Stub mAppOpsService.publish(); Slog.d("AppOps", "AppOpsService published"); LocalServices.addService(ActivityManagerInternal.class, mInternal); + LocalManagerRegistry.addManager(ActivityManagerLocal.class, + (ActivityManagerLocal) mInternal); mActivityTaskManager.onActivityManagerInternalAdded(); mPendingIntentController.onActivityManagerInternalAdded(); mAppProfiler.onActivityManagerInternalAdded(); @@ -15086,7 +15089,8 @@ public class ActivityManagerService extends IActivityManager.Stub } @VisibleForTesting - public final class LocalService extends ActivityManagerInternal { + public final class LocalService extends ActivityManagerInternal + implements ActivityManagerLocal { @Override public String checkContentProviderAccess(String authority, int userId) { return mCpHelper.checkContentProviderAccess(authority, userId); @@ -16008,6 +16012,13 @@ public class ActivityManagerService extends IActivityManager.Stub public void unregisterAnrController(AnrController controller) { mActivityTaskManager.unregisterAnrController(controller); } + + @Override + public boolean canStartForegroundService(int pid, int uid, @NonNull String packageName) { + synchronized (ActivityManagerService.this) { + return mServices.canStartForegroundServiceLocked(pid, uid, packageName); + } + } } long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) { diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 0a074e1e7c500..b10d56b62acc2 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -1068,6 +1068,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); @@ -1085,6 +1091,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 23d84298b41e6..46ece74180fe4 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -63,6 +63,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Message; +import android.os.PowerExemptionManager; import android.os.PowerManager; import android.os.Process; import android.os.RemoteCallbackList; @@ -82,9 +83,11 @@ import android.view.ViewConfiguration; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.server.LocalManagerRegistry; import com.android.server.SystemService; import com.android.server.Watchdog; import com.android.server.Watchdog.Monitor; +import com.android.server.am.ActivityManagerLocal; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -114,6 +117,13 @@ public class MediaSessionService extends SystemService implements Monitor { */ private static final String MEDIA_BUTTON_RECEIVER = "media_button_receiver"; + /** + * Denotes the duration during which an app receiving a media session callback will be + * exempted from FGS-from-BG restriction and so will be allowed to start an FGS even if it is + * in the background state while it receives a media session callback. + */ + private static final long FGS_STARTS_TEMP_ALLOWLIST_DURATION_MS = 10_000; + private final Context mContext; private final SessionManagerImpl mSessionManagerImpl; private final MessageHandler mHandler = new MessageHandler(); @@ -136,6 +146,7 @@ public class MediaSessionService extends SystemService implements Monitor { private KeyguardManager mKeyguardManager; private AudioManager mAudioManager; private boolean mHasFeatureLeanback; + private ActivityManagerLocal mActivityManagerLocal; // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile) // It's always not null after the MediaSessionService is started. @@ -219,6 +230,8 @@ public class MediaSessionService extends SystemService implements Monitor { final IntentFilter filter = new IntentFilter( NotificationManager.ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED); mContext.registerReceiver(mNotificationListenerEnabledChangedReceiver, filter); + + mActivityManagerLocal = LocalManagerRegistry.getManager(ActivityManagerLocal.class); } @Override @@ -538,6 +551,26 @@ 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 && mActivityManagerLocal.canStartForegroundService( + callingPid, callingUid, callingPackage)) { + final Context userContext = mContext.createContextAsUser( + UserHandle.of(UserHandle.getUserId(targetUid)), /* flags= */ 0); + final PowerExemptionManager powerExemptionManager = userContext.getSystemService( + PowerExemptionManager.class); + powerExemptionManager.addToTemporaryAllowList(targetPackage, + FGS_STARTS_TEMP_ALLOWLIST_DURATION_MS, + PowerExemptionManager.REASON_MEDIA_SESSION_CALLBACK, reason); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + /** * Checks a caller's authorization to register an IRemoteControlDisplay. * Authorization is granted if one of the following is true: