Update the summary of "unused apps" based on how many apps are hibernated or auto revoked. Test: HibernatedAppsPreferenceControllerTest Bug: 181172051 Change-Id: I274eb3c0ada3d063937368232f31e52c20287552
146 lines
5.5 KiB
Java
146 lines
5.5 KiB
Java
/*
|
|
* 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. This method should only be called to initialize the
|
|
* controller.
|
|
* @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.getPackageUid(packageName, /* flags */ 0);
|
|
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);
|
|
}
|
|
}
|