1 /*
2  * Copyright (C) 2018 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.fuelgauge;
18 
19 import android.app.settings.SettingsEnums;
20 import android.content.Context;
21 import android.content.pm.ApplicationInfo;
22 import android.content.pm.PackageManager;
23 import android.os.Bundle;
24 import android.os.UserHandle;
25 import android.util.IconDrawableFactory;
26 import android.util.Log;
27 import android.util.SparseLongArray;
28 
29 import androidx.annotation.VisibleForTesting;
30 import androidx.preference.CheckBoxPreference;
31 import androidx.preference.Preference;
32 import androidx.preference.PreferenceGroup;
33 
34 import com.android.settings.R;
35 import com.android.settings.Utils;
36 import com.android.settings.core.InstrumentedPreferenceFragment;
37 import com.android.settings.core.SubSettingLauncher;
38 import com.android.settings.dashboard.DashboardFragment;
39 import com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper;
40 import com.android.settings.fuelgauge.batterytip.AppInfo;
41 import com.android.settings.fuelgauge.batterytip.BatteryDatabaseManager;
42 import com.android.settings.fuelgauge.batterytip.BatteryTipDialogFragment;
43 import com.android.settings.fuelgauge.batterytip.BatteryTipPreferenceController;
44 import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
45 import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip;
46 import com.android.settings.fuelgauge.batterytip.tips.UnrestrictAppTip;
47 import com.android.settings.overlay.FeatureFactory;
48 import com.android.settings.widget.AppCheckBoxPreference;
49 import com.android.settingslib.core.AbstractPreferenceController;
50 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
51 import com.android.settingslib.utils.StringUtil;
52 
53 import java.util.List;
54 
55 /**
56  * Fragment to show a list of anomaly apps, where user could handle these anomalies
57  */
58 public class RestrictedAppDetails extends DashboardFragment implements
59         BatteryTipPreferenceController.BatteryTipListener {
60 
61     public static final String TAG = "RestrictedAppDetails";
62 
63     @VisibleForTesting
64     static final String EXTRA_APP_INFO_LIST = "app_info_list";
65     private static final String KEY_PREF_RESTRICTED_APP_LIST = "restrict_app_list";
66     private static final long TIME_NULL = -1;
67 
68     @VisibleForTesting
69     List<AppInfo> mAppInfos;
70     @VisibleForTesting
71     IconDrawableFactory mIconDrawableFactory;
72     @VisibleForTesting
73     PreferenceGroup mRestrictedAppListGroup;
74     @VisibleForTesting
75     BatteryUtils mBatteryUtils;
76     @VisibleForTesting
77     PackageManager mPackageManager;
78     @VisibleForTesting
79     BatteryDatabaseManager mBatteryDatabaseManager;
80 
81     private MetricsFeatureProvider mMetricsFeatureProvider;
82 
startRestrictedAppDetails(InstrumentedPreferenceFragment fragment, List<AppInfo> appInfos)83     public static void startRestrictedAppDetails(InstrumentedPreferenceFragment fragment,
84             List<AppInfo> appInfos) {
85         final Bundle args = new Bundle();
86         args.putParcelableList(EXTRA_APP_INFO_LIST, appInfos);
87 
88         new SubSettingLauncher(fragment.getContext())
89                 .setDestination(RestrictedAppDetails.class.getName())
90                 .setArguments(args)
91                 .setTitleRes(R.string.restricted_app_title)
92                 .setSourceMetricsCategory(fragment.getMetricsCategory())
93                 .launch();
94     }
95 
96     @Override
onCreate(Bundle icicle)97     public void onCreate(Bundle icicle) {
98         super.onCreate(icicle);
99         final Context context = getContext();
100 
101         mRestrictedAppListGroup = (PreferenceGroup) findPreference(KEY_PREF_RESTRICTED_APP_LIST);
102         mAppInfos = getArguments().getParcelableArrayList(EXTRA_APP_INFO_LIST);
103         mPackageManager = context.getPackageManager();
104         mIconDrawableFactory = IconDrawableFactory.newInstance(context);
105         mBatteryUtils = BatteryUtils.getInstance(context);
106         mBatteryDatabaseManager = BatteryDatabaseManager.getInstance(context);
107         mMetricsFeatureProvider =
108                 FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider();
109         refreshUi();
110     }
111 
112     @Override
onPreferenceTreeClick(Preference preference)113     public boolean onPreferenceTreeClick(Preference preference) {
114 
115         return super.onPreferenceTreeClick(preference);
116     }
117 
118     @Override
getLogTag()119     protected String getLogTag() {
120         return TAG;
121     }
122 
123     @Override
getPreferenceScreenResId()124     protected int getPreferenceScreenResId() {
125         return R.xml.restricted_apps_detail;
126     }
127 
128     @Override
createPreferenceControllers(Context context)129     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
130         return null;
131     }
132 
133     @Override
getMetricsCategory()134     public int getMetricsCategory() {
135         return SettingsEnums.FUELGAUGE_RESTRICTED_APP_DETAILS;
136     }
137 
138     @Override
getHelpResource()139     public int getHelpResource() {
140         return R.string.help_uri_restricted_apps;
141     }
142 
143     @VisibleForTesting
refreshUi()144     void refreshUi() {
145         mRestrictedAppListGroup.removeAll();
146         final Context context = getPrefContext();
147         final SparseLongArray timestampArray = mBatteryDatabaseManager
148                 .queryActionTime(AnomalyDatabaseHelper.ActionType.RESTRICTION);
149         final long now = System.currentTimeMillis();
150 
151         for (int i = 0, size = mAppInfos.size(); i < size; i++) {
152             final CheckBoxPreference checkBoxPreference = new AppCheckBoxPreference(context);
153             final AppInfo appInfo = mAppInfos.get(i);
154             try {
155                 final ApplicationInfo applicationInfo = mPackageManager.getApplicationInfoAsUser(
156                         appInfo.packageName, 0 /* flags */, UserHandle.getUserId(appInfo.uid));
157                 checkBoxPreference.setChecked(
158                         mBatteryUtils.isForceAppStandbyEnabled(appInfo.uid, appInfo.packageName));
159                 checkBoxPreference.setTitle(mPackageManager.getApplicationLabel(applicationInfo));
160                 checkBoxPreference.setIcon(
161                         Utils.getBadgedIcon(mIconDrawableFactory, mPackageManager,
162                                 appInfo.packageName,
163                                 UserHandle.getUserId(appInfo.uid)));
164                 checkBoxPreference.setKey(getKeyFromAppInfo(appInfo));
165                 checkBoxPreference.setOnPreferenceChangeListener((pref, value) -> {
166                     final BatteryTipDialogFragment fragment = createDialogFragment(appInfo,
167                             (Boolean) value);
168                     fragment.setTargetFragment(this, 0 /* requestCode */);
169                     fragment.show(getFragmentManager(), TAG);
170                     mMetricsFeatureProvider.action(getContext(),
171                             SettingsEnums.ACTION_APP_RESTRICTED_LIST_UNCHECKED,
172                             appInfo.packageName);
173                     return false;
174                 });
175 
176                 final long timestamp = timestampArray.get(appInfo.uid, TIME_NULL);
177                 if (timestamp != TIME_NULL) {
178                     checkBoxPreference.setSummary(getString(R.string.restricted_app_time_summary,
179                             StringUtil.formatRelativeTime(context, now - timestamp, false)));
180                 }
181                 final CharSequence test = checkBoxPreference.getSummaryOn();
182                 mRestrictedAppListGroup.addPreference(checkBoxPreference);
183             } catch (PackageManager.NameNotFoundException e) {
184                 Log.e(TAG, "Can't find package: " + appInfo.packageName);
185             }
186         }
187     }
188 
189     @Override
onBatteryTipHandled(BatteryTip batteryTip)190     public void onBatteryTipHandled(BatteryTip batteryTip) {
191         final AppInfo appInfo;
192         final boolean isRestricted = batteryTip instanceof RestrictAppTip;
193         if (isRestricted) {
194             appInfo = ((RestrictAppTip) batteryTip).getRestrictAppList().get(0);
195         } else {
196             appInfo = ((UnrestrictAppTip) batteryTip).getUnrestrictAppInfo();
197         }
198 
199         CheckBoxPreference preference = (CheckBoxPreference) mRestrictedAppListGroup
200                 .findPreference(getKeyFromAppInfo(appInfo));
201         if (preference != null) {
202             preference.setChecked(isRestricted);
203         }
204     }
205 
206     @VisibleForTesting
createDialogFragment(AppInfo appInfo, boolean toRestrict)207     BatteryTipDialogFragment createDialogFragment(AppInfo appInfo, boolean toRestrict) {
208         final BatteryTip batteryTip = toRestrict
209                 ? new RestrictAppTip(BatteryTip.StateType.NEW, appInfo)
210                 : new UnrestrictAppTip(BatteryTip.StateType.NEW, appInfo);
211 
212         return BatteryTipDialogFragment.newInstance(
213                 batteryTip, getMetricsCategory());
214     }
215 
216     @VisibleForTesting
getKeyFromAppInfo(AppInfo appInfo)217     String getKeyFromAppInfo(AppInfo appInfo) {
218         return appInfo.uid + "," + appInfo.packageName;
219     }
220 }
221