1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.applications.appinfo; 18 19 import static android.app.AppOpsManager.MODE_ALLOWED; 20 import static android.app.AppOpsManager.MODE_DEFAULT; 21 import static android.app.AppOpsManager.MODE_IGNORED; 22 import static android.app.AppOpsManager.OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED; 23 import static android.permission.PermissionControllerManager.HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM; 24 import static android.permission.PermissionControllerManager.HIBERNATION_ELIGIBILITY_UNKNOWN; 25 import static android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION; 26 27 import static com.android.settings.Utils.PROPERTY_APP_HIBERNATION_ENABLED; 28 import static com.android.settings.Utils.PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS; 29 30 import android.app.AppOpsManager; 31 import android.apphibernation.AppHibernationManager; 32 import android.content.Context; 33 import android.content.pm.PackageManager; 34 import android.permission.PermissionControllerManager; 35 import android.provider.DeviceConfig; 36 import android.util.Slog; 37 38 import androidx.annotation.NonNull; 39 import androidx.preference.Preference; 40 import androidx.preference.TwoStatePreference; 41 42 import com.google.common.annotations.VisibleForTesting; 43 44 /** 45 * A PreferenceController handling the logic for exempting hibernation of app 46 */ 47 public final class HibernationSwitchPreferenceController extends AppInfoPreferenceControllerBase 48 implements Preference.OnPreferenceChangeListener { 49 private static final String TAG = "HibernationSwitchPrefController"; 50 private String mPackageName; 51 private final AppOpsManager mAppOpsManager; 52 private final PermissionControllerManager mPermissionControllerManager; 53 private int mPackageUid; 54 private boolean mHibernationEligibilityLoaded; 55 private int mHibernationEligibility = HIBERNATION_ELIGIBILITY_UNKNOWN; 56 @VisibleForTesting 57 boolean mIsPackageSet; 58 private boolean mIsPackageExemptByDefault; 59 HibernationSwitchPreferenceController(Context context, String preferenceKey)60 public HibernationSwitchPreferenceController(Context context, 61 String preferenceKey) { 62 super(context, preferenceKey); 63 mAppOpsManager = context.getSystemService(AppOpsManager.class); 64 mPermissionControllerManager = context.getSystemService(PermissionControllerManager.class); 65 } 66 67 @Override getAvailabilityStatus()68 public int getAvailabilityStatus() { 69 return isHibernationEnabled() && mIsPackageSet ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; 70 } 71 72 /** 73 * Set the package. And also retrieve details from package manager. Some packages may be 74 * exempted from hibernation by default. This method should only be called to initialize the 75 * controller. 76 * @param packageName The name of the package whose hibernation state to be managed. 77 */ setPackage(@onNull String packageName)78 void setPackage(@NonNull String packageName) { 79 mPackageName = packageName; 80 final PackageManager packageManager = mContext.getPackageManager(); 81 82 // Q- packages exempt by default, except R- on Auto since Auto-Revoke was skipped in R 83 final int maxTargetSdkVersionForExemptApps = 84 packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) 85 ? android.os.Build.VERSION_CODES.R 86 : android.os.Build.VERSION_CODES.Q; 87 try { 88 mPackageUid = packageManager.getPackageUid(packageName, /* flags */ 0); 89 mIsPackageExemptByDefault = 90 hibernationTargetsPreSApps() 91 ? false 92 : packageManager.getTargetSdkVersion(packageName) 93 <= maxTargetSdkVersionForExemptApps; 94 mIsPackageSet = true; 95 } catch (PackageManager.NameNotFoundException e) { 96 Slog.w(TAG, "Package [" + mPackageName + "] is not found!"); 97 mIsPackageSet = false; 98 } 99 } 100 isAppEligibleForHibernation()101 private boolean isAppEligibleForHibernation() { 102 return mHibernationEligibilityLoaded 103 && mHibernationEligibility != HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM 104 && mHibernationEligibility != HIBERNATION_ELIGIBILITY_UNKNOWN; 105 } 106 107 @Override updateState(Preference preference)108 public void updateState(Preference preference) { 109 super.updateState(preference); 110 ((TwoStatePreference) preference).setChecked(isAppEligibleForHibernation() 111 && !isPackageHibernationExemptByUser()); 112 preference.setEnabled(isAppEligibleForHibernation()); 113 if (!mHibernationEligibilityLoaded) { 114 mPermissionControllerManager.getHibernationEligibility(mPackageName, 115 mContext.getMainExecutor(), 116 eligibility -> { 117 mHibernationEligibility = eligibility; 118 mHibernationEligibilityLoaded = true; 119 updateState(preference); 120 }); 121 } 122 } 123 124 @VisibleForTesting isPackageHibernationExemptByUser()125 boolean isPackageHibernationExemptByUser() { 126 if (!mIsPackageSet) return true; 127 final int mode = mAppOpsManager.unsafeCheckOpNoThrow( 128 OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, mPackageUid, mPackageName); 129 130 return mode == MODE_DEFAULT ? mIsPackageExemptByDefault : mode != MODE_ALLOWED; 131 } 132 133 @Override onPreferenceChange(Preference preference, Object isChecked)134 public boolean onPreferenceChange(Preference preference, Object isChecked) { 135 try { 136 final boolean checked = (boolean) isChecked; 137 mAppOpsManager.setUidMode(OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, mPackageUid, 138 checked ? MODE_ALLOWED : MODE_IGNORED); 139 if (!checked) { 140 final AppHibernationManager ahm = 141 mContext.getSystemService(AppHibernationManager.class); 142 ahm.setHibernatingForUser(mPackageName, false); 143 ahm.setHibernatingGlobally(mPackageName, false); 144 } 145 } catch (RuntimeException e) { 146 return false; 147 } 148 return true; 149 } 150 isHibernationEnabled()151 private static boolean isHibernationEnabled() { 152 return DeviceConfig.getBoolean( 153 NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED, true); 154 } 155 hibernationTargetsPreSApps()156 private static boolean hibernationTargetsPreSApps() { 157 return DeviceConfig.getBoolean( 158 NAMESPACE_APP_HIBERNATION, PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS, false); 159 } 160 } 161