Merge "Apps can opt in to be stoppable in the task manager" into main
diff --git a/core/res/res/values/stoppable_fgs_system_apps.xml b/core/res/res/values/stoppable_fgs_system_apps.xml
new file mode 100644
index 0000000..165ff61
--- /dev/null
+++ b/core/res/res/values/stoppable_fgs_system_apps.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (C) 2024 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.
+ */
+-->
+<resources>
+ <!-- A list of system apps whose FGS can be stopped in the task manager. -->
+ <string-array translatable="false" name="stoppable_fgs_system_apps">
+ </string-array>
+ <!-- stoppable_fgs_system_apps which is supposed to be overridden by vendor -->
+ <string-array translatable="false" name="vendor_stoppable_fgs_system_apps">
+ </string-array>
+</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index aa08d5e..db81a3b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1306,6 +1306,8 @@
<java-symbol type="array" name="vendor_policy_exempt_apps" />
<java-symbol type="array" name="cloneable_apps" />
<java-symbol type="array" name="config_securityStatePackages" />
+ <java-symbol type="array" name="stoppable_fgs_system_apps" />
+ <java-symbol type="array" name="vendor_stoppable_fgs_system_apps" />
<java-symbol type="drawable" name="default_wallpaper" />
<java-symbol type="drawable" name="default_lock_wallpaper" />
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 3bf3e24..87ea2a7 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1743,3 +1743,13 @@
description: "An implementation of shortcut customizations through shortcut helper."
bug: "365064144"
}
+
+flag {
+ name: "stoppable_fgs_system_app"
+ namespace: "systemui"
+ description: "System app with foreground service can opt in to be stoppable."
+ bug: "376564917"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java
index 16ae466..0356422 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java
@@ -42,6 +42,7 @@
import android.os.Binder;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
import android.provider.DeviceConfig;
import android.testing.TestableLooper;
@@ -49,6 +50,7 @@
import androidx.test.filters.SmallTest;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -315,13 +317,36 @@
}
@Test
+ @EnableFlags(Flags.FLAG_STOPPABLE_FGS_SYSTEM_APP)
+ public void testButtonVisibilityOfStoppableApps() throws Exception {
+ setUserProfiles(0);
+ setBackgroundRestrictionExemptionReason("pkg", 12345, REASON_ALLOWLISTED_PACKAGE);
+ setBackgroundRestrictionExemptionReason("vendor_pkg", 67890, REASON_ALLOWLISTED_PACKAGE);
+
+ // Same as above, but apps are opt-in to be stoppable
+ setStoppableApps(new String[] {"pkg"}, /* vendor */ false);
+ setStoppableApps(new String[] {"vendor_pkg"}, /* vendor */ true);
+
+ final Binder binder = new Binder();
+ setShowStopButtonForUserAllowlistedApps(true);
+ // Both are foreground.
+ mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, true);
+ mIForegroundServiceObserver.onForegroundStateChanged(binder, "vendor_pkg", 0, true);
+ Assert.assertEquals(2, mFmc.visibleButtonsCount());
+
+ // The vendor package is no longer foreground. Only `pkg` remains.
+ mIForegroundServiceObserver.onForegroundStateChanged(binder, "vendor_pkg", 0, false);
+ Assert.assertEquals(1, mFmc.visibleButtonsCount());
+ }
+
+ @Test
public void testShowUserVisibleJobsOnCreation() {
// Test when the default is on.
mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
"true", false);
FgsManagerController fmc = new FgsManagerControllerImpl(
- mContext,
+ mContext.getResources(),
mMainExecutor,
mBackgroundExecutor,
mSystemClock,
@@ -348,7 +373,7 @@
SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
"false", false);
fmc = new FgsManagerControllerImpl(
- mContext,
+ mContext.getResources(),
mMainExecutor,
mBackgroundExecutor,
mSystemClock,
@@ -446,6 +471,11 @@
.getBackgroundRestrictionExemptionReason(uid);
}
+ private void setStoppableApps(String[] packageNames, boolean vendor) throws Exception {
+ overrideResource(vendor ? com.android.internal.R.array.vendor_stoppable_fgs_system_apps
+ : com.android.internal.R.array.stoppable_fgs_system_apps, packageNames);
+ }
+
FgsManagerController createFgsManagerController() throws RemoteException {
ArgumentCaptor<IForegroundServiceObserver> iForegroundServiceObserverArgumentCaptor =
ArgumentCaptor.forClass(IForegroundServiceObserver.class);
@@ -455,7 +485,7 @@
ArgumentCaptor.forClass(BroadcastReceiver.class);
FgsManagerController result = new FgsManagerControllerImpl(
- mContext,
+ mContext.getResources(),
mMainExecutor,
mBackgroundExecutor,
mSystemClock,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index a1071907..2a5ffc6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -27,6 +27,7 @@
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.content.pm.UserInfo
+import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.os.IBinder
import android.os.PowerExemptionManager
@@ -54,6 +55,7 @@
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.Dumpable
+import com.android.systemui.Flags;
import com.android.systemui.res.R
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
@@ -137,7 +139,7 @@
@SysUISingleton
class FgsManagerControllerImpl @Inject constructor(
- private val context: Context,
+ @Main private val resources: Resources,
@Main private val mainExecutor: Executor,
@Background private val backgroundExecutor: Executor,
private val systemClock: SystemClock,
@@ -223,6 +225,14 @@
private val userVisibleJobObserver = UserVisibleJobObserver()
+ private val stoppableApps by lazy { resources
+ .getStringArray(com.android.internal.R.array.stoppable_fgs_system_apps)
+ }
+
+ private val vendorStoppableApps by lazy { resources
+ .getStringArray(com.android.internal.R.array.vendor_stoppable_fgs_system_apps)
+ }
+
override fun init() {
synchronized(lock) {
if (initialized) {
@@ -725,9 +735,22 @@
}
else -> UIControl.NORMAL
}
+ // If the app wants to be a good citizen by being stoppable, even if the category it
+ // belongs to is exempted for background restriction, let it be stoppable by user.
+ if (Flags.stoppableFgsSystemApp()) {
+ if (isStoppableApp(packageName)) {
+ uiControl = UIControl.NORMAL
+ }
+ }
+
uiControlInitialized = true
}
+ fun isStoppableApp(packageName: String): Boolean {
+ return stoppableApps.contains(packageName) ||
+ vendorStoppableApps.contains(packageName)
+ }
+
override fun equals(other: Any?): Boolean {
if (other !is UserPackage) {
return false