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