diff --git a/res/values/strings.xml b/res/values/strings.xml index 6f242ca7d32..b49d9292019 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -2377,6 +2377,8 @@ Location off Recent location requests + + No recent apps Location services diff --git a/src/com/android/settings/fuelgauge/BatterySipper.java b/src/com/android/settings/fuelgauge/BatterySipper.java index 524543475e0..fcc8f697044 100644 --- a/src/com/android/settings/fuelgauge/BatterySipper.java +++ b/src/com/android/settings/fuelgauge/BatterySipper.java @@ -119,6 +119,14 @@ public class BatterySipper implements Comparable { return mPackages; } + public int getUid() { + // Bail out if the current sipper is not an App sipper. + if (uidObj == null) { + return 0; + } + return uidObj.getUid(); + } + void getQuickNameIconForUid(Uid uidObj) { final int uid = uidObj.getUid(); final String uidString = Integer.toString(uid); diff --git a/src/com/android/settings/location/RecentLocationApps.java b/src/com/android/settings/location/RecentLocationApps.java index fda734cf228..9a59563303f 100644 --- a/src/com/android/settings/location/RecentLocationApps.java +++ b/src/com/android/settings/location/RecentLocationApps.java @@ -20,6 +20,7 @@ import android.app.AppOpsManager; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.preference.Preference; import android.preference.PreferenceActivity; @@ -33,9 +34,11 @@ import com.android.settings.applications.InstalledAppDetails; import com.android.settings.fuelgauge.BatterySipper; import com.android.settings.fuelgauge.BatteryStatsHelper; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; -import java.util.Map; /** * Retrieves the information of applications which accessed location recently. @@ -93,12 +96,69 @@ public class RecentLocationApps { } } + private PreferenceScreen createRecentLocationEntry( + PreferenceManager preferenceManager, + Drawable icon, + CharSequence label, + boolean isHighBattery, + Preference.OnPreferenceClickListener listener) { + PreferenceScreen screen = preferenceManager.createPreferenceScreen(mActivity); + screen.setIcon(icon); + screen.setTitle(label); + if (isHighBattery) { + screen.setSummary(R.string.location_high_battery_use); + } else { + screen.setSummary(R.string.location_low_battery_use); + } + screen.setOnPreferenceClickListener(listener); + return screen; + } + + /** + * Stores a BatterySipper object and records whether the sipper has been used. + */ + private static final class BatterySipperWrapper { + private BatterySipper mSipper; + private boolean mUsed; + + public BatterySipperWrapper(BatterySipper sipper) { + mSipper = sipper; + mUsed = false; + } + + public BatterySipper batterySipper() { + return mSipper; + } + + public boolean used() { + return mUsed; + } + + public void setUsed() { + mUsed = true; + } + } + /** * Fills a list of applications which queried location recently within * specified time. */ public void fillAppList(PreferenceCategory container) { - HashMap packageMap = new HashMap(); + // Retrieve Uid-based battery blaming info and generate a package to BatterySipper HashMap + // for later faster looking up. + mStatsHelper.refreshStats(); + List usageList = mStatsHelper.getUsageList(); + // Key: package Uid. Value: BatterySipperWrapper. + HashMap sipperMap = + new HashMap(usageList.size()); + for (BatterySipper sipper: usageList) { + int uid = sipper.getUid(); + if (uid != 0) { + sipperMap.put(uid, new BatterySipperWrapper(sipper)); + } + } + + // Retrieve a location usage list from AppOps AppOpsManager aoManager = (AppOpsManager) mActivity.getSystemService(Context.APP_OPS_SERVICE); List appOps = aoManager.getPackagesForOps( @@ -107,68 +167,56 @@ public class RecentLocationApps { AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, }); PreferenceManager preferenceManager = container.getPreferenceManager(); + + // Process the AppOps list and generate a preference list. + ArrayList prefs = new ArrayList(); long now = System.currentTimeMillis(); for (AppOpsManager.PackageOps ops : appOps) { - processPackageOps(now, container, preferenceManager, ops, packageMap); - } - - mStatsHelper.refreshStats(); - List usageList = mStatsHelper.getUsageList(); - for (BatterySipper sipper : usageList) { - sipper.loadNameAndIcon(); - String[] packages = sipper.getPackages(); - if (packages == null) { - continue; - } - for (String curPackage : packages) { - if (packageMap.containsKey(curPackage)) { - PreferenceScreen screen = preferenceManager.createPreferenceScreen(mActivity); - screen.setIcon(sipper.getIcon()); - screen.setTitle(sipper.getLabel()); - if (packageMap.get(curPackage)) { - screen.setSummary(R.string.location_high_battery_use); - } else { - screen.setSummary(R.string.location_low_battery_use); - } - container.addPreference(screen); - screen.setOnPreferenceClickListener(new UidEntryClickedListener(sipper)); - packageMap.remove(curPackage); - break; - } + BatterySipperWrapper wrapper = sipperMap.get(ops.getUid()); + PreferenceScreen screen = getScreenFromOps(preferenceManager, now, ops, wrapper); + if (screen != null) { + prefs.add(screen); } } - // Typically there shouldn't be any entry left in the HashMap. But if there are any, add - // them to the list and link them to the app info page. - for (Map.Entry entry : packageMap.entrySet()) { - try { - PreferenceScreen screen = preferenceManager.createPreferenceScreen(mActivity); - ApplicationInfo appInfo = mPackageManager.getApplicationInfo( - entry.getKey(), PackageManager.GET_META_DATA); - screen.setIcon(mPackageManager.getApplicationIcon(appInfo)); - screen.setTitle(mPackageManager.getApplicationLabel(appInfo)); - // if used both high and low battery within the time interval, show as "high - // battery" - if (entry.getValue()) { - screen.setSummary(R.string.location_high_battery_use); - } else { - screen.setSummary(R.string.location_low_battery_use); + if (prefs.size() > 0) { + // If there's some items to display, sort the items and add them to the container. + Collections.sort(prefs, new Comparator() { + @Override + public int compare(PreferenceScreen lhs, PreferenceScreen rhs) { + return lhs.getTitle().toString().compareTo(rhs.getTitle().toString()); } - screen.setOnPreferenceClickListener( - new PackageEntryClickedListener(entry.getKey())); - container.addPreference(screen); - } catch (PackageManager.NameNotFoundException e) { - // ignore the current app and move on to the next. + }); + for (PreferenceScreen entry : prefs) { + container.addPreference(entry); } + } else { + // If there's no item to display, add a "No recent apps" item. + PreferenceScreen screen = preferenceManager.createPreferenceScreen(mActivity); + screen.setTitle(R.string.location_no_recent_apps); + screen.setSelectable(false); + screen.setEnabled(false); + container.addPreference(screen); } } - private void processPackageOps( - long now, - PreferenceCategory container, + /** + * Creates a PreferenceScreen entry for the given PackageOps. + * + * This method examines the time interval of the PackageOps first. If the PackageOps is older + * than the designated interval, this method ignores the PackageOps object and returns null. + * + * When the PackageOps is fresh enough, if the package has a corresponding battery blaming entry + * in the Uid-based battery sipper list, this method returns a PreferenceScreen pointing to the + * Uid battery blaming page. If the package doesn't have a battery sipper entry (typically + * shouldn't happen), this method returns a PreferenceScreen pointing to the App Info page for + * that package. + */ + private PreferenceScreen getScreenFromOps( PreferenceManager preferenceManager, + long now, AppOpsManager.PackageOps ops, - HashMap packageMap) { + BatterySipperWrapper wrapper) { String packageName = ops.getPackageName(); List entries = ops.getOps(); boolean highBattery = false; @@ -193,9 +241,47 @@ public class RecentLocationApps { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, packageName + " hadn't used location within the time interval."); } - return; + return null; } - packageMap.put(packageName, highBattery); + // The package is fresh enough, continue. + + PreferenceScreen screen = null; + if (wrapper != null) { + // Contains sipper. Link to Battery Blaming page. + + // We're listing by UID rather than package. Check whether the entry has been used + // before to prevent the same UID from showing up twice. + if (!wrapper.used()) { + BatterySipper sipper = wrapper.batterySipper(); + sipper.loadNameAndIcon(); + screen = createRecentLocationEntry( + preferenceManager, + sipper.getIcon(), + sipper.getLabel(), + highBattery, + new UidEntryClickedListener(sipper)); + wrapper.setUsed(); + } + } else { + // No corresponding sipper. Link to App Info page. + + // This is grouped by package rather than UID, but that's OK because this branch + // shouldn't happen in practice. + try { + ApplicationInfo appInfo = mPackageManager.getApplicationInfo( + packageName, PackageManager.GET_META_DATA); + screen = createRecentLocationEntry( + preferenceManager, + mPackageManager.getApplicationIcon(appInfo), + mPackageManager.getApplicationLabel(appInfo), + highBattery, + new PackageEntryClickedListener(packageName)); + } catch (PackageManager.NameNotFoundException e) { + Log.wtf(TAG, "Package not found: " + packageName); + } + } + + return screen; } }