2 * Copyright (C) 2017 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
15 package com.android.settings.fuelgauge;
17 import android.app.AlertDialog;
18 import android.app.AppOpsManager;
19 import android.app.Dialog;
20 import android.app.Fragment;
21 import android.app.admin.DevicePolicyManager;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.content.pm.ApplicationInfo;
25 import android.content.pm.PackageManager;
26 import android.os.Build;
27 import android.os.Bundle;
28 import android.os.UserManager;
29 import android.support.annotation.VisibleForTesting;
30 import android.support.v14.preference.SwitchPreference;
31 import android.support.v7.preference.Preference;
32 import android.util.Log;
34 import com.android.settings.R;
35 import com.android.settings.Utils;
36 import com.android.settings.core.PreferenceControllerMixin;
37 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
38 import com.android.settings.wrapper.DevicePolicyManagerWrapper;
39 import com.android.settingslib.core.AbstractPreferenceController;
40 import com.android.settingslib.fuelgauge.PowerWhitelistBackend;
43 * Controller to control whether an app can run in the background
45 public class BackgroundActivityPreferenceController extends AbstractPreferenceController
46 implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
48 private static final String TAG = "BgActivityPrefContr";
49 private static final String KEY_BACKGROUND_ACTIVITY = "background_activity";
51 private final PackageManager mPackageManager;
52 private final AppOpsManager mAppOpsManager;
53 private final UserManager mUserManager;
54 private final int mUid;
56 DevicePolicyManagerWrapper mDpm;
57 private Fragment mFragment;
58 private String mTargetPackage;
59 private boolean mIsPreOApp;
60 private PowerWhitelistBackend mPowerWhitelistBackend;
62 public BackgroundActivityPreferenceController(Context context, Fragment fragment,
63 int uid, String packageName) {
64 this(context, fragment, uid, packageName, PowerWhitelistBackend.getInstance());
68 BackgroundActivityPreferenceController(Context context, Fragment fragment,
69 int uid, String packageName, PowerWhitelistBackend backend) {
71 mPowerWhitelistBackend = backend;
72 mPackageManager = context.getPackageManager();
73 mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
74 mDpm = new DevicePolicyManagerWrapper(
75 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE));
76 mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
79 mTargetPackage = packageName;
80 mIsPreOApp = isLegacyApp(packageName);
84 public void updateState(Preference preference) {
85 final int mode = mAppOpsManager
86 .checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mTargetPackage);
87 final boolean whitelisted = mPowerWhitelistBackend.isWhitelisted(mTargetPackage);
88 // Set checked or not before we may set it disabled
89 if (mode != AppOpsManager.MODE_ERRORED) {
90 final boolean checked = whitelisted || mode != AppOpsManager.MODE_IGNORED;
91 ((SwitchPreference) preference).setChecked(checked);
93 if (whitelisted || mode == AppOpsManager.MODE_ERRORED
94 || Utils.isProfileOrDeviceOwner(mUserManager, mDpm, mTargetPackage)) {
95 preference.setEnabled(false);
97 preference.setEnabled(true);
99 updateSummary(preference);
103 public boolean isAvailable() {
104 return mTargetPackage != null;
108 * Called from the warning dialog, if the user decides to go ahead and disable background
109 * activity for this package
111 public void setUnchecked(Preference preference) {
113 mAppOpsManager.setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, mUid, mTargetPackage,
114 AppOpsManager.MODE_IGNORED);
116 mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mTargetPackage,
117 AppOpsManager.MODE_IGNORED);
118 ((SwitchPreference) preference).setChecked(false);
119 updateSummary(preference);
123 public String getPreferenceKey() {
124 return KEY_BACKGROUND_ACTIVITY;
128 public boolean onPreferenceChange(Preference preference, Object newValue) {
129 final boolean switchOn = (Boolean) newValue;
131 final WarningDialogFragment dialogFragment = new WarningDialogFragment();
132 dialogFragment.setTargetFragment(mFragment, 0);
133 dialogFragment.show(mFragment.getFragmentManager(), TAG);
137 mAppOpsManager.setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, mUid, mTargetPackage,
138 AppOpsManager.MODE_ALLOWED);
140 mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mTargetPackage,
141 AppOpsManager.MODE_ALLOWED);
142 updateSummary(preference);
147 boolean isLegacyApp(final String packageName) {
149 ApplicationInfo info = mPackageManager.getApplicationInfo(packageName,
150 PackageManager.GET_META_DATA);
152 return info.targetSdkVersion < Build.VERSION_CODES.O;
153 } catch (PackageManager.NameNotFoundException e) {
154 Log.e(TAG, "Cannot find package: " + packageName, e);
161 void updateSummary(Preference preference) {
162 if (mPowerWhitelistBackend.isWhitelisted(mTargetPackage)) {
163 preference.setSummary(R.string.background_activity_summary_whitelisted);
166 final int mode = mAppOpsManager
167 .checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mTargetPackage);
169 if (mode == AppOpsManager.MODE_ERRORED) {
170 preference.setSummary(R.string.background_activity_summary_disabled);
172 final boolean checked = mode != AppOpsManager.MODE_IGNORED;
173 preference.setSummary(checked ? R.string.background_activity_summary_on
174 : R.string.background_activity_summary_off);
178 interface WarningConfirmationListener {
179 void onLimitBackgroundActivity();
183 * Warning dialog to show to the user as turning off background activity can lead to
184 * apps misbehaving as their background task scheduling guarantees will no longer be honored.
186 public static class WarningDialogFragment extends InstrumentedDialogFragment {
188 public int getMetricsCategory() {
189 // TODO (b/65494831): add metric id
194 public Dialog onCreateDialog(Bundle savedInstanceState) {
195 final WarningConfirmationListener listener =
196 (WarningConfirmationListener) getTargetFragment();
197 return new AlertDialog.Builder(getContext())
198 .setTitle(R.string.background_activity_warning_dialog_title)
199 .setMessage(R.string.background_activity_warning_dialog_text)
200 .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
202 public void onClick(DialogInterface dialog, int which) {
203 listener.onLimitBackgroundActivity();
206 .setNegativeButton(R.string.dlg_cancel, null)