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