Adding switch for Forced App Standby

Reusing the 'Background activity' switch found in App Info. The switch
now needs to be shown for all apps and will toggle another app op
RUN_ANY_IN_BACKGROUND which controls whether jobs or alarms are run for
background apps.
Also fixed handling of multiple packages with shared uid. The controller
was picking the first package for uid but the order of packages can
change on a reboot which would cause wrong app ops settings across
packages of the same uid.

Test: make -j32 RunSettingsRoboTests

Bug: 65176793
Change-Id: I2a9b96bc02730776172c3ae317cb7f7f890bec30
This commit is contained in:
Suprabh Shukla
2017-09-07 16:27:08 -07:00
parent 14e4964be0
commit 4c64777b2a
4 changed files with 190 additions and 85 deletions

View File

@@ -14,12 +14,17 @@
package com.android.settings.fuelgauge;
import android.app.AlertDialog;
import android.app.AppOpsManager;
import android.app.Dialog;
import android.app.Fragment;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.UserManager;
import android.support.annotation.VisibleForTesting;
import android.support.v14.preference.SwitchPreference;
@@ -29,6 +34,7 @@ import android.util.Log;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.enterprise.DevicePolicyManagerWrapper;
import com.android.settings.enterprise.DevicePolicyManagerWrapperImpl;
import com.android.settingslib.core.AbstractPreferenceController;
@@ -45,54 +51,72 @@ public class BackgroundActivityPreferenceController extends AbstractPreferenceCo
private final PackageManager mPackageManager;
private final AppOpsManager mAppOpsManager;
private final UserManager mUserManager;
private final String[] mPackages;
private final int mUid;
@VisibleForTesting
DevicePolicyManagerWrapper mDpm;
private Fragment mFragment;
private String mTargetPackage;
private boolean mIsPreOApp;
private PowerWhitelistBackend mPowerWhitelistBackend;
public BackgroundActivityPreferenceController(Context context, int uid) {
public BackgroundActivityPreferenceController(Context context, Fragment fragment,
int uid, String packageName) {
this(context, fragment, uid, packageName, PowerWhitelistBackend.getInstance());
}
@VisibleForTesting
BackgroundActivityPreferenceController(Context context, Fragment fragment,
int uid, String packageName, PowerWhitelistBackend backend) {
super(context);
mPowerWhitelistBackend = backend;
mPackageManager = context.getPackageManager();
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
mDpm = new DevicePolicyManagerWrapperImpl(
(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE));
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mUid = uid;
mPackages = mPackageManager.getPackagesForUid(mUid);
mFragment = fragment;
mTargetPackage = packageName;
mIsPreOApp = isLegacyApp(packageName);
}
@Override
public void updateState(Preference preference) {
final int mode = mAppOpsManager
.checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, mUid, mTargetPackage);
.checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mTargetPackage);
final boolean whitelisted = mPowerWhitelistBackend.isWhitelisted(mTargetPackage);
// Set checked or not before we may set it disabled
if (mode != AppOpsManager.MODE_ERRORED) {
final boolean checked = mode != AppOpsManager.MODE_IGNORED;
final boolean checked = whitelisted || mode != AppOpsManager.MODE_IGNORED;
((SwitchPreference) preference).setChecked(checked);
}
if (mode == AppOpsManager.MODE_ERRORED
if (whitelisted || mode == AppOpsManager.MODE_ERRORED
|| Utils.isProfileOrDeviceOwner(mUserManager, mDpm, mTargetPackage)) {
preference.setEnabled(false);
} else {
preference.setEnabled(true);
}
updateSummary(preference);
}
@Override
public boolean isAvailable() {
if (mPackages == null) {
return false;
}
for (final String packageName : mPackages) {
if (isLegacyApp(packageName)) {
mTargetPackage = packageName;
return true;
}
}
return mTargetPackage != null;
}
return false;
/**
* Called from the warning dialog, if the user decides to go ahead and disable background
* activity for this package
*/
public void setUnchecked(Preference preference) {
if (mIsPreOApp) {
mAppOpsManager.setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, mUid, mTargetPackage,
AppOpsManager.MODE_IGNORED);
}
mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mTargetPackage,
AppOpsManager.MODE_IGNORED);
((SwitchPreference) preference).setChecked(false);
updateSummary(preference);
}
@Override
@@ -102,19 +126,23 @@ public class BackgroundActivityPreferenceController extends AbstractPreferenceCo
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
boolean switchOn = (Boolean) newValue;
mAppOpsManager.setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, mUid, mTargetPackage,
switchOn ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
final boolean switchOn = (Boolean) newValue;
if (!switchOn) {
final WarningDialogFragment dialogFragment = new WarningDialogFragment();
dialogFragment.setTargetFragment(mFragment, 0);
dialogFragment.show(mFragment.getFragmentManager(), TAG);
return false;
}
if (mIsPreOApp) {
mAppOpsManager.setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, mUid, mTargetPackage,
AppOpsManager.MODE_ALLOWED);
}
mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mTargetPackage,
AppOpsManager.MODE_ALLOWED);
updateSummary(preference);
return true;
}
@VisibleForTesting
String getTargetPackage() {
return mTargetPackage;
}
@VisibleForTesting
boolean isLegacyApp(final String packageName) {
try {
@@ -131,8 +159,12 @@ public class BackgroundActivityPreferenceController extends AbstractPreferenceCo
@VisibleForTesting
void updateSummary(Preference preference) {
if (mPowerWhitelistBackend.isWhitelisted(mTargetPackage)) {
preference.setSummary(R.string.background_activity_summary_whitelisted);
return;
}
final int mode = mAppOpsManager
.checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, mUid, mTargetPackage);
.checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mTargetPackage);
if (mode == AppOpsManager.MODE_ERRORED) {
preference.setSummary(R.string.background_activity_summary_disabled);
@@ -142,4 +174,37 @@ public class BackgroundActivityPreferenceController extends AbstractPreferenceCo
: R.string.background_activity_summary_off);
}
}
interface WarningConfirmationListener {
void onLimitBackgroundActivity();
}
/**
* Warning dialog to show to the user as turning off background activity can lead to
* apps misbehaving as their background task scheduling guarantees will no longer be honored.
*/
public static class WarningDialogFragment extends InstrumentedDialogFragment {
@Override
public int getMetricsCategory() {
// TODO (b/65494831): add metric id
return 0;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final WarningConfirmationListener listener =
(WarningConfirmationListener) getTargetFragment();
return new AlertDialog.Builder(getContext())
.setTitle(R.string.background_activity_warning_dialog_title)
.setMessage(R.string.background_activity_warning_dialog_text)
.setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
listener.onLimitBackgroundActivity();
}
})
.setNegativeButton(R.string.dlg_cancel, null)
.create();
}
}
}