1 /* 2 * Copyright (C) 2009 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.BatteryBroadcastReceiver.BatteryUpdateType; 20 21 import android.app.settings.SettingsEnums; 22 import android.content.Context; 23 import android.database.ContentObserver; 24 import android.net.Uri; 25 import android.os.BatteryStats; 26 import android.os.Bundle; 27 import android.os.Handler; 28 import android.provider.SearchIndexableResource; 29 import android.provider.Settings.Global; 30 import android.text.format.Formatter; 31 import android.view.Menu; 32 import android.view.MenuInflater; 33 import android.view.MenuItem; 34 import android.view.View; 35 import android.view.View.OnLongClickListener; 36 import android.widget.TextView; 37 38 import androidx.annotation.VisibleForTesting; 39 import androidx.loader.app.LoaderManager; 40 import androidx.loader.app.LoaderManager.LoaderCallbacks; 41 import androidx.loader.content.Loader; 42 43 import com.android.settings.R; 44 import com.android.settings.SettingsActivity; 45 import com.android.settings.Utils; 46 import com.android.settings.core.SubSettingLauncher; 47 import com.android.settings.fuelgauge.batterytip.BatteryTipLoader; 48 import com.android.settings.fuelgauge.batterytip.BatteryTipPreferenceController; 49 import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; 50 import com.android.settings.overlay.FeatureFactory; 51 import com.android.settings.search.BaseSearchIndexProvider; 52 import com.android.settingslib.fuelgauge.EstimateKt; 53 import com.android.settingslib.search.SearchIndexable; 54 import com.android.settingslib.utils.PowerUtil; 55 import com.android.settingslib.utils.StringUtil; 56 import com.android.settingslib.widget.LayoutPreference; 57 58 import java.util.Collections; 59 import java.util.List; 60 61 /** 62 * Displays a list of apps and subsystems that consume power, ordered by how much power was 63 * consumed since the last time it was unplugged. 64 */ 65 @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) 66 public class PowerUsageSummary extends PowerUsageBase implements OnLongClickListener, 67 BatteryTipPreferenceController.BatteryTipListener { 68 69 static final String TAG = "PowerUsageSummary"; 70 71 private static final boolean DEBUG = false; 72 private static final String KEY_BATTERY_HEADER = "battery_header"; 73 74 private static final String KEY_SCREEN_USAGE = "screen_usage"; 75 private static final String KEY_TIME_SINCE_LAST_FULL_CHARGE = "last_full_charge"; 76 private static final String KEY_BATTERY_SAVER_SUMMARY = "battery_saver_summary"; 77 78 @VisibleForTesting 79 static final int BATTERY_INFO_LOADER = 1; 80 @VisibleForTesting 81 static final int BATTERY_TIP_LOADER = 2; 82 @VisibleForTesting 83 static final int MENU_STATS_TYPE = Menu.FIRST; 84 @VisibleForTesting 85 static final int MENU_ADVANCED_BATTERY = Menu.FIRST + 1; 86 public static final int DEBUG_INFO_LOADER = 3; 87 88 @VisibleForTesting 89 PowerGaugePreference mScreenUsagePref; 90 @VisibleForTesting 91 PowerGaugePreference mLastFullChargePref; 92 @VisibleForTesting 93 PowerUsageFeatureProvider mPowerFeatureProvider; 94 @VisibleForTesting 95 BatteryUtils mBatteryUtils; 96 @VisibleForTesting 97 LayoutPreference mBatteryLayoutPref; 98 @VisibleForTesting 99 BatteryInfo mBatteryInfo; 100 101 @VisibleForTesting 102 BatteryHeaderPreferenceController mBatteryHeaderPreferenceController; 103 @VisibleForTesting 104 boolean mNeedUpdateBatteryTip; 105 @VisibleForTesting 106 BatteryTipPreferenceController mBatteryTipPreferenceController; 107 private int mStatsType = BatteryStats.STATS_SINCE_CHARGED; 108 109 @VisibleForTesting 110 final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) { 111 @Override 112 public void onChange(boolean selfChange, Uri uri) { 113 restartBatteryInfoLoader(); 114 } 115 }; 116 117 @VisibleForTesting 118 LoaderManager.LoaderCallbacks<BatteryInfo> mBatteryInfoLoaderCallbacks = 119 new LoaderManager.LoaderCallbacks<BatteryInfo>() { 120 121 @Override 122 public Loader<BatteryInfo> onCreateLoader(int i, Bundle bundle) { 123 return new BatteryInfoLoader(getContext(), mStatsHelper); 124 } 125 126 @Override 127 public void onLoadFinished(Loader<BatteryInfo> loader, BatteryInfo batteryInfo) { 128 mBatteryHeaderPreferenceController.updateHeaderPreference(batteryInfo); 129 mBatteryInfo = batteryInfo; 130 updateLastFullChargePreference(); 131 } 132 133 @Override 134 public void onLoaderReset(Loader<BatteryInfo> loader) { 135 // do nothing 136 } 137 }; 138 139 LoaderManager.LoaderCallbacks<List<BatteryInfo>> mBatteryInfoDebugLoaderCallbacks = 140 new LoaderCallbacks<List<BatteryInfo>>() { 141 @Override 142 public Loader<List<BatteryInfo>> onCreateLoader(int i, Bundle bundle) { 143 return new DebugEstimatesLoader(getContext(), mStatsHelper); 144 } 145 146 @Override 147 public void onLoadFinished(Loader<List<BatteryInfo>> loader, 148 List<BatteryInfo> batteryInfos) { 149 updateViews(batteryInfos); 150 } 151 152 @Override 153 public void onLoaderReset(Loader<List<BatteryInfo>> loader) { 154 } 155 }; 156 updateViews(List<BatteryInfo> batteryInfos)157 protected void updateViews(List<BatteryInfo> batteryInfos) { 158 final BatteryMeterView batteryView = mBatteryLayoutPref 159 .findViewById(R.id.battery_header_icon); 160 final TextView percentRemaining = 161 mBatteryLayoutPref.findViewById(R.id.battery_percent); 162 final TextView summary1 = mBatteryLayoutPref.findViewById(R.id.summary1); 163 BatteryInfo oldInfo = batteryInfos.get(0); 164 BatteryInfo newInfo = batteryInfos.get(1); 165 percentRemaining.setText(Utils.formatPercentage(oldInfo.batteryLevel)); 166 167 // set the text to the old estimate (copied from battery info). Note that this 168 // can sometimes say 0 time remaining because battery stats requires the phone 169 // be unplugged for a period of time before being willing ot make an estimate. 170 final String OldEstimateString = mPowerFeatureProvider.getOldEstimateDebugString( 171 Formatter.formatShortElapsedTime(getContext(), 172 PowerUtil.convertUsToMs(oldInfo.remainingTimeUs))); 173 final String NewEstimateString = mPowerFeatureProvider.getEnhancedEstimateDebugString( 174 Formatter.formatShortElapsedTime(getContext(), 175 PowerUtil.convertUsToMs(newInfo.remainingTimeUs))); 176 summary1.setText(OldEstimateString + "\n" + NewEstimateString); 177 178 batteryView.setBatteryLevel(oldInfo.batteryLevel); 179 batteryView.setCharging(!oldInfo.discharging); 180 } 181 182 private LoaderManager.LoaderCallbacks<List<BatteryTip>> mBatteryTipsCallbacks = 183 new LoaderManager.LoaderCallbacks<List<BatteryTip>>() { 184 185 @Override 186 public Loader<List<BatteryTip>> onCreateLoader(int id, Bundle args) { 187 return new BatteryTipLoader(getContext(), mStatsHelper); 188 } 189 190 @Override 191 public void onLoadFinished(Loader<List<BatteryTip>> loader, 192 List<BatteryTip> data) { 193 mBatteryTipPreferenceController.updateBatteryTips(data); 194 } 195 196 @Override 197 public void onLoaderReset(Loader<List<BatteryTip>> loader) { 198 199 } 200 }; 201 202 @Override onAttach(Context context)203 public void onAttach(Context context) { 204 super.onAttach(context); 205 final SettingsActivity activity = (SettingsActivity) getActivity(); 206 207 mBatteryHeaderPreferenceController = use(BatteryHeaderPreferenceController.class); 208 mBatteryHeaderPreferenceController.setActivity(activity); 209 mBatteryHeaderPreferenceController.setFragment(this); 210 mBatteryHeaderPreferenceController.setLifecycle(getSettingsLifecycle()); 211 212 mBatteryTipPreferenceController = use(BatteryTipPreferenceController.class); 213 mBatteryTipPreferenceController.setActivity(activity); 214 mBatteryTipPreferenceController.setFragment(this); 215 mBatteryTipPreferenceController.setBatteryTipListener(this::onBatteryTipHandled); 216 } 217 218 @Override onCreate(Bundle icicle)219 public void onCreate(Bundle icicle) { 220 super.onCreate(icicle); 221 setAnimationAllowed(true); 222 223 initFeatureProvider(); 224 mBatteryLayoutPref = (LayoutPreference) findPreference(KEY_BATTERY_HEADER); 225 226 mScreenUsagePref = (PowerGaugePreference) findPreference(KEY_SCREEN_USAGE); 227 mLastFullChargePref = (PowerGaugePreference) findPreference( 228 KEY_TIME_SINCE_LAST_FULL_CHARGE); 229 mFooterPreferenceMixin.createFooterPreference().setTitle(R.string.battery_footer_summary); 230 mBatteryUtils = BatteryUtils.getInstance(getContext()); 231 232 restartBatteryInfoLoader(); 233 mBatteryTipPreferenceController.restoreInstanceState(icicle); 234 updateBatteryTipFlag(icicle); 235 } 236 237 @Override onResume()238 public void onResume() { 239 super.onResume(); 240 getContentResolver().registerContentObserver( 241 Global.getUriFor(Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME), 242 false, 243 mSettingsObserver); 244 } 245 246 @Override onPause()247 public void onPause() { 248 getContentResolver().unregisterContentObserver(mSettingsObserver); 249 super.onPause(); 250 } 251 252 @Override getMetricsCategory()253 public int getMetricsCategory() { 254 return SettingsEnums.FUELGAUGE_POWER_USAGE_SUMMARY_V2; 255 } 256 257 @Override getLogTag()258 protected String getLogTag() { 259 return TAG; 260 } 261 262 @Override getPreferenceScreenResId()263 protected int getPreferenceScreenResId() { 264 return R.xml.power_usage_summary; 265 } 266 267 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)268 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 269 if (DEBUG) { 270 menu.add(Menu.NONE, MENU_STATS_TYPE, Menu.NONE, R.string.menu_stats_total) 271 .setIcon(com.android.internal.R.drawable.ic_menu_info_details) 272 .setAlphabeticShortcut('t'); 273 } 274 275 menu.add(Menu.NONE, MENU_ADVANCED_BATTERY, Menu.NONE, R.string.advanced_battery_title); 276 277 super.onCreateOptionsMenu(menu, inflater); 278 } 279 280 @Override getHelpResource()281 public int getHelpResource() { 282 return R.string.help_url_battery; 283 } 284 285 @Override onOptionsItemSelected(MenuItem item)286 public boolean onOptionsItemSelected(MenuItem item) { 287 switch (item.getItemId()) { 288 case MENU_STATS_TYPE: 289 if (mStatsType == BatteryStats.STATS_SINCE_CHARGED) { 290 mStatsType = BatteryStats.STATS_SINCE_UNPLUGGED; 291 } else { 292 mStatsType = BatteryStats.STATS_SINCE_CHARGED; 293 } 294 refreshUi(BatteryUpdateType.MANUAL); 295 return true; 296 case MENU_ADVANCED_BATTERY: 297 new SubSettingLauncher(getContext()) 298 .setDestination(PowerUsageAdvanced.class.getName()) 299 .setSourceMetricsCategory(getMetricsCategory()) 300 .setTitleRes(R.string.advanced_battery_title) 301 .launch(); 302 return true; 303 default: 304 return super.onOptionsItemSelected(item); 305 } 306 } 307 refreshUi(@atteryUpdateType int refreshType)308 protected void refreshUi(@BatteryUpdateType int refreshType) { 309 final Context context = getContext(); 310 if (context == null) { 311 return; 312 } 313 314 // Skip BatteryTipLoader if device is rotated or only battery level change 315 if (mNeedUpdateBatteryTip 316 && refreshType != BatteryUpdateType.BATTERY_LEVEL) { 317 restartBatteryTipLoader(); 318 } else { 319 mNeedUpdateBatteryTip = true; 320 } 321 322 // reload BatteryInfo and updateUI 323 restartBatteryInfoLoader(); 324 updateLastFullChargePreference(); 325 mScreenUsagePref.setSubtitle(StringUtil.formatElapsedTime(getContext(), 326 mBatteryUtils.calculateScreenUsageTime(mStatsHelper), false)); 327 } 328 329 @VisibleForTesting restartBatteryTipLoader()330 void restartBatteryTipLoader() { 331 getLoaderManager().restartLoader(BATTERY_TIP_LOADER, Bundle.EMPTY, mBatteryTipsCallbacks); 332 } 333 334 @VisibleForTesting setBatteryLayoutPreference(LayoutPreference layoutPreference)335 void setBatteryLayoutPreference(LayoutPreference layoutPreference) { 336 mBatteryLayoutPref = layoutPreference; 337 } 338 339 @VisibleForTesting updateLastFullChargePreference()340 void updateLastFullChargePreference() { 341 if (mBatteryInfo != null && mBatteryInfo.averageTimeToDischarge 342 != EstimateKt.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN) { 343 mLastFullChargePref.setTitle(R.string.battery_full_charge_last); 344 mLastFullChargePref.setSubtitle( 345 StringUtil.formatElapsedTime(getContext(), mBatteryInfo.averageTimeToDischarge, 346 false /* withSeconds */)); 347 } else { 348 final long lastFullChargeTime = mBatteryUtils.calculateLastFullChargeTime(mStatsHelper, 349 System.currentTimeMillis()); 350 mLastFullChargePref.setTitle(R.string.battery_last_full_charge); 351 mLastFullChargePref.setSubtitle( 352 StringUtil.formatRelativeTime(getContext(), lastFullChargeTime, 353 false /* withSeconds */)); 354 } 355 } 356 357 @VisibleForTesting showBothEstimates()358 void showBothEstimates() { 359 final Context context = getContext(); 360 if (context == null 361 || !mPowerFeatureProvider.isEnhancedBatteryPredictionEnabled(context)) { 362 return; 363 } 364 getLoaderManager().restartLoader(DEBUG_INFO_LOADER, Bundle.EMPTY, 365 mBatteryInfoDebugLoaderCallbacks); 366 } 367 368 @VisibleForTesting initFeatureProvider()369 void initFeatureProvider() { 370 final Context context = getContext(); 371 mPowerFeatureProvider = FeatureFactory.getFactory(context) 372 .getPowerUsageFeatureProvider(context); 373 } 374 375 @VisibleForTesting restartBatteryInfoLoader()376 void restartBatteryInfoLoader() { 377 if (getContext() == null) { 378 return; 379 } 380 getLoaderManager().restartLoader(BATTERY_INFO_LOADER, Bundle.EMPTY, 381 mBatteryInfoLoaderCallbacks); 382 if (mPowerFeatureProvider.isEstimateDebugEnabled()) { 383 // Set long click action for summary to show debug info 384 View header = mBatteryLayoutPref.findViewById(R.id.summary1); 385 header.setOnLongClickListener(this); 386 } 387 } 388 389 @VisibleForTesting updateBatteryTipFlag(Bundle icicle)390 void updateBatteryTipFlag(Bundle icicle) { 391 mNeedUpdateBatteryTip = icicle == null || mBatteryTipPreferenceController.needUpdate(); 392 } 393 394 @Override onLongClick(View view)395 public boolean onLongClick(View view) { 396 showBothEstimates(); 397 view.setOnLongClickListener(null); 398 return true; 399 } 400 401 @Override restartBatteryStatsLoader(@atteryUpdateType int refreshType)402 protected void restartBatteryStatsLoader(@BatteryUpdateType int refreshType) { 403 super.restartBatteryStatsLoader(refreshType); 404 mBatteryHeaderPreferenceController.quickUpdateHeaderPreference(); 405 } 406 407 @Override onSaveInstanceState(Bundle outState)408 public void onSaveInstanceState(Bundle outState) { 409 super.onSaveInstanceState(outState); 410 mBatteryTipPreferenceController.saveInstanceState(outState); 411 } 412 413 @Override onBatteryTipHandled(BatteryTip batteryTip)414 public void onBatteryTipHandled(BatteryTip batteryTip) { 415 restartBatteryTipLoader(); 416 } 417 418 419 public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 420 new BaseSearchIndexProvider() { 421 @Override 422 public List<SearchIndexableResource> getXmlResourcesToIndex( 423 Context context, boolean enabled) { 424 final SearchIndexableResource sir = new SearchIndexableResource(context); 425 sir.xmlResId = R.xml.power_usage_summary; 426 return Collections.singletonList(sir); 427 } 428 }; 429 } 430