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