Update settings for app hibernation
Created an preference in Apps & Notifcations page as the entry point for
apps that are hibernated. Also added a switch preference in AppInfo page
for users to exempt an app from hibernation.
Bug: 181172051
Test: HibernatedAppsPreferenceControllerTest;
HibernationSwitchPreferenceControllerTest;
AppInfoDashboardFragmentTest
Change-Id: I72a90ab391cd521150fc155a6d9c67c846b7360d
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index e44ee78..4bf97cc 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -153,6 +153,9 @@
public static final String PROPERTY_LOCATION_INDICATOR_SETTINGS_ENABLED =
"location_indicator_settings_enabled";
+ /** Whether or not app hibernation is enabled on the device **/
+ public static final String PROPERTY_APP_HIBERNATION_ENABLED = "app_hibernation_enabled";
+
/**
* Finds a matching activity for a preference's intent. If a matching
* activity is not found, it will remove the preference.
diff --git a/src/com/android/settings/applications/HibernatedAppsPreferenceController.java b/src/com/android/settings/applications/HibernatedAppsPreferenceController.java
new file mode 100644
index 0000000..40cbb2e
--- /dev/null
+++ b/src/com/android/settings/applications/HibernatedAppsPreferenceController.java
@@ -0,0 +1,61 @@
+/*
+ * 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.settings.applications;
+
+import static android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION;
+
+import static com.android.settings.Utils.PROPERTY_APP_HIBERNATION_ENABLED;
+
+import android.content.Context;
+import android.provider.DeviceConfig;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+
+/**
+ * A preference controller handling the logic for updating summary of hibernated apps.
+ * TODO(b/181172051): add intent to launch Auto Revoke UI in app_and_notification.xml
+ */
+public final class HibernatedAppsPreferenceController extends BasePreferenceController {
+ private static final String TAG = "HibernatedAppsPrefController";
+
+ public HibernatedAppsPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return isHibernationEnabled() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ final int numHibernated = getNumHibernated();
+ return mContext.getResources().getQuantityString(
+ R.plurals.unused_apps_summary, numHibernated, numHibernated);
+ }
+
+ private int getNumHibernated() {
+ //TODO(b/181172051): hook into hibernation service to get the number of hibernated apps.
+ return 0;
+ }
+
+ private static boolean isHibernationEnabled() {
+ return DeviceConfig.getBoolean(
+ NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED, false);
+ }
+}
diff --git a/src/com/android/settings/applications/appinfo/AppHibernationPreferenceCategoryController.java b/src/com/android/settings/applications/appinfo/AppHibernationPreferenceCategoryController.java
new file mode 100644
index 0000000..ef89168
--- /dev/null
+++ b/src/com/android/settings/applications/appinfo/AppHibernationPreferenceCategoryController.java
@@ -0,0 +1,30 @@
+/*
+ * 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.settings.applications.appinfo;
+
+import android.content.Context;
+
+import com.android.settings.widget.PreferenceCategoryController;
+
+/**
+ * A preference category controller serves as the parent for app hibernation related preference.
+ */
+public final class AppHibernationPreferenceCategoryController extends PreferenceCategoryController {
+ public AppHibernationPreferenceCategoryController(Context context, String key) {
+ super(context, key);
+ }
+}
diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
index 1878f5f..6a5c5df 100755
--- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
+++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
@@ -170,6 +170,13 @@
use(ExtraAppInfoPreferenceController.class).setPackageName(packageName);
}
+ final HibernationSwitchPreferenceController appHibernationSettings =
+ use(HibernationSwitchPreferenceController.class);
+ appHibernationSettings.setParentFragment(this);
+ appHibernationSettings.setPackage(packageName);
+ use(AppHibernationPreferenceCategoryController.class).setChildren(
+ Arrays.asList(appHibernationSettings));
+
final WriteSystemSettingsPreferenceController writeSystemSettings =
use(WriteSystemSettingsPreferenceController.class);
writeSystemSettings.setParentFragment(this);
diff --git a/src/com/android/settings/applications/appinfo/HibernationSwitchPreferenceController.java b/src/com/android/settings/applications/appinfo/HibernationSwitchPreferenceController.java
new file mode 100644
index 0000000..8ab2c9d
--- /dev/null
+++ b/src/com/android/settings/applications/appinfo/HibernationSwitchPreferenceController.java
@@ -0,0 +1,145 @@
+/*
+ * 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.settings.applications.appinfo;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED;
+import static android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION;
+
+import static com.android.settings.Utils.PROPERTY_APP_HIBERNATION_ENABLED;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.provider.DeviceConfig;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
+import androidx.preference.Preference;
+import androidx.preference.SwitchPreference;
+
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * A PreferenceController handling the logic for exempting hibernation of app
+ */
+public final class HibernationSwitchPreferenceController extends AppInfoPreferenceControllerBase
+ implements LifecycleObserver, AppOpsManager.OnOpChangedListener,
+ Preference.OnPreferenceChangeListener {
+ private static final String TAG = "HibernationSwitchPrefController";
+ private String mPackageName;
+ private final AppOpsManager mAppOpsManager;
+ private int mPackageUid;
+ @VisibleForTesting
+ boolean mIsPackageSet;
+ private boolean mIsPackageExemptByDefault;
+
+ public HibernationSwitchPreferenceController(Context context,
+ String preferenceKey) {
+ super(context, preferenceKey);
+ mAppOpsManager = context.getSystemService(AppOpsManager.class);
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ public void onResume() {
+ if (mIsPackageSet) {
+ mAppOpsManager.startWatchingMode(
+ OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, mPackageName, this);
+ }
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+ public void onPause() {
+ mAppOpsManager.stopWatchingMode(this);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return isHibernationEnabled() && mIsPackageSet ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ }
+
+ /**
+ * Set the package. And also retrieve details from package manager. Some packages may be
+ * exempted from hibernation by default.
+ * @param packageName The name of the package whose hibernation state to be managed.
+ */
+ void setPackage(@NonNull String packageName) {
+ mPackageName = packageName;
+ final PackageManager packageManager = mContext.getPackageManager();
+
+ // Q- packages exempt by default, except R- on Auto since Auto-Revoke was skipped in R
+ final int maxTargetSdkVersionForExemptApps =
+ packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ ? android.os.Build.VERSION_CODES.R
+ : android.os.Build.VERSION_CODES.Q;
+ try {
+ mPackageUid = packageManager.getPackageUidAsUser(
+ packageName, mContext.getUserId());
+ mIsPackageExemptByDefault = packageManager.getTargetSdkVersion(packageName)
+ <= maxTargetSdkVersionForExemptApps;
+ mIsPackageSet = true;
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Package [" + mPackageName + "] is not found!");
+ mIsPackageSet = false;
+ }
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+ ((SwitchPreference) preference).setChecked(!isPackageHibernationExemptByUser());
+ }
+
+ @VisibleForTesting
+ boolean isPackageHibernationExemptByUser() {
+ if (!mIsPackageSet) return true;
+ final int mode = mAppOpsManager.unsafeCheckOpNoThrow(
+ OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, mPackageUid, mPackageName);
+
+ return mode == MODE_DEFAULT ? mIsPackageExemptByDefault : mode != MODE_ALLOWED;
+ }
+
+ @Override
+ public void onOpChanged(String op, String packageName) {
+ if (OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED.equals(op)
+ && TextUtils.equals(mPackageName, packageName)) {
+ updateState(mPreference);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object isChecked) {
+ try {
+ mAppOpsManager.setUidMode(OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, mPackageUid,
+ (boolean) isChecked ? MODE_ALLOWED : MODE_IGNORED);
+ } catch (RuntimeException e) {
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean isHibernationEnabled() {
+ return DeviceConfig.getBoolean(
+ NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED, false);
+ }
+}