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.content.pm.PackageManager;
22 import android.os.AsyncTask;
23 import android.os.BatteryUsageStats;
24 import android.os.Bundle;
25 import android.os.UidBatteryConsumer;
26 import android.os.UserHandle;
27 import android.os.UserManager;
28 import android.util.Log;
29 
30 import androidx.annotation.NonNull;
31 import androidx.annotation.VisibleForTesting;
32 import androidx.loader.app.LoaderManager;
33 import androidx.loader.content.Loader;
34 import androidx.preference.Preference;
35 import androidx.preference.PreferenceScreen;
36 
37 import com.android.settings.R;
38 import com.android.settings.Utils;
39 import com.android.settings.core.BasePreferenceController;
40 import com.android.settings.fuelgauge.AdvancedPowerUsageDetail;
41 import com.android.settings.fuelgauge.BatteryChartPreferenceController;
42 import com.android.settings.fuelgauge.BatteryDiffEntry;
43 import com.android.settings.fuelgauge.BatteryEntry;
44 import com.android.settings.fuelgauge.BatteryUsageStatsLoader;
45 import com.android.settings.fuelgauge.BatteryUtils;
46 import com.android.settings.fuelgauge.ConvertUtils;
47 import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
48 import com.android.settings.overlay.FeatureFactory;
49 import com.android.settingslib.core.lifecycle.Lifecycle;
50 import com.android.settingslib.core.lifecycle.LifecycleObserver;
51 import com.android.settingslib.core.lifecycle.events.OnPause;
52 import com.android.settingslib.core.lifecycle.events.OnResume;
53 
54 import java.util.List;
55 
56 public class AppBatteryPreferenceController extends BasePreferenceController
57         implements LifecycleObserver, OnResume, OnPause {
58 
59     private static final String TAG = "AppBatteryPreferenceController";
60     private static final String KEY_BATTERY = "battery";
61 
62     @VisibleForTesting
63     final BatteryUsageStatsLoaderCallbacks mBatteryUsageStatsLoaderCallbacks =
64             new BatteryUsageStatsLoaderCallbacks();
65     @VisibleForTesting
66     BatteryUtils mBatteryUtils;
67     @VisibleForTesting
68     BatteryUsageStats mBatteryUsageStats;
69     @VisibleForTesting
70     UidBatteryConsumer mUidBatteryConsumer;
71     @VisibleForTesting
72     BatteryDiffEntry mBatteryDiffEntry;
73     @VisibleForTesting
74     boolean mIsChartGraphEnabled;
75 
76     private Preference mPreference;
77     private final AppInfoDashboardFragment mParent;
78     private String mBatteryPercent;
79     private final String mPackageName;
80     private final int mUid;
81     private final int mUserId;
82     private boolean mBatteryUsageStatsLoaded = false;
83     private boolean mBatteryDiffEntriesLoaded = false;
84 
AppBatteryPreferenceController(Context context, AppInfoDashboardFragment parent, String packageName, int uid, Lifecycle lifecycle)85     public AppBatteryPreferenceController(Context context, AppInfoDashboardFragment parent,
86             String packageName, int uid, Lifecycle lifecycle) {
87         super(context, KEY_BATTERY);
88         mParent = parent;
89         mBatteryUtils = BatteryUtils.getInstance(mContext);
90         mPackageName = packageName;
91         mUid = uid;
92         mUserId = mContext.getUserId();
93         refreshFeatureFlag(mContext);
94         if (lifecycle != null) {
95             lifecycle.addObserver(this);
96         }
97     }
98 
99     @Override
getAvailabilityStatus()100     public int getAvailabilityStatus() {
101         return mContext.getResources().getBoolean(R.bool.config_show_app_info_settings_battery)
102                 ? AVAILABLE
103                 : CONDITIONALLY_UNAVAILABLE;
104     }
105 
106     @Override
displayPreference(PreferenceScreen screen)107     public void displayPreference(PreferenceScreen screen) {
108         super.displayPreference(screen);
109         mPreference = screen.findPreference(getPreferenceKey());
110         mPreference.setEnabled(false);
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, "BatteryDiffEntry not null, launch : "
122                     + mBatteryDiffEntry.getPackageName()
123                     + " | uid : "
124                     + mBatteryDiffEntry.mBatteryHistEntry.mUid
125                     + " with DiffEntry data");
126             AdvancedPowerUsageDetail.startBatteryDetailPage(
127                     mParent.getActivity(),
128                     mParent,
129                     mBatteryDiffEntry,
130                     Utils.formatPercentage(
131                             mBatteryDiffEntry.getPercentOfTotal(), /* round */ true),
132                     /*isValidToShowSummary=*/ true,
133                     /*slotInformation=*/ null);
134             return true;
135         }
136 
137         if (isBatteryStatsAvailable()) {
138             final UserManager userManager =
139                     (UserManager) mContext.getSystemService(Context.USER_SERVICE);
140             final BatteryEntry entry = new BatteryEntry(mContext, /* handler */null, userManager,
141                     mUidBatteryConsumer, /* isHidden */ false,
142                     mUidBatteryConsumer.getUid(), /* packages */ null, mPackageName);
143             Log.i(TAG, "Battery consumer available, launch : "
144                     + entry.getDefaultPackageName()
145                     + " | uid : "
146                     + entry.getUid()
147                     + " with BatteryEntry data");
148             AdvancedPowerUsageDetail.startBatteryDetailPage(mParent.getActivity(), mParent, entry,
149                     mIsChartGraphEnabled ? Utils.formatPercentage(0) : mBatteryPercent,
150                     !mIsChartGraphEnabled);
151         } else {
152             Log.i(TAG, "Launch : " + mPackageName + " with package name");
153             AdvancedPowerUsageDetail.startBatteryDetailPage(mParent.getActivity(), mParent,
154                     mPackageName);
155         }
156         return true;
157     }
158 
159     @Override
onResume()160     public void onResume() {
161         mParent.getLoaderManager().restartLoader(
162                 AppInfoDashboardFragment.LOADER_BATTERY_USAGE_STATS, Bundle.EMPTY,
163                 mBatteryUsageStatsLoaderCallbacks);
164     }
165 
166     @Override
onPause()167     public void onPause() {
168         mParent.getLoaderManager().destroyLoader(
169                 AppInfoDashboardFragment.LOADER_BATTERY_USAGE_STATS);
170     }
171 
loadBatteryDiffEntries()172     private void loadBatteryDiffEntries() {
173         new AsyncTask<Void, Void, BatteryDiffEntry>() {
174             @Override
175             protected BatteryDiffEntry doInBackground(Void... unused) {
176                 if (mPackageName == null) {
177                     return null;
178                 }
179                 final List<BatteryDiffEntry> batteryDiffEntries =
180                         BatteryChartPreferenceController.getBatteryLast24HrUsageData(mContext);
181                 if (batteryDiffEntries == null) {
182                     return null;
183                 }
184                 // Filter entry with consumer type to avoid system app,
185                 // then use user id to divide normal app and work profile app,
186                 // return target application from filter list by package name.
187                 return batteryDiffEntries.stream()
188                         .filter(entry -> entry.mBatteryHistEntry.mConsumerType
189                                 == ConvertUtils.CONSUMER_TYPE_UID_BATTERY)
190                         .filter(entry -> entry.mBatteryHistEntry.mUserId == mUserId)
191                         .filter(entry -> {
192                             if (mPackageName.equals(entry.getPackageName())) {
193                                 Log.i(TAG, "Return target application: "
194                                         + entry.mBatteryHistEntry.mPackageName
195                                         + " | uid: " + entry.mBatteryHistEntry.mUid
196                                         + " | userId: " + entry.mBatteryHistEntry.mUserId);
197                                 return true;
198                             }
199                             return false;
200                         })
201                         .findFirst()
202                         .orElse(/* other */null);
203             }
204 
205             @Override
206             protected void onPostExecute(BatteryDiffEntry batteryDiffEntry) {
207                 mBatteryDiffEntry = batteryDiffEntry;
208                 updateBatteryWithDiffEntry();
209             }
210         }.execute();
211     }
212 
213     @VisibleForTesting
214     void updateBatteryWithDiffEntry() {
215         if (mIsChartGraphEnabled) {
216             if (mBatteryDiffEntry != null && mBatteryDiffEntry.mConsumePower > 0) {
217                 mBatteryPercent = Utils.formatPercentage(
218                         mBatteryDiffEntry.getPercentOfTotal(), /* round */ true);
219                 mPreference.setSummary(mContext.getString(
220                         R.string.battery_summary_24hr, mBatteryPercent));
221             } else {
222                 mPreference.setSummary(
223                         mContext.getString(R.string.no_battery_summary_24hr));
224             }
225         }
226 
227         mBatteryDiffEntriesLoaded = true;
228         mPreference.setEnabled(mBatteryUsageStatsLoaded);
229     }
230 
231     private void onLoadFinished() {
232         if (mBatteryUsageStats == null) {
233             return;
234         }
235 
236         final PackageInfo packageInfo = mParent.getPackageInfo();
237         if (packageInfo != null) {
238             mUidBatteryConsumer = findTargetUidBatteryConsumer(mBatteryUsageStats,
239                     packageInfo.applicationInfo.uid);
240             if (mParent.getActivity() != null) {
241                 updateBattery();
242             }
243         }
244     }
245 
246     private void refreshFeatureFlag(Context context) {
247         if (isWorkProfile(context)) {
248             try {
249                 context = context.createPackageContextAsUser(
250                         context.getPackageName(), 0, UserHandle.OWNER);
251             } catch (PackageManager.NameNotFoundException e) {
252                 Log.e(TAG, "context.createPackageContextAsUser() fail: " + e);
253             }
254         }
255 
256         final PowerUsageFeatureProvider powerUsageFeatureProvider =
257                 FeatureFactory.getFactory(context).getPowerUsageFeatureProvider(context);
258         mIsChartGraphEnabled = powerUsageFeatureProvider.isChartGraphEnabled(context);
259     }
260 
261     private boolean isWorkProfile(Context context) {
262         final UserManager userManager = context.getSystemService(UserManager.class);
263         return userManager.isManagedProfile() && !userManager.isSystemUser();
264     }
265 
266     @VisibleForTesting
267     void updateBattery() {
268         mBatteryUsageStatsLoaded = true;
269         mPreference.setEnabled(mBatteryDiffEntriesLoaded);
270         if (mIsChartGraphEnabled) {
271             return;
272         }
273         if (isBatteryStatsAvailable()) {
274             final int percentOfMax = (int) mBatteryUtils.calculateBatteryPercent(
275                     mUidBatteryConsumer.getConsumedPower(), mBatteryUsageStats.getConsumedPower(),
276                     mBatteryUsageStats.getDischargePercentage());
277             mBatteryPercent = Utils.formatPercentage(percentOfMax);
278             mPreference.setSummary(mContext.getString(R.string.battery_summary, mBatteryPercent));
279         } else {
280             mPreference.setSummary(mContext.getString(R.string.no_battery_summary));
281         }
282     }
283 
284     @VisibleForTesting
285     boolean isBatteryStatsAvailable() {
286         return mUidBatteryConsumer != null;
287     }
288 
289     @VisibleForTesting
290     UidBatteryConsumer findTargetUidBatteryConsumer(BatteryUsageStats batteryUsageStats, int uid) {
291         final List<UidBatteryConsumer> usageList = batteryUsageStats.getUidBatteryConsumers();
292         for (int i = 0, size = usageList.size(); i < size; i++) {
293             final UidBatteryConsumer consumer = usageList.get(i);
294             if (consumer.getUid() == uid) {
295                 return consumer;
296             }
297         }
298         return null;
299     }
300 
301     private class BatteryUsageStatsLoaderCallbacks
302             implements LoaderManager.LoaderCallbacks<BatteryUsageStats> {
303         @Override
304         @NonNull
305         public Loader<BatteryUsageStats> onCreateLoader(int id, Bundle args) {
306             return new BatteryUsageStatsLoader(mContext, /* includeBatteryHistory */ false);
307         }
308 
309         @Override
310         public void onLoadFinished(Loader<BatteryUsageStats> loader,
311                 BatteryUsageStats batteryUsageStats) {
312             mBatteryUsageStats = batteryUsageStats;
313             AppBatteryPreferenceController.this.onLoadFinished();
314         }
315 
316         @Override
317         public void onLoaderReset(Loader<BatteryUsageStats> loader) {
318         }
319     }
320 }
321