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 android.annotation.UserIdInt;
20 import android.app.Activity;
21 import android.app.ActivityManager;
22 import android.app.settings.SettingsEnums;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.pm.PackageManager;
26 import android.os.BatteryStats;
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.VisibleForTesting;
34 import androidx.preference.Preference;
35 
36 import com.android.internal.os.BatterySipper;
37 import com.android.internal.os.BatteryStatsHelper;
38 import com.android.internal.util.ArrayUtils;
39 import com.android.settings.R;
40 import com.android.settings.SettingsActivity;
41 import com.android.settings.Utils;
42 import com.android.settings.applications.appinfo.AppButtonsPreferenceController;
43 import com.android.settings.applications.appinfo.ButtonActionDialogFragment;
44 import com.android.settings.core.InstrumentedPreferenceFragment;
45 import com.android.settings.core.SubSettingLauncher;
46 import com.android.settings.dashboard.DashboardFragment;
47 import com.android.settings.fuelgauge.batterytip.BatteryTipPreferenceController;
48 import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
49 import com.android.settings.widget.EntityHeaderController;
50 import com.android.settingslib.applications.AppUtils;
51 import com.android.settingslib.applications.ApplicationsState;
52 import com.android.settingslib.core.AbstractPreferenceController;
53 import com.android.settingslib.utils.StringUtil;
54 import com.android.settingslib.widget.LayoutPreference;
55 
56 import java.util.ArrayList;
57 import java.util.List;
58 
59 /**
60  * Power usage detail fragment for each app, this fragment contains
61  *
62  * 1. Detail battery usage information for app(i.e. usage time, usage amount)
63  * 2. Battery related controls for app(i.e uninstall, force stop)
64  */
65 public class AdvancedPowerUsageDetail extends DashboardFragment implements
66         ButtonActionDialogFragment.AppButtonsDialogListener,
67         BatteryTipPreferenceController.BatteryTipListener {
68 
69     public static final String TAG = "AdvancedPowerDetail";
70     public static final String EXTRA_UID = "extra_uid";
71     public static final String EXTRA_PACKAGE_NAME = "extra_package_name";
72     public static final String EXTRA_FOREGROUND_TIME = "extra_foreground_time";
73     public static final String EXTRA_BACKGROUND_TIME = "extra_background_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_FOREGROUND = "app_usage_foreground";
80     private static final String KEY_PREF_BACKGROUND = "app_usage_background";
81     private static final String KEY_PREF_HEADER = "header_view";
82 
83     private static final int REQUEST_UNINSTALL = 0;
84     private static final int REQUEST_REMOVE_DEVICE_ADMIN = 1;
85 
86     @VisibleForTesting
87     LayoutPreference mHeaderPreference;
88     @VisibleForTesting
89     ApplicationsState mState;
90     @VisibleForTesting
91     ApplicationsState.AppEntry mAppEntry;
92     @VisibleForTesting
93     BatteryUtils mBatteryUtils;
94 
95     @VisibleForTesting
96     Preference mForegroundPreference;
97     @VisibleForTesting
98     Preference mBackgroundPreference;
99     private AppButtonsPreferenceController mAppButtonsPreferenceController;
100     private BackgroundActivityPreferenceController mBackgroundActivityPreferenceController;
101 
102     private String mPackageName;
103 
104     @VisibleForTesting
startBatteryDetailPage(Activity caller, BatteryUtils batteryUtils, InstrumentedPreferenceFragment fragment, BatteryStatsHelper helper, int which, BatteryEntry entry, String usagePercent)105     static void startBatteryDetailPage(Activity caller, BatteryUtils batteryUtils,
106             InstrumentedPreferenceFragment fragment, BatteryStatsHelper helper, int which,
107             BatteryEntry entry, String usagePercent) {
108         // Initialize mStats if necessary.
109         helper.getStats();
110 
111         final Bundle args = new Bundle();
112         final BatterySipper sipper = entry.sipper;
113         final BatteryStats.Uid uid = sipper.uidObj;
114         final boolean isTypeApp = sipper.drainType == BatterySipper.DrainType.APP;
115 
116         final long foregroundTimeMs = isTypeApp ? batteryUtils.getProcessTimeMs(
117                 BatteryUtils.StatusType.FOREGROUND, uid, which) : sipper.usageTimeMs;
118         final long backgroundTimeMs = isTypeApp ? batteryUtils.getProcessTimeMs(
119                 BatteryUtils.StatusType.BACKGROUND, uid, which) : 0;
120 
121         if (ArrayUtils.isEmpty(sipper.mPackages)) {
122             // populate data for system app
123             args.putString(EXTRA_LABEL, entry.getLabel());
124             args.putInt(EXTRA_ICON_ID, entry.iconId);
125             args.putString(EXTRA_PACKAGE_NAME, null);
126         } else {
127             // populate data for normal app
128             args.putString(EXTRA_PACKAGE_NAME, entry.defaultPackageName != null
129                     ? entry.defaultPackageName
130                     : sipper.mPackages[0]);
131         }
132 
133         args.putInt(EXTRA_UID, sipper.getUid());
134         args.putLong(EXTRA_BACKGROUND_TIME, backgroundTimeMs);
135         args.putLong(EXTRA_FOREGROUND_TIME, foregroundTimeMs);
136         args.putString(EXTRA_POWER_USAGE_PERCENT, usagePercent);
137         args.putInt(EXTRA_POWER_USAGE_AMOUNT, (int) sipper.totalPowerMah);
138 
139         new SubSettingLauncher(caller)
140                 .setDestination(AdvancedPowerUsageDetail.class.getName())
141                 .setTitleRes(R.string.battery_details_title)
142                 .setArguments(args)
143                 .setSourceMetricsCategory(fragment.getMetricsCategory())
144                 .setUserHandle(new UserHandle(getUserIdToLaunchAdvancePowerUsageDetail(sipper)))
145                 .launch();
146     }
147 
148     private static @UserIdInt
getUserIdToLaunchAdvancePowerUsageDetail(BatterySipper bs)149     int getUserIdToLaunchAdvancePowerUsageDetail(BatterySipper bs) {
150         if (bs.drainType == BatterySipper.DrainType.USER) {
151             return ActivityManager.getCurrentUser();
152         }
153         return UserHandle.getUserId(bs.getUid());
154     }
155 
startBatteryDetailPage(Activity caller, InstrumentedPreferenceFragment fragment, BatteryStatsHelper helper, int which, BatteryEntry entry, String usagePercent)156     public static void startBatteryDetailPage(Activity caller,
157             InstrumentedPreferenceFragment fragment, BatteryStatsHelper helper, int which,
158             BatteryEntry entry, String usagePercent) {
159         startBatteryDetailPage(caller, BatteryUtils.getInstance(caller), fragment, helper, which,
160                 entry, usagePercent);
161     }
162 
startBatteryDetailPage(Activity caller, InstrumentedPreferenceFragment fragment, String packageName)163     public static void startBatteryDetailPage(Activity caller,
164             InstrumentedPreferenceFragment fragment, String packageName) {
165         final Bundle args = new Bundle(3);
166         final PackageManager packageManager = caller.getPackageManager();
167         args.putString(EXTRA_PACKAGE_NAME, packageName);
168         args.putString(EXTRA_POWER_USAGE_PERCENT, Utils.formatPercentage(0));
169         try {
170             args.putInt(EXTRA_UID, packageManager.getPackageUid(packageName, 0 /* no flag */));
171         } catch (PackageManager.NameNotFoundException e) {
172             Log.w(TAG, "Cannot find package: " + packageName, e);
173         }
174 
175         new SubSettingLauncher(caller)
176                 .setDestination(AdvancedPowerUsageDetail.class.getName())
177                 .setTitleRes(R.string.battery_details_title)
178                 .setArguments(args)
179                 .setSourceMetricsCategory(fragment.getMetricsCategory())
180                 .launch();
181     }
182 
183     @Override
onAttach(Activity activity)184     public void onAttach(Activity activity) {
185         super.onAttach(activity);
186 
187         mState = ApplicationsState.getInstance(getActivity().getApplication());
188         mBatteryUtils = BatteryUtils.getInstance(getContext());
189     }
190 
191     @Override
onCreate(Bundle icicle)192     public void onCreate(Bundle icicle) {
193         super.onCreate(icicle);
194 
195         mPackageName = getArguments().getString(EXTRA_PACKAGE_NAME);
196         mForegroundPreference = findPreference(KEY_PREF_FOREGROUND);
197         mBackgroundPreference = findPreference(KEY_PREF_BACKGROUND);
198         mHeaderPreference = (LayoutPreference) findPreference(KEY_PREF_HEADER);
199 
200         if (mPackageName != null) {
201             mAppEntry = mState.getEntry(mPackageName, UserHandle.myUserId());
202         }
203     }
204 
205     @Override
onResume()206     public void onResume() {
207         super.onResume();
208 
209         initHeader();
210         initPreference();
211     }
212 
213     @VisibleForTesting
initHeader()214     void initHeader() {
215         final View appSnippet = mHeaderPreference.findViewById(R.id.entity_header);
216         final Activity context = getActivity();
217         final Bundle bundle = getArguments();
218         EntityHeaderController controller = EntityHeaderController
219                 .newInstance(context, this, appSnippet)
220                 .setRecyclerView(getListView(), getSettingsLifecycle())
221                 .setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
222                         EntityHeaderController.ActionType.ACTION_NONE);
223 
224         if (mAppEntry == null) {
225             controller.setLabel(bundle.getString(EXTRA_LABEL));
226 
227             final int iconId = bundle.getInt(EXTRA_ICON_ID, 0);
228             if (iconId == 0) {
229                 controller.setIcon(context.getPackageManager().getDefaultActivityIcon());
230             } else {
231                 controller.setIcon(context.getDrawable(bundle.getInt(EXTRA_ICON_ID)));
232             }
233         } else {
234             mState.ensureIcon(mAppEntry);
235             controller.setLabel(mAppEntry);
236             controller.setIcon(mAppEntry);
237             boolean isInstantApp = AppUtils.isInstant(mAppEntry.info);
238             controller.setIsInstantApp(AppUtils.isInstant(mAppEntry.info));
239         }
240 
241         controller.done(context, true /* rebindActions */);
242     }
243 
244     @VisibleForTesting
initPreference()245     void initPreference() {
246         final Bundle bundle = getArguments();
247         final Context context = getContext();
248 
249         final long foregroundTimeMs = bundle.getLong(EXTRA_FOREGROUND_TIME);
250         final long backgroundTimeMs = bundle.getLong(EXTRA_BACKGROUND_TIME);
251         mForegroundPreference.setSummary(
252                 TextUtils.expandTemplate(getText(R.string.battery_used_for),
253                         StringUtil.formatElapsedTime(context, foregroundTimeMs, false)));
254         mBackgroundPreference.setSummary(
255                 TextUtils.expandTemplate(getText(R.string.battery_active_for),
256                         StringUtil.formatElapsedTime(context, backgroundTimeMs, false)));
257     }
258 
259     @Override
getMetricsCategory()260     public int getMetricsCategory() {
261         return SettingsEnums.FUELGAUGE_POWER_USAGE_DETAIL;
262     }
263 
264     @Override
getLogTag()265     protected String getLogTag() {
266         return TAG;
267     }
268 
269     @Override
getPreferenceScreenResId()270     protected int getPreferenceScreenResId() {
271         return R.xml.power_usage_detail;
272     }
273 
274     @Override
createPreferenceControllers(Context context)275     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
276         final List<AbstractPreferenceController> controllers = new ArrayList<>();
277         final Bundle bundle = getArguments();
278         final int uid = bundle.getInt(EXTRA_UID, 0);
279         final String packageName = bundle.getString(EXTRA_PACKAGE_NAME);
280 
281         mBackgroundActivityPreferenceController = new BackgroundActivityPreferenceController(
282                 context, this, uid, packageName);
283         controllers.add(mBackgroundActivityPreferenceController);
284         controllers.add(new BatteryOptimizationPreferenceController(
285                 (SettingsActivity) getActivity(), this, packageName));
286         mAppButtonsPreferenceController = new AppButtonsPreferenceController(
287                 (SettingsActivity) getActivity(), this, getSettingsLifecycle(), packageName, mState,
288                 REQUEST_UNINSTALL, REQUEST_REMOVE_DEVICE_ADMIN);
289         controllers.add(mAppButtonsPreferenceController);
290 
291         return controllers;
292     }
293 
294     @Override
onActivityResult(int requestCode, int resultCode, Intent data)295     public void onActivityResult(int requestCode, int resultCode, Intent data) {
296         super.onActivityResult(requestCode, resultCode, data);
297         if (mAppButtonsPreferenceController != null) {
298             mAppButtonsPreferenceController.handleActivityResult(requestCode, resultCode, data);
299         }
300     }
301 
302     @Override
handleDialogClick(int id)303     public void handleDialogClick(int id) {
304         if (mAppButtonsPreferenceController != null) {
305             mAppButtonsPreferenceController.handleDialogClick(id);
306         }
307     }
308 
309     @Override
onBatteryTipHandled(BatteryTip batteryTip)310     public void onBatteryTipHandled(BatteryTip batteryTip) {
311         mBackgroundActivityPreferenceController.updateSummary(
312                 findPreference(mBackgroundActivityPreferenceController.getPreferenceKey()));
313     }
314 }
315