1 /* 2 * Copyright (C) 2014 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.fuelgauge; 18 19 import android.app.AppGlobals; 20 import android.content.Context; 21 import android.content.pm.ApplicationInfo; 22 import android.content.pm.IPackageManager; 23 import android.content.pm.PackageInfo; 24 import android.content.pm.PackageManager; 25 import android.content.pm.PackageManager.NameNotFoundException; 26 import android.content.pm.UserInfo; 27 import android.graphics.drawable.Drawable; 28 import android.os.Handler; 29 import android.os.Process; 30 import android.os.RemoteException; 31 import android.os.UserHandle; 32 import android.os.UserManager; 33 import android.util.Log; 34 35 import com.android.internal.os.BatterySipper; 36 import com.android.settings.R; 37 import com.android.settingslib.Utils; 38 39 import java.util.ArrayList; 40 import java.util.HashMap; 41 import java.util.Locale; 42 43 /** 44 * Wraps the power usage data of a BatterySipper with information about package name 45 * and icon image. 46 */ 47 public class BatteryEntry { 48 public static final int MSG_UPDATE_NAME_ICON = 1; 49 public static final int MSG_REPORT_FULLY_DRAWN = 2; 50 51 private static final String TAG = "BatteryEntry"; 52 private static final String PACKAGE_SYSTEM = "android"; 53 54 static final HashMap<String,UidToDetail> sUidCache = new HashMap<String,UidToDetail>(); 55 56 static final ArrayList<BatteryEntry> mRequestQueue = new ArrayList<BatteryEntry>(); 57 static Handler sHandler; 58 59 static Locale sCurrentLocale = null; 60 61 static private class NameAndIconLoader extends Thread { 62 private boolean mAbort = false; 63 NameAndIconLoader()64 public NameAndIconLoader() { 65 super("BatteryUsage Icon Loader"); 66 } 67 abort()68 public void abort() { 69 mAbort = true; 70 } 71 72 @Override run()73 public void run() { 74 while (true) { 75 BatteryEntry be; 76 synchronized (mRequestQueue) { 77 if (mRequestQueue.isEmpty() || mAbort) { 78 if (sHandler != null) { 79 sHandler.sendEmptyMessage(MSG_REPORT_FULLY_DRAWN); 80 } 81 mRequestQueue.clear(); 82 return; 83 } 84 be = mRequestQueue.remove(0); 85 } 86 be.loadNameAndIcon(); 87 } 88 } 89 } 90 91 private static NameAndIconLoader mRequestThread; 92 startRequestQueue()93 public static void startRequestQueue() { 94 if (sHandler != null) { 95 synchronized (mRequestQueue) { 96 if (!mRequestQueue.isEmpty()) { 97 if (mRequestThread != null) { 98 mRequestThread.abort(); 99 } 100 mRequestThread = new NameAndIconLoader(); 101 mRequestThread.setPriority(Thread.MIN_PRIORITY); 102 mRequestThread.start(); 103 mRequestQueue.notify(); 104 } 105 } 106 } 107 } 108 stopRequestQueue()109 public static void stopRequestQueue() { 110 synchronized (mRequestQueue) { 111 if (mRequestThread != null) { 112 mRequestThread.abort(); 113 mRequestThread = null; 114 sHandler = null; 115 } 116 } 117 } 118 clearUidCache()119 public static void clearUidCache() { 120 sUidCache.clear(); 121 } 122 123 public final Context context; 124 public final BatterySipper sipper; 125 126 public String name; 127 public Drawable icon; 128 public int iconId; // For passing to the detail screen. 129 public String defaultPackageName; 130 131 static class UidToDetail { 132 String name; 133 String packageName; 134 Drawable icon; 135 } 136 BatteryEntry(Context context, Handler handler, UserManager um, BatterySipper sipper)137 public BatteryEntry(Context context, Handler handler, UserManager um, BatterySipper sipper) { 138 sHandler = handler; 139 this.context = context; 140 this.sipper = sipper; 141 switch (sipper.drainType) { 142 case IDLE: 143 name = context.getResources().getString(R.string.power_idle); 144 iconId = R.drawable.ic_settings_phone_idle; 145 break; 146 case CELL: 147 name = context.getResources().getString(R.string.power_cell); 148 iconId = R.drawable.ic_cellular_1_bar; 149 break; 150 case PHONE: 151 name = context.getResources().getString(R.string.power_phone); 152 iconId = R.drawable.ic_settings_voice_calls; 153 break; 154 case WIFI: 155 name = context.getResources().getString(R.string.power_wifi); 156 iconId = R.drawable.ic_settings_wireless; 157 break; 158 case BLUETOOTH: 159 name = context.getResources().getString(R.string.power_bluetooth); 160 iconId = com.android.internal.R.drawable.ic_settings_bluetooth; 161 break; 162 case SCREEN: 163 name = context.getResources().getString(R.string.power_screen); 164 iconId = R.drawable.ic_settings_display; 165 break; 166 case FLASHLIGHT: 167 name = context.getResources().getString(R.string.power_flashlight); 168 iconId = R.drawable.ic_settings_display; 169 break; 170 case APP: 171 PackageManager pm = context.getPackageManager(); 172 sipper.mPackages = pm.getPackagesForUid(sipper.uidObj.getUid()); 173 // Apps should only have one package 174 if (sipper.mPackages == null || sipper.mPackages.length != 1) { 175 name = sipper.packageWithHighestDrain; 176 } else { 177 defaultPackageName = pm.getPackagesForUid(sipper.uidObj.getUid())[0]; 178 try { 179 ApplicationInfo appInfo = 180 pm.getApplicationInfo(defaultPackageName, 0 /* no flags */); 181 name = pm.getApplicationLabel(appInfo).toString(); 182 } catch (NameNotFoundException e) { 183 Log.d(TAG, "PackageManager failed to retrieve ApplicationInfo for: " 184 + defaultPackageName); 185 name = defaultPackageName; 186 } 187 } 188 break; 189 case USER: { 190 UserInfo info = um.getUserInfo(sipper.userId); 191 if (info != null) { 192 icon = Utils.getUserIcon(context, um, info); 193 name = Utils.getUserLabel(context, info); 194 } else { 195 icon = null; 196 name = context.getResources().getString( 197 R.string.running_process_item_removed_user_label); 198 } 199 } break; 200 case UNACCOUNTED: 201 name = context.getResources().getString(R.string.power_unaccounted); 202 iconId = R.drawable.ic_android; 203 break; 204 case OVERCOUNTED: 205 name = context.getResources().getString(R.string.power_overcounted); 206 iconId = R.drawable.ic_android; 207 break; 208 case CAMERA: 209 name = context.getResources().getString(R.string.power_camera); 210 iconId = R.drawable.ic_settings_camera; 211 break; 212 case AMBIENT_DISPLAY: 213 name = context.getResources().getString(R.string.ambient_display_screen_title); 214 iconId = R.drawable.ic_settings_aod; 215 break; 216 } 217 if (iconId > 0) { 218 icon = context.getDrawable(iconId); 219 } 220 if ((name == null || iconId == 0) && this.sipper.uidObj != null) { 221 getQuickNameIconForUid(this.sipper.uidObj.getUid()); 222 } 223 } 224 getIcon()225 public Drawable getIcon() { 226 return icon; 227 } 228 229 /** 230 * Gets the application name 231 */ getLabel()232 public String getLabel() { 233 return name; 234 } 235 getQuickNameIconForUid(final int uid)236 void getQuickNameIconForUid(final int uid) { 237 // Locale sync to system config in Settings 238 final Locale locale = Locale.getDefault(); 239 if (sCurrentLocale != locale) { 240 clearUidCache(); 241 sCurrentLocale = locale; 242 } 243 244 final String uidString = Integer.toString(uid); 245 if (sUidCache.containsKey(uidString)) { 246 UidToDetail utd = sUidCache.get(uidString); 247 defaultPackageName = utd.packageName; 248 name = utd.name; 249 icon = utd.icon; 250 return; 251 } 252 PackageManager pm = context.getPackageManager(); 253 icon = pm.getDefaultActivityIcon(); 254 if (pm.getPackagesForUid(uid) == null) { 255 if (uid == 0) { 256 name = context.getResources().getString(R.string.process_kernel_label); 257 } else if ("mediaserver".equals(name)) { 258 name = context.getResources().getString(R.string.process_mediaserver_label); 259 } else if ("dex2oat".equals(name)) { 260 name = context.getResources().getString(R.string.process_dex2oat_label); 261 } 262 iconId = R.drawable.ic_power_system; 263 icon = context.getDrawable(iconId); 264 } 265 266 if (sHandler != null) { 267 synchronized (mRequestQueue) { 268 mRequestQueue.add(this); 269 } 270 } 271 } 272 273 /** 274 * Loads the app label and icon image and stores into the cache. 275 */ loadNameAndIcon()276 public void loadNameAndIcon() { 277 // Bail out if the current sipper is not an App sipper. 278 if (sipper.uidObj == null) { 279 return; 280 } 281 282 PackageManager pm = context.getPackageManager(); 283 final int uid = sipper.uidObj.getUid(); 284 if (sipper.mPackages == null) { 285 sipper.mPackages = pm.getPackagesForUid(uid); 286 } 287 288 final String[] packages = extractPackagesFromSipper(sipper); 289 if (packages != null) { 290 String[] packageLabels = new String[packages.length]; 291 System.arraycopy(packages, 0, packageLabels, 0, packages.length); 292 293 // Convert package names to user-facing labels where possible 294 IPackageManager ipm = AppGlobals.getPackageManager(); 295 final int userId = UserHandle.getUserId(uid); 296 for (int i = 0; i < packageLabels.length; i++) { 297 try { 298 final ApplicationInfo ai = ipm.getApplicationInfo(packageLabels[i], 299 0 /* no flags */, userId); 300 if (ai == null) { 301 Log.d(TAG, "Retrieving null app info for package " 302 + packageLabels[i] + ", user " + userId); 303 continue; 304 } 305 CharSequence label = ai.loadLabel(pm); 306 if (label != null) { 307 packageLabels[i] = label.toString(); 308 } 309 if (ai.icon != 0) { 310 defaultPackageName = packages[i]; 311 icon = ai.loadIcon(pm); 312 break; 313 } 314 } catch (RemoteException e) { 315 Log.d(TAG, "Error while retrieving app info for package " 316 + packageLabels[i] + ", user " + userId, e); 317 } 318 } 319 320 if (packageLabels.length == 1) { 321 name = packageLabels[0]; 322 } else { 323 // Look for an official name for this UID. 324 for (String pkgName : packages) { 325 try { 326 final PackageInfo pi = ipm.getPackageInfo(pkgName, 0 /* no flags */, userId); 327 if (pi == null) { 328 Log.d(TAG, "Retrieving null package info for package " 329 + pkgName + ", user " + userId); 330 continue; 331 } 332 if (pi.sharedUserLabel != 0) { 333 final CharSequence nm = pm.getText(pkgName, 334 pi.sharedUserLabel, pi.applicationInfo); 335 if (nm != null) { 336 name = nm.toString(); 337 if (pi.applicationInfo.icon != 0) { 338 defaultPackageName = pkgName; 339 icon = pi.applicationInfo.loadIcon(pm); 340 } 341 break; 342 } 343 } 344 } catch (RemoteException e) { 345 Log.d(TAG, "Error while retrieving package info for package " 346 + pkgName + ", user " + userId, e); 347 } 348 } 349 } 350 } 351 352 final String uidString = Integer.toString(uid); 353 if (name == null) { 354 name = uidString; 355 } 356 357 if (icon == null) { 358 icon = pm.getDefaultActivityIcon(); 359 } 360 361 UidToDetail utd = new UidToDetail(); 362 utd.name = name; 363 utd.icon = icon; 364 utd.packageName = defaultPackageName; 365 sUidCache.put(uidString, utd); 366 if (sHandler != null) { 367 sHandler.sendMessage(sHandler.obtainMessage(MSG_UPDATE_NAME_ICON, this)); 368 } 369 } 370 extractPackagesFromSipper(BatterySipper sipper)371 String[] extractPackagesFromSipper(BatterySipper sipper) { 372 // Only use system package if uid is system uid, so it could find a consistent name and icon 373 return sipper.getUid() == Process.SYSTEM_UID 374 ? new String[]{PACKAGE_SYSTEM} 375 : sipper.mPackages; 376 } 377 } 378