/* * Copyright (C) 2017 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.settings.applications.appinfo; import android.content.Context; import android.content.pm.PackageInfo; import android.os.AsyncTask; import android.os.BatteryUsageStats; import android.os.Bundle; import android.os.UidBatteryConsumer; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.loader.app.LoaderManager; import androidx.loader.content.Loader; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.core.BasePreferenceController; import com.android.settings.fuelgauge.AdvancedPowerUsageDetail; import com.android.settings.fuelgauge.BatteryUtils; import com.android.settings.fuelgauge.batteryusage.BatteryChartPreferenceController; import com.android.settings.fuelgauge.batteryusage.BatteryDiffEntry; import com.android.settings.fuelgauge.batteryusage.BatteryEntry; import com.android.settings.fuelgauge.batteryusage.BatteryUsageStatsLoader; import com.android.settingslib.applications.AppUtils; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnPause; import com.android.settingslib.core.lifecycle.events.OnResume; import java.util.List; public class AppBatteryPreferenceController extends BasePreferenceController implements LifecycleObserver, OnResume, OnPause { private static final String TAG = "AppBatteryPreferenceController"; private static final String KEY_BATTERY = "battery"; @VisibleForTesting final BatteryUsageStatsLoaderCallbacks mBatteryUsageStatsLoaderCallbacks = new BatteryUsageStatsLoaderCallbacks(); @VisibleForTesting BatteryUtils mBatteryUtils; @VisibleForTesting BatteryUsageStats mBatteryUsageStats; @VisibleForTesting UidBatteryConsumer mUidBatteryConsumer; @VisibleForTesting BatteryDiffEntry mBatteryDiffEntry; @VisibleForTesting final AppInfoDashboardFragment mParent; private Preference mPreference; private String mBatteryPercent; private final String mPackageName; private final int mUid; private final int mUserId; private boolean mBatteryUsageStatsLoaded = false; private boolean mBatteryDiffEntriesLoaded = false; public AppBatteryPreferenceController(Context context, AppInfoDashboardFragment parent, String packageName, int uid, Lifecycle lifecycle) { super(context, KEY_BATTERY); mParent = parent; mBatteryUtils = BatteryUtils.getInstance(mContext); mPackageName = packageName; mUid = uid; mUserId = mContext.getUserId(); if (lifecycle != null) { lifecycle.addObserver(this); } } @Override public int getAvailabilityStatus() { return mContext.getResources().getBoolean(R.bool.config_show_app_info_settings_battery) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mPreference = screen.findPreference(getPreferenceKey()); mPreference.setEnabled(false); if (!AppUtils.isAppInstalled(mParent.getAppEntry())) { mPreference.setSummary(""); return; } loadBatteryDiffEntries(); } @Override public boolean handlePreferenceTreeClick(Preference preference) { if (!KEY_BATTERY.equals(preference.getKey())) { return false; } if (mBatteryDiffEntry != null) { Log.i(TAG, "handlePreferenceTreeClick():\n" + mBatteryDiffEntry); AdvancedPowerUsageDetail.startBatteryDetailPage( mParent.getActivity(), mParent.getMetricsCategory(), mBatteryDiffEntry, Utils.formatPercentage( mBatteryDiffEntry.getPercentage(), /*round=*/ true), /*slotInformation=*/ null, /*showTimeInformation=*/ false, /*anomalyHintPrefKey=*/ null, /*anomalyHintText=*/ null); return true; } if (isBatteryStatsAvailable()) { final UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); final BatteryEntry entry = new BatteryEntry(mContext, userManager, mUidBatteryConsumer, /* isHidden */ false, mUidBatteryConsumer.getUid(), /* packages */ null, mPackageName); Log.i(TAG, "Battery consumer available, launch : " + entry.getDefaultPackageName() + " | uid : " + entry.getUid() + " with BatteryEntry data"); AdvancedPowerUsageDetail.startBatteryDetailPage( mParent.getActivity(), mParent, entry, Utils.formatPercentage(0)); } else { Log.i(TAG, "Launch : " + mPackageName + " with package name"); AdvancedPowerUsageDetail.startBatteryDetailPage(mParent.getActivity(), mParent, mPackageName, UserHandle.CURRENT); } return true; } @Override public void onResume() { mParent.getLoaderManager().restartLoader( AppInfoDashboardFragment.LOADER_BATTERY_USAGE_STATS, Bundle.EMPTY, mBatteryUsageStatsLoaderCallbacks); } @Override public void onPause() { mParent.getLoaderManager().destroyLoader( AppInfoDashboardFragment.LOADER_BATTERY_USAGE_STATS); closeBatteryUsageStats(); } private void loadBatteryDiffEntries() { new AsyncTask() { @Override protected BatteryDiffEntry doInBackground(Void... unused) { if (mPackageName == null) { return null; } final BatteryDiffEntry entry = BatteryChartPreferenceController.getAppBatteryUsageData( mContext, mPackageName, mUserId); Log.d(TAG, "loadBatteryDiffEntries():\n" + entry); return entry; } @Override protected void onPostExecute(BatteryDiffEntry batteryDiffEntry) { mBatteryDiffEntry = batteryDiffEntry; updateBatteryWithDiffEntry(); } }.execute(); } @VisibleForTesting void updateBatteryWithDiffEntry() { if (mBatteryDiffEntry != null && mBatteryDiffEntry.mConsumePower > 0) { mBatteryPercent = Utils.formatPercentage( mBatteryDiffEntry.getPercentage(), /* round */ true); mPreference.setSummary(mContext.getString( R.string.battery_summary, mBatteryPercent)); } else { mPreference.setSummary( mContext.getString(R.string.no_battery_summary)); } mBatteryDiffEntriesLoaded = true; mPreference.setEnabled(mBatteryUsageStatsLoaded); } private void onLoadFinished() { if (mBatteryUsageStats == null) { return; } final PackageInfo packageInfo = mParent.getPackageInfo(); if (packageInfo != null) { mUidBatteryConsumer = findTargetUidBatteryConsumer(mBatteryUsageStats, packageInfo.applicationInfo.uid); if (mParent.getActivity() != null) { updateBattery(); } } } private void updateBattery() { mBatteryUsageStatsLoaded = true; mPreference.setEnabled(mBatteryDiffEntriesLoaded); } @VisibleForTesting boolean isBatteryStatsAvailable() { return mUidBatteryConsumer != null; } @VisibleForTesting UidBatteryConsumer findTargetUidBatteryConsumer(BatteryUsageStats batteryUsageStats, int uid) { final List usageList = batteryUsageStats.getUidBatteryConsumers(); for (int i = 0, size = usageList.size(); i < size; i++) { final UidBatteryConsumer consumer = usageList.get(i); if (consumer.getUid() == uid) { return consumer; } } return null; } private class BatteryUsageStatsLoaderCallbacks implements LoaderManager.LoaderCallbacks { @Override @NonNull public Loader onCreateLoader(int id, Bundle args) { return new BatteryUsageStatsLoader(mContext, /* includeBatteryHistory */ false); } @Override public void onLoadFinished(Loader loader, BatteryUsageStats batteryUsageStats) { closeBatteryUsageStats(); mBatteryUsageStats = batteryUsageStats; AppBatteryPreferenceController.this.onLoadFinished(); } @Override public void onLoaderReset(Loader loader) { } } private void closeBatteryUsageStats() { if (mBatteryUsageStats != null) { try { mBatteryUsageStats.close(); } catch (Exception e) { Log.e(TAG, "BatteryUsageStats.close() failed", e); } finally { mBatteryUsageStats = null; } } } }