Merge "Add non-app power consumers to the PowerStatsViewer"

This commit is contained in:
TreeHugger Robot
2020-11-17 02:21:48 +00:00
committed by Android (Google) Code Review
12 changed files with 653 additions and 455 deletions

View File

@@ -35,8 +35,7 @@
</intent-filter>
</activity>
<activity android:name=".AppPickerActivity"
android:label="Power Stats - Select an App"/>
<activity android:name=".PowerConsumerPickerActivity"
android:label="Select a power consumer"/>
</application>
</manifest>

View File

@@ -52,7 +52,7 @@
android:textAppearance="?android:attr/textAppearanceListItem"/>
<TextView
android:id="@+id/uid"
android:id="@+id/details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"

View File

@@ -0,0 +1,33 @@
<!--
~ 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="match_parent"
android:orientation="vertical">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<androidx.viewpager.widget.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>

View File

@@ -20,7 +20,7 @@
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/app_list_view"
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"/>

View File

@@ -42,7 +42,7 @@
android:paddingStart="10dp"
android:paddingEnd="10dp">
<include layout="@layout/app_info_layout"/>
<include layout="@layout/power_consumer_info_layout"/>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -1,114 +0,0 @@
/*
* 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;
}
}

View File

@@ -1,251 +0,0 @@
/*
* 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);
}
}
}

View File

@@ -0,0 +1,156 @@
/*
* 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.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Process;
import androidx.annotation.NonNull;
import com.android.internal.os.BatterySipper;
import java.util.Locale;
class PowerConsumerInfoHelper {
private static final String SYSTEM_SERVER_PACKAGE_NAME = "android";
public static class PowerConsumerInfo {
public String id;
public CharSequence label;
public double powerMah;
public ApplicationInfo iconInfo;
public CharSequence packages;
public CharSequence details;
}
@NonNull
public static PowerConsumerInfo makePowerConsumerInfo(PackageManager packageManager,
@NonNull BatterySipper sipper) {
PowerConsumerInfo info = new PowerConsumerInfo();
info.id = PowerStatsData.powerConsumerId(sipper);
sipper.sumPower();
info.powerMah = sipper.totalSmearedPowerMah;
switch (sipper.drainType) {
case APP: {
int uid = sipper.getUid();
info.details = String.format("UID: %d", uid);
String packageWithHighestDrain = sipper.packageWithHighestDrain;
if (uid == Process.ROOT_UID) {
info.label = "<root>";
} else {
String[] packages = packageManager.getPackagesForUid(uid);
String primaryPackageName = null;
if (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;
}
}
break;
}
case USER:
info.label = "User";
info.details = String.format(Locale.getDefault(), "User ID: %d", sipper.userId);
break;
case AMBIENT_DISPLAY:
info.label = "Ambient display";
break;
case BLUETOOTH:
info.label = "Bluetooth";
break;
case CAMERA:
info.label = "Camera";
break;
case CELL:
info.label = "Cell";
break;
case FLASHLIGHT:
info.label = "Flashlight";
break;
case IDLE:
info.label = "Idle";
break;
case MEMORY:
info.label = "Memory";
break;
case OVERCOUNTED:
info.label = "Overcounted";
break;
case PHONE:
info.label = "Phone";
break;
case SCREEN:
info.label = "Screen";
break;
case UNACCOUNTED:
info.label = "Unaccounted";
break;
case WIFI:
info.label = "WiFi";
break;
}
// 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;
}
}

View File

@@ -0,0 +1,116 @@
/*
* 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.Intent;
import android.os.Bundle;
import androidx.activity.result.contract.ActivityResultContract;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.tabs.TabLayout;
/**
* Picker, showing a sorted lists of applications and other types of entities consuming power.
* Returns the selected entity ID or null.
*/
public class PowerConsumerPickerActivity extends FragmentActivity {
public static final ActivityResultContract<Void, String> CONTRACT =
new ActivityResultContract<Void, String>() {
@NonNull
@Override
public Intent createIntent(@NonNull Context context, Void aVoid) {
return new Intent(context, PowerConsumerPickerActivity.class);
}
@Override
public String parseResult(int resultCode, @Nullable Intent intent) {
if (resultCode != RESULT_OK || intent == null) {
return null;
}
return intent.getStringExtra(Intent.EXTRA_RETURN_RESULT);
}
};
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
getActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.power_consumer_picker_activity_layout);
ViewPager viewPager = findViewById(R.id.pager);
FragmentStatePagerAdapter adapter = new FragmentStatePagerAdapter(
getSupportFragmentManager()) {
@Override
public int getCount() {
return 2;
}
@NonNull
@Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new PowerConsumerPickerFragment(
PowerConsumerPickerFragment.PICKER_TYPE_APP);
case 1:
default:
return new PowerConsumerPickerFragment(
PowerConsumerPickerFragment.PICKER_TYPE_DRAIN);
}
}
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return "Apps";
case 1:
return "Drains";
}
return null;
}
};
viewPager.setAdapter(adapter);
TabLayout tabLayout = findViewById(R.id.tab_layout);
tabLayout.setupWithViewPager(viewPager);
}
public void setSelectedPowerConsumer(String id) {
Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_RETURN_RESULT, id);
setResult(RESULT_OK, intent);
finish();
}
@Override
public boolean onNavigateUp() {
onBackPressed();
return true;
}
}

