diff options
10 files changed, 195 insertions, 32 deletions
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 1edd7f5e429d..af3da0cbf5ee 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -243,6 +243,8 @@ public abstract class ActivityManagerInternal { public abstract void ensureBootCompleted(); public abstract void updateOomLevelsForDisplay(int displayId); public abstract boolean isActivityStartsLoggingEnabled(); + /** Returns true if the background activity starts is enabled. */ + public abstract boolean isBackgroundActivityStartsEnabled(); public abstract void reportCurKeyguardUsageEvent(boolean keyguardShowing); /** Input dispatch timeout to a window, start the ANR process. */ diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 848b041007c2..83be63c6cb22 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -11035,6 +11035,16 @@ public final class Settings { = "activity_starts_logging_enabled"; /** + * Feature flag to enable or disable the background activity starts. + * When disabled, apps aren't allowed to start activities unless they're in the foreground. + * Type: int (0 for false, 1 for true) + * Default: 1 + * @hide + */ + public static final String BACKGROUND_ACTIVITY_STARTS_ENABLED = + "background_activity_starts_enabled"; + + /** * @hide * @see com.android.server.appbinding.AppBindingConstants */ diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 8f583307adab..21e7b9c64c5c 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -126,6 +126,7 @@ public class SettingsBackupTest { Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS, Settings.Global.AUTOFILL_SMART_SUGGESTION_EMULATION_FLAGS, Settings.Global.AUTOMATIC_POWER_SAVER_MODE, + Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED, Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD, Settings.Global.BATTERY_DISCHARGE_THRESHOLD, Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS, diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 5c77f0a3ad47..8571ae6b07f6 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -16,6 +16,8 @@ package com.android.server.am; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK; + import android.content.ContentResolver; import android.database.ContentObserver; import android.net.Uri; @@ -26,8 +28,6 @@ import android.util.Slog; import java.io.PrintWriter; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK; - /** * Settings constants that can modify the activity manager's behavior. */ @@ -222,6 +222,10 @@ final class ActivityManagerConstants extends ContentObserver { // Controlled by Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED volatile boolean mFlagActivityStartsLoggingEnabled; + // Indicates whether the background activity starts is enabled. + // Controlled by Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED + volatile boolean mFlagBackgroundActivityStartsEnabled; + private final ActivityManagerService mService; private ContentResolver mResolver; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -256,6 +260,10 @@ final class ActivityManagerConstants extends ContentObserver { private static final Uri ACTIVITY_STARTS_LOGGING_ENABLED_URI = Settings.Global.getUriFor( Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED); + private static final Uri BACKGROUND_ACTIVITY_STARTS_ENABLED_URI = + Settings.Global.getUriFor( + Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED); + public ActivityManagerConstants(ActivityManagerService service, Handler handler) { super(handler); mService = service; @@ -266,8 +274,10 @@ final class ActivityManagerConstants extends ContentObserver { mResolver = resolver; mResolver.registerContentObserver(ACTIVITY_MANAGER_CONSTANTS_URI, false, this); mResolver.registerContentObserver(ACTIVITY_STARTS_LOGGING_ENABLED_URI, false, this); + mResolver.registerContentObserver(BACKGROUND_ACTIVITY_STARTS_ENABLED_URI, false, this); updateConstants(); updateActivityStartsLoggingEnabled(); + updateBackgroundActivityStartsEnabled(); } public void setOverrideMaxCachedProcesses(int value) { @@ -290,6 +300,8 @@ final class ActivityManagerConstants extends ContentObserver { updateConstants(); } else if (ACTIVITY_STARTS_LOGGING_ENABLED_URI.equals(uri)) { updateActivityStartsLoggingEnabled(); + } else if (BACKGROUND_ACTIVITY_STARTS_ENABLED_URI.equals(uri)) { + updateBackgroundActivityStartsEnabled(); } } @@ -373,6 +385,11 @@ final class ActivityManagerConstants extends ContentObserver { Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED, 0) == 1; } + private void updateBackgroundActivityStartsEnabled() { + mFlagBackgroundActivityStartsEnabled = Settings.Global.getInt(mResolver, + Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED, 1) == 1; + } + private void updateMaxCachedProcesses() { CUR_MAX_CACHED_PROCESSES = mOverrideMaxCachedProcesses < 0 ? MAX_CACHED_PROCESSES : mOverrideMaxCachedProcesses; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 80e73132f542..b62f648956dd 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -19204,6 +19204,10 @@ public class ActivityManagerService extends IActivityManager.Stub return mConstants.mFlagActivityStartsLoggingEnabled; } + public boolean isBackgroundActivityStartsEnabled() { + return mConstants.mFlagBackgroundActivityStartsEnabled; + } + public void reportCurKeyguardUsageEvent(boolean keyguardShowing) { synchronized(ActivityManagerService.this) { ActivityManagerService.this.reportGlobalUsageEventLocked(keyguardShowing diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index 1c08d039207b..7c7553f34a3a 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -850,9 +850,10 @@ class ActivityMetricsLogger { builder.addTaggedData(FIELD_TARGET_UID_HAS_ANY_VISIBLE_WINDOW, targetUidHasAnyVisibleWindow ? 1 : 0); builder.addTaggedData(FIELD_TARGET_WHITELIST_TAG, targetWhitelistTag); - builder.addTaggedData(FIELD_TARGET_SHORT_COMPONENT_NAME, r.shortComponentName); builder.addTaggedData(FIELD_COMING_FROM_PENDING_INTENT, comingFromPendingIntent ? 1 : 0); - builder.addTaggedData(FIELD_INTENT_ACTION, intent.getAction()); + if (intent != null) { + builder.addTaggedData(FIELD_INTENT_ACTION, intent.getAction()); + } if (callerApp != null) { builder.addTaggedData(FIELD_PROCESS_RECORD_PROCESS_NAME, callerApp.mName); builder.addTaggedData(FIELD_PROCESS_RECORD_CUR_PROC_STATE, @@ -881,29 +882,34 @@ class ActivityMetricsLogger { (nowUptime - callerApp.getWhenUnimportant())); } } - builder.addTaggedData(FIELD_ACTIVITY_RECORD_LAUNCH_MODE, r.info.launchMode); - builder.addTaggedData(FIELD_ACTIVITY_RECORD_TARGET_ACTIVITY, r.info.targetActivity); - builder.addTaggedData(FIELD_ACTIVITY_RECORD_FLAGS, r.info.flags); - builder.addTaggedData(FIELD_ACTIVITY_RECORD_REAL_ACTIVITY, r.realActivity.toShortString()); - builder.addTaggedData(FIELD_ACTIVITY_RECORD_SHORT_COMPONENT_NAME, r.shortComponentName); - builder.addTaggedData(FIELD_ACTIVITY_RECORD_PROCESS_NAME, r.processName); - builder.addTaggedData(FIELD_ACTIVITY_RECORD_IS_FULLSCREEN, r.fullscreen ? 1 : 0); - builder.addTaggedData(FIELD_ACTIVITY_RECORD_IS_NO_DISPLAY, r.noDisplay ? 1 : 0); - if (r.lastVisibleTime != 0) { - builder.addTaggedData(FIELD_ACTIVITY_RECORD_MILLIS_SINCE_LAST_VISIBLE, - (nowUptime - r.lastVisibleTime)); - } - if (r.resultTo != null) { - builder.addTaggedData(FIELD_ACTIVITY_RECORD_RESULT_TO_PKG_NAME, r.resultTo.packageName); - builder.addTaggedData(FIELD_ACTIVITY_RECORD_RESULT_TO_SHORT_COMPONENT_NAME, - r.resultTo.shortComponentName); - } - builder.addTaggedData(FIELD_ACTIVITY_RECORD_IS_VISIBLE, r.visible ? 1 : 0); - builder.addTaggedData(FIELD_ACTIVITY_RECORD_IS_VISIBLE_IGNORING_KEYGUARD, - r.visibleIgnoringKeyguard ? 1 : 0); - if (r.lastLaunchTime != 0) { - builder.addTaggedData(FIELD_ACTIVITY_RECORD_MILLIS_SINCE_LAST_LAUNCH, - (nowUptime - r.lastLaunchTime)); + if (r != null) { + builder.addTaggedData(FIELD_TARGET_SHORT_COMPONENT_NAME, r.shortComponentName); + builder.addTaggedData(FIELD_ACTIVITY_RECORD_LAUNCH_MODE, r.info.launchMode); + builder.addTaggedData(FIELD_ACTIVITY_RECORD_TARGET_ACTIVITY, r.info.targetActivity); + builder.addTaggedData(FIELD_ACTIVITY_RECORD_FLAGS, r.info.flags); + builder.addTaggedData(FIELD_ACTIVITY_RECORD_REAL_ACTIVITY, + r.realActivity.toShortString()); + builder.addTaggedData(FIELD_ACTIVITY_RECORD_SHORT_COMPONENT_NAME, r.shortComponentName); + builder.addTaggedData(FIELD_ACTIVITY_RECORD_PROCESS_NAME, r.processName); + builder.addTaggedData(FIELD_ACTIVITY_RECORD_IS_FULLSCREEN, r.fullscreen ? 1 : 0); + builder.addTaggedData(FIELD_ACTIVITY_RECORD_IS_NO_DISPLAY, r.noDisplay ? 1 : 0); + if (r.lastVisibleTime != 0) { + builder.addTaggedData(FIELD_ACTIVITY_RECORD_MILLIS_SINCE_LAST_VISIBLE, + (nowUptime - r.lastVisibleTime)); + } + if (r.resultTo != null) { + builder.addTaggedData(FIELD_ACTIVITY_RECORD_RESULT_TO_PKG_NAME, + r.resultTo.packageName); + builder.addTaggedData(FIELD_ACTIVITY_RECORD_RESULT_TO_SHORT_COMPONENT_NAME, + r.resultTo.shortComponentName); + } + builder.addTaggedData(FIELD_ACTIVITY_RECORD_IS_VISIBLE, r.visible ? 1 : 0); + builder.addTaggedData(FIELD_ACTIVITY_RECORD_IS_VISIBLE_IGNORING_KEYGUARD, + r.visibleIgnoringKeyguard ? 1 : 0); + if (r.lastLaunchTime != 0) { + builder.addTaggedData(FIELD_ACTIVITY_RECORD_MILLIS_SINCE_LAST_LAUNCH, + (nowUptime - r.lastLaunchTime)); + } } mMetricsLogger.write(builder); } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 90f3ff84a027..1735ef89918a 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -98,6 +98,7 @@ import android.graphics.Rect; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; @@ -108,6 +109,7 @@ import android.text.TextUtils; import android.util.EventLog; import android.util.Pools.SynchronizedPool; import android.util.Slog; +import android.widget.Toast; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.HeavyWeightSwitcherActivity; @@ -731,6 +733,12 @@ class ActivityStarter { abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid, callingPid, resolvedType, aInfo.applicationInfo); + // not sure if we need to create START_ABORTED_BACKGROUND so for now piggybacking + // on START_ABORTED + if (!abort) { + abort |= shouldAbortBackgroundActivityStart(callingUid, callingPackage, callerApp); + } + // Merge the two options bundles, while realCallerOptions takes precedence. ActivityOptions checkedOptions = options != null ? options.getOptions(intent, aInfo, callerApp, mSupervisor) : null; @@ -774,6 +782,8 @@ class ActivityStarter { // We pretend to the caller that it was really started, but // they will just get a cancel result. ActivityOptions.abort(checkedOptions); + maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp, + /* r= */ null, originatingPendingIntent, /* abortedStart= */ true); return START_ABORTED; } @@ -866,19 +876,50 @@ class ActivityStarter { mController.doPendingActivityLaunches(false); maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp, r, - originatingPendingIntent); + originatingPendingIntent, /* abortedStart= */ false); return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags, true /* doResume */, checkedOptions, inTask, outActivity); } + private boolean shouldAbortBackgroundActivityStart(int callingUid, final String callingPackage, + WindowProcessController callerApp) { + if (mService.isBackgroundActivityStartsEnabled()) { + return false; + } + // don't abort for the most important UIDs + if (callingUid == Process.ROOT_UID || callingUid == Process.SYSTEM_UID) { + return false; + } + // don't abort if the callerApp has any visible activity + if (callerApp != null && callerApp.hasForegroundActivities()) { + return false; + } + // don't abort if the callingUid's process is important enough + if (mService.getUidStateLocked(callingUid) <= ActivityManager.PROCESS_STATE_TOP) { + return false; + } + // don't abort if the callingUid has any visible window + if (mService.mWindowManager.isAnyWindowVisibleForUid(callingUid)) { + return false; + } + // anything that has fallen through will currently be aborted + // TODO: remove this toast after feature development is done + mService.mUiHandler.post(() -> { + Toast.makeText(mService.mContext, + "Blocking background activity start for " + callingPackage, + Toast.LENGTH_SHORT).show(); + }); + return true; + } + private void maybeLogActivityStart(int callingUid, String callingPackage, int realCallingUid, Intent intent, WindowProcessController callerApp, ActivityRecord r, - PendingIntentRecord originatingPendingIntent) { + PendingIntentRecord originatingPendingIntent, boolean abortedStart) { boolean callerAppHasForegroundActivity = callerApp != null && callerApp.hasForegroundActivities(); if (!mService.isActivityStartsLoggingEnabled() || callerAppHasForegroundActivity - || r == null) { + || (!abortedStart && r == null)) { // skip logging in this case return; } @@ -894,8 +935,8 @@ class ActivityStarter { final boolean realCallingUidHasAnyVisibleWindow = (callingUid == realCallingUid) ? callingUidHasAnyVisibleWindow : mService.mWindowManager.isAnyWindowVisibleForUid(realCallingUid); - final String targetPackage = r.packageName; - final int targetUid = (r.appInfo != null) ? r.appInfo.uid : -1; + final String targetPackage = (r != null) ? r.packageName : null; + final int targetUid = (r!= null) ? ((r.appInfo != null) ? r.appInfo.uid : -1) : -1; final int targetUidProcState = mService.getUidStateLocked(targetUid); final boolean targetUidHasAnyVisibleWindow = (targetUid != -1) ? mService.mWindowManager.isAnyWindowVisibleForUid(targetUid) diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index d0e3fb47730e..054b105fbfa4 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -5016,6 +5016,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return mAmInternal.isActivityStartsLoggingEnabled(); } + boolean isBackgroundActivityStartsEnabled() { + return mAmInternal.isBackgroundActivityStartsEnabled(); + } + void enableScreenAfterBoot(boolean booted) { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_ENABLE_SCREEN, SystemClock.uptimeMillis()); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 50aa541a9549..f6ff05b077d1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManager.START_ABORTED; import static android.app.ActivityManager.START_CLASS_NOT_FOUND; import static android.app.ActivityManager.START_DELIVERED_TO_TOP; @@ -69,6 +70,7 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageManagerInternal; import android.graphics.Rect; import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.service.voice.IVoiceInteractionSession; @@ -110,6 +112,7 @@ public class ActivityStarterTests extends ActivityTestsBase { private static final int FAKE_CALLING_UID = 666; private static final int FAKE_REAL_CALLING_UID = 667; private static final String FAKE_CALLING_PACKAGE = "com.whatever.dude"; + private static final int UNIMPORTANT_UID = 12345; @Before public void setUp() throws Exception { @@ -551,6 +554,79 @@ public class ActivityStarterTests extends ActivityTestsBase { } /** + * This test ensures that unsupported usecases aren't aborted when background starts are + * allowed. + */ + @Test + public void testBackgroundActivityStartsAllowed_noStartsAborted() { + doReturn(true).when(mService).isBackgroundActivityStartsEnabled(); + + runAndVerifyBackgroundActivityStartsSubtest("allowed_noStartsAborted", + false, UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1, false); + } + + /** + * This test ensures that unsupported usecases are aborted when background starts are + * disallowed. + */ + @Test + public void testBackgroundActivityStartsDisallowed_unsupportedStartsAborted() { + doReturn(false).when(mService).isBackgroundActivityStartsEnabled(); + + runAndVerifyBackgroundActivityStartsSubtest("disallowed_unsupportedUsecase_aborted", + true, UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1, false); + } + + /** + * This test ensures that supported usecases aren't aborted when background starts are + * disallowed. + * The scenarios each have only one condidion that makes them supported. + */ + @Test + public void testBackgroundActivityStartsDisallowed_supportedStartsNotAborted() { + doReturn(false).when(mService).isBackgroundActivityStartsEnabled(); + + runAndVerifyBackgroundActivityStartsSubtest("disallowed_rootUid_notAborted", + false, Process.ROOT_UID, false, PROCESS_STATE_TOP + 1, false); + runAndVerifyBackgroundActivityStartsSubtest("disallowed_systemUid_notAborted", + false, Process.SYSTEM_UID, false, PROCESS_STATE_TOP + 1, false); + runAndVerifyBackgroundActivityStartsSubtest("disallowed_hasVisibleWindow_notAborted", + false, UNIMPORTANT_UID, true, PROCESS_STATE_TOP + 1, false); + runAndVerifyBackgroundActivityStartsSubtest("disallowed_processStateTop_notAborted", + false, UNIMPORTANT_UID, false, PROCESS_STATE_TOP, false); + runAndVerifyBackgroundActivityStartsSubtest("disallowed_hasForegroundActivities_notAborted", + false, UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1, true); + } + + private void runAndVerifyBackgroundActivityStartsSubtest(String name, boolean shouldHaveAborted, + int testCallingUid, boolean hasVisibleWindow, int procState, + boolean hasForegroundActivities) { + // window visibility + doReturn(hasVisibleWindow).when(mService.mWindowManager).isAnyWindowVisibleForUid( + testCallingUid); + // process importance + doReturn(procState).when(mService).getUidStateLocked(testCallingUid); + // foreground activities + final IApplicationThread caller = mock(IApplicationThread.class); + final ApplicationInfo ai = new ApplicationInfo(); + ai.uid = testCallingUid; + final WindowProcessController callerApp = + new WindowProcessController(mService, ai, null, testCallingUid, -1, null, null); + callerApp.setHasForegroundActivities(hasForegroundActivities); + doReturn(callerApp).when(mService).getProcessController(caller); + + final ActivityOptions options = spy(ActivityOptions.makeBasic()); + ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK).setCaller(caller) + .setCallingUid(testCallingUid).setActivityOptions(new SafeActivityOptions(options)); + + final int result = starter.setReason("testBackgroundActivityStarts_" + name).execute(); + + assertEquals(ActivityStarter.getExternalResult( + shouldHaveAborted ? START_ABORTED : START_SUCCESS), result); + verify(options, times(shouldHaveAborted ? 1 : 0)).abort(); + } + + /** * This test ensures that when starting an existing single task activity on secondary display * which is not the top focused display, it should deliver new intent to the activity and not * create a new stack. diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java index c2ab3acd4933..cc0ae948b054 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -402,6 +402,8 @@ class ActivityTestsBase { spyOn(getLifecycleManager()); spyOn(getLockTaskController()); doReturn(mock(IPackageManager.class)).when(this).getPackageManager(); + // allow background activity starts by default + doReturn(true).when(this).isBackgroundActivityStartsEnabled(); } void setActivityManagerService(IntentFirewall intentFirewall, |