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