View File

@@ -0,0 +1,256 @@
/*
* 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.pm.PackageManager;
import android.os.BatteryStats;
import android.os.Bundle;
import android.os.UserHandle;
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.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
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.PowerConsumerInfoHelper.PowerConsumerInfo;
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 lists of applications or other types of entities consuming power.
* Returns the selected entity ID or null.
*/
public class PowerConsumerPickerFragment extends Fragment {
private static final String TAG = "AppPicker";
public static final String PICKER_TYPE = "pickertype";
public static final int PICKER_TYPE_APP = 0;
public static final int PICKER_TYPE_DRAIN = 1;
private PowerConsumerListAdapter mPowerConsumerListAdapter;
private RecyclerView mAppList;
private View mLoadingView;
private interface OnPowerConsumerSelectedListener {
void onPowerConsumerSelected(String uid);
}
public PowerConsumerPickerFragment(int pickerType) {
Bundle args = new Bundle();
args.putInt(PICKER_TYPE, pickerType);
setArguments(args);
}
public PowerConsumerPickerFragment() {
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.power_consumer_picker_layout, container, false);
mLoadingView = view.findViewById(R.id.loading_view);
mAppList = view.findViewById(R.id.list_view);
mAppList.setLayoutManager(new LinearLayoutManager(getContext()));
mPowerConsumerListAdapter = new PowerConsumerListAdapter(
PowerConsumerPickerFragment.this::setSelectedPowerConsumer);
mAppList.setAdapter(mPowerConsumerListAdapter);
LoaderManager.getInstance(this).initLoader(0, getArguments(),
new PowerConsumerListLoaderCallbacks());
return view;
}
public void setSelectedPowerConsumer(String id) {
((PowerConsumerPickerActivity) getActivity()).setSelectedPowerConsumer(id);
}
private static class PowerConsumerListLoader extends
AsyncLoaderCompat<List<PowerConsumerInfo>> {
private final BatteryStatsHelper mStatsHelper;
private final int mPickerType;
private final UserManager mUserManager;
private final PackageManager mPackageManager;
PowerConsumerListLoader(Context context, int pickerType) {
super(context);
mUserManager = context.getSystemService(UserManager.class);
mStatsHelper = new BatteryStatsHelper(context, false /* collectBatteryBroadcast */);
mPickerType = pickerType;
mStatsHelper.create((Bundle) null);
mStatsHelper.clearStats();
mPackageManager = context.getPackageManager();
}
@Override
public List<PowerConsumerInfo> loadInBackground() {
List<PowerConsumerInfoHelper.PowerConsumerInfo> powerConsumerList = new ArrayList<>();
mStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId());
final List<BatterySipper> usageList = mStatsHelper.getUsageList();
for (BatterySipper sipper : usageList) {
switch (mPickerType) {
case PICKER_TYPE_APP:
if (sipper.drainType != BatterySipper.DrainType.APP) {
continue;
}
break;
case PICKER_TYPE_DRAIN:
default:
if (sipper.drainType == BatterySipper.DrainType.APP) {
continue;
}
}
powerConsumerList.add(
PowerConsumerInfoHelper.makePowerConsumerInfo(mPackageManager, sipper));
}
powerConsumerList.sort(
Comparator.comparing((PowerConsumerInfo a) -> a.powerMah).reversed());
return powerConsumerList;
}
@Override
protected void onDiscardResult(List<PowerConsumerInfo> result) {
}
}
private class PowerConsumerListLoaderCallbacks implements
LoaderManager.LoaderCallbacks<List<PowerConsumerInfo>> {
@NonNull
@Override
public Loader<List<PowerConsumerInfo>> onCreateLoader(int id, Bundle args) {
return new PowerConsumerListLoader(getContext(), args.getInt(PICKER_TYPE));
}
@Override
public void onLoadFinished(@NonNull Loader<List<PowerConsumerInfo>> loader,
List<PowerConsumerInfoHelper.PowerConsumerInfo> powerConsumerList) {
mPowerConsumerListAdapter.setPowerConsumerList(powerConsumerList);
mAppList.setVisibility(View.VISIBLE);
mLoadingView.setVisibility(View.GONE);
}
@Override
public void onLoaderReset(
@NonNull Loader<List<PowerConsumerInfoHelper.PowerConsumerInfo>> loader) {
}
}
public class PowerConsumerListAdapter extends RecyclerView.Adapter<PowerConsumerViewHolder> {
private final OnPowerConsumerSelectedListener mListener;
private List<PowerConsumerInfo> mPowerConsumerList;
public PowerConsumerListAdapter(OnPowerConsumerSelectedListener listener) {
mListener = listener;
}
void setPowerConsumerList(List<PowerConsumerInfo> powerConsumerList) {
mPowerConsumerList = powerConsumerList;
notifyDataSetChanged();
}
@Override
public int getItemCount() {
return mPowerConsumerList.size();
}
@NonNull
@Override
public PowerConsumerViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
int position) {
LayoutInflater layoutInflater = LayoutInflater.from(viewGroup.getContext());
View view = layoutInflater.inflate(R.layout.power_consumer_info_layout, viewGroup,
false);
return new PowerConsumerViewHolder(view, mListener);
}
@Override
public void onBindViewHolder(@NonNull PowerConsumerViewHolder viewHolder, int position) {
PowerConsumerInfoHelper.PowerConsumerInfo item = mPowerConsumerList.get(position);
viewHolder.id = item.id;
viewHolder.titleView.setText(item.label);
if (item.details != null) {
viewHolder.detailsView.setText(item.details);
viewHolder.detailsView.setVisibility(View.VISIBLE);
} else {
viewHolder.detailsView.setVisibility(View.GONE);
}
viewHolder.powerView.setText(
String.format(Locale.getDefault(), "%.1f mAh", item.powerMah));
viewHolder.iconView.setImageDrawable(
item.iconInfo.loadIcon(getContext().getPackageManager()));
if (item.packages != null) {
viewHolder.packagesView.setText(item.packages);
viewHolder.packagesView.setVisibility(View.VISIBLE);
} else {
viewHolder.packagesView.setVisibility(View.GONE);
}
}
}
// View Holder used when displaying apps
public static class PowerConsumerViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener {
private final OnPowerConsumerSelectedListener mListener;
public String id;
public TextView titleView;
public TextView detailsView;
public ImageView iconView;
public TextView packagesView;
public TextView powerView;
PowerConsumerViewHolder(View view, OnPowerConsumerSelectedListener listener) {
super(view);
mListener = listener;
view.setOnClickListener(this);
titleView = view.findViewById(android.R.id.title);
detailsView = view.findViewById(R.id.details);
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.onPowerConsumerSelected(id);
}
}
}

View File

@@ -44,11 +44,11 @@ public class PowerStatsData {
public double total;
}
private final AppInfoHelper.AppInfo mAppInfo;
private final PowerConsumerInfoHelper.PowerConsumerInfo mPowerConsumerInfo;
private final List<Entry> mEntries = new ArrayList<>();
public PowerStatsData(Context context, BatteryStatsHelper batteryStatsHelper,
int uid) {
String powerConsumerId) {
List<BatterySipper> usageList = batteryStatsHelper.getUsageList();
double totalPowerMah = 0;
@@ -81,18 +81,14 @@ public class PowerStatsData {
long totalAudioTimeMs = 0;
long totalVideoTimeMs = 0;
BatterySipper uidSipper = null;
BatterySipper requestedPowerConsumer = 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;
if (powerConsumerId(sipper).equals(powerConsumerId)) {
requestedPowerConsumer = sipper;
}
totalPowerMah += sipper.sumPower();
@@ -129,79 +125,71 @@ public class PowerStatsData {
totalVideoTimeMs += sipper.videoTimeMs;
}
mAppInfo = AppInfoHelper.makeApplicationInfo(context.getPackageManager(), uid, uidSipper);
if (uidSipper == null) {
if (requestedPowerConsumer == null) {
mPowerConsumerInfo = null;
return;
}
mPowerConsumerInfo = PowerConsumerInfoHelper.makePowerConsumerInfo(
context.getPackageManager(), requestedPowerConsumer);
addEntry("Total power", EntryType.POWER,
uidSipper.totalSmearedPowerMah, totalSmearedPowerMah);
requestedPowerConsumer.totalSmearedPowerMah, totalSmearedPowerMah);
addEntry("... excluding system", EntryType.POWER,
uidSipper.totalSmearedPowerMah, totalPowerExcludeSystemMah);
requestedPowerConsumer.totalSmearedPowerMah, totalPowerExcludeSystemMah);
addEntry("Screen, smeared", EntryType.POWER,
uidSipper.screenPowerMah, totalScreenPower);
requestedPowerConsumer.screenPowerMah, totalScreenPower);
addEntry("Other, smeared", EntryType.POWER,
uidSipper.proportionalSmearMah, totalProportionalSmearMah);
requestedPowerConsumer.proportionalSmearMah, totalProportionalSmearMah);
addEntry("Excluding smeared", EntryType.POWER,
uidSipper.totalPowerMah, totalPowerMah);
requestedPowerConsumer.totalPowerMah, totalPowerMah);
addEntry("CPU", EntryType.POWER,
uidSipper.cpuPowerMah, totalCpuPowerMah);
requestedPowerConsumer.cpuPowerMah, totalCpuPowerMah);
addEntry("System services", EntryType.POWER,
uidSipper.systemServiceCpuPowerMah, totalSystemServiceCpuPowerMah);
addEntry("RAM", EntryType.POWER,
uidSipper.usagePowerMah, totalUsagePowerMah);
requestedPowerConsumer.systemServiceCpuPowerMah, totalSystemServiceCpuPowerMah);
addEntry("Usage", EntryType.POWER,
requestedPowerConsumer.usagePowerMah, totalUsagePowerMah);
addEntry("Wake lock", EntryType.POWER,
uidSipper.wakeLockPowerMah, totalWakeLockPowerMah);
requestedPowerConsumer.wakeLockPowerMah, totalWakeLockPowerMah);
addEntry("Mobile radio", EntryType.POWER,
uidSipper.mobileRadioPowerMah, totalMobileRadioPowerMah);
requestedPowerConsumer.mobileRadioPowerMah, totalMobileRadioPowerMah);
addEntry("WiFi", EntryType.POWER,
uidSipper.wifiPowerMah, totalWifiPowerMah);
requestedPowerConsumer.wifiPowerMah, totalWifiPowerMah);
addEntry("Bluetooth", EntryType.POWER,
uidSipper.bluetoothPowerMah, totalBluetoothPowerMah);
requestedPowerConsumer.bluetoothPowerMah, totalBluetoothPowerMah);
addEntry("GPS", EntryType.POWER,
uidSipper.gpsPowerMah, totalGpsPowerMah);
requestedPowerConsumer.gpsPowerMah, totalGpsPowerMah);
addEntry("Camera", EntryType.POWER,
uidSipper.cameraPowerMah, totalCameraPowerMah);
requestedPowerConsumer.cameraPowerMah, totalCameraPowerMah);
addEntry("Flashlight", EntryType.POWER,
uidSipper.flashlightPowerMah, totalFlashlightPowerMah);
requestedPowerConsumer.flashlightPowerMah, totalFlashlightPowerMah);
addEntry("Sensors", EntryType.POWER,
uidSipper.sensorPowerMah, totalSensorPowerMah);
requestedPowerConsumer.sensorPowerMah, totalSensorPowerMah);
addEntry("Audio", EntryType.POWER,
uidSipper.audioPowerMah, totalAudioPowerMah);
requestedPowerConsumer.audioPowerMah, totalAudioPowerMah);
addEntry("Video", EntryType.POWER,
uidSipper.videoPowerMah, totalVideoPowerMah);
requestedPowerConsumer.videoPowerMah, totalVideoPowerMah);
addEntry("CPU time", EntryType.DURATION,
uidSipper.cpuTimeMs, totalCpuTimeMs);
requestedPowerConsumer.cpuTimeMs, totalCpuTimeMs);
addEntry("CPU foreground time", EntryType.DURATION,
uidSipper.cpuFgTimeMs, totalCpuFgTimeMs);
requestedPowerConsumer.cpuFgTimeMs, totalCpuFgTimeMs);
addEntry("Wake lock time", EntryType.DURATION,
uidSipper.wakeLockTimeMs, totalWakeLockTimeMs);
requestedPowerConsumer.wakeLockTimeMs, totalWakeLockTimeMs);
addEntry("WiFi running time", EntryType.DURATION,
uidSipper.wifiRunningTimeMs, totalWifiRunningTimeMs);
requestedPowerConsumer.wifiRunningTimeMs, totalWifiRunningTimeMs);
addEntry("Bluetooth time", EntryType.DURATION,
uidSipper.bluetoothRunningTimeMs, totalBluetoothRunningTimeMs);
requestedPowerConsumer.bluetoothRunningTimeMs, totalBluetoothRunningTimeMs);
addEntry("GPS time", EntryType.DURATION,
uidSipper.gpsTimeMs, totalGpsTimeMs);
requestedPowerConsumer.gpsTimeMs, totalGpsTimeMs);
addEntry("Camera time", EntryType.DURATION,
uidSipper.cameraTimeMs, totalCameraTimeMs);
requestedPowerConsumer.cameraTimeMs, totalCameraTimeMs);
addEntry("Flashlight time", EntryType.DURATION,
uidSipper.flashlightTimeMs, totalFlashlightTimeMs);
requestedPowerConsumer.flashlightTimeMs, totalFlashlightTimeMs);
addEntry("Audio time", EntryType.DURATION,
uidSipper.audioTimeMs, totalAudioTimeMs);
requestedPowerConsumer.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;
requestedPowerConsumer.videoTimeMs, totalVideoTimeMs);
}
private boolean isSystemSipper(BatterySipper sipper) {
@@ -230,11 +218,15 @@ public class PowerStatsData {
mEntries.add(entry);
}
public AppInfoHelper.AppInfo getAppInfo() {
return mAppInfo;
public PowerConsumerInfoHelper.PowerConsumerInfo getPowerConsumerInfo() {
return mPowerConsumerInfo;
}
public List<Entry> getEntries() {
return mEntries;
}
public static String powerConsumerId(BatterySipper sipper) {
return sipper.drainType + "|" + sipper.userId + "|" + sipper.getUid();
}
}

View File

@@ -20,7 +20,7 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.os.BatteryStats;
import android.os.Bundle;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.view.LayoutInflater;
import android.view.View;
@@ -46,15 +46,15 @@ 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";
public static final String PREF_SELECTED_POWER_CONSUMER = "powerConsumerId";
private static final String LOADER_ARG_POWER_CONSUMER_ID = "powerConsumerId";
private PowerStatsDataAdapter mPowerStatsDataAdapter;
private Runnable mPowerStatsRefresh = this::periodicPowerStatsRefresh;
private SharedPreferences mSharedPref;
private int mUid = Process.INVALID_UID;
private String mPowerConsumerId;
private TextView mTitleView;
private TextView mUidView;
private TextView mDetailsView;
private ImageView mIconView;
private TextView mPackagesView;
private RecyclerView mPowerStatsDataView;
@@ -73,7 +73,7 @@ public class PowerStatsViewerActivity extends ComponentActivity {
appCard.setOnClickListener((e) -> startAppPicker());
mTitleView = findViewById(android.R.id.title);
mUidView = findViewById(R.id.uid);
mDetailsView = findViewById(R.id.details);
mIconView = findViewById(android.R.id.icon);
mPackagesView = findViewById(R.id.packages);
@@ -85,9 +85,9 @@ public class PowerStatsViewerActivity extends ComponentActivity {
mLoadingView = findViewById(R.id.loading_view);
mEmptyView = findViewById(R.id.empty_view);
mUid = mSharedPref.getInt(PREF_SELECTED_UID, Process.INVALID_UID);
mPowerConsumerId = mSharedPref.getString(PREF_SELECTED_POWER_CONSUMER, null);
loadPowerStats();
if (mUid == Process.INVALID_UID) {
if (mPowerConsumerId == null) {
startAppPicker();
}
}
@@ -105,18 +105,18 @@ public class PowerStatsViewerActivity extends ComponentActivity {
}
private void startAppPicker() {
registerForActivityResult(AppPickerActivity.CONTRACT, this::onApplicationSelected)
registerForActivityResult(PowerConsumerPickerActivity.CONTRACT, this::onApplicationSelected)
.launch(null);
}
private void onApplicationSelected(int uid) {
if (uid == -1) {
if (mUid == Process.INVALID_UID) {
private void onApplicationSelected(String powerConsumerId) {
if (powerConsumerId == null) {
if (mPowerConsumerId == null) {
finish();
}
} else {
mUid = uid;
mSharedPref.edit().putInt(PREF_SELECTED_UID, mUid).apply();
mPowerConsumerId = powerConsumerId;
mSharedPref.edit().putString(PREF_SELECTED_POWER_CONSUMER, mPowerConsumerId).apply();
mLoadingView.setVisibility(View.VISIBLE);
loadPowerStats();
}
@@ -129,18 +129,18 @@ public class PowerStatsViewerActivity extends ComponentActivity {
private void loadPowerStats() {
Bundle args = new Bundle();
args.putInt(LOADER_ARG_UID, mUid);
args.putString(LOADER_ARG_POWER_CONSUMER_ID, mPowerConsumerId);
LoaderManager.getInstance(this).restartLoader(0, args, new PowerStatsDataLoaderCallbacks());
}
private static class PowerStatsDataLoader extends AsyncLoaderCompat<PowerStatsData> {
private final int mUid;
private final String mPowerConsumerId;
private final BatteryStatsHelper mBatteryStatsHelper;
private final UserManager mUserManager;
PowerStatsDataLoader(Context context, int uid) {
PowerStatsDataLoader(Context context, String powerConsumerId) {
super(context);
mUid = uid;
mPowerConsumerId = powerConsumerId;
mUserManager = context.getSystemService(UserManager.class);
mBatteryStatsHelper = new BatteryStatsHelper(context,
false /* collectBatteryBroadcast */);
@@ -151,8 +151,8 @@ public class PowerStatsViewerActivity extends ComponentActivity {
@Override
public PowerStatsData loadInBackground() {
mBatteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED,
mUserManager.getUserProfiles());
return new PowerStatsData(getContext(), mBatteryStatsHelper, mUid);
UserHandle.myUserId());
return new PowerStatsData(getContext(), mBatteryStatsHelper, mPowerConsumerId);
}
@Override
@@ -165,27 +165,38 @@ public class PowerStatsViewerActivity extends ComponentActivity {
@Override
public Loader<PowerStatsData> onCreateLoader(int id, Bundle args) {
return new PowerStatsDataLoader(PowerStatsViewerActivity.this,
args.getInt(LOADER_ARG_UID, Process.INVALID_UID));
args.getString(LOADER_ARG_POWER_CONSUMER_ID));
}
@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 {
PowerConsumerInfoHelper.PowerConsumerInfo
powerConsumerInfo = powerStatsData.getPowerConsumerInfo();
if (powerConsumerInfo == null) {
mTitleView.setText("Power consumer not found");
mPackagesView.setVisibility(View.GONE);
} else {
mTitleView.setText(powerConsumerInfo.label);
if (powerConsumerInfo.details != null) {
mDetailsView.setText(powerConsumerInfo.details);
mDetailsView.setVisibility(View.VISIBLE);
} else {
mDetailsView.setVisibility(View.GONE);
}
mIconView.setImageDrawable(
powerConsumerInfo.iconInfo.loadIcon(getPackageManager()));
if (powerConsumerInfo.packages != null) {
mPackagesView.setText(powerConsumerInfo.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);