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