Merge "Add non-app power consumers to the PowerStatsViewer"
This commit is contained in:
committed by
Android (Google) Code Review
commit
92c2f231ed
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
@@ -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>
|
||||
@@ -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"/>
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user