diff --git a/core/api/system-current.txt b/core/api/system-current.txt index ebf2f061dce2f..49204e0c2098e 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -607,7 +607,10 @@ package android.app { method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean); method public void setDontSendToRestrictedApps(boolean); method @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(long); + method @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(int, long); method public android.os.Bundle toBundle(); + field public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0; // 0x0 + field public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1; // 0x1 } public class DownloadManager { diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index 298c45582174e..a16f6a84f18f1 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -16,11 +16,15 @@ package android.app; +import android.annotation.IntDef; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.os.Build; import android.os.Bundle; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Helper class for building an options Bundle that can be used with * {@link android.content.Context#sendBroadcast(android.content.Intent) @@ -30,6 +34,7 @@ import android.os.Bundle; @SystemApi public class BroadcastOptions { private long mTemporaryAppWhitelistDuration; + private @TempAllowListType int mTemporaryAppWhitelistType; private int mMinManifestReceiverApiLevel = 0; private int mMaxManifestReceiverApiLevel = Build.VERSION_CODES.CUR_DEVELOPMENT; private boolean mDontSendToRestrictedApps = false; @@ -42,6 +47,9 @@ public class BroadcastOptions { static final String KEY_TEMPORARY_APP_WHITELIST_DURATION = "android:broadcast.temporaryAppWhitelistDuration"; + static final String KEY_TEMPORARY_APP_WHITELIST_TYPE + = "android:broadcast.temporaryAppWhitelistType"; + /** * Corresponds to {@link #setMinManifestReceiverApiLevel}. */ @@ -66,6 +74,27 @@ public class BroadcastOptions { static final String KEY_ALLOW_BACKGROUND_ACTIVITY_STARTS = "android:broadcast.allowBackgroundActivityStarts"; + /** + * Allow the temp allowlist behavior, plus allow foreground service start from background. + */ + public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0; + /** + * Only allow the temp allowlist behavior, not allow foreground service start from + * background. + */ + public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1; + + /** + * The list of temp allowlist types. + * @hide + */ + @IntDef(flag = true, prefix = { "TEMPORARY_WHITELIST_TYPE_" }, value = { + TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface TempAllowListType {} + public static BroadcastOptions makeBasic() { BroadcastOptions opts = new BroadcastOptions(); return opts; @@ -77,6 +106,7 @@ public class BroadcastOptions { /** @hide */ public BroadcastOptions(Bundle opts) { mTemporaryAppWhitelistDuration = opts.getLong(KEY_TEMPORARY_APP_WHITELIST_DURATION); + mTemporaryAppWhitelistType = opts.getInt(KEY_TEMPORARY_APP_WHITELIST_TYPE); mMinManifestReceiverApiLevel = opts.getInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, 0); mMaxManifestReceiverApiLevel = opts.getInt(KEY_MAX_MANIFEST_RECEIVER_API_LEVEL, Build.VERSION_CODES.CUR_DEVELOPMENT); @@ -95,6 +125,22 @@ public class BroadcastOptions { android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(long duration) { mTemporaryAppWhitelistDuration = duration; + mTemporaryAppWhitelistType = TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED; + } + + /** + * Set a duration for which the system should temporary place an application on the + * power allowlist when this broadcast is being delivered to it, specify the temp allowlist + * type. + * @param type one of {@link TempAllowListType} + * @param duration the duration in milliseconds; 0 means to not place on allowlist. + */ + @RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, + android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, + android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) + public void setTemporaryAppWhitelistDuration(@TempAllowListType int type, long duration) { + mTemporaryAppWhitelistDuration = duration; + mTemporaryAppWhitelistType = type; } /** @@ -105,6 +151,14 @@ public class BroadcastOptions { return mTemporaryAppWhitelistDuration; } + /** + * Return {@link #mTemporaryAppWhitelistType}. + * @hide + */ + public @TempAllowListType int getTemporaryAppWhitelistType() { + return mTemporaryAppWhitelistType; + } + /** * Set the minimum target API level of receivers of the broadcast. If an application * is targeting an API level less than this, the broadcast will not be delivered to @@ -190,6 +244,9 @@ public class BroadcastOptions { if (mTemporaryAppWhitelistDuration > 0) { b.putLong(KEY_TEMPORARY_APP_WHITELIST_DURATION, mTemporaryAppWhitelistDuration); } + if (mTemporaryAppWhitelistType != 0) { + b.putInt(KEY_TEMPORARY_APP_WHITELIST_TYPE, mTemporaryAppWhitelistType); + } if (mMinManifestReceiverApiLevel != 0) { b.putInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, mMinManifestReceiverApiLevel); } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index c7e3b2344f54b..dffe0ba7ac788 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -50,6 +50,7 @@ import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.ApplicationExitInfo; +import android.app.BroadcastOptions; import android.app.IApplicationThread; import android.app.IServiceConnection; import android.app.Notification; @@ -3201,7 +3202,8 @@ public final class ActiveServices { + " for fg-service launch"); } mAm.tempWhitelistUidLocked(r.appInfo.uid, - SERVICE_START_FOREGROUND_TIMEOUT, "fg-service-launch"); + SERVICE_START_FOREGROUND_TIMEOUT, "fg-service-launch", + BroadcastOptions.TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED); } if (!mPendingServices.contains(r)) { @@ -5284,8 +5286,9 @@ public final class ActiveServices { if (ret == FGS_FEATURE_DENIED) { if (mAm.mConstants.mFlagFgsStartTempAllowListEnabled - && mAm.isOnDeviceIdleWhitelistLocked(r.appInfo.uid, false)) { - // uid is on DeviceIdleController's allowlist. + && mAm.isWhitelistedForFgsStartLocked(r.appInfo.uid)) { + // uid is on DeviceIdleController's user/system allowlist + // or AMS's FgsStartTempAllowList. ret = FGS_FEATURE_ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST; } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index bcd122d33f7da..c376e31fdc987 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1093,6 +1093,11 @@ public class ActivityManagerService extends IActivityManager.Stub final PendingTempWhitelists mPendingTempWhitelist = new PendingTempWhitelists(this); + /** + * The temp-allowlist that is allowed to start FGS from background. + */ + final FgsStartTempAllowList mFgsStartTempAllowList = new FgsStartTempAllowList(); + /** * Information about and control over application operations */ @@ -5510,6 +5515,12 @@ public class ActivityManagerService extends IActivityManager.Stub || mPendingTempWhitelist.indexOfKey(uid) >= 0; } + boolean isWhitelistedForFgsStartLocked(int uid) { + final int appId = UserHandle.getAppId(uid); + return Arrays.binarySearch(mDeviceIdleExceptIdleWhitelist, appId) >= 0 + || mFgsStartTempAllowList.isAllowed(uid); + } + /** * @return whitelist tag for a uid from mPendingTempWhitelist, null if not currently on * the whitelist @@ -15324,17 +15335,22 @@ public class ActivityManagerService extends IActivityManager.Stub } } - tempWhitelistUidLocked(targetUid, duration, tag); + tempWhitelistUidLocked(targetUid, duration, tag, + BroadcastOptions.TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED); } /** * Whitelists {@code targetUid} to temporarily bypass Power Save mode. */ @GuardedBy("this") - void tempWhitelistUidLocked(int targetUid, long duration, String tag) { + void tempWhitelistUidLocked(int targetUid, long duration, String tag, int type) { mPendingTempWhitelist.put(targetUid, new PendingTempWhitelist(targetUid, duration, tag)); setUidTempWhitelistStateLocked(targetUid, true); mUiHandler.obtainMessage(PUSH_TEMP_WHITELIST_UI_MSG).sendToTarget(); + + if (type == BroadcastOptions.TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED) { + mFgsStartTempAllowList.add(targetUid, duration); + } } void pushTempWhitelist() { diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 0877dd96c19fc..d2ee69e0e00df 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -896,7 +896,8 @@ public final class BroadcastQueue { return false; } - final void scheduleTempWhitelistLocked(int uid, long duration, BroadcastRecord r) { + final void scheduleTempWhitelistLocked(int uid, long duration, BroadcastRecord r, + @BroadcastOptions.TempAllowListType int type) { if (duration > Integer.MAX_VALUE) { duration = Integer.MAX_VALUE; } @@ -919,9 +920,9 @@ public final class BroadcastQueue { } if (DEBUG_BROADCAST) { Slog.v(TAG, "Broadcast temp whitelist uid=" + uid + " duration=" + duration - + " : " + b.toString()); + + " type=" + type + " : " + b.toString()); } - mService.tempWhitelistUidLocked(uid, duration, b.toString()); + mService.tempWhitelistUidLocked(uid, duration, b.toString(), type); } /** @@ -1318,7 +1319,8 @@ public final class BroadcastQueue { } if (brOptions != null && brOptions.getTemporaryAppWhitelistDuration() > 0) { scheduleTempWhitelistLocked(filter.owningUid, - brOptions.getTemporaryAppWhitelistDuration(), r); + brOptions.getTemporaryAppWhitelistDuration(), r, + brOptions.getTemporaryAppWhitelistType()); } } return; @@ -1610,7 +1612,8 @@ public final class BroadcastQueue { (brOptions != null && brOptions.getTemporaryAppWhitelistDuration() > 0); if (isActivityCapable) { scheduleTempWhitelistLocked(receiverUid, - brOptions.getTemporaryAppWhitelistDuration(), r); + brOptions.getTemporaryAppWhitelistDuration(), r, + brOptions.getTemporaryAppWhitelistType()); } // Broadcast is being executed, its package can't be stopped. diff --git a/services/core/java/com/android/server/am/FgsStartTempAllowList.java b/services/core/java/com/android/server/am/FgsStartTempAllowList.java new file mode 100644 index 0000000000000..1f9039392405e --- /dev/null +++ b/services/core/java/com/android/server/am/FgsStartTempAllowList.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020 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 temporarily allowed to start FGS from background. + */ +final class FgsStartTempAllowList { + 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(); + + FgsStartTempAllowList() { + } + + void add(int uid, long duration) { + if (duration <= 0) { + Slog.e(TAG_AM, "FgsStartTempAllowList bad duration:" + duration + " 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, "FgsStartTempAllowList 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 + duration; + if (existingExpirationTime == -1 || existingExpirationTime < expirationTime) { + mTempAllowListFgs.put(uid, expirationTime); + } + } + + boolean isAllowed(int uid) { + 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; + } + } +}