• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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 static com.android.settings.fuelgauge.batteryusage.ConvertUtils.isUserConsumer;
20 
21 import android.app.Activity;
22 import android.app.ActivityManager;
23 import android.app.settings.SettingsEnums;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.pm.PackageManager;
27 import android.os.Bundle;
28 import android.os.UserHandle;
29 import android.text.TextUtils;
30 import android.util.Log;
31 import android.view.View;
32 
33 import androidx.annotation.NonNull;
34 import androidx.annotation.VisibleForTesting;
35 import androidx.preference.Preference;
36 
37 import com.android.settings.R;
38 import com.android.settings.SettingsActivity;
39 import com.android.settings.Utils;
40 import com.android.settings.applications.appinfo.AppButtonsPreferenceController;
41 import com.android.settings.applications.appinfo.ButtonActionDialogFragment;
42 import com.android.settings.core.InstrumentedPreferenceFragment;
43 import com.android.settings.core.SubSettingLauncher;
44 import com.android.settings.dashboard.DashboardFragment;
45 import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action;
46 import com.android.settings.fuelgauge.batteryusage.AppOptModeSharedPreferencesUtils;
47 import com.android.settings.fuelgauge.batteryusage.BatteryDiffEntry;
48 import com.android.settings.fuelgauge.batteryusage.BatteryEntry;
49 import com.android.settings.overlay.FeatureFactory;
50 import com.android.settings.widget.EntityHeaderController;
51 import com.android.settingslib.PrimarySwitchPreference;
52 import com.android.settingslib.applications.AppUtils;
53 import com.android.settingslib.applications.ApplicationsState;
54 import com.android.settingslib.core.AbstractPreferenceController;
55 import com.android.settingslib.core.instrumentation.Instrumentable;
56 import com.android.settingslib.widget.LayoutPreference;
57 
58 import java.util.ArrayList;
59 import java.util.List;
60 import java.util.concurrent.ExecutorService;
61 import java.util.concurrent.Executors;
62 
63 /**
64  * Power usage detail fragment for each app, this fragment contains <br>
65  * <br>
66  * 1. Detail battery usage information for app(i.e. usage time, usage amount) <br>
67  * 2. Battery related controls for app(i.e uninstall, force stop)
68  */
69 public class AdvancedPowerUsageDetail extends DashboardFragment
70         implements ButtonActionDialogFragment.AppButtonsDialogListener,
71                 Preference.OnPreferenceClickListener,
72                 Preference.OnPreferenceChangeListener {
73     public static final String TAG = "AdvancedPowerDetail";
74     public static final String EXTRA_UID = "extra_uid";
75     public static final String EXTRA_PACKAGE_NAME = "extra_package_name";
76     public static final String EXTRA_FOREGROUND_TIME = "extra_foreground_time";
77     public static final String EXTRA_BACKGROUND_TIME = "extra_background_time";
78     public static final String EXTRA_SCREEN_ON_TIME = "extra_screen_on_time";
79     public static final String EXTRA_ANOMALY_HINT_PREF_KEY = "extra_anomaly_hint_pref_key";
80     public static final String EXTRA_ANOMALY_HINT_TEXT = "extra_anomaly_hint_text";
81     public static final String EXTRA_SHOW_TIME_INFO = "extra_show_time_info";
82     public static final String EXTRA_SLOT_TIME = "extra_slot_time";
83     public static final String EXTRA_LABEL = "extra_label";
84     public static final String EXTRA_ICON_ID = "extra_icon_id";
85     public static final String EXTRA_POWER_USAGE_PERCENT = "extra_power_usage_percent";
86     public static final String EXTRA_POWER_USAGE_AMOUNT = "extra_power_usage_amount";
87 
88     private static final String KEY_PREF_HEADER = "header_view";
89     private static final String KEY_ALLOW_BACKGROUND_USAGE = "allow_background_usage";
90 
91     private static final int REQUEST_UNINSTALL = 0;
92     private static final int REQUEST_REMOVE_DEVICE_ADMIN = 1;
93 
94     private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
95 
96     private AppButtonsPreferenceController mAppButtonsPreferenceController;
97     private PowerUsageTimeController mPowerUsageTimeController;
98 
99     @VisibleForTesting LayoutPreference mHeaderPreference;
100     @VisibleForTesting ApplicationsState mState;
101     @VisibleForTesting ApplicationsState.AppEntry mAppEntry;
102     @VisibleForTesting BatteryOptimizeUtils mBatteryOptimizeUtils;
103     @VisibleForTesting PrimarySwitchPreference mAllowBackgroundUsagePreference;
104 
105     @VisibleForTesting @BatteryOptimizeUtils.OptimizationMode
106     int mOptimizationMode = BatteryOptimizeUtils.MODE_UNKNOWN;
107 
108     @VisibleForTesting StringBuilder mLogStringBuilder;
109 
110     // A wrapper class to carry LaunchBatteryDetailPage required arguments.
111     private static final class LaunchBatteryDetailPageArgs {
112         private String mUsagePercent;
113         private String mPackageName;
114         private String mAppLabel;
115         private String mSlotInformation;
116         private String mAnomalyHintText;
117         private String mAnomalyHintPrefKey;
118         private int mUid;
119         private int mIconId;
120         private int mConsumedPower;
121         private long mForegroundTimeMs;
122         private long mBackgroundTimeMs;
123         private long mScreenOnTimeMs;
124         private boolean mShowTimeInformation;
125         private boolean mIsUserEntry;
126     }
127 
128     /** Launches battery details page for an individual battery consumer fragment. */
startBatteryDetailPage( Context context, int sourceMetricsCategory, BatteryDiffEntry diffEntry, String usagePercent, String slotInformation, boolean showTimeInformation, String anomalyHintPrefKey, String anomalyHintText)129     public static void startBatteryDetailPage(
130             Context context,
131             int sourceMetricsCategory,
132             BatteryDiffEntry diffEntry,
133             String usagePercent,
134             String slotInformation,
135             boolean showTimeInformation,
136             String anomalyHintPrefKey,
137             String anomalyHintText) {
138         final LaunchBatteryDetailPageArgs launchArgs = new LaunchBatteryDetailPageArgs();
139         // configure the launch argument.
140         launchArgs.mUsagePercent = usagePercent;
141         launchArgs.mPackageName = diffEntry.getPackageName();
142         launchArgs.mAppLabel = diffEntry.getAppLabel();
143         launchArgs.mSlotInformation = slotInformation;
144         launchArgs.mUid = (int) diffEntry.mUid;
145         launchArgs.mIconId = diffEntry.getAppIconId();
146         launchArgs.mConsumedPower = (int) diffEntry.mConsumePower;
147         launchArgs.mShowTimeInformation = showTimeInformation;
148         if (launchArgs.mShowTimeInformation) {
149             launchArgs.mForegroundTimeMs = diffEntry.mForegroundUsageTimeInMs;
150             launchArgs.mBackgroundTimeMs =
151                     diffEntry.mBackgroundUsageTimeInMs + diffEntry.mForegroundServiceUsageTimeInMs;
152             launchArgs.mScreenOnTimeMs = diffEntry.mScreenOnTimeInMs;
153             launchArgs.mAnomalyHintPrefKey = anomalyHintPrefKey;
154             launchArgs.mAnomalyHintText = anomalyHintText;
155         }
156         launchArgs.mIsUserEntry = isUserConsumer(diffEntry.mConsumerType);
157         startBatteryDetailPage(context, sourceMetricsCategory, launchArgs);
158     }
159 
160     /** Launches battery details page for an individual battery consumer. */
startBatteryDetailPage( Activity caller, InstrumentedPreferenceFragment fragment, BatteryEntry entry, String usagePercent)161     public static void startBatteryDetailPage(
162             Activity caller,
163             InstrumentedPreferenceFragment fragment,
164             BatteryEntry entry,
165             String usagePercent) {
166         final LaunchBatteryDetailPageArgs launchArgs = new LaunchBatteryDetailPageArgs();
167         // configure the launch argument.
168         launchArgs.mUsagePercent = usagePercent;
169         launchArgs.mPackageName = entry.getDefaultPackageName();
170         launchArgs.mAppLabel = entry.getLabel();
171         launchArgs.mUid = entry.getUid();
172         launchArgs.mIconId = entry.mIconId;
173         launchArgs.mConsumedPower = (int) entry.getConsumedPower();
174         launchArgs.mIsUserEntry = entry.isUserEntry();
175         launchArgs.mShowTimeInformation = false;
176         startBatteryDetailPage(caller, fragment.getMetricsCategory(), launchArgs);
177     }
178 
startBatteryDetailPage( Context context, int sourceMetricsCategory, LaunchBatteryDetailPageArgs launchArgs)179     private static void startBatteryDetailPage(
180             Context context, int sourceMetricsCategory, LaunchBatteryDetailPageArgs launchArgs) {
181         final Bundle args = new Bundle();
182         if (launchArgs.mPackageName == null) {
183             // populate data for system app
184             args.putString(EXTRA_LABEL, launchArgs.mAppLabel);
185             args.putInt(EXTRA_ICON_ID, launchArgs.mIconId);
186             args.putString(EXTRA_PACKAGE_NAME, null);
187         } else {
188             // populate data for normal app
189             args.putString(EXTRA_PACKAGE_NAME, launchArgs.mPackageName);
190         }
191 
192         args.putInt(EXTRA_UID, launchArgs.mUid);
193         args.putLong(EXTRA_BACKGROUND_TIME, launchArgs.mBackgroundTimeMs);
194         args.putLong(EXTRA_FOREGROUND_TIME, launchArgs.mForegroundTimeMs);
195         args.putLong(EXTRA_SCREEN_ON_TIME, launchArgs.mScreenOnTimeMs);
196         args.putString(EXTRA_SLOT_TIME, launchArgs.mSlotInformation);
197         args.putString(EXTRA_POWER_USAGE_PERCENT, launchArgs.mUsagePercent);
198         args.putInt(EXTRA_POWER_USAGE_AMOUNT, launchArgs.mConsumedPower);
199         args.putBoolean(EXTRA_SHOW_TIME_INFO, launchArgs.mShowTimeInformation);
200         args.putString(EXTRA_ANOMALY_HINT_PREF_KEY, launchArgs.mAnomalyHintPrefKey);
201         args.putString(EXTRA_ANOMALY_HINT_TEXT, launchArgs.mAnomalyHintText);
202         final int userId =
203                 launchArgs.mIsUserEntry
204                         ? ActivityManager.getCurrentUser()
205                         : UserHandle.getUserId(launchArgs.mUid);
206 
207         new SubSettingLauncher(context)
208                 .setDestination(AdvancedPowerUsageDetail.class.getName())
209                 .setTitleRes(R.string.battery_details_title)
210                 .setArguments(args)
211                 .setSourceMetricsCategory(sourceMetricsCategory)
212                 .setUserHandle(new UserHandle(userId))
213                 .launch();
214     }
215 
216     /** Start packageName's battery detail page. */
startBatteryDetailPage( Activity caller, Instrumentable instrumentable, String packageName, UserHandle userHandle)217     public static void startBatteryDetailPage(
218             Activity caller,
219             Instrumentable instrumentable,
220             String packageName,
221             UserHandle userHandle) {
222         final Bundle args = new Bundle(3);
223         final PackageManager packageManager = caller.getPackageManager();
224         args.putString(EXTRA_PACKAGE_NAME, packageName);
225         args.putString(EXTRA_POWER_USAGE_PERCENT, Utils.formatPercentage(0));
226         try {
227             args.putInt(EXTRA_UID, packageManager.getPackageUid(packageName, 0 /* no flag */));
228         } catch (PackageManager.NameNotFoundException e) {
229             Log.w(TAG, "Cannot find package: " + packageName, e);
230         }
231 
232         new SubSettingLauncher(caller)
233                 .setDestination(AdvancedPowerUsageDetail.class.getName())
234                 .setTitleRes(R.string.battery_details_title)
235                 .setArguments(args)
236                 .setSourceMetricsCategory(instrumentable.getMetricsCategory())
237                 .setUserHandle(userHandle)
238                 .launch();
239     }
240 
241     @Override
onAttach(Activity activity)242     public void onAttach(Activity activity) {
243         super.onAttach(activity);
244 
245         mState = ApplicationsState.getInstance(getActivity().getApplication());
246     }
247 
248     @Override
onCreate(Bundle icicle)249     public void onCreate(Bundle icicle) {
250         super.onCreate(icicle);
251 
252         final String packageName = getArguments().getString(EXTRA_PACKAGE_NAME);
253         onCreateBackgroundUsageState(packageName);
254         mHeaderPreference = findPreference(KEY_PREF_HEADER);
255 
256         if (packageName != null) {
257             mAppEntry = mState.getEntry(packageName, UserHandle.myUserId());
258         }
259     }
260 
261     @Override
onResume()262     public void onResume() {
263         super.onResume();
264 
265         initHeader();
266         mOptimizationMode = mBatteryOptimizeUtils.getAppOptimizationMode();
267         initFooter();
268         mLogStringBuilder = new StringBuilder("onResume mode = ").append(mOptimizationMode);
269     }
270 
271     @Override
onPause()272     public void onPause() {
273         super.onPause();
274 
275         final int currentOptimizeMode = mBatteryOptimizeUtils.getAppOptimizationMode();
276         mLogStringBuilder.append(", onPause mode = ").append(currentOptimizeMode);
277         logMetricCategory(currentOptimizeMode);
278         mExecutor.execute(
279                 () -> {
280                     if (currentOptimizeMode != mOptimizationMode) {
281                         AppOptModeSharedPreferencesUtils.deleteAppOptimizationModeEventByUid(
282                                 getContext(), mBatteryOptimizeUtils.getUid());
283                     }
284                     BatteryOptimizeLogUtils.writeLog(
285                             getContext().getApplicationContext(),
286                             Action.LEAVE,
287                             BatteryOptimizeLogUtils.getPackageNameWithUserId(
288                                     mBatteryOptimizeUtils.getPackageName(), UserHandle.myUserId()),
289                             mLogStringBuilder.toString());
290                 });
291         Log.d(TAG, "Leave with mode: " + currentOptimizeMode);
292     }
293 
294     @VisibleForTesting
initHeader()295     void initHeader() {
296         final View appSnippet = mHeaderPreference.findViewById(R.id.entity_header);
297         final Activity context = getActivity();
298         final Bundle bundle = getArguments();
299         EntityHeaderController controller =
300                 EntityHeaderController.newInstance(context, this, appSnippet)
301                         .setButtonActions(
302                                 EntityHeaderController.ActionType.ACTION_NONE,
303                                 EntityHeaderController.ActionType.ACTION_NONE);
304 
305         if (mAppEntry == null) {
306             controller.setLabel(bundle.getString(EXTRA_LABEL));
307 
308             final int iconId = bundle.getInt(EXTRA_ICON_ID, 0);
309             if (iconId == 0) {
310                 controller.setIcon(context.getPackageManager().getDefaultActivityIcon());
311             } else {
312                 controller.setIcon(context.getDrawable(bundle.getInt(EXTRA_ICON_ID)));
313             }
314         } else {
315             mState.ensureIcon(mAppEntry);
316             controller.setLabel(mAppEntry);
317             controller.setIcon(mAppEntry);
318             controller.setIsInstantApp(AppUtils.isInstant(mAppEntry.info));
319         }
320 
321         if (mPowerUsageTimeController != null) {
322             final String slotTime = bundle.getString(EXTRA_SLOT_TIME);
323             final long screenOnTimeInMs = bundle.getLong(EXTRA_SCREEN_ON_TIME);
324             final long backgroundTimeMs = bundle.getLong(EXTRA_BACKGROUND_TIME);
325             final String anomalyHintPrefKey = bundle.getString(EXTRA_ANOMALY_HINT_PREF_KEY);
326             final String anomalyHintText = bundle.getString(EXTRA_ANOMALY_HINT_TEXT);
327             mPowerUsageTimeController.handleScreenTimeUpdated(
328                     slotTime,
329                     screenOnTimeInMs,
330                     backgroundTimeMs,
331                     anomalyHintPrefKey,
332                     anomalyHintText);
333         }
334         controller.done(true /* rebindActions */);
335     }
336 
337     @VisibleForTesting
initFooter()338     void initFooter() {
339         final String stateString;
340         final String detailInfoString;
341         final Context context = getContext();
342 
343         if (mBatteryOptimizeUtils.isDisabledForOptimizeModeOnly()) {
344             // Present optimized only string when the package name is invalid.
345             stateString = context.getString(R.string.manager_battery_usage_optimized_only);
346             detailInfoString =
347                     context.getString(R.string.manager_battery_usage_footer_limited, stateString);
348         } else if (mBatteryOptimizeUtils.isSystemOrDefaultApp()) {
349             // Present unrestricted only string when the package is system or default active app.
350             stateString = context.getString(R.string.manager_battery_usage_unrestricted_only);
351             detailInfoString =
352                     context.getString(R.string.manager_battery_usage_footer_limited, stateString);
353         } else {
354             // Present default string to normal app.
355             detailInfoString =
356                     context.getString(
357                             R.string.manager_battery_usage_allow_background_usage_summary);
358         }
359         mAllowBackgroundUsagePreference.setSummary(detailInfoString);
360     }
361 
362     @Override
getMetricsCategory()363     public int getMetricsCategory() {
364         return SettingsEnums.FUELGAUGE_POWER_USAGE_DETAIL;
365     }
366 
367     @Override
getLogTag()368     protected String getLogTag() {
369         return TAG;
370     }
371 
372     @Override
getPreferenceScreenResId()373     protected int getPreferenceScreenResId() {
374         return R.xml.power_usage_detail;
375     }
376 
377     @Override
createPreferenceControllers(Context context)378     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
379         final List<AbstractPreferenceController> controllers = new ArrayList<>();
380         final Bundle bundle = getArguments();
381         final int uid = bundle.getInt(EXTRA_UID, 0);
382         final String packageName = bundle.getString(EXTRA_PACKAGE_NAME);
383 
384         mAppButtonsPreferenceController =
385                 new AppButtonsPreferenceController(
386                         (SettingsActivity) getActivity(),
387                         this,
388                         getSettingsLifecycle(),
389                         packageName,
390                         mState,
391                         REQUEST_UNINSTALL,
392                         REQUEST_REMOVE_DEVICE_ADMIN);
393         if (bundle.getBoolean(EXTRA_SHOW_TIME_INFO, false)) {
394             mPowerUsageTimeController = new PowerUsageTimeController(getContext());
395             controllers.add(mPowerUsageTimeController);
396         }
397         controllers.add(mAppButtonsPreferenceController);
398         controllers.add(new AllowBackgroundPreferenceController(context, uid, packageName));
399 
400         return controllers;
401     }
402 
403     @Override
onActivityResult(int requestCode, int resultCode, Intent data)404     public void onActivityResult(int requestCode, int resultCode, Intent data) {
405         super.onActivityResult(requestCode, resultCode, data);
406         if (mAppButtonsPreferenceController != null) {
407             mAppButtonsPreferenceController.handleActivityResult(requestCode, resultCode, data);
408         }
409     }
410 
411     @Override
handleDialogClick(int id)412     public void handleDialogClick(int id) {
413         if (mAppButtonsPreferenceController != null) {
414             mAppButtonsPreferenceController.handleDialogClick(id);
415         }
416     }
417 
418     @Override
onPreferenceClick(Preference preference)419     public boolean onPreferenceClick(Preference preference) {
420         if (!(preference instanceof PrimarySwitchPreference)
421                 || !TextUtils.equals(preference.getKey(), KEY_ALLOW_BACKGROUND_USAGE)) {
422             return false;
423         }
424         PowerBackgroundUsageDetail.startPowerBackgroundUsageDetailPage(
425                 getContext(), getArguments());
426         return true;
427     }
428 
429     @Override
onPreferenceChange(@onNull Preference preference, Object newValue)430     public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
431         if (!(preference instanceof PrimarySwitchPreference)
432                 || !TextUtils.equals(preference.getKey(), KEY_ALLOW_BACKGROUND_USAGE)) {
433             return false;
434         }
435         if (newValue instanceof Boolean) {
436             final boolean isAllowBackgroundUsage = (boolean) newValue;
437             mBatteryOptimizeUtils.setAppUsageState(
438                     isAllowBackgroundUsage
439                             ? BatteryOptimizeUtils.MODE_OPTIMIZED
440                             : BatteryOptimizeUtils.MODE_RESTRICTED,
441                     Action.APPLY);
442         }
443         return true;
444     }
445 
logMetricCategory(int currentOptimizeMode)446     private void logMetricCategory(int currentOptimizeMode) {
447         if (currentOptimizeMode == mOptimizationMode) {
448             return;
449         }
450         int metricCategory = 0;
451         switch (currentOptimizeMode) {
452             case BatteryOptimizeUtils.MODE_UNRESTRICTED:
453             case BatteryOptimizeUtils.MODE_OPTIMIZED:
454                 metricCategory = SettingsEnums.ACTION_APP_BATTERY_USAGE_ALLOW_BACKGROUND;
455                 break;
456             case BatteryOptimizeUtils.MODE_RESTRICTED:
457                 metricCategory = SettingsEnums.ACTION_APP_BATTERY_USAGE_DISABLE_BACKGROUND;
458                 break;
459         }
460         if (metricCategory == 0) {
461             return;
462         }
463         int finalMetricCategory = metricCategory;
464         mExecutor.execute(
465                 () -> {
466                     String packageName =
467                             BatteryUtils.getLoggingPackageName(
468                                     getContext(), mBatteryOptimizeUtils.getPackageName());
469                     FeatureFactory.getFeatureFactory()
470                             .getMetricsFeatureProvider()
471                             .action(
472                                     /* attribution */ SettingsEnums.LEAVE_APP_BATTERY_USAGE,
473                                     /* action */ finalMetricCategory,
474                                     /* pageId */ SettingsEnums.FUELGAUGE_POWER_USAGE_DETAIL,
475                                     packageName,
476                                     getArguments().getInt(EXTRA_POWER_USAGE_AMOUNT));
477                 });
478     }
479 
onCreateBackgroundUsageState(String packageName)480     private void onCreateBackgroundUsageState(String packageName) {
481         mAllowBackgroundUsagePreference = findPreference(KEY_ALLOW_BACKGROUND_USAGE);
482         if (mAllowBackgroundUsagePreference != null) {
483             mAllowBackgroundUsagePreference.setOnPreferenceClickListener(this);
484             mAllowBackgroundUsagePreference.setOnPreferenceChangeListener(this);
485         }
486 
487         mBatteryOptimizeUtils =
488                 new BatteryOptimizeUtils(
489                         getContext(), getArguments().getInt(EXTRA_UID), packageName);
490     }
491 }
492