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/res/values/strings.xml b/res/values/strings.xml
index 7e10006..c464da0 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -9800,6 +9800,18 @@
<!-- Runtime permissions preference summary, which describes what the permission manager does. [CHAR LIMIT=NONE] -->
<string name="runtime_permissions_summary_control_app_access">Control app access to your data</string>
+ <!-- Label for showing apps that have not been used for months. [CHAR LIMIT=40]-->
+ <string name="unused_apps">Unused apps</string>
+
+ <!-- Summary of number of apps that have not been used for months. [CHAR LIMIT=40]-->
+ <plurals name="unused_apps_summary">
+ <item quantity="one"><xliff:g id="count" example="1">%d</xliff:g> unused app</item>
+ <item quantity="other"><xliff:g id="count" example="10">%d</xliff:g> unused apps</item>
+ </plurals>
+
+ <!-- Label of a switch preference that controls whether the system will remove the permissions and free up space when the app has not been used for months [CHAR LIMIT=40]-->
+ <string name="unused_apps_switch">Remove permissions and free up space</string>
+
<!-- Label for showing all apps in list [CHAR LIMIT=30] -->
<string name="filter_all_apps">All apps</string>
<!-- Label for showing enabled apps in list [CHAR LIMIT=30] -->
diff --git a/res/xml/app_and_notification.xml b/res/xml/app_and_notification.xml
index 49d8e9b..8b4c912 100644
--- a/res/xml/app_and_notification.xml
+++ b/res/xml/app_and_notification.xml
@@ -83,6 +83,16 @@
<intent android:action="android.intent.action.MANAGE_PERMISSIONS"/>
</Preference>
+ <Preference
+ android:key="hibernated_apps"
+ android:title="@string/unused_apps"
+ android:summary="@string/summary_placeholder"
+ android:order="13"
+ settings:keywords="app_hibernation_key"
+ settings:controller="com.android.settings.applications.HibernatedAppsPreferenceController">
+ <intent android:action="android.intent.action.MANAGE_UNUSED_APPS"/>
+ </Preference>
+
<com.android.settingslib.RestrictedPreference
android:key="app_and_notif_cell_broadcast_settings"
android:title="@string/cell_broadcast_settings"
diff --git a/res/xml/app_info_settings.xml b/res/xml/app_info_settings.xml
index a76f0d9..f5d927e 100644
--- a/res/xml/app_info_settings.xml
+++ b/res/xml/app_info_settings.xml
@@ -120,6 +120,19 @@
android:title="@string/sms_application_title"
android:summary="@string/summary_placeholder" />
+ <PreferenceCategory
+ android:key="app_hibernation_info"
+ android:title="@string/unused_apps"
+ settings:controller=
+ "com.android.settings.applications.appinfo.AppHibernationPreferenceCategoryController">
+
+ <SwitchPreference
+ android:key="hibernation_switch"
+ android:title="@string/unused_apps_switch"
+ settings:controller=
+ "com.android.settings.applications.appinfo.HibernationSwitchPreferenceController" />
+ </PreferenceCategory>
+
<!-- Advanced apps settings -->
<PreferenceCategory
android:key="advanced_app_info"
diff --git a/res/xml/app_info_settings_v2.xml b/res/xml/app_info_settings_v2.xml
index 574fc01..80c449a 100644
--- a/res/xml/app_info_settings_v2.xml
+++ b/res/xml/app_info_settings_v2.xml
@@ -134,6 +134,19 @@
android:title="@string/sms_application_title"
android:summary="@string/summary_placeholder" />
+ <PreferenceCategory
+ android:key="app_hibernation_info"
+ android:title="@string/unused_apps"
+ settings:controller=
+ "com.android.settings.applications.appinfo.AppHibernationPreferenceCategoryController">
+
+ <SwitchPreference
+ android:key="hibernation_switch"
+ android:title="@string/unused_apps_switch"
+ settings:controller=
+ "com.android.settings.applications.appinfo.HibernationSwitchPreferenceController" />
+ </PreferenceCategory>
+
<!-- Advanced apps settings -->
<PreferenceCategory
android:key="advanced_app_info"
diff --git a/res/xml/apps.xml b/res/xml/apps.xml
index df295bf..cefb67a 100644
--- a/res/xml/apps.xml
+++ b/res/xml/apps.xml
@@ -67,6 +67,16 @@
android:order="10"/>
<Preference
+ android:key="hibernated_apps"
+ android:title="@string/unused_apps"
+ android:summary="@string/summary_placeholder"
+ android:order="15"
+ settings:keywords="app_hibernation_key"
+ settings:controller="com.android.settings.applications.HibernatedAppsPreferenceController">
+ <intent android:action="android.intent.action.MANAGE_UNUSED_APPS"/>
+ </Preference>
+
+ <Preference
android:key="special_access_v2"
android:fragment="com.android.settings.applications.specialaccess.SpecialAccessSettings"
android:title="@string/special_access"
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);
+ }
+}
diff --git a/tests/unit/src/com/android/settings/applications/HibernatedAppsPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/HibernatedAppsPreferenceControllerTest.java
new file mode 100644
index 0000000..cf4c53e
--- /dev/null
+++ b/tests/unit/src/com/android/settings/applications/HibernatedAppsPreferenceControllerTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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 static com.android.settings.core.BasePreferenceController.AVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.provider.DeviceConfig;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * TODO(b/181172051): test getNumberHibernated() when the API implemented
+ */
+@RunWith(AndroidJUnit4.class)
+public class HibernatedAppsPreferenceControllerTest {
+
+ private static final String KEY = "key";
+ private Context mContext;
+ private HibernatedAppsPreferenceController mController;
+
+ @Before
+ public void setUp() {
+ DeviceConfig.setProperty(NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED,
+ "true", false);
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ mController = new HibernatedAppsPreferenceController(mContext, KEY);
+ }
+
+ @Test
+ public void getAvailabilityStatus_featureDisabled_shouldNotReturnAvailable() {
+ DeviceConfig.setProperty(NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED,
+ "false", true);
+
+ assertThat((mController).getAvailabilityStatus()).isNotEqualTo(AVAILABLE);
+ }
+}
diff --git a/tests/unit/src/com/android/settings/applications/appinfo/HibernationSwitchPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/appinfo/HibernationSwitchPreferenceControllerTest.java
new file mode 100644
index 0000000..e27942a
--- /dev/null
+++ b/tests/unit/src/com/android/settings/applications/appinfo/HibernationSwitchPreferenceControllerTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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 static com.android.settings.core.BasePreferenceController.AVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.provider.DeviceConfig;
+
+import androidx.preference.SwitchPreference;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class HibernationSwitchPreferenceControllerTest {
+ private static final int PACKAGE_UID = 1;
+ private static final String INVALID_PACKAGE_NAME = "invalid_package";
+ private static final String KEY = "key";
+ private static final String VALID_PACKAGE_NAME = "package";
+ private static final String EXEMPTED_PACKAGE_NAME = "exempted_package";
+ private static final String UNEXEMPTED_PACKAGE_NAME = "unexempted_package";
+ @Mock
+ private AppOpsManager mAppOpsManager;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private SwitchPreference mPreference;
+
+ private HibernationSwitchPreferenceController mController;
+ private Context mContext;
+
+ @Before
+ public void setUp() throws PackageManager.NameNotFoundException {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
+ when(mPackageManager.getPackageUidAsUser(eq(VALID_PACKAGE_NAME), anyInt()))
+ .thenReturn(PACKAGE_UID);
+ when(mPackageManager.getPackageUidAsUser(eq(INVALID_PACKAGE_NAME), anyInt()))
+ .thenThrow(new PackageManager.NameNotFoundException());
+ when(mPackageManager.getTargetSdkVersion(eq(EXEMPTED_PACKAGE_NAME)))
+ .thenReturn(android.os.Build.VERSION_CODES.Q);
+ when(mPackageManager.getTargetSdkVersion(eq(UNEXEMPTED_PACKAGE_NAME)))
+ .thenReturn(android.os.Build.VERSION_CODES.S);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ DeviceConfig.setProperty(NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED,
+ "true", true /* makeDefault */);
+ mController = new HibernationSwitchPreferenceController(mContext, KEY);
+ when(mPreference.getKey()).thenReturn(mController.getPreferenceKey());
+ }
+
+ @Test
+ public void getAvailabilityStatus_featureNotEnabled_shouldNotReturnAvailable() {
+ mController.setPackage(VALID_PACKAGE_NAME);
+ DeviceConfig.setProperty(NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED,
+ "false", true /* makeDefault */);
+
+ assertThat(mController.getAvailabilityStatus()).isNotEqualTo(AVAILABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_invalidPackage_shouldReturnNotAvailable() {
+ mController.setPackage(INVALID_PACKAGE_NAME);
+
+ assertThat(mController.getAvailabilityStatus()).isNotEqualTo(AVAILABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_validPackage_shouldReturnAvailable() {
+ mController.setPackage(VALID_PACKAGE_NAME);
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+ }
+
+ @Test
+ public void updateState_exemptedByDefaultPackage_shouldNotCheck() {
+ when(mAppOpsManager.unsafeCheckOpNoThrow(
+ eq(OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED), anyInt(), eq(EXEMPTED_PACKAGE_NAME)))
+ .thenReturn(MODE_DEFAULT);
+ mController.setPackage(EXEMPTED_PACKAGE_NAME);
+
+ mController.updateState(mPreference);
+
+ verify(mPreference).setChecked(false);
+ }
+
+ @Test
+ public void updateState_exemptedPackageOverrideByUser_shouldCheck() {
+ when(mAppOpsManager.unsafeCheckOpNoThrow(
+ eq(OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED), anyInt(), eq(EXEMPTED_PACKAGE_NAME)))
+ .thenReturn(MODE_ALLOWED);
+ mController.setPackage(EXEMPTED_PACKAGE_NAME);
+
+ mController.updateState(mPreference);
+
+ verify(mPreference).setChecked(true);
+ }
+
+ @Test
+ public void updateState_unexemptedPackageOverrideByUser_shouldNotCheck() {
+ when(mAppOpsManager.unsafeCheckOpNoThrow(
+ eq(OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED), anyInt(), eq(UNEXEMPTED_PACKAGE_NAME)))
+ .thenReturn(MODE_IGNORED);
+ mController.setPackage(UNEXEMPTED_PACKAGE_NAME);
+
+ mController.updateState(mPreference);
+
+ verify(mPreference).setChecked(false);
+ }
+}