Add a new special app access screen for long background tasks.
This new screen shows apps that hold the new RUN_LONG_JOBS permission.
Also add a reference to this screen in an app's info page under the
"Advanced" section for apps that have requested this permission.
Bug: 255821578
Test: atest AppFilterRegistryTest
Test: make -j RunSettingsRoboTests \
ROBOTTEST_FILTER="LongBackgroundTasksDetailsTest|
LongBackgroundTasksDetailsPreferenceControllerTest"
Test: visually via the Settings page
Change-Id: Idc498e52d29abc6df757c35e8bc91f00de92ba4a
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 74cf865..6d637c8 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -847,6 +847,37 @@
</activity>
<activity
+ android:name="Settings$LongBackgroundTasksActivity"
+ android:exported="true"
+ android:label="@string/long_background_tasks_label">
+ <intent-filter android:priority="1">
+ <action android:name="android.settings.MANAGE_APP_LONG_JOBS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+ android:value="com.android.settings.applications.manageapplications.ManageApplications" />
+ <meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
+ android:value="@string/menu_key_apps"/>
+ <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
+ android:value="true" />
+ </activity>
+
+ <activity
+ android:name="Settings$LongBackgroundTasksAppActivity"
+ android:exported="true"
+ android:label="@string/long_background_tasks_label">
+ <intent-filter android:priority="1">
+ <action android:name="android.settings.MANAGE_APP_LONG_JOBS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="package" />
+ </intent-filter>
+ <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+ android:value="com.android.settings.applications.appinfo.LongBackgroundTasksDetails" />
+ <meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
+ android:value="@string/menu_key_apps"/>
+ </activity>
+
+ <activity
android:name="Settings$DateTimeSettingsActivity"
android:label="@string/date_and_time"
android:exported="true"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 783b0be..e8ac3af 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -6032,7 +6032,8 @@
<string name="help_uri_about" translatable="false"></string>
<!-- Help URI, manage apps that can set alarms and reminders [DO NOT TRANSLATE] -->
<string name="help_uri_alarms_and_reminders" translatable="false"></string>
-
+ <!-- Help URI, manage apps that can run long background tasks [DO NOT TRANSLATE] -->
+ <string name="help_uri_long_background_tasks" translatable="false"></string>
<!-- Help URL, WiFi [DO NOT TRANSLATE] -->
<string name="help_url_wifi" translatable="false"></string>
<!-- Help URL, WiFi Direct [DO NOT TRANSLATE] -->
@@ -9123,6 +9124,22 @@
<!-- Title for the See more preference item in Special app access settings [CHAR LIMIT=30] -->
<string name="special_access_more">See more</string>
+ <!-- Label for the settings activity for controlling apps that can run long background tasks [CHAR LIMIT=30] -->
+ <string name="long_background_tasks_label">Long background tasks</string>
+ <!-- Label for the switch to toggle the permission for running long background tasks [CHAR LIMIT=50] -->
+ <string name="long_background_tasks_switch_title">Allow long-running background tasks</string>
+ <!-- Title for the settings screen for controlling apps that can run long background tasks [CHAR LIMIT=30] -->
+ <string name="long_background_tasks_title">Long background tasks</string>
+ <!-- Description that appears below the long_background_tasks switch [CHAR LIMIT=NONE] -->
+ <string name="long_background_tasks_footer_title">
+ Allow this app to run long background tasks. This lets the app run tasks that might
+ take longer than a few minutes to finish, such as downloads and uploads.
+ \n\nIf this permission is denied, the system will limit how long the app can perform
+ such tasks in the background.
+ </string>
+ <!-- Keywords for settings screen for controlling apps that can run long background tasks [CHAR LIMIT=NONE] -->
+ <string name="keywords_long_background_tasks">long jobs, data transfer, background tasks</string>
+
<!-- Reset rate-limiting in the system service ShortcutManager. "ShortcutManager" is the name of a system service and not translatable.
If the word "rate-limit" is hard to translate, use "Reset ShortcutManager API call limit" as the source text, which means
the same thing in this context.
diff --git a/res/xml/app_info_settings.xml b/res/xml/app_info_settings.xml
index 2bb05d0..e004f28 100644
--- a/res/xml/app_info_settings.xml
+++ b/res/xml/app_info_settings.xml
@@ -189,6 +189,12 @@
android:summary="@string/summary_placeholder"
settings:controller="com.android.settings.applications.appinfo.AlarmsAndRemindersDetailPreferenceController" />
+ <Preference
+ android:key="long_background_tasks"
+ android:title="@string/long_background_tasks_title"
+ android:summary="@string/summary_placeholder"
+ settings:controller="com.android.settings.applications.appinfo.LongBackgroundTasksDetailsPreferenceController" />
+
</PreferenceCategory>
<!-- App installer info -->
diff --git a/res/xml/long_background_tasks.xml b/res/xml/long_background_tasks.xml
new file mode 100644
index 0000000..405b0cf
--- /dev/null
+++ b/res/xml/long_background_tasks.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ android:title="@string/long_background_tasks_title">
+
+ <com.android.settings.widget.FilterTouchesRestrictedSwitchPreference
+ android:key="long_background_tasks_switch"
+ android:title="@string/long_background_tasks_switch_title" />
+
+ <com.android.settingslib.widget.FooterPreference
+ android:key="long_background_tasks_description"
+ android:title="@string/long_background_tasks_footer_title"
+ android:selectable="false" />
+
+</PreferenceScreen>
diff --git a/res/xml/special_access.xml b/res/xml/special_access.xml
index 3dd6e18..0d2ee51 100644
--- a/res/xml/special_access.xml
+++ b/res/xml/special_access.xml
@@ -108,6 +108,16 @@
settings:controller="com.android.settings.applications.specialaccess.DataSaverController" />
<Preference
+ android:key="long_background_tasks"
+ android:title="@string/long_background_tasks_title"
+ android:fragment="com.android.settings.applications.manageapplications.ManageApplications"
+ settings:keywords="@string/keywords_long_background_tasks">
+ <extra
+ android:name="classname"
+ android:value="com.android.settings.Settings$LongBackgroundTasksActivity" />
+ </Preference>
+
+ <Preference
android:key="manage_external_sources"
android:title="@string/install_other_apps"
android:fragment="com.android.settings.applications.manageapplications.ManageApplications"
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index b95c9b0..e394e45 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -430,6 +430,11 @@
}
}
+ /** Actviity to manage apps with {@link android.Manifest.permission#RUN_LONG_JOBS} */
+ public static class LongBackgroundTasksActivity extends SettingsActivity { /* empty */ }
+ /** App specific version of {@link LongBackgroundTasksActivity} */
+ public static class LongBackgroundTasksAppActivity extends SettingsActivity { /* empty */ }
+
/**
* Activity for BugReportHandlerPicker.
*/
diff --git a/src/com/android/settings/applications/AppStateLongBackgroundTasksBridge.java b/src/com/android/settings/applications/AppStateLongBackgroundTasksBridge.java
new file mode 100644
index 0000000..d286c5e
--- /dev/null
+++ b/src/com/android/settings/applications/AppStateLongBackgroundTasksBridge.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2022 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.settings.applications;
+
+import android.Manifest;
+import android.app.AppGlobals;
+import android.app.job.JobScheduler;
+import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+import com.android.settingslib.applications.ApplicationsState.AppFilter;
+
+import libcore.util.EmptyArray;
+
+import java.util.List;
+
+/**
+ * Connects app op info to the ApplicationsState. Extends {@link AppStateAppOpsBridge} to tailor
+ * to the semantics of {@link Manifest.permission#RUN_LONG_JOBS}.
+ * Also provides app filters that can use the info.
+ */
+public class AppStateLongBackgroundTasksBridge extends AppStateBaseBridge {
+ private static final String PERMISSION = Manifest.permission.RUN_LONG_JOBS;
+ private static final String TAG = "LongBackgroundTasksBridge";
+
+ @VisibleForTesting
+ JobScheduler mJobScheduler;
+ @VisibleForTesting
+ String[] mRequesterPackages;
+
+ public AppStateLongBackgroundTasksBridge(Context context, ApplicationsState appState,
+ Callback callback) {
+ super(appState, callback);
+
+ mJobScheduler = context.getSystemService(JobScheduler.class);
+ final IPackageManager iPm = AppGlobals.getPackageManager();
+ try {
+ mRequesterPackages = iPm.getAppOpPermissionPackages(PERMISSION, context.getUserId());
+ } catch (RemoteException re) {
+ Log.e(TAG, "Cannot reach package manager", re);
+ mRequesterPackages = EmptyArray.STRING;
+ }
+ }
+
+ /**
+ * Returns information regarding {@link Manifest.permission#RUN_LONG_JOBS} for the given
+ * package and uid.
+ */
+ public LongBackgroundTasksState createPermissionState(String packageName, int uid) {
+ final int userId = UserHandle.getUserId(uid);
+
+ final boolean permissionRequested = ArrayUtils.contains(mRequesterPackages, packageName);
+ final boolean permissionGranted = mJobScheduler.hasRunLongJobsPermission(packageName,
+ userId);
+ return new LongBackgroundTasksState(permissionRequested, permissionGranted);
+ }
+
+ @Override
+ protected void updateExtraInfo(AppEntry app, String pkg, int uid) {
+ app.extraInfo = createPermissionState(pkg, uid);
+ }
+
+ @Override
+ protected void loadAllExtraInfo() {
+ final List<AppEntry> allApps = mAppSession.getAllApps();
+ for (int i = 0; i < allApps.size(); i++) {
+ final AppEntry currentEntry = allApps.get(i);
+ updateExtraInfo(currentEntry, currentEntry.info.packageName, currentEntry.info.uid);
+ }
+ }
+
+ public static final AppFilter FILTER_LONG_JOBS_APPS = new AppFilter() {
+
+ @Override
+ public void init() {
+ }
+
+ @Override
+ public boolean filterApp(AppEntry info) {
+ if (info.extraInfo instanceof LongBackgroundTasksState) {
+ final LongBackgroundTasksState state = (LongBackgroundTasksState) info.extraInfo;
+ return state.shouldBeVisible();
+ }
+ return false;
+ }
+ };
+
+ /**
+ * Class to denote the state of an app regarding
+ * {@link Manifest.permission#RUN_LONG_JOBS}.
+ */
+ public static class LongBackgroundTasksState {
+ private boolean mPermissionRequested;
+ private boolean mPermissionGranted;
+
+ LongBackgroundTasksState(boolean permissionRequested, boolean permissionGranted) {
+ mPermissionRequested = permissionRequested;
+ mPermissionGranted = permissionGranted;
+ }
+
+ /** Should the app associated with this state appear on the Settings screen */
+ public boolean shouldBeVisible() {
+ return mPermissionRequested;
+ }
+
+ /** Is the permission granted to the app associated with this state */
+ public boolean isAllowed() {
+ return mPermissionGranted;
+ }
+ }
+}
diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
index 7049b56..4a00260 100644
--- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
+++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
@@ -215,10 +215,15 @@
alarmsAndReminders.setPackageName(packageName);
alarmsAndReminders.setParentFragment(this);
+ final LongBackgroundTasksDetailsPreferenceController longBackgroundTasks =
+ use(LongBackgroundTasksDetailsPreferenceController.class);
+ longBackgroundTasks.setPackageName(packageName);
+ longBackgroundTasks.setParentFragment(this);
+
final AdvancedAppInfoPreferenceCategoryController advancedAppInfo =
use(AdvancedAppInfoPreferenceCategoryController.class);
advancedAppInfo.setChildren(Arrays.asList(writeSystemSettings, drawOverlay, pip,
- externalSource, acrossProfiles, alarmsAndReminders));
+ externalSource, acrossProfiles, alarmsAndReminders, longBackgroundTasks));
advancedAppInfo.setAppEntry(mAppEntry);
final AppLocalePreferenceController appLocale =
diff --git a/src/com/android/settings/applications/appinfo/LongBackgroundTasksDetails.java b/src/com/android/settings/applications/appinfo/LongBackgroundTasksDetails.java
new file mode 100644
index 0000000..1e5d11a
--- /dev/null
+++ b/src/com/android/settings/applications/appinfo/LongBackgroundTasksDetails.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2022 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.settings.applications.appinfo;
+
+import static android.app.Activity.RESULT_CANCELED;
+import static android.app.Activity.RESULT_OK;
+
+import android.app.AppOpsManager;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.preference.Preference;
+import androidx.preference.Preference.OnPreferenceChangeListener;
+
+import com.android.settings.R;
+import com.android.settings.Settings;
+import com.android.settings.applications.AppInfoWithHeader;
+import com.android.settings.applications.AppStateLongBackgroundTasksBridge;
+import com.android.settingslib.RestrictedSwitchPreference;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+
+/**
+ * App specific activity to show details about
+ * {@link android.Manifest.permission#RUN_LONG_JOBS}.
+ */
+public class LongBackgroundTasksDetails extends AppInfoWithHeader
+ implements OnPreferenceChangeListener {
+
+ private static final String KEY_SWITCH = "long_background_tasks_switch";
+ private static final String UNCOMMITTED_STATE_KEY = "uncommitted_state";
+
+ private AppStateLongBackgroundTasksBridge mAppBridge;
+ private AppOpsManager mAppOpsManager;
+ private RestrictedSwitchPreference mSwitchPref;
+ private AppStateLongBackgroundTasksBridge.LongBackgroundTasksState mPermissionState;
+ private volatile Boolean mUncommittedState;
+
+ /**
+ * Returns the string that states whether the app has access to
+ * {@link android.Manifest.permission#RUN_LONG_JOBS}.
+ */
+ public static CharSequence getSummary(Context context, AppEntry entry) {
+ final AppStateLongBackgroundTasksBridge.LongBackgroundTasksState state =
+ new AppStateLongBackgroundTasksBridge(context, /*appState=*/null,
+ /*callback=*/null).createPermissionState(entry.info.packageName,
+ entry.info.uid);
+
+ return context.getString(state.isAllowed() ? R.string.app_permission_summary_allowed
+ : R.string.app_permission_summary_not_allowed);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Context context = getActivity();
+ mAppBridge = new AppStateLongBackgroundTasksBridge(context, mState, /*callback=*/null);
+ mAppOpsManager = context.getSystemService(AppOpsManager.class);
+
+ if (savedInstanceState != null) {
+ mUncommittedState = (Boolean) savedInstanceState.get(UNCOMMITTED_STATE_KEY);
+ if (mUncommittedState != null && isAppSpecific()) {
+ setResult(mUncommittedState ? RESULT_OK : RESULT_CANCELED);
+ }
+ }
+ addPreferencesFromResource(R.xml.long_background_tasks);
+ mSwitchPref = findPreference(KEY_SWITCH);
+ mSwitchPref.setOnPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (mUncommittedState != null) {
+ outState.putObject(UNCOMMITTED_STATE_KEY, mUncommittedState);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (preference == mSwitchPref) {
+ mUncommittedState = (Boolean) newValue;
+ if (isAppSpecific()) {
+ setResult(mUncommittedState ? RESULT_OK : RESULT_CANCELED);
+ }
+ refreshUi();
+ return true;
+ }
+ return false;
+ }
+
+ private void setCanRunLongJobs(boolean newState) {
+ final int uid = mPackageInfo.applicationInfo.uid;
+ mAppOpsManager.setUidMode(AppOpsManager.OPSTR_RUN_LONG_JOBS, uid,
+ newState ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_ERRORED);
+ }
+
+ private void logPermissionChange(boolean newState, String packageName) {
+ mMetricsFeatureProvider.action(
+ mMetricsFeatureProvider.getAttribution(getActivity()),
+ SettingsEnums.ACTION_LONG_BACKGROUND_TASKS_TOGGLE,
+ getMetricsCategory(),
+ packageName,
+ newState ? 1 : 0);
+ }
+
+ private boolean isAppSpecific() {
+ return Settings.LongBackgroundTasksAppActivity.class.getName().equals(
+ getIntent().getComponent().getClassName());
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (getActivity().isChangingConfigurations()) {
+ return;
+ }
+ if (mPermissionState != null && mUncommittedState != null
+ && mUncommittedState != mPermissionState.isAllowed()) {
+ setCanRunLongJobs(mUncommittedState);
+ logPermissionChange(mUncommittedState, mPackageName);
+ mUncommittedState = null;
+ }
+ }
+
+ @Override
+ protected boolean refreshUi() {
+ if (mPackageInfo == null || mPackageInfo.applicationInfo == null) {
+ return false;
+ }
+ mPermissionState = mAppBridge.createPermissionState(mPackageName,
+ mPackageInfo.applicationInfo.uid);
+ mSwitchPref.setEnabled(mPermissionState.shouldBeVisible());
+ mSwitchPref.setChecked(
+ mUncommittedState != null ? mUncommittedState : mPermissionState.isAllowed());
+ return true;
+ }
+
+ @Override
+ protected AlertDialog createDialog(int id, int errorCode) {
+ return null;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.LONG_BACKGROUND_TASKS;
+ }
+}
diff --git a/src/com/android/settings/applications/appinfo/LongBackgroundTasksDetailsPreferenceController.java b/src/com/android/settings/applications/appinfo/LongBackgroundTasksDetailsPreferenceController.java
new file mode 100644
index 0000000..a41280b
--- /dev/null
+++ b/src/com/android/settings/applications/appinfo/LongBackgroundTasksDetailsPreferenceController.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 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.settings.applications.appinfo;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.applications.AppStateLongBackgroundTasksBridge;
+
+/**
+ * Preference controller for
+ * {@link LongBackgroundTasksDetails} Settings fragment.
+ */
+public class LongBackgroundTasksDetailsPreferenceController extends
+ AppInfoPreferenceControllerBase {
+
+ private String mPackageName;
+
+ public LongBackgroundTasksDetailsPreferenceController(Context context, String key) {
+ super(context, key);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return isCandidate() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ preference.setSummary(getPreferenceSummary());
+ }
+
+ @Override
+ protected Class<? extends SettingsPreferenceFragment> getDetailFragmentClass() {
+ return LongBackgroundTasksDetails.class;
+ }
+
+ @VisibleForTesting
+ CharSequence getPreferenceSummary() {
+ return LongBackgroundTasksDetails.getSummary(mContext, mParent.getAppEntry());
+ }
+
+ @VisibleForTesting
+ boolean isCandidate() {
+ final PackageInfo packageInfo = mParent.getPackageInfo();
+ if (packageInfo == null) {
+ return false;
+ }
+ final AppStateLongBackgroundTasksBridge.LongBackgroundTasksState appState =
+ new AppStateLongBackgroundTasksBridge(
+ mContext, /*appState=*/null, /*callback=*/null)
+ .createPermissionState(mPackageName, packageInfo.applicationInfo.uid);
+ return appState.shouldBeVisible();
+ }
+
+ void setPackageName(String packageName) {
+ mPackageName = packageName;
+ }
+}
diff --git a/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java b/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java
index 15cf8e7..b48bdbb 100644
--- a/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java
+++ b/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java
@@ -23,6 +23,7 @@
import com.android.settings.applications.AppStateAppBatteryUsageBridge;
import com.android.settings.applications.AppStateInstallAppsBridge;
import com.android.settings.applications.AppStateLocaleBridge;
+import com.android.settings.applications.AppStateLongBackgroundTasksBridge;
import com.android.settings.applications.AppStateManageExternalStorageBridge;
import com.android.settings.applications.AppStateMediaManagementAppsBridge;
import com.android.settings.applications.AppStateNotificationBridge;
@@ -61,6 +62,7 @@
FILTER_APPS_BATTERY_UNRESTRICTED,
FILTER_APPS_BATTERY_OPTIMIZED,
FILTER_APPS_BATTERY_RESTRICTED,
+ FILTER_LONG_BACKGROUND_TASKS,
})
@interface FilterType {}
@@ -89,8 +91,9 @@
public static final int FILTER_APPS_BATTERY_UNRESTRICTED = 21;
public static final int FILTER_APPS_BATTERY_OPTIMIZED = 22;
public static final int FILTER_APPS_BATTERY_RESTRICTED = 23;
- // Next id: 24. If you add an entry here, please change NUM_FILTER_ENTRIES.
- private static final int NUM_FILTER_ENTRIES = 24;
+ public static final int FILTER_LONG_BACKGROUND_TASKS = 24;
+ // Next id: 25. If you add an entry here, please change NUM_FILTER_ENTRIES.
+ private static final int NUM_FILTER_ENTRIES = 25;
private static AppFilterRegistry sRegistry;
@@ -242,6 +245,12 @@
AppStateAppBatteryUsageBridge.FILTER_BATTERY_RESTRICTED_APPS,
FILTER_APPS_BATTERY_RESTRICTED,
R.string.filter_battery_restricted_title);
+
+ // Apps that can run long background tasks
+ mFilters[FILTER_LONG_BACKGROUND_TASKS] = new AppFilterItem(
+ AppStateLongBackgroundTasksBridge.FILTER_LONG_JOBS_APPS,
+ FILTER_LONG_BACKGROUND_TASKS,
+ R.string.long_background_tasks_title);
}
@@ -280,6 +289,8 @@
return FILTER_APPS_LOCALE;
case ManageApplications.LIST_TYPE_BATTERY_OPTIMIZATION:
return FILTER_APPS_BATTERY_OPTIMIZED;
+ case ManageApplications.LIST_TYPE_LONG_BACKGROUND_TASKS:
+ return FILTER_LONG_BACKGROUND_TASKS;
default:
return FILTER_APPS_ALL;
}
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java
index a41230a..e129954 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplications.java
+++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java
@@ -94,6 +94,7 @@
import com.android.settings.Settings.ChangeWifiStateActivity;
import com.android.settings.Settings.GamesStorageActivity;
import com.android.settings.Settings.HighPowerApplicationsActivity;
+import com.android.settings.Settings.LongBackgroundTasksActivity;
import com.android.settings.Settings.ManageExternalSourcesActivity;
import com.android.settings.Settings.ManageExternalStorageActivity;
import com.android.settings.Settings.MediaManagementAppsActivity;
@@ -112,6 +113,7 @@
import com.android.settings.applications.AppStateBaseBridge;
import com.android.settings.applications.AppStateInstallAppsBridge;
import com.android.settings.applications.AppStateLocaleBridge;
+import com.android.settings.applications.AppStateLongBackgroundTasksBridge;
import com.android.settings.applications.AppStateManageExternalStorageBridge;
import com.android.settings.applications.AppStateMediaManagementAppsBridge;
import com.android.settings.applications.AppStateNotificationBridge;
@@ -128,6 +130,7 @@
import com.android.settings.applications.appinfo.AppLocaleDetails;
import com.android.settings.applications.appinfo.DrawOverlayDetails;
import com.android.settings.applications.appinfo.ExternalSourcesDetails;
+import com.android.settings.applications.appinfo.LongBackgroundTasksDetails;
import com.android.settings.applications.appinfo.ManageExternalStorageDetails;
import com.android.settings.applications.appinfo.MediaManagementAppsDetails;
import com.android.settings.applications.appinfo.WriteSettingsDetails;
@@ -256,6 +259,7 @@
public static final int LIST_TYPE_MEDIA_MANAGEMENT_APPS = 13;
public static final int LIST_TYPE_APPS_LOCALE = 14;
public static final int LIST_TYPE_BATTERY_OPTIMIZATION = 15;
+ public static final int LIST_TYPE_LONG_BACKGROUND_TASKS = 16;
// List types that should show instant apps.
public static final Set<Integer> LIST_TYPES_WITH_INSTANT = new ArraySet<>(Arrays.asList(
@@ -402,6 +406,8 @@
mListType = LIST_TYPE_APPS_LOCALE;
} else if (className.equals(AppBatteryUsageActivity.class.getName())) {
mListType = LIST_TYPE_BATTERY_OPTIMIZATION;
+ } else if (className.equals(LongBackgroundTasksActivity.class.getName())) {
+ mListType = LIST_TYPE_LONG_BACKGROUND_TASKS;
} else {
mListType = LIST_TYPE_MAIN;
}
@@ -598,6 +604,8 @@
return SettingsEnums.APPS_LOCALE_LIST;
case LIST_TYPE_BATTERY_OPTIMIZATION:
return SettingsEnums.BATTERY_OPTIMIZED_APPS_LIST;
+ case LIST_TYPE_LONG_BACKGROUND_TASKS:
+ return SettingsEnums.LONG_BACKGROUND_TASKS;
default:
return SettingsEnums.PAGE_UNKNOWN;
}
@@ -735,6 +743,10 @@
getActivity(), this, mCurrentPkgName,
UserHandle.getUserHandleForUid(mCurrentUid));
break;
+ case LIST_TYPE_LONG_BACKGROUND_TASKS:
+ startAppInfoFragment(LongBackgroundTasksDetails.class,
+ R.string.long_background_tasks_label);
+ break;
// TODO: Figure out if there is a way where we can spin up the profile's settings
// process ahead of time, to avoid a long load of data when user clicks on a managed
// app. Maybe when they load the list of apps that contains managed profile apps.
@@ -828,6 +840,8 @@
return R.string.help_uri_alarms_and_reminders;
case LIST_TYPE_MEDIA_MANAGEMENT_APPS:
return R.string.help_uri_media_management_apps;
+ case LIST_TYPE_LONG_BACKGROUND_TASKS:
+ return R.string.help_uri_long_background_tasks;
default:
case LIST_TYPE_MAIN:
return R.string.help_uri_apps;
@@ -1030,6 +1044,8 @@
screenTitle = R.string.app_locales_picker_menu_title;
} else if (className.equals(AppBatteryUsageActivity.class.getName())) {
screenTitle = R.string.app_battery_usage_title;
+ } else if (className.equals(LongBackgroundTasksActivity.class.getName())) {
+ screenTitle = R.string.long_background_tasks_title;
} else {
if (screenTitle == -1) {
screenTitle = R.string.all_apps;
@@ -1233,6 +1249,8 @@
mManageApplications.mUserManager);
} else if (mManageApplications.mListType == LIST_TYPE_BATTERY_OPTIMIZATION) {
mExtraInfoBridge = new AppStateAppBatteryUsageBridge(mContext, mState, this);
+ } else if (mManageApplications.mListType == LIST_TYPE_LONG_BACKGROUND_TASKS) {
+ mExtraInfoBridge = new AppStateLongBackgroundTasksBridge(mContext, mState, this);
} else {
mExtraInfoBridge = null;
}
@@ -1762,6 +1780,9 @@
case LIST_TYPE_BATTERY_OPTIMIZATION:
holder.setSummary(null);
break;
+ case LIST_TYPE_LONG_BACKGROUND_TASKS:
+ holder.setSummary(LongBackgroundTasksDetails.getSummary(mContext, entry));
+ break;
default:
holder.updateSizeText(entry, mManageApplications.mInvalidSizeStr, mWhichSize);
break;
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index b55b024..7904fdb 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -48,6 +48,7 @@
import com.android.settings.applications.appinfo.AppLocaleDetails;
import com.android.settings.applications.appinfo.DrawOverlayDetails;
import com.android.settings.applications.appinfo.ExternalSourcesDetails;
+import com.android.settings.applications.appinfo.LongBackgroundTasksDetails;
import com.android.settings.applications.appinfo.ManageExternalStorageDetails;
import com.android.settings.applications.appinfo.MediaManagementAppsDetails;
import com.android.settings.applications.appinfo.WriteSettingsDetails;
@@ -357,7 +358,8 @@
TurnScreenOnSettings.class.getName(),
TurnScreenOnDetails.class.getName(),
NfcAndPaymentFragment.class.getName(),
- ColorAndMotionFragment.class.getName()
+ ColorAndMotionFragment.class.getName(),
+ LongBackgroundTasksDetails.class.getName()
};
public static final String[] SETTINGS_FOR_RESTRICTED = {
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/LongBackgroundTasksDetailsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/LongBackgroundTasksDetailsPreferenceControllerTest.java
new file mode 100644
index 0000000..1b361d2
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/LongBackgroundTasksDetailsPreferenceControllerTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 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.settings.applications.appinfo;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.settings.core.BasePreferenceController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class LongBackgroundTasksDetailsPreferenceControllerTest {
+
+ @Mock
+ private AppInfoDashboardFragment mFragment;
+ @Mock
+ private Preference mPreference;
+
+ private Context mContext;
+ private LongBackgroundTasksDetailsPreferenceController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+ mController = spy(new LongBackgroundTasksDetailsPreferenceController(mContext, "test_key"));
+ mController.setPackageName("Package1");
+ mController.setParentFragment(mFragment);
+ final String key = mController.getPreferenceKey();
+ when(mPreference.getKey()).thenReturn(key);
+ }
+
+ @Test
+ public void getAvailabilityStatus_notCandidate_shouldReturnUnavailable() {
+ doReturn(false).when(mController).isCandidate();
+
+ assertThat(mController.getAvailabilityStatus())
+ .isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_isCandidate_shouldReturnAvailable() {
+ doReturn(true).when(mController).isCandidate();
+
+ assertThat(mController.getAvailabilityStatus())
+ .isEqualTo(BasePreferenceController.AVAILABLE);
+ }
+
+ @Test
+ public void getDetailFragmentClass_shouldReturnAlarmsAndRemindersDetails() {
+ assertThat(mController.getDetailFragmentClass())
+ .isEqualTo(LongBackgroundTasksDetails.class);
+ }
+
+ @Test
+ public void updateState_shouldSetSummary() {
+ final String summary = "test summary";
+ doReturn(summary).when(mController).getPreferenceSummary();
+
+ mController.updateState(mPreference);
+
+ verify(mPreference).setSummary(summary);
+ }
+
+ @Test
+ public void isCandidate_nullPackageInfo_shouldNotCrash() {
+ mController.isCandidate();
+ // no crash
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/LongBackgroundTasksDetailsTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/LongBackgroundTasksDetailsTest.java
new file mode 100644
index 0000000..7d159b9
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/LongBackgroundTasksDetailsTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2022 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.settings.applications.appinfo;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+
+import com.android.settings.applications.AppStateLongBackgroundTasksBridge;
+import com.android.settingslib.RestrictedSwitchPreference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.util.ReflectionHelpers;
+
+@RunWith(RobolectricTestRunner.class)
+public class LongBackgroundTasksDetailsTest {
+
+ @Mock
+ private RestrictedSwitchPreference mSwitchPref;
+ @Mock
+ private PackageInfo mPackageInfo;
+ @Mock
+ private AppStateLongBackgroundTasksBridge mAppStateBridge;
+ @Mock
+ private AppStateLongBackgroundTasksBridge.LongBackgroundTasksState mPermissionState;
+
+ private LongBackgroundTasksDetails mFragment = new LongBackgroundTasksDetails();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ ReflectionHelpers.setField(mFragment, "mSwitchPref", mSwitchPref);
+ ReflectionHelpers.setField(mFragment, "mAppBridge", mAppStateBridge);
+ }
+
+ @Test
+ public void refreshUi_noPackageInfo_shouldReturnFalseAndNoCrash() {
+ mFragment.refreshUi();
+
+ assertThat(mFragment.refreshUi()).isFalse();
+ // should not crash
+ }
+
+ @Test
+ public void refreshUi_noApplicationInfo_shouldReturnFalseAndNoCrash() {
+ ReflectionHelpers.setField(mFragment, "mPackageInfo", mPackageInfo);
+
+ mFragment.refreshUi();
+
+ assertThat(mFragment.refreshUi()).isFalse();
+ // should not crash
+ }
+
+ @Test
+ public void refreshUi_hasApplicationInfo_shouldReturnTrue() {
+ ReflectionHelpers.setField(mFragment, "mPackageInfo", mPackageInfo);
+ mPackageInfo.applicationInfo = new ApplicationInfo();
+ when(mAppStateBridge.createPermissionState(nullable(String.class), anyInt()))
+ .thenReturn(mPermissionState);
+
+ mFragment.refreshUi();
+
+ assertThat(mFragment.refreshUi()).isTrue();
+ }
+
+ @Test
+ public void refreshUi_switchPreferenceEnabled() {
+ ReflectionHelpers.setField(mFragment, "mPackageInfo", mPackageInfo);
+ mPackageInfo.applicationInfo = new ApplicationInfo();
+ when(mAppStateBridge.createPermissionState(nullable(String.class), anyInt()))
+ .thenReturn(mPermissionState);
+ when(mPermissionState.shouldBeVisible()).thenReturn(false);
+
+ mFragment.refreshUi();
+ verify(mSwitchPref).setEnabled(false);
+
+ when(mPermissionState.shouldBeVisible()).thenReturn(true);
+
+ mFragment.refreshUi();
+ verify(mSwitchPref).setEnabled(true);
+ }
+
+ @Test
+ public void refreshUi_switchPreferenceChecked() {
+ ReflectionHelpers.setField(mFragment, "mPackageInfo", mPackageInfo);
+ mPackageInfo.applicationInfo = new ApplicationInfo();
+ when(mAppStateBridge.createPermissionState(nullable(String.class), anyInt()))
+ .thenReturn(mPermissionState);
+
+ when(mPermissionState.isAllowed()).thenReturn(true);
+ mFragment.refreshUi();
+ verify(mSwitchPref).setChecked(true);
+
+ when(mPermissionState.isAllowed()).thenReturn(false);
+ mFragment.refreshUi();
+ verify(mSwitchPref).setChecked(false);
+ }
+}
diff --git a/tests/unit/src/com/android/settings/applications/manageapplications/AppFilterRegistryTest.java b/tests/unit/src/com/android/settings/applications/manageapplications/AppFilterRegistryTest.java
index 13bc3db..ff5c0d8 100644
--- a/tests/unit/src/com/android/settings/applications/manageapplications/AppFilterRegistryTest.java
+++ b/tests/unit/src/com/android/settings/applications/manageapplications/AppFilterRegistryTest.java
@@ -26,10 +26,12 @@
import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_USAGE_ACCESS;
import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_WITH_OVERLAY;
import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_WRITE_SETTINGS;
+import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_LONG_BACKGROUND_TASKS;
import static com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_ALARMS_AND_REMINDERS;
import static com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_BATTERY_OPTIMIZATION;
import static com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_GAMES;
import static com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_HIGH_POWER;
+import static com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_LONG_BACKGROUND_TASKS;
import static com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_MAIN;
import static com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_MANAGE_SOURCES;
import static com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_MEDIA_MANAGEMENT_APPS;
@@ -77,5 +79,8 @@
assertThat(registry.getDefaultFilterType(LIST_TYPE_STORAGE)).isEqualTo(FILTER_APPS_ALL);
assertThat(registry.getDefaultFilterType(LIST_TYPE_GAMES)).isEqualTo(FILTER_APPS_ALL);
+
+ assertThat(registry.getDefaultFilterType(LIST_TYPE_LONG_BACKGROUND_TASKS))
+ .isEqualTo(FILTER_LONG_BACKGROUND_TASKS);
}
}