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;
}
}