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 = 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