1 /* 2 * Copyright (C) 2020 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.frameworks.core.batterystatsviewer; 18 19 import android.content.Context; 20 import android.os.BatteryStatsManager; 21 import android.os.BatteryUsageStats; 22 import android.os.BatteryUsageStatsQuery; 23 import android.os.Bundle; 24 import android.view.LayoutInflater; 25 import android.view.View; 26 import android.view.ViewGroup; 27 import android.widget.ImageView; 28 import android.widget.TextView; 29 import android.widget.Toast; 30 31 import androidx.activity.ComponentActivity; 32 import androidx.annotation.NonNull; 33 import androidx.annotation.Nullable; 34 import androidx.loader.app.LoaderManager; 35 import androidx.loader.app.LoaderManager.LoaderCallbacks; 36 import androidx.loader.content.Loader; 37 import androidx.recyclerview.widget.LinearLayoutManager; 38 import androidx.recyclerview.widget.RecyclerView; 39 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; 40 41 import com.android.settingslib.utils.AsyncLoaderCompat; 42 43 import java.util.Collections; 44 import java.util.List; 45 import java.util.Locale; 46 47 public class BatteryStatsViewerActivity extends ComponentActivity { 48 public static final String EXTRA_BATTERY_CONSUMER = "batteryConsumerId"; 49 50 private static final int BATTERY_STATS_REFRESH_RATE_MILLIS = 60 * 1000; 51 private static final int MILLIS_IN_MINUTE = 60000; 52 private static final String FORCE_FRESH_STATS = "force_fresh_stats"; 53 54 private BatteryStatsDataAdapter mBatteryStatsDataAdapter; 55 private final Runnable mBatteryStatsRefresh = this::refreshPeriodically; 56 private String mBatteryConsumerId; 57 private TextView mTitleView; 58 private TextView mDetailsView; 59 private ImageView mIconView; 60 private TextView mPackagesView; 61 private View mHeadingsView; 62 private RecyclerView mBatteryConsumerDataView; 63 private SwipeRefreshLayout mSwipeRefreshLayout; 64 private View mCardView; 65 private View mEmptyView; 66 private List<BatteryUsageStats> mBatteryUsageStats; 67 68 @Override onCreate(@ullable Bundle savedInstanceState)69 protected void onCreate(@Nullable Bundle savedInstanceState) { 70 super.onCreate(savedInstanceState); 71 72 mBatteryConsumerId = getIntent().getStringExtra(EXTRA_BATTERY_CONSUMER); 73 74 setContentView(R.layout.battery_stats_viewer_layout); 75 76 mSwipeRefreshLayout = findViewById(R.id.swipe_refresh); 77 mSwipeRefreshLayout.setColorSchemeResources(android.R.color.holo_green_light); 78 mSwipeRefreshLayout.setRefreshing(true); 79 mSwipeRefreshLayout.setOnRefreshListener(this::onRefresh); 80 81 mCardView = findViewById(R.id.app_card); 82 mTitleView = findViewById(android.R.id.title); 83 mDetailsView = findViewById(R.id.details); 84 mIconView = findViewById(android.R.id.icon); 85 mPackagesView = findViewById(R.id.packages); 86 mHeadingsView = findViewById(R.id.headings); 87 88 mBatteryConsumerDataView = findViewById(R.id.battery_consumer_data_view); 89 mBatteryConsumerDataView.setLayoutManager(new LinearLayoutManager(this)); 90 mBatteryStatsDataAdapter = new BatteryStatsDataAdapter(); 91 mBatteryConsumerDataView.setAdapter(mBatteryStatsDataAdapter); 92 93 mEmptyView = findViewById(R.id.empty_view); 94 } 95 96 @Override onResume()97 protected void onResume() { 98 super.onResume(); 99 refreshPeriodically(); 100 } 101 102 @Override onPause()103 protected void onPause() { 104 super.onPause(); 105 getMainThreadHandler().removeCallbacks(mBatteryStatsRefresh); 106 } 107 refreshPeriodically()108 private void refreshPeriodically() { 109 loadBatteryUsageStats(false); 110 getMainThreadHandler().postDelayed(mBatteryStatsRefresh, BATTERY_STATS_REFRESH_RATE_MILLIS); 111 } 112 onRefresh()113 private void onRefresh() { 114 loadBatteryUsageStats(true); 115 } 116 loadBatteryUsageStats(boolean forceFreshStats)117 private void loadBatteryUsageStats(boolean forceFreshStats) { 118 Bundle args = new Bundle(); 119 args.putBoolean(FORCE_FRESH_STATS, forceFreshStats); 120 LoaderManager.getInstance(this).restartLoader(0, args, 121 new BatteryUsageStatsLoaderCallbacks()); 122 } 123 124 private static class BatteryUsageStatsLoader extends 125 AsyncLoaderCompat<List<BatteryUsageStats>> { 126 private final BatteryStatsManager mBatteryStatsManager; 127 private final boolean mForceFreshStats; 128 BatteryUsageStatsLoader(Context context, boolean forceFreshStats)129 BatteryUsageStatsLoader(Context context, boolean forceFreshStats) { 130 super(context); 131 mBatteryStatsManager = context.getSystemService(BatteryStatsManager.class); 132 mForceFreshStats = forceFreshStats; 133 } 134 135 @Override loadInBackground()136 public List<BatteryUsageStats> loadInBackground() { 137 final int maxStatsAgeMs = mForceFreshStats ? 0 : BATTERY_STATS_REFRESH_RATE_MILLIS; 138 final BatteryUsageStatsQuery queryDefault = 139 new BatteryUsageStatsQuery.Builder() 140 .includePowerModels() 141 .includeProcessStateData() 142 .setMaxStatsAgeMs(maxStatsAgeMs) 143 .build(); 144 final BatteryUsageStatsQuery queryPowerProfileModeledOnly = 145 new BatteryUsageStatsQuery.Builder() 146 .powerProfileModeledOnly() 147 .includePowerModels() 148 .includeProcessStateData() 149 .setMaxStatsAgeMs(maxStatsAgeMs) 150 .build(); 151 return mBatteryStatsManager.getBatteryUsageStats( 152 List.of(queryDefault, queryPowerProfileModeledOnly)); 153 } 154 155 @Override onDiscardResult(List<BatteryUsageStats> result)156 protected void onDiscardResult(List<BatteryUsageStats> result) { 157 } 158 } 159 160 private class BatteryUsageStatsLoaderCallbacks 161 implements LoaderCallbacks<List<BatteryUsageStats>> { 162 @NonNull 163 @Override onCreateLoader(int id, Bundle args)164 public Loader<List<BatteryUsageStats>> onCreateLoader(int id, Bundle args) { 165 return new BatteryUsageStatsLoader(BatteryStatsViewerActivity.this, 166 args.getBoolean(FORCE_FRESH_STATS)); 167 } 168 169 @Override onLoadFinished(@onNull Loader<List<BatteryUsageStats>> loader, List<BatteryUsageStats> batteryUsageStats)170 public void onLoadFinished(@NonNull Loader<List<BatteryUsageStats>> loader, 171 List<BatteryUsageStats> batteryUsageStats) { 172 onBatteryUsageStatsLoaded(batteryUsageStats); 173 } 174 175 @Override onLoaderReset(@onNull Loader<List<BatteryUsageStats>> loader)176 public void onLoaderReset(@NonNull Loader<List<BatteryUsageStats>> loader) { 177 } 178 } 179 onBatteryUsageStatsLoaded(List<BatteryUsageStats> batteryUsageStats)180 private void onBatteryUsageStatsLoaded(List<BatteryUsageStats> batteryUsageStats) { 181 mBatteryUsageStats = batteryUsageStats; 182 onBatteryStatsDataLoaded(); 183 } 184 onBatteryStatsDataLoaded()185 public void onBatteryStatsDataLoaded() { 186 BatteryConsumerData batteryConsumerData = new BatteryConsumerData(this, 187 mBatteryUsageStats, mBatteryConsumerId); 188 189 BatteryConsumerInfoHelper.BatteryConsumerInfo 190 batteryConsumerInfo = batteryConsumerData.getBatteryConsumerInfo(); 191 if (batteryConsumerInfo == null) { 192 Toast.makeText(this, "Battery consumer not found: " + mBatteryConsumerId, 193 Toast.LENGTH_SHORT).show(); 194 finish(); 195 return; 196 } else { 197 mTitleView.setText(batteryConsumerInfo.label); 198 if (batteryConsumerInfo.details != null) { 199 mDetailsView.setText(batteryConsumerInfo.details); 200 mDetailsView.setVisibility(View.VISIBLE); 201 } else { 202 mDetailsView.setVisibility(View.GONE); 203 } 204 if (batteryConsumerInfo.iconInfo != null) { 205 mIconView.setImageDrawable( 206 batteryConsumerInfo.iconInfo.loadIcon(getPackageManager())); 207 } else { 208 mIconView.setImageResource(R.drawable.gm_device_24); 209 } 210 if (batteryConsumerInfo.packages != null) { 211 mPackagesView.setText(batteryConsumerInfo.packages); 212 mPackagesView.setVisibility(View.VISIBLE); 213 } else { 214 mPackagesView.setVisibility(View.GONE); 215 } 216 217 if (batteryConsumerInfo.consumerType 218 == BatteryConsumerData.ConsumerType.DEVICE_POWER_COMPONENT) { 219 mHeadingsView.setVisibility(View.VISIBLE); 220 } else { 221 mHeadingsView.setVisibility(View.GONE); 222 } 223 } 224 225 mBatteryStatsDataAdapter.setEntries(batteryConsumerData.getEntries()); 226 if (batteryConsumerData.getEntries().isEmpty()) { 227 mEmptyView.setVisibility(View.VISIBLE); 228 mBatteryConsumerDataView.setVisibility(View.GONE); 229 } else { 230 mEmptyView.setVisibility(View.GONE); 231 mBatteryConsumerDataView.setVisibility(View.VISIBLE); 232 } 233 234 mCardView.setVisibility(View.VISIBLE); 235 mSwipeRefreshLayout.setRefreshing(false); 236 } 237 238 private static class BatteryStatsDataAdapter extends 239 RecyclerView.Adapter<BatteryStatsDataAdapter.ViewHolder> { 240 public static class ViewHolder extends RecyclerView.ViewHolder { 241 public ImageView iconImageView; 242 public TextView titleTextView; 243 public TextView value1TextView; 244 public TextView value2TextView; 245 ViewHolder(View itemView)246 ViewHolder(View itemView) { 247 super(itemView); 248 249 iconImageView = itemView.findViewById(R.id.icon); 250 titleTextView = itemView.findViewById(R.id.title); 251 value1TextView = itemView.findViewById(R.id.value1); 252 value2TextView = itemView.findViewById(R.id.value2); 253 } 254 } 255 256 private List<BatteryConsumerData.Entry> mEntries = Collections.emptyList(); 257 setEntries(List<BatteryConsumerData.Entry> entries)258 public void setEntries(List<BatteryConsumerData.Entry> entries) { 259 mEntries = entries; 260 notifyDataSetChanged(); 261 } 262 263 @Override getItemCount()264 public int getItemCount() { 265 return mEntries.size(); 266 } 267 268 @NonNull 269 @Override onCreateViewHolder(@onNull ViewGroup parent, int position)270 public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position) { 271 LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); 272 View itemView = layoutInflater.inflate(R.layout.battery_consumer_entry_layout, parent, 273 false); 274 return new ViewHolder(itemView); 275 } 276 277 @Override onBindViewHolder(@onNull ViewHolder viewHolder, int position)278 public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) { 279 BatteryConsumerData.Entry entry = mEntries.get(position); 280 281 switch (entry.entryType) { 282 case UID_TOTAL_POWER: 283 setTitleIconAndBackground(viewHolder, entry.title, 284 R.drawable.gm_sum_24, 0); 285 setPowerText(viewHolder.value1TextView, entry.value1); 286 setProportionText(viewHolder.value2TextView, entry); 287 break; 288 case UID_POWER_PROFILE: 289 setTitleIconAndBackground(viewHolder, entry.title, 290 R.drawable.gm_calculate_24, 291 R.color.battery_consumer_bg_power_profile); 292 setPowerText(viewHolder.value1TextView, entry.value1); 293 setProportionText(viewHolder.value2TextView, entry); 294 break; 295 case UID_POWER_PROFILE_PROCESS_STATE: 296 setTitleIconAndBackground(viewHolder, " " + entry.title, 297 R.drawable.gm_calculate_24, 298 R.color.battery_consumer_bg_power_profile); 299 setPowerText(viewHolder.value1TextView, entry.value1); 300 viewHolder.value2TextView.setVisibility(View.INVISIBLE); 301 break; 302 case UID_POWER_ENERGY_CONSUMPTION: 303 setTitleIconAndBackground(viewHolder, entry.title, 304 R.drawable.gm_energy_24, 305 R.color.battery_consumer_bg_energy_consumption); 306 setPowerText(viewHolder.value1TextView, entry.value1); 307 setProportionText(viewHolder.value2TextView, entry); 308 break; 309 case UID_POWER_ENERGY_PROCESS_STATE: 310 setTitleIconAndBackground(viewHolder, " " + entry.title, 311 R.drawable.gm_energy_24, 312 R.color.battery_consumer_bg_energy_consumption); 313 setPowerText(viewHolder.value1TextView, entry.value1); 314 viewHolder.value2TextView.setVisibility(View.INVISIBLE); 315 break; 316 case UID_POWER_CUSTOM: 317 setTitleIconAndBackground(viewHolder, entry.title, 318 R.drawable.gm_energy_24, 319 R.color.battery_consumer_bg_energy_consumption); 320 setPowerText(viewHolder.value1TextView, entry.value1); 321 setProportionText(viewHolder.value2TextView, entry); 322 break; 323 case UID_DURATION: 324 setTitleIconAndBackground(viewHolder, entry.title, 325 R.drawable.gm_timer_24, 0); 326 setDurationText(viewHolder.value1TextView, (long) entry.value1); 327 setProportionText(viewHolder.value2TextView, entry); 328 break; 329 case DEVICE_TOTAL_POWER: 330 setTitleIconAndBackground(viewHolder, entry.title, 331 R.drawable.gm_sum_24, 0); 332 setPowerText(viewHolder.value1TextView, entry.value1); 333 setPowerText(viewHolder.value2TextView, entry.value2); 334 break; 335 case DEVICE_POWER_MODELED: 336 setTitleIconAndBackground(viewHolder, entry.title, 337 R.drawable.gm_calculate_24, 338 R.color.battery_consumer_bg_power_profile); 339 setPowerText(viewHolder.value1TextView, entry.value1); 340 setPowerText(viewHolder.value2TextView, entry.value2); 341 break; 342 case DEVICE_POWER_ENERGY_CONSUMPTION: 343 setTitleIconAndBackground(viewHolder, entry.title, 344 R.drawable.gm_energy_24, 345 R.color.battery_consumer_bg_energy_consumption); 346 setPowerText(viewHolder.value1TextView, entry.value1); 347 setPowerText(viewHolder.value2TextView, entry.value2); 348 break; 349 case DEVICE_POWER_CUSTOM: 350 setTitleIconAndBackground(viewHolder, entry.title, 351 R.drawable.gm_energy_24, 352 R.color.battery_consumer_bg_energy_consumption); 353 setPowerText(viewHolder.value1TextView, entry.value1); 354 setPowerText(viewHolder.value2TextView, entry.value2); 355 break; 356 case DEVICE_DURATION: 357 setTitleIconAndBackground(viewHolder, entry.title, 358 R.drawable.gm_timer_24, 0); 359 setDurationText(viewHolder.value1TextView, (long) entry.value1); 360 viewHolder.value2TextView.setVisibility(View.GONE); 361 break; 362 } 363 } 364 setTitleIconAndBackground(ViewHolder viewHolder, String title, int icon, int background)365 private void setTitleIconAndBackground(ViewHolder viewHolder, String title, int icon, 366 int background) { 367 viewHolder.titleTextView.setText(title); 368 viewHolder.iconImageView.setImageResource(icon); 369 viewHolder.itemView.setBackgroundResource(background); 370 } 371 setProportionText(TextView textView, BatteryConsumerData.Entry entry)372 private void setProportionText(TextView textView, BatteryConsumerData.Entry entry) { 373 final double proportion = entry.value2 != 0 ? entry.value1 * 100 / entry.value2 : 0; 374 textView.setText( 375 String.format(Locale.getDefault(), "%.1f%%", proportion)); 376 textView.setVisibility(View.VISIBLE); 377 } 378 setPowerText(TextView textView, double powerMah)379 private void setPowerText(TextView textView, double powerMah) { 380 textView.setText(String.format(Locale.getDefault(), "%.1f", powerMah)); 381 textView.setVisibility(View.VISIBLE); 382 } 383 setDurationText(TextView textView, long durationMs)384 private void setDurationText(TextView textView, long durationMs) { 385 CharSequence text; 386 if (durationMs < MILLIS_IN_MINUTE) { 387 text = String.format(Locale.getDefault(), "%,d ms", durationMs); 388 } else { 389 text = String.format(Locale.getDefault(), "%,d m %d s", 390 durationMs / MILLIS_IN_MINUTE, 391 (durationMs % MILLIS_IN_MINUTE) / 1000); 392 } 393 394 textView.setText(text); 395 textView.setVisibility(View.VISIBLE); 396 } 397 } 398 } 399