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