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.applications.appinfo;
18 
19 import android.content.Context;
20 import android.content.pm.PackageInfo;
21 import android.os.AsyncTask;
22 import android.os.BatteryUsageStats;
23 import android.os.Bundle;
24 import android.os.UidBatteryConsumer;
25 import android.os.UserHandle;
26 import android.os.UserManager;
27 import android.util.Log;
28 
29 import androidx.annotation.NonNull;
30 import androidx.annotation.VisibleForTesting;
31 import androidx.loader.app.LoaderManager;
32 import androidx.loader.content.Loader;
33 import androidx.preference.Preference;
34 import androidx.preference.PreferenceScreen;
35 
36 import com.android.settings.R;
37 import com.android.settings.Utils;
38 import com.android.settings.core.BasePreferenceController;
39 import com.android.settings.fuelgauge.AdvancedPowerUsageDetail;
40 import com.android.settings.fuelgauge.BatteryUtils;
41 import com.android.settings.fuelgauge.batteryusage.BatteryChartPreferenceController;
42 import com.android.settings.fuelgauge.batteryusage.BatteryDiffEntry;
43 import com.android.settings.fuelgauge.batteryusage.BatteryEntry;
44 import com.android.settings.fuelgauge.batteryusage.BatteryUsageStatsLoader;
45 import com.android.settingslib.applications.AppUtils;
46 import com.android.settingslib.core.lifecycle.Lifecycle;
47 import com.android.settingslib.core.lifecycle.LifecycleObserver;
48 import com.android.settingslib.core.lifecycle.events.OnPause;
49 import com.android.settingslib.core.lifecycle.events.OnResume;
50 
51 import java.util.List;
52 
53 public class AppBatteryPreferenceController extends BasePreferenceController
54         implements LifecycleObserver, OnResume, OnPause {
55 
56     private static final String TAG = "AppBatteryPreferenceController";
57     private static final String KEY_BATTERY = "battery";
58 
59     @VisibleForTesting
60     final BatteryUsageStatsLoaderCallbacks mBatteryUsageStatsLoaderCallbacks =
61             new BatteryUsageStatsLoaderCallbacks();
62     @VisibleForTesting
63     BatteryUtils mBatteryUtils;
64     @VisibleForTesting
65     BatteryUsageStats mBatteryUsageStats;
66     @VisibleForTesting
67     UidBatteryConsumer mUidBatteryConsumer;
68     @VisibleForTesting
69     BatteryDiffEntry mBatteryDiffEntry;
70     @VisibleForTesting
71     final AppInfoDashboardFragment mParent;
72 
73     private Preference mPreference;
74     private String mBatteryPercent;
75     private final String mPackageName;
76     private final int mUid;
77     private final int mUserId;
78     private boolean mBatteryUsageStatsLoaded = false;
79     private boolean mBatteryDiffEntriesLoaded = false;
80 
AppBatteryPreferenceController(Context context, AppInfoDashboardFragment parent, String packageName, int uid, Lifecycle lifecycle)81     public AppBatteryPreferenceController(Context context, AppInfoDashboardFragment parent,
82             String packageName, int uid, Lifecycle lifecycle) {
83         super(context, KEY_BATTERY);
84         mParent = parent;
85         mBatteryUtils = BatteryUtils.getInstance(mContext);
86         mPackageName = packageName;
87         mUid = uid;
88         mUserId = mContext.getUserId();
89         if (lifecycle != null) {
90             lifecycle.addObserver(this);
91         }
92     }
93 
94     @Override
getAvailabilityStatus()95     public int getAvailabilityStatus() {
96         return mContext.getResources().getBoolean(R.bool.config_show_app_info_settings_battery)
97                 ? AVAILABLE
98                 : CONDITIONALLY_UNAVAILABLE;
99     }
100 
101     @Override
displayPreference(PreferenceScreen screen)102     public void displayPreference(PreferenceScreen screen) {
103         super.displayPreference(screen);
104         mPreference = screen.findPreference(getPreferenceKey());
105         mPreference.setEnabled(false);
106         if (!AppUtils.isAppInstalled(mParent.getAppEntry())) {
107             mPreference.setSummary("");
108             return;
109         }
110 
111         loadBatteryDiffEntries();
112     }
113 
114     @Override
handlePreferenceTreeClick(Preference preference)115     public boolean handlePreferenceTreeClick(Preference preference) {
116         if (!KEY_BATTERY.equals(preference.getKey())) {
117             return false;
118         }
119 
120         if (mBatteryDiffEntry != null) {
121             Log.i(TAG, "handlePreferenceTreeClick():\n" + mBatteryDiffEntry);
122             AdvancedPowerUsageDetail.startBatteryDetailPage(
123                     mParent.getActivity(),
124                     mParent.getMetricsCategory(),
125                     mBatteryDiffEntry,
126                     Utils.formatPercentage(
127                             mBatteryDiffEntry.getPercentage(), /*round=*/ true),
128                     /*slotInformation=*/ null, /*showTimeInformation=*/ false,
129                     /*anomalyHintPrefKey=*/ null, /*anomalyHintText=*/ null);
130             return true;
131         }
132 
133         if (isBatteryStatsAvailable()) {
134             final UserManager userManager =
135                     (UserManager) mContext.getSystemService(Context.USER_SERVICE);
136             final BatteryEntry entry = new BatteryEntry(mContext, userManager,
137                     mUidBatteryConsumer, /* isHidden */ false,
138                     mUidBatteryConsumer.getUid(), /* packages */ null, mPackageName);
139             Log.i(TAG, "Battery consumer available, launch : "
140                     + entry.getDefaultPackageName()
141                     + " | uid : "
142                     + entry.getUid()
143                     + " with BatteryEntry data");
144             AdvancedPowerUsageDetail.startBatteryDetailPage(
145                     mParent.getActivity(), mParent, entry, Utils.formatPercentage(0));
146         } else {
147             Log.i(TAG, "Launch : " + mPackageName + " with package name");
148             AdvancedPowerUsageDetail.startBatteryDetailPage(mParent.getActivity(), mParent,
149                     mPackageName, UserHandle.CURRENT);
150         }
151         return true;
152     }
153 
154     @Override
onResume()155     public void onResume() {
156         mParent.getLoaderManager().restartLoader(
157                 AppInfoDashboardFragment.LOADER_BATTERY_USAGE_STATS, Bundle.EMPTY,
158                 mBatteryUsageStatsLoaderCallbacks);
159     }
160 
161     @Override
onPause()162     public void onPause() {
163         mParent.getLoaderManager().destroyLoader(
164                 AppInfoDashboardFragment.LOADER_BATTERY_USAGE_STATS);
165         closeBatteryUsageStats();
166     }
167 
loadBatteryDiffEntries()168     private void loadBatteryDiffEntries() {
169         new AsyncTask<Void, Void, BatteryDiffEntry>() {
170             @Override
171             protected BatteryDiffEntry doInBackground(Void... unused) {
172                 if (mPackageName == null) {
173                     return null;
174                 }
175                 final BatteryDiffEntry entry =
176                         BatteryChartPreferenceController.getAppBatteryUsageData(
177                                 mContext, mPackageName, mUserId);
178                 Log.d(TAG, "loadBatteryDiffEntries():\n" + entry);
179                 return entry;
180             }
181 
182             @Override
183             protected void onPostExecute(BatteryDiffEntry batteryDiffEntry) {
184                 mBatteryDiffEntry = batteryDiffEntry;
185                 updateBatteryWithDiffEntry();
186             }
187         }.execute();
188     }
189 
190     @VisibleForTesting
updateBatteryWithDiffEntry()191     void updateBatteryWithDiffEntry() {
192         if (mBatteryDiffEntry != null && mBatteryDiffEntry.mConsumePower > 0) {
193             mBatteryPercent = Utils.formatPercentage(
194                     mBatteryDiffEntry.getPercentage(), /* round */ true);
195             mPreference.setSummary(mContext.getString(
196                     R.string.battery_summary, mBatteryPercent));
197         } else {
198             mPreference.setSummary(
199                     mContext.getString(R.string.no_battery_summary));
200         }
201 
202         mBatteryDiffEntriesLoaded = true;
203         mPreference.setEnabled(mBatteryUsageStatsLoaded);
204     }
205 
onLoadFinished()206     private void onLoadFinished() {
207         if (mBatteryUsageStats == null) {
208             return;
209         }
210 
211         final PackageInfo packageInfo = mParent.getPackageInfo();
212         if (packageInfo != null) {
213             mUidBatteryConsumer = findTargetUidBatteryConsumer(mBatteryUsageStats,
214                     packageInfo.applicationInfo.uid);
215             if (mParent.getActivity() != null) {
216                 updateBattery();
217             }
218         }
219     }
220 
updateBattery()221     private void updateBattery() {
222         mBatteryUsageStatsLoaded = true;
223         mPreference.setEnabled(mBatteryDiffEntriesLoaded);
224     }
225 
226     @VisibleForTesting
isBatteryStatsAvailable()227     boolean isBatteryStatsAvailable() {
228         return mUidBatteryConsumer != null;
229     }
230 
231     @VisibleForTesting
findTargetUidBatteryConsumer(BatteryUsageStats batteryUsageStats, int uid)232     UidBatteryConsumer findTargetUidBatteryConsumer(BatteryUsageStats batteryUsageStats, int uid) {
233         final List<UidBatteryConsumer> usageList = batteryUsageStats.getUidBatteryConsumers();
234         for (int i = 0, size = usageList.size(); i < size; i++) {
235             final UidBatteryConsumer consumer = usageList.get(i);
236             if (consumer.getUid() == uid) {
237                 return consumer;
238             }
239         }
240         return null;
241     }
242 
243     private class BatteryUsageStatsLoaderCallbacks
244             implements LoaderManager.LoaderCallbacks<BatteryUsageStats> {
245         @Override
246         @NonNull
onCreateLoader(int id, Bundle args)247         public Loader<BatteryUsageStats> onCreateLoader(int id, Bundle args) {
248             return new BatteryUsageStatsLoader(mContext, /* includeBatteryHistory */ false);
249         }
250 
251         @Override
onLoadFinished(Loader<BatteryUsageStats> loader, BatteryUsageStats batteryUsageStats)252         public void onLoadFinished(Loader<BatteryUsageStats> loader,
253                 BatteryUsageStats batteryUsageStats) {
254             closeBatteryUsageStats();
255             mBatteryUsageStats = batteryUsageStats;
256             AppBatteryPreferenceController.this.onLoadFinished();
257         }
258 
259         @Override
onLoaderReset(Loader<BatteryUsageStats> loader)260         public void onLoaderReset(Loader<BatteryUsageStats> loader) {
261         }
262     }
263 
closeBatteryUsageStats()264     private void closeBatteryUsageStats() {
265         if (mBatteryUsageStats != null) {
266             try {
267                 mBatteryUsageStats.close();
268             } catch (Exception e) {
269                 Log.e(TAG, "BatteryUsageStats.close() failed", e);
270             } finally {
271                 mBatteryUsageStats = null;
272             }
273         }
274     }
275 }
276