Merge "Add power stats viewer"
This commit is contained in:
committed by
Android (Google) Code Review
commit
b356f07062
13
core/tests/powertests/PowerStatsViewer/Android.bp
Normal file
13
core/tests/powertests/PowerStatsViewer/Android.bp
Normal file
@@ -0,0 +1,13 @@
|
||||
android_test {
|
||||
name: "PowerStatsViewer",
|
||||
srcs: ["src/**/*.java"],
|
||||
defaults: ["SettingsLibDefaults"],
|
||||
static_libs: [
|
||||
"androidx.appcompat_appcompat",
|
||||
"androidx.cardview_cardview",
|
||||
"androidx.recyclerview_recyclerview",
|
||||
"com.google.android.material_material",
|
||||
],
|
||||
platform_apis: true,
|
||||
certificate: "platform",
|
||||
}
|
||||
42
core/tests/powertests/PowerStatsViewer/AndroidManifest.xml
Normal file
42
core/tests/powertests/PowerStatsViewer/AndroidManifest.xml
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.frameworks.core.powerstatsviewer"
|
||||
android:sharedUserId="android.uid.system">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.BATTERY_STATS"/>
|
||||
|
||||
<application
|
||||
android:theme="@style/Theme"
|
||||
android:label="Power Stats Viewer">
|
||||
<activity android:name=".PowerStatsViewerActivity"
|
||||
android:label="Power Stats Viewer"
|
||||
android:launchMode="singleTop"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".AppPickerActivity"
|
||||
android:label="Power Stats - Select an App"/>
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2018 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
|
||||
|
||||
<ImageView
|
||||
android:id="@android:id/icon"
|
||||
android:layout_width="@dimen/secondary_app_icon_size"
|
||||
android:layout_height="@dimen/secondary_app_icon_size"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:gravity="start|center_vertical"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/uid"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/packages"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textDirection="locale"
|
||||
android:maxLines="3"
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorSecondary"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/power_mah"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_marginStart="16dp"
|
||||
android:gravity="right"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/app_list_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<ProgressBar
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:id="@+id/loading_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"/>
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearanceBody"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/amount"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="0.7"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="right"
|
||||
android:textAppearance="@style/TextAppearanceBody"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/percent"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="right"
|
||||
android:textAppearance="@style/TextAppearanceBody"/>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
style="@style/LoadTestCardView"
|
||||
android:id="@+id/app_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:padding="20dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:minHeight="80dp"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp">
|
||||
|
||||
<include layout="@layout/app_info_layout"/>
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/power_stats_data_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/empty_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
android:text="No power stats available"/>
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/loading_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#AAFFFFFF">
|
||||
<ProgressBar
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"/>
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
34
core/tests/powertests/PowerStatsViewer/res/values/styles.xml
Normal file
34
core/tests/powertests/PowerStatsViewer/res/values/styles.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
~ Copyright (C) 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<style name="Theme" parent="Theme.MaterialComponents.Light">
|
||||
<item name="colorPrimary">#34a853</item>
|
||||
<item name="android:windowActionBar">true</item>
|
||||
<item name="android:windowNoTitle">false</item>
|
||||
</style>
|
||||
|
||||
<style name="LoadTestCardView" parent="Widget.MaterialComponents.CardView">
|
||||
<item name="cardBackgroundColor">#ceead6</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearanceBody" parent="android:TextAppearance.DeviceDefault">
|
||||
<item name="android:textColor">#000000</item>
|
||||
<item name="android:textSize">18sp</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.frameworks.core.powerstatsviewer;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Process;
|
||||
|
||||
import com.android.internal.os.BatterySipper;
|
||||
|
||||
class AppInfoHelper {
|
||||
|
||||
private static final String SYSTEM_SERVER_PACKAGE_NAME = "android";
|
||||
|
||||
public static class AppInfo {
|
||||
public int uid;
|
||||
public CharSequence label;
|
||||
public double powerMah;
|
||||
public ApplicationInfo iconInfo;
|
||||
public CharSequence packages;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static AppInfo makeApplicationInfo(PackageManager packageManager, int uid,
|
||||
@Nullable BatterySipper sipper) {
|
||||
if (sipper != null && sipper.drainType != BatterySipper.DrainType.APP) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String packageWithHighestDrain = null;
|
||||
|
||||
AppInfo info = new AppInfo();
|
||||
info.uid = uid;
|
||||
if (sipper != null) {
|
||||
sipper.sumPower();
|
||||
info.powerMah = sipper.totalSmearedPowerMah;
|
||||
packageWithHighestDrain = sipper.packageWithHighestDrain;
|
||||
}
|
||||
if (info.uid == Process.ROOT_UID) {
|
||||
info.label = "<root>";
|
||||
} else {
|
||||
String[] packages = packageManager.getPackagesForUid(info.uid);
|
||||
String primaryPackageName = null;
|
||||
if (info.uid == Process.SYSTEM_UID) {
|
||||
primaryPackageName = SYSTEM_SERVER_PACKAGE_NAME;
|
||||
} else if (packages != null) {
|
||||
for (String name : packages) {
|
||||
primaryPackageName = name;
|
||||
if (name.equals(packageWithHighestDrain)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (primaryPackageName != null) {
|
||||
try {
|
||||
ApplicationInfo applicationInfo =
|
||||
packageManager.getApplicationInfo(primaryPackageName, 0);
|
||||
info.label = applicationInfo.loadLabel(packageManager);
|
||||
info.iconInfo = applicationInfo;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
info.label = primaryPackageName;
|
||||
}
|
||||
} else if (packageWithHighestDrain != null) {
|
||||
info.label = packageWithHighestDrain;
|
||||
}
|
||||
|
||||
if (packages != null && packages.length > 0) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (primaryPackageName != null) {
|
||||
sb.append(primaryPackageName);
|
||||
}
|
||||
for (String packageName : packages) {
|
||||
if (packageName.equals(primaryPackageName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sb.length() != 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append(packageName);
|
||||
}
|
||||
|
||||
info.packages = sb;
|
||||
}
|
||||
}
|
||||
|
||||
// Default the app icon to System Server. This includes root, dex2oat and other UIDs.
|
||||
if (info.iconInfo == null) {
|
||||
try {
|
||||
info.iconInfo =
|
||||
packageManager.getApplicationInfo(SYSTEM_SERVER_PACKAGE_NAME, 0);
|
||||
} catch (PackageManager.NameNotFoundException nameNotFoundException) {
|
||||
// Won't happen
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.frameworks.core.powerstatsviewer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.BatteryStats;
|
||||
import android.os.Bundle;
|
||||
import android.os.Process;
|
||||
import android.os.UserManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.ComponentActivity;
|
||||
import androidx.activity.result.contract.ActivityResultContract;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.Loader;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.frameworks.core.powerstatsviewer.AppInfoHelper.AppInfo;
|
||||
import com.android.internal.os.BatterySipper;
|
||||
import com.android.internal.os.BatteryStatsHelper;
|
||||
import com.android.settingslib.utils.AsyncLoaderCompat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Picker, showing a sorted list of applications consuming power. Returns the selected
|
||||
* application UID or Process.INVALID_UID.
|
||||
*/
|
||||
public class AppPickerActivity extends ComponentActivity {
|
||||
private static final String TAG = "AppPicker";
|
||||
|
||||
public static final ActivityResultContract<Void, Integer> CONTRACT =
|
||||
new ActivityResultContract<Void, Integer>() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Intent createIntent(@NonNull Context context, Void aVoid) {
|
||||
return new Intent(context, AppPickerActivity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer parseResult(int resultCode, @Nullable Intent intent) {
|
||||
if (resultCode != RESULT_OK || intent == null) {
|
||||
return Process.INVALID_UID;
|
||||
}
|
||||
return intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
|
||||
}
|
||||
};
|
||||
|
||||
private AppListAdapter mAppListAdapter;
|
||||
private RecyclerView mAppList;
|
||||
private View mLoadingView;
|
||||
|
||||
private interface OnAppSelectedListener {
|
||||
void onAppSelected(int uid);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
setContentView(R.layout.app_picker_layout);
|
||||
|
||||
mLoadingView = findViewById(R.id.loading_view);
|
||||
|
||||
mAppList = findViewById(R.id.app_list_view);
|
||||
mAppList.setLayoutManager(new LinearLayoutManager(this));
|
||||
mAppListAdapter = new AppListAdapter(AppPickerActivity.this::setSelectedUid);
|
||||
mAppList.setAdapter(mAppListAdapter);
|
||||
|
||||
LoaderManager.getInstance(this).initLoader(0, null,
|
||||
new AppListLoaderCallbacks());
|
||||
}
|
||||
|
||||
protected void setSelectedUid(int uid) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(Intent.EXTRA_UID, uid);
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNavigateUp() {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static class AppListLoader extends AsyncLoaderCompat<List<AppInfo>> {
|
||||
private final BatteryStatsHelper mStatsHelper;
|
||||
private final UserManager mUserManager;
|
||||
private final PackageManager mPackageManager;
|
||||
|
||||
AppListLoader(Context context) {
|
||||
super(context);
|
||||
mUserManager = context.getSystemService(UserManager.class);
|
||||
mStatsHelper = new BatteryStatsHelper(context, false /* collectBatteryBroadcast */);
|
||||
mStatsHelper.create((Bundle) null);
|
||||
mStatsHelper.clearStats();
|
||||
mPackageManager = context.getPackageManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AppInfo> loadInBackground() {
|
||||
List<AppInfo> applicationList = new ArrayList<>();
|
||||
|
||||
mStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED,
|
||||
mUserManager.getUserProfiles());
|
||||
|
||||
final List<BatterySipper> usageList = mStatsHelper.getUsageList();
|
||||
for (BatterySipper sipper : usageList) {
|
||||
AppInfo info =
|
||||
AppInfoHelper.makeApplicationInfo(mPackageManager, sipper.getUid(), sipper);
|
||||
if (info != null) {
|
||||
applicationList.add(info);
|
||||
}
|
||||
}
|
||||
|
||||
applicationList.sort(
|
||||
Comparator.comparing((AppInfo a) -> a.powerMah).reversed());
|
||||
return applicationList;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDiscardResult(List<AppInfo> result) {
|
||||
}
|
||||
}
|
||||
|
||||
private class AppListLoaderCallbacks implements
|
||||
LoaderManager.LoaderCallbacks<List<AppInfo>> {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Loader<List<AppInfo>> onCreateLoader(int id, Bundle args) {
|
||||
return new AppListLoader(AppPickerActivity.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(@NonNull Loader<List<AppInfo>> loader,
|
||||
List<AppInfo> applicationList) {
|
||||
mAppListAdapter.setApplicationList(applicationList);
|
||||
mAppList.setVisibility(View.VISIBLE);
|
||||
mLoadingView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(@NonNull Loader<List<AppInfo>> loader) {
|
||||
}
|
||||
}
|
||||
|
||||
public class AppListAdapter extends RecyclerView.Adapter<AppViewHolder> {
|
||||
private final OnAppSelectedListener mListener;
|
||||
private List<AppInfo> mApplicationList;
|
||||
|
||||
public AppListAdapter(OnAppSelectedListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
void setApplicationList(List<AppInfo> applicationList) {
|
||||
mApplicationList = applicationList;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mApplicationList.size();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AppViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int position) {
|
||||
LayoutInflater layoutInflater = LayoutInflater.from(viewGroup.getContext());
|
||||
View view = layoutInflater.inflate(R.layout.app_info_layout, viewGroup, false);
|
||||
return new AppViewHolder(view, mListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull AppViewHolder appViewHolder, int position) {
|
||||
AppInfo item = mApplicationList.get(position);
|
||||
appViewHolder.uid = item.uid;
|
||||
appViewHolder.titleView.setText(item.label);
|
||||
appViewHolder.uidView.setText(
|
||||
String.format(Locale.getDefault(), "UID: %d", item.uid));
|
||||
appViewHolder.powerView.setText(
|
||||
String.format(Locale.getDefault(), "%.1f mAh", item.powerMah));
|
||||
appViewHolder.iconView.setImageDrawable(
|
||||
item.iconInfo.loadIcon(getPackageManager()));
|
||||
if (item.packages != null) {
|
||||
appViewHolder.packagesView.setText(item.packages);
|
||||
appViewHolder.packagesView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
appViewHolder.packagesView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// View Holder used when displaying apps
|
||||
public static class AppViewHolder extends RecyclerView.ViewHolder
|
||||
implements View.OnClickListener {
|
||||
private final OnAppSelectedListener mListener;
|
||||
|
||||
public int uid;
|
||||
public TextView titleView;
|
||||
public TextView uidView;
|
||||
public ImageView iconView;
|
||||
public TextView packagesView;
|
||||
public TextView powerView;
|
||||
|
||||
AppViewHolder(View view, OnAppSelectedListener listener) {
|
||||
super(view);
|
||||
mListener = listener;
|
||||
view.setOnClickListener(this);
|
||||
titleView = view.findViewById(android.R.id.title);
|
||||
uidView = view.findViewById(R.id.uid);
|
||||
iconView = view.findViewById(android.R.id.icon);
|
||||
packagesView = view.findViewById(R.id.packages);
|
||||
powerView = view.findViewById(R.id.power_mah);
|
||||
powerView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mListener.onAppSelected(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.frameworks.core.powerstatsviewer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Process;
|
||||
|
||||
import com.android.internal.os.BatterySipper;
|
||||
import com.android.internal.os.BatteryStatsHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PowerStatsData {
|
||||
private static final String PACKAGE_CALENDAR_PROVIDER = "com.android.providers.calendar";
|
||||
private static final String PACKAGE_MEDIA_PROVIDER = "com.android.providers.media";
|
||||
private static final String PACKAGE_SYSTEMUI = "com.android.systemui";
|
||||
private static final String[] PACKAGES_SYSTEM = {PACKAGE_MEDIA_PROVIDER,
|
||||
PACKAGE_CALENDAR_PROVIDER, PACKAGE_SYSTEMUI};
|
||||
|
||||
enum EntryType {
|
||||
POWER,
|
||||
DURATION,
|
||||
}
|
||||
|
||||
public static class Entry {
|
||||
public String title;
|
||||
public EntryType entryType;
|
||||
public double value;
|
||||
public double total;
|
||||
}
|
||||
|
||||
private final AppInfoHelper.AppInfo mAppInfo;
|
||||
private final List<Entry> mEntries = new ArrayList<>();
|
||||
|
||||
public PowerStatsData(Context context, BatteryStatsHelper batteryStatsHelper,
|
||||
int uid) {
|
||||
List<BatterySipper> usageList = batteryStatsHelper.getUsageList();
|
||||
|
||||
double totalPowerMah = 0;
|
||||
double totalSmearedPowerMah = 0;
|
||||
double totalPowerExcludeSystemMah = 0;
|
||||
double totalScreenPower = 0;
|
||||
double totalProportionalSmearMah = 0;
|
||||
double totalCpuPowerMah = 0;
|
||||
double totalSystemServiceCpuPowerMah = 0;
|
||||
double totalUsagePowerMah = 0;
|
||||
double totalWakeLockPowerMah = 0;
|
||||
double totalMobileRadioPowerMah = 0;
|
||||
double totalWifiPowerMah = 0;
|
||||
double totalBluetoothPowerMah = 0;
|
||||
double totalGpsPowerMah = 0;
|
||||
double totalCameraPowerMah = 0;
|
||||
double totalFlashlightPowerMah = 0;
|
||||
double totalSensorPowerMah = 0;
|
||||
double totalAudioPowerMah = 0;
|
||||
double totalVideoPowerMah = 0;
|
||||
|
||||
long totalCpuTimeMs = 0;
|
||||
long totalCpuFgTimeMs = 0;
|
||||
long totalWakeLockTimeMs = 0;
|
||||
long totalWifiRunningTimeMs = 0;
|
||||
long totalBluetoothRunningTimeMs = 0;
|
||||
long totalGpsTimeMs = 0;
|
||||
long totalCameraTimeMs = 0;
|
||||
long totalFlashlightTimeMs = 0;
|
||||
long totalAudioTimeMs = 0;
|
||||
long totalVideoTimeMs = 0;
|
||||
|
||||
BatterySipper uidSipper = null;
|
||||
for (BatterySipper sipper : usageList) {
|
||||
if (sipper.drainType == BatterySipper.DrainType.SCREEN) {
|
||||
totalScreenPower = sipper.sumPower();
|
||||
}
|
||||
|
||||
if (isHiddenDrainType(sipper.drainType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sipper.drainType == BatterySipper.DrainType.APP && sipper.getUid() == uid) {
|
||||
uidSipper = sipper;
|
||||
}
|
||||
|
||||
totalPowerMah += sipper.sumPower();
|
||||
totalSmearedPowerMah += sipper.totalSmearedPowerMah;
|
||||
totalProportionalSmearMah += sipper.proportionalSmearMah;
|
||||
|
||||
if (!isSystemSipper(sipper)) {
|
||||
totalPowerExcludeSystemMah += sipper.totalSmearedPowerMah;
|
||||
}
|
||||
|
||||
totalCpuPowerMah += sipper.cpuPowerMah;
|
||||
totalSystemServiceCpuPowerMah += sipper.systemServiceCpuPowerMah;
|
||||
totalUsagePowerMah += sipper.usagePowerMah;
|
||||
totalWakeLockPowerMah += sipper.wakeLockPowerMah;
|
||||
totalMobileRadioPowerMah += sipper.mobileRadioPowerMah;
|
||||
totalWifiPowerMah += sipper.wifiPowerMah;
|
||||
totalBluetoothPowerMah += sipper.bluetoothPowerMah;
|
||||
totalGpsPowerMah += sipper.gpsPowerMah;
|
||||
totalCameraPowerMah += sipper.cameraPowerMah;
|
||||
totalFlashlightPowerMah += sipper.flashlightPowerMah;
|
||||
totalSensorPowerMah += sipper.sensorPowerMah;
|
||||
totalAudioPowerMah += sipper.audioPowerMah;
|
||||
totalVideoPowerMah += sipper.videoPowerMah;
|
||||
|
||||
totalCpuTimeMs += sipper.cpuTimeMs;
|
||||
totalCpuFgTimeMs += sipper.cpuFgTimeMs;
|
||||
totalWakeLockTimeMs += sipper.wakeLockTimeMs;
|
||||
totalWifiRunningTimeMs += sipper.wifiRunningTimeMs;
|
||||
totalBluetoothRunningTimeMs += sipper.bluetoothRunningTimeMs;
|
||||
totalGpsTimeMs += sipper.gpsTimeMs;
|
||||
totalCameraTimeMs += sipper.cameraTimeMs;
|
||||
totalFlashlightTimeMs += sipper.flashlightTimeMs;
|
||||
totalAudioTimeMs += sipper.audioTimeMs;
|
||||
totalVideoTimeMs += sipper.videoTimeMs;
|
||||
}
|
||||
|
||||
mAppInfo = AppInfoHelper.makeApplicationInfo(context.getPackageManager(), uid, uidSipper);
|
||||
|
||||
if (uidSipper == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
addEntry("Total power", EntryType.POWER,
|
||||
uidSipper.totalSmearedPowerMah, totalSmearedPowerMah);
|
||||
addEntry("... excluding system", EntryType.POWER,
|
||||
uidSipper.totalSmearedPowerMah, totalPowerExcludeSystemMah);
|
||||
addEntry("Screen, smeared", EntryType.POWER,
|
||||
uidSipper.screenPowerMah, totalScreenPower);
|
||||
addEntry("Other, smeared", EntryType.POWER,
|
||||
uidSipper.proportionalSmearMah, totalProportionalSmearMah);
|
||||
addEntry("Excluding smeared", EntryType.POWER,
|
||||
uidSipper.totalPowerMah, totalPowerMah);
|
||||
addEntry("CPU", EntryType.POWER,
|
||||
uidSipper.cpuPowerMah, totalCpuPowerMah);
|
||||
addEntry("System services", EntryType.POWER,
|
||||
uidSipper.systemServiceCpuPowerMah, totalSystemServiceCpuPowerMah);
|
||||
addEntry("RAM", EntryType.POWER,
|
||||
uidSipper.usagePowerMah, totalUsagePowerMah);
|
||||
addEntry("Wake lock", EntryType.POWER,
|
||||
uidSipper.wakeLockPowerMah, totalWakeLockPowerMah);
|
||||
addEntry("Mobile radio", EntryType.POWER,
|
||||
uidSipper.mobileRadioPowerMah, totalMobileRadioPowerMah);
|
||||
addEntry("WiFi", EntryType.POWER,
|
||||
uidSipper.wifiPowerMah, totalWifiPowerMah);
|
||||
addEntry("Bluetooth", EntryType.POWER,
|
||||
uidSipper.bluetoothPowerMah, totalBluetoothPowerMah);
|
||||
addEntry("GPS", EntryType.POWER,
|
||||
uidSipper.gpsPowerMah, totalGpsPowerMah);
|
||||
addEntry("Camera", EntryType.POWER,
|
||||
uidSipper.cameraPowerMah, totalCameraPowerMah);
|
||||
addEntry("Flashlight", EntryType.POWER,
|
||||
uidSipper.flashlightPowerMah, totalFlashlightPowerMah);
|
||||
addEntry("Sensors", EntryType.POWER,
|
||||
uidSipper.sensorPowerMah, totalSensorPowerMah);
|
||||
addEntry("Audio", EntryType.POWER,
|
||||
uidSipper.audioPowerMah, totalAudioPowerMah);
|
||||
addEntry("Video", EntryType.POWER,
|
||||
uidSipper.videoPowerMah, totalVideoPowerMah);
|
||||
|
||||
addEntry("CPU time", EntryType.DURATION,
|
||||
uidSipper.cpuTimeMs, totalCpuTimeMs);
|
||||
addEntry("CPU foreground time", EntryType.DURATION,
|
||||
uidSipper.cpuFgTimeMs, totalCpuFgTimeMs);
|
||||
addEntry("Wake lock time", EntryType.DURATION,
|
||||
uidSipper.wakeLockTimeMs, totalWakeLockTimeMs);
|
||||
addEntry("WiFi running time", EntryType.DURATION,
|
||||
uidSipper.wifiRunningTimeMs, totalWifiRunningTimeMs);
|
||||
addEntry("Bluetooth time", EntryType.DURATION,
|
||||
uidSipper.bluetoothRunningTimeMs, totalBluetoothRunningTimeMs);
|
||||
addEntry("GPS time", EntryType.DURATION,
|
||||
uidSipper.gpsTimeMs, totalGpsTimeMs);
|
||||
addEntry("Camera time", EntryType.DURATION,
|
||||
uidSipper.cameraTimeMs, totalCameraTimeMs);
|
||||
addEntry("Flashlight time", EntryType.DURATION,
|
||||
uidSipper.flashlightTimeMs, totalFlashlightTimeMs);
|
||||
addEntry("Audio time", EntryType.DURATION,
|
||||
uidSipper.audioTimeMs, totalAudioTimeMs);
|
||||
addEntry("Video time", EntryType.DURATION,
|
||||
uidSipper.videoTimeMs, totalVideoTimeMs);
|
||||
}
|
||||
|
||||
protected boolean isHiddenDrainType(BatterySipper.DrainType drainType) {
|
||||
return drainType == BatterySipper.DrainType.IDLE
|
||||
|| drainType == BatterySipper.DrainType.CELL
|
||||
|| drainType == BatterySipper.DrainType.SCREEN
|
||||
|| drainType == BatterySipper.DrainType.UNACCOUNTED
|
||||
|| drainType == BatterySipper.DrainType.OVERCOUNTED
|
||||
|| drainType == BatterySipper.DrainType.BLUETOOTH
|
||||
|| drainType == BatterySipper.DrainType.WIFI;
|
||||
}
|
||||
|
||||
private boolean isSystemSipper(BatterySipper sipper) {
|
||||
final int uid = sipper.uidObj == null ? -1 : sipper.getUid();
|
||||
if (uid >= Process.ROOT_UID && uid < Process.FIRST_APPLICATION_UID) {
|
||||
return true;
|
||||
} else if (sipper.mPackages != null) {
|
||||
for (final String packageName : sipper.mPackages) {
|
||||
for (final String systemPackage : PACKAGES_SYSTEM) {
|
||||
if (systemPackage.equals(packageName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void addEntry(String title, EntryType entryType, double amount, double totalAmount) {
|
||||
Entry entry = new Entry();
|
||||
entry.title = title;
|
||||
entry.entryType = entryType;
|
||||
entry.value = amount;
|
||||
entry.total = totalAmount;
|
||||
mEntries.add(entry);
|
||||
}
|
||||
|
||||
public AppInfoHelper.AppInfo getAppInfo() {
|
||||
return mAppInfo;
|
||||
}
|
||||
|
||||
public List<Entry> getEntries() {
|
||||
return mEntries;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.frameworks.core.powerstatsviewer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.BatteryStats;
|
||||
import android.os.Bundle;
|
||||
import android.os.Process;
|
||||
import android.os.UserManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.ComponentActivity;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.app.LoaderManager.LoaderCallbacks;
|
||||
import androidx.loader.content.Loader;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.internal.os.BatteryStatsHelper;
|
||||
import com.android.settingslib.utils.AsyncLoaderCompat;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class PowerStatsViewerActivity extends ComponentActivity {
|
||||
private static final int POWER_STATS_REFRESH_RATE_MILLIS = 60 * 1000;
|
||||
public static final String PREF_SELECTED_UID = "selectedUid";
|
||||
private static final String LOADER_ARG_UID = "uid";
|
||||
|
||||
private PowerStatsDataAdapter mPowerStatsDataAdapter;
|
||||
private Runnable mPowerStatsRefresh = this::periodicPowerStatsRefresh;
|
||||
private SharedPreferences mSharedPref;
|
||||
private int mUid = Process.INVALID_UID;
|
||||
private TextView mTitleView;
|
||||
private TextView mUidView;
|
||||
private ImageView mIconView;
|
||||
private TextView mPackagesView;
|
||||
private RecyclerView mPowerStatsDataView;
|
||||
private View mLoadingView;
|
||||
private View mEmptyView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mSharedPref = getPreferences(Context.MODE_PRIVATE);
|
||||
|
||||
setContentView(R.layout.power_stats_viewer_layout);
|
||||
|
||||
View appCard = findViewById(R.id.app_card);
|
||||
appCard.setOnClickListener((e) -> startAppPicker());
|
||||
|
||||
mTitleView = findViewById(android.R.id.title);
|
||||
mUidView = findViewById(R.id.uid);
|
||||
mIconView = findViewById(android.R.id.icon);
|
||||
mPackagesView = findViewById(R.id.packages);
|
||||
|
||||
mPowerStatsDataView = findViewById(R.id.power_stats_data_view);
|
||||
mPowerStatsDataView.setLayoutManager(new LinearLayoutManager(this));
|
||||
mPowerStatsDataAdapter = new PowerStatsDataAdapter();
|
||||
mPowerStatsDataView.setAdapter(mPowerStatsDataAdapter);
|
||||
|
||||
mLoadingView = findViewById(R.id.loading_view);
|
||||
mEmptyView = findViewById(R.id.empty_view);
|
||||
|
||||
mUid = mSharedPref.getInt(PREF_SELECTED_UID, Process.INVALID_UID);
|
||||
loadPowerStats();
|
||||
if (mUid == Process.INVALID_UID) {
|
||||
startAppPicker();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
periodicPowerStatsRefresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
getMainThreadHandler().removeCallbacks(mPowerStatsRefresh);
|
||||
}
|
||||
|
||||
private void startAppPicker() {
|
||||
registerForActivityResult(AppPickerActivity.CONTRACT, this::onApplicationSelected)
|
||||
.launch(null);
|
||||
}
|
||||
|
||||
private void onApplicationSelected(int uid) {
|
||||
if (uid == -1) {
|
||||
if (mUid == Process.INVALID_UID) {
|
||||
finish();
|
||||
}
|
||||
} else {
|
||||
mUid = uid;
|
||||
mSharedPref.edit().putInt(PREF_SELECTED_UID, mUid).apply();
|
||||
mLoadingView.setVisibility(View.VISIBLE);
|
||||
loadPowerStats();
|
||||
}
|
||||
}
|
||||
|
||||
private void periodicPowerStatsRefresh() {
|
||||
loadPowerStats();
|
||||
getMainThreadHandler().postDelayed(mPowerStatsRefresh, POWER_STATS_REFRESH_RATE_MILLIS);
|
||||
}
|
||||
|
||||
private void loadPowerStats() {
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(LOADER_ARG_UID, mUid);
|
||||
LoaderManager.getInstance(this).restartLoader(0, args, new PowerStatsDataLoaderCallbacks());
|
||||
}
|
||||
|
||||
private static class PowerStatsDataLoader extends AsyncLoaderCompat<PowerStatsData> {
|
||||
private final int mUid;
|
||||
private final BatteryStatsHelper mBatteryStatsHelper;
|
||||
private final UserManager mUserManager;
|
||||
|
||||
PowerStatsDataLoader(Context context, int uid) {
|
||||
super(context);
|
||||
mUid = uid;
|
||||
mUserManager = context.getSystemService(UserManager.class);
|
||||
mBatteryStatsHelper = new BatteryStatsHelper(context,
|
||||
false /* collectBatteryBroadcast */);
|
||||
mBatteryStatsHelper.create((Bundle) null);
|
||||
mBatteryStatsHelper.clearStats();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PowerStatsData loadInBackground() {
|
||||
mBatteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED,
|
||||
mUserManager.getUserProfiles());
|
||||
return new PowerStatsData(getContext(), mBatteryStatsHelper, mUid);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDiscardResult(PowerStatsData result) {
|
||||
}
|
||||
}
|
||||
|
||||
private class PowerStatsDataLoaderCallbacks implements LoaderCallbacks<PowerStatsData> {
|
||||
@NonNull
|
||||
@Override
|
||||
public Loader<PowerStatsData> onCreateLoader(int id, Bundle args) {
|
||||
return new PowerStatsDataLoader(PowerStatsViewerActivity.this,
|
||||
args.getInt(LOADER_ARG_UID, Process.INVALID_UID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(@NonNull Loader<PowerStatsData> loader,
|
||||
PowerStatsData powerStatsData) {
|
||||
|
||||
AppInfoHelper.AppInfo appInfo = powerStatsData.getAppInfo();
|
||||
mTitleView.setText(appInfo.label);
|
||||
mUidView.setText(String.format(Locale.getDefault(), "UID: %d", appInfo.uid));
|
||||
mIconView.setImageDrawable(appInfo.iconInfo.loadIcon(getPackageManager()));
|
||||
|
||||
if (appInfo.packages != null) {
|
||||
mPackagesView.setText(appInfo.packages);
|
||||
mPackagesView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mPackagesView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
mPowerStatsDataAdapter.setEntries(powerStatsData.getEntries());
|
||||
|
||||
if (powerStatsData.getEntries().isEmpty()) {
|
||||
mEmptyView.setVisibility(View.VISIBLE);
|
||||
mPowerStatsDataView.setVisibility(View.GONE);
|
||||
} else {
|
||||
mEmptyView.setVisibility(View.GONE);
|
||||
mPowerStatsDataView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
mLoadingView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(@NonNull Loader<PowerStatsData> loader) {
|
||||
}
|
||||
}
|
||||
|
||||
private static class PowerStatsDataAdapter extends
|
||||
RecyclerView.Adapter<PowerStatsDataAdapter.ViewHolder> {
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public TextView titleTextView;
|
||||
public TextView amountTextView;
|
||||
public TextView percentTextView;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
titleTextView = itemView.findViewById(R.id.title);
|
||||
amountTextView = itemView.findViewById(R.id.amount);
|
||||
percentTextView = itemView.findViewById(R.id.percent);
|
||||
}
|
||||
}
|
||||
|
||||
private List<PowerStatsData.Entry> mEntries = Collections.emptyList();
|
||||
|
||||
public void setEntries(List<PowerStatsData.Entry> entries) {
|
||||
mEntries = entries;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mEntries.size();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position) {
|
||||
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
|
||||
View itemView = layoutInflater.inflate(R.layout.power_stats_entry_layout, parent,
|
||||
false);
|
||||
return new ViewHolder(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
|
||||
PowerStatsData.Entry entry = mEntries.get(position);
|
||||
switch (entry.entryType) {
|
||||
case POWER:
|
||||
viewHolder.titleTextView.setText(entry.title);
|
||||
viewHolder.amountTextView.setText(
|
||||
String.format(Locale.getDefault(), "%.1f mAh", entry.value));
|
||||
break;
|
||||
case DURATION:
|
||||
viewHolder.titleTextView.setText(entry.title);
|
||||
viewHolder.amountTextView.setText(
|
||||
String.format(Locale.getDefault(), "%,d ms", (long) entry.value));
|
||||
break;
|
||||
}
|
||||
|
||||
double proportion = entry.total != 0 ? entry.value * 100 / entry.total : 0;
|
||||
viewHolder.percentTextView.setText(String.format(Locale.getDefault(), "%.1f%%",
|
||||
proportion));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user