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