1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.applications.appinfo; 18 19 import android.content.Context; 20 import android.content.pm.PackageInfo; 21 import android.content.pm.PackageManager; 22 import android.os.AsyncTask; 23 import android.os.BatteryUsageStats; 24 import android.os.Bundle; 25 import android.os.UidBatteryConsumer; 26 import android.os.UserHandle; 27 import android.os.UserManager; 28 import android.util.Log; 29 30 import androidx.annotation.NonNull; 31 import androidx.annotation.VisibleForTesting; 32 import androidx.loader.app.LoaderManager; 33 import androidx.loader.content.Loader; 34 import androidx.preference.Preference; 35 import androidx.preference.PreferenceScreen; 36 37 import com.android.settings.R; 38 import com.android.settings.Utils; 39 import com.android.settings.core.BasePreferenceController; 40 import com.android.settings.fuelgauge.AdvancedPowerUsageDetail; 41 import com.android.settings.fuelgauge.BatteryChartPreferenceController; 42 import com.android.settings.fuelgauge.BatteryDiffEntry; 43 import com.android.settings.fuelgauge.BatteryEntry; 44 import com.android.settings.fuelgauge.BatteryUsageStatsLoader; 45 import com.android.settings.fuelgauge.BatteryUtils; 46 import com.android.settings.fuelgauge.ConvertUtils; 47 import com.android.settings.fuelgauge.PowerUsageFeatureProvider; 48 import com.android.settings.overlay.FeatureFactory; 49 import com.android.settingslib.core.lifecycle.Lifecycle; 50 import com.android.settingslib.core.lifecycle.LifecycleObserver; 51 import com.android.settingslib.core.lifecycle.events.OnPause; 52 import com.android.settingslib.core.lifecycle.events.OnResume; 53 54 import java.util.List; 55 56 public class AppBatteryPreferenceController extends BasePreferenceController 57 implements LifecycleObserver, OnResume, OnPause { 58 59 private static final String TAG = "AppBatteryPreferenceController"; 60 private static final String KEY_BATTERY = "battery"; 61 62 @VisibleForTesting 63 final BatteryUsageStatsLoaderCallbacks mBatteryUsageStatsLoaderCallbacks = 64 new BatteryUsageStatsLoaderCallbacks(); 65 @VisibleForTesting 66 BatteryUtils mBatteryUtils; 67 @VisibleForTesting 68 BatteryUsageStats mBatteryUsageStats; 69 @VisibleForTesting 70 UidBatteryConsumer mUidBatteryConsumer; 71 @VisibleForTesting 72 BatteryDiffEntry mBatteryDiffEntry; 73 @VisibleForTesting 74 boolean mIsChartGraphEnabled; 75 76 private Preference mPreference; 77 private final AppInfoDashboardFragment mParent; 78 private String mBatteryPercent; 79 private final String mPackageName; 80 private final int mUid; 81 private final int mUserId; 82 private boolean mBatteryUsageStatsLoaded = false; 83 private boolean mBatteryDiffEntriesLoaded = false; 84 AppBatteryPreferenceController(Context context, AppInfoDashboardFragment parent, String packageName, int uid, Lifecycle lifecycle)85 public AppBatteryPreferenceController(Context context, AppInfoDashboardFragment parent, 86 String packageName, int uid, Lifecycle lifecycle) { 87 super(context, KEY_BATTERY); 88 mParent = parent; 89 mBatteryUtils = BatteryUtils.getInstance(mContext); 90 mPackageName = packageName; 91 mUid = uid; 92 mUserId = mContext.getUserId(); 93 refreshFeatureFlag(mContext); 94 if (lifecycle != null) { 95 lifecycle.addObserver(this); 96 } 97 } 98 99 @Override getAvailabilityStatus()100 public int getAvailabilityStatus() { 101 return mContext.getResources().getBoolean(R.bool.config_show_app_info_settings_battery) 102 ? AVAILABLE 103 : CONDITIONALLY_UNAVAILABLE; 104 } 105 106 @Override displayPreference(PreferenceScreen screen)107 public void displayPreference(PreferenceScreen screen) { 108 super.displayPreference(screen); 109 mPreference = screen.findPreference(getPreferenceKey()); 110 mPreference.setEnabled(false); 111 loadBatteryDiffEntries(); 112 } 113 114 @Override handlePreferenceTreeClick(Preference preference)115 public boolean handlePreferenceTreeClick(Preference preference) { 116 if (!KEY_BATTERY.equals(preference.getKey())) { 117 return false; 118 } 119 120 if (mBatteryDiffEntry != null) { 121 Log.i(TAG, "BatteryDiffEntry not null, launch : " 122 + mBatteryDiffEntry.getPackageName() 123 + " | uid : " 124 + mBatteryDiffEntry.mBatteryHistEntry.mUid 125 + " with DiffEntry data"); 126 AdvancedPowerUsageDetail.startBatteryDetailPage( 127 mParent.getActivity(), 128 mParent, 129 mBatteryDiffEntry, 130 Utils.formatPercentage( 131 mBatteryDiffEntry.getPercentOfTotal(), /* round */ true), 132 /*isValidToShowSummary=*/ true, 133 /*slotInformation=*/ null); 134 return true; 135 } 136 137 if (isBatteryStatsAvailable()) { 138 final UserManager userManager = 139 (UserManager) mContext.getSystemService(Context.USER_SERVICE); 140 final BatteryEntry entry = new BatteryEntry(mContext, /* handler */null, userManager, 141 mUidBatteryConsumer, /* isHidden */ false, 142 mUidBatteryConsumer.getUid(), /* packages */ null, mPackageName); 143 Log.i(TAG, "Battery consumer available, launch : " 144 + entry.getDefaultPackageName() 145 + " | uid : " 146 + entry.getUid() 147 + " with BatteryEntry data"); 148 AdvancedPowerUsageDetail.startBatteryDetailPage(mParent.getActivity(), mParent, entry, 149 mIsChartGraphEnabled ? Utils.formatPercentage(0) : mBatteryPercent, 150 !mIsChartGraphEnabled); 151 } else { 152 Log.i(TAG, "Launch : " + mPackageName + " with package name"); 153 AdvancedPowerUsageDetail.startBatteryDetailPage(mParent.getActivity(), mParent, 154 mPackageName); 155 } 156 return true; 157 } 158 159 @Override onResume()160 public void onResume() { 161 mParent.getLoaderManager().restartLoader( 162 AppInfoDashboardFragment.LOADER_BATTERY_USAGE_STATS, Bundle.EMPTY, 163 mBatteryUsageStatsLoaderCallbacks); 164 } 165 166 @Override onPause()167 public void onPause() { 168 mParent.getLoaderManager().destroyLoader( 169 AppInfoDashboardFragment.LOADER_BATTERY_USAGE_STATS); 170 } 171 loadBatteryDiffEntries()172 private void loadBatteryDiffEntries() { 173 new AsyncTask<Void, Void, BatteryDiffEntry>() { 174 @Override 175 protected BatteryDiffEntry doInBackground(Void... unused) { 176 if (mPackageName == null) { 177 return null; 178 } 179 final List<BatteryDiffEntry> batteryDiffEntries = 180 BatteryChartPreferenceController.getBatteryLast24HrUsageData(mContext); 181 if (batteryDiffEntries == null) { 182 return null; 183 } 184 // Filter entry with consumer type to avoid system app, 185 // then use user id to divide normal app and work profile app, 186 // return target application from filter list by package name. 187 return batteryDiffEntries.stream() 188 .filter(entry -> entry.mBatteryHistEntry.mConsumerType 189 == ConvertUtils.CONSUMER_TYPE_UID_BATTERY) 190 .filter(entry -> entry.mBatteryHistEntry.mUserId == mUserId) 191 .filter(entry -> { 192 if (mPackageName.equals(entry.getPackageName())) { 193 Log.i(TAG, "Return target application: " 194 + entry.mBatteryHistEntry.mPackageName 195 + " | uid: " + entry.mBatteryHistEntry.mUid 196 + " | userId: " + entry.mBatteryHistEntry.mUserId); 197 return true; 198 } 199 return false; 200 }) 201 .findFirst() 202 .orElse(/* other */null); 203 } 204 205 @Override 206 protected void onPostExecute(BatteryDiffEntry batteryDiffEntry) { 207 mBatteryDiffEntry = batteryDiffEntry; 208 updateBatteryWithDiffEntry(); 209 } 210 }.execute(); 211 } 212 213 @VisibleForTesting 214 void updateBatteryWithDiffEntry() { 215 if (mIsChartGraphEnabled) { 216 if (mBatteryDiffEntry != null && mBatteryDiffEntry.mConsumePower > 0) { 217 mBatteryPercent = Utils.formatPercentage( 218 mBatteryDiffEntry.getPercentOfTotal(), /* round */ true); 219 mPreference.setSummary(mContext.getString( 220 R.string.battery_summary_24hr, mBatteryPercent)); 221 } else { 222 mPreference.setSummary( 223 mContext.getString(R.string.no_battery_summary_24hr)); 224 } 225 } 226 227 mBatteryDiffEntriesLoaded = true; 228 mPreference.setEnabled(mBatteryUsageStatsLoaded); 229 } 230 231 private void onLoadFinished() { 232 if (mBatteryUsageStats == null) { 233 return; 234 } 235 236 final PackageInfo packageInfo = mParent.getPackageInfo(); 237 if (packageInfo != null) { 238 mUidBatteryConsumer = findTargetUidBatteryConsumer(mBatteryUsageStats, 239 packageInfo.applicationInfo.uid); 240 if (mParent.getActivity() != null) { 241 updateBattery(); 242 } 243 } 244 } 245 246 private void refreshFeatureFlag(Context context) { 247 if (isWorkProfile(context)) { 248 try { 249 context = context.createPackageContextAsUser( 250 context.getPackageName(), 0, UserHandle.OWNER); 251 } catch (PackageManager.NameNotFoundException e) { 252 Log.e(TAG, "context.createPackageContextAsUser() fail: " + e); 253 } 254 } 255 256 final PowerUsageFeatureProvider powerUsageFeatureProvider = 257 FeatureFactory.getFactory(context).getPowerUsageFeatureProvider(context); 258 mIsChartGraphEnabled = powerUsageFeatureProvider.isChartGraphEnabled(context); 259 } 260 261 private boolean isWorkProfile(Context context) { 262 final UserManager userManager = context.getSystemService(UserManager.class); 263 return userManager.isManagedProfile() && !userManager.isSystemUser(); 264 } 265 266 @VisibleForTesting 267 void updateBattery() { 268 mBatteryUsageStatsLoaded = true; 269 mPreference.setEnabled(mBatteryDiffEntriesLoaded); 270 if (mIsChartGraphEnabled) { 271 return; 272 } 273 if (isBatteryStatsAvailable()) { 274 final int percentOfMax = (int) mBatteryUtils.calculateBatteryPercent( 275 mUidBatteryConsumer.getConsumedPower(), mBatteryUsageStats.getConsumedPower(), 276 mBatteryUsageStats.getDischargePercentage()); 277 mBatteryPercent = Utils.formatPercentage(percentOfMax); 278 mPreference.setSummary(mContext.getString(R.string.battery_summary, mBatteryPercent)); 279 } else { 280 mPreference.setSummary(mContext.getString(R.string.no_battery_summary)); 281 } 282 } 283 284 @VisibleForTesting 285 boolean isBatteryStatsAvailable() { 286 return mUidBatteryConsumer != null; 287 } 288 289 @VisibleForTesting 290 UidBatteryConsumer findTargetUidBatteryConsumer(BatteryUsageStats batteryUsageStats, int uid) { 291 final List<UidBatteryConsumer> usageList = batteryUsageStats.getUidBatteryConsumers(); 292 for (int i = 0, size = usageList.size(); i < size; i++) { 293 final UidBatteryConsumer consumer = usageList.get(i); 294 if (consumer.getUid() == uid) { 295 return consumer; 296 } 297 } 298 return null; 299 } 300 301 private class BatteryUsageStatsLoaderCallbacks 302 implements LoaderManager.LoaderCallbacks<BatteryUsageStats> { 303 @Override 304 @NonNull 305 public Loader<BatteryUsageStats> onCreateLoader(int id, Bundle args) { 306 return new BatteryUsageStatsLoader(mContext, /* includeBatteryHistory */ false); 307 } 308 309 @Override 310 public void onLoadFinished(Loader<BatteryUsageStats> loader, 311 BatteryUsageStats batteryUsageStats) { 312 mBatteryUsageStats = batteryUsageStats; 313 AppBatteryPreferenceController.this.onLoadFinished(); 314 } 315 316 @Override 317 public void onLoaderReset(Loader<BatteryUsageStats> loader) { 318 } 319 } 320 } 321