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
17package com.android.settings.fuelgauge;
18
19import android.app.AppGlobals;
20import android.content.Context;
21import android.content.pm.ApplicationInfo;
22import android.content.pm.IPackageManager;
23import android.content.pm.PackageInfo;
24import android.content.pm.PackageManager;
25import android.content.pm.UserInfo;
26import android.graphics.drawable.Drawable;
27import android.os.BatteryStats;
28import android.os.Handler;
29import android.os.RemoteException;
30import android.os.UserHandle;
31import android.os.UserManager;
32import android.util.Log;
33
34import com.android.internal.os.BatterySipper;
35import com.android.settings.R;
36import com.android.settings.Utils;
37
38import java.util.ArrayList;
39import java.util.HashMap;
40
41/**
42 * Wraps the power usage data of a BatterySipper with information about package name
43 * and icon image.
44 */
45public 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 = Utils.getUserLabel(context, info);
171                } else {
172                    icon = null;
173                    name = context.getResources().getString(
174                            R.string.running_process_item_removed_user_label);
175                }
176            } break;
177            case UNACCOUNTED:
178                name = context.getResources().getString(R.string.power_unaccounted);
179                iconId = R.drawable.ic_power_system;
180                break;
181            case OVERCOUNTED:
182                name = context.getResources().getString(R.string.power_overcounted);
183                iconId = R.drawable.ic_power_system;
184                break;
185        }
186        if (iconId > 0) {
187            icon = context.getDrawable(iconId);
188        }
189        if ((name == null || iconId == 0) && this.sipper.uidObj != null) {
190            getQuickNameIconForUid(this.sipper.uidObj);
191        }
192    }
193
194    public Drawable getIcon() {
195        return icon;
196    }
197
198    /**
199     * Gets the application name
200     */
201    public String getLabel() {
202        return name;
203    }
204
205    void getQuickNameIconForUid(BatteryStats.Uid uidObj) {
206        final int uid = uidObj.getUid();
207        final String uidString = Integer.toString(uid);
208        if (sUidCache.containsKey(uidString)) {
209            UidToDetail utd = sUidCache.get(uidString);
210            defaultPackageName = utd.packageName;
211            name = utd.name;
212            icon = utd.icon;
213            return;
214        }
215        PackageManager pm = context.getPackageManager();
216        String[] packages = pm.getPackagesForUid(uid);
217        icon = pm.getDefaultActivityIcon();
218        if (packages == null) {
219            //name = Integer.toString(uid);
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            }
225            iconId = R.drawable.ic_power_system;
226            icon = context.getDrawable(iconId);
227            return;
228        } else {
229            //name = packages[0];
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     */
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        PackageManager pm = context.getPackageManager();
247        final int uid = sipper.uidObj.getUid();
248        final Drawable defaultActivityIcon = pm.getDefaultActivityIcon();
249        sipper.mPackages = pm.getPackagesForUid(uid);
250        if (sipper.mPackages == null) {
251            name = Integer.toString(uid);
252            return;
253        }
254
255        String[] packageLabels = new String[sipper.mPackages.length];
256        System.arraycopy(sipper.mPackages, 0, packageLabels, 0, sipper.mPackages.length);
257
258        // Convert package names to user-facing labels where possible
259        IPackageManager ipm = AppGlobals.getPackageManager();
260        final int userId = UserHandle.getUserId(uid);
261        for (int i = 0; i < packageLabels.length; i++) {
262            try {
263                final ApplicationInfo ai = ipm.getApplicationInfo(packageLabels[i],
264                        0 /* no flags */, userId);
265                if (ai == null) {
266                    Log.d(PowerUsageSummary.TAG, "Retrieving null app info for package "
267                            + packageLabels[i] + ", user " + userId);
268                    continue;
269                }
270                CharSequence label = ai.loadLabel(pm);
271                if (label != null) {
272                    packageLabels[i] = label.toString();
273                }
274                if (ai.icon != 0) {
275                    defaultPackageName = sipper.mPackages[i];
276                    icon = ai.loadIcon(pm);
277                    break;
278                }
279            } catch (RemoteException e) {
280                Log.d(PowerUsageSummary.TAG, "Error while retrieving app info for package "
281                        + packageLabels[i] + ", user " + userId, e);
282            }
283        }
284        if (icon == null) {
285            icon = defaultActivityIcon;
286        }
287
288        if (packageLabels.length == 1) {
289            name = packageLabels[0];
290        } else {
291            // Look for an official name for this UID.
292            for (String pkgName : sipper.mPackages) {
293                try {
294                    final PackageInfo pi = ipm.getPackageInfo(pkgName, 0 /* no flags */, userId);
295                    if (pi == null) {
296                        Log.d(PowerUsageSummary.TAG, "Retrieving null package info for package "
297                                + pkgName + ", user " + userId);
298                        continue;
299                    }
300                    if (pi.sharedUserLabel != 0) {
301                        final CharSequence nm = pm.getText(pkgName,
302                                pi.sharedUserLabel, pi.applicationInfo);
303                        if (nm != null) {
304                            name = nm.toString();
305                            if (pi.applicationInfo.icon != 0) {
306                                defaultPackageName = pkgName;
307                                icon = pi.applicationInfo.loadIcon(pm);
308                            }
309                            break;
310                        }
311                    }
312                } catch (RemoteException e) {
313                    Log.d(PowerUsageSummary.TAG, "Error while retrieving package info for package "
314                            + pkgName + ", user " + userId, e);
315                }
316            }
317        }
318        final String uidString = Integer.toString(sipper.uidObj.getUid());
319        UidToDetail utd = new UidToDetail();
320        utd.name = name;
321        utd.icon = icon;
322        utd.packageName = defaultPackageName;
323        sUidCache.put(uidString, utd);
324        if (sHandler != null) {
325            sHandler.sendMessage(sHandler.obtainMessage(MSG_UPDATE_NAME_ICON, this));
326        }
327    }
328}
329