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