RecentLocationApps.java revision 8b4cdbebcfb53b57ac2afec1b1e3377d6a02bb21
1/* 2 * Copyright (C) 2013 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.location; 18 19import android.app.AppOpsManager; 20import android.content.Context; 21import android.content.pm.ApplicationInfo; 22import android.content.pm.PackageManager; 23import android.graphics.drawable.Drawable; 24import android.os.Bundle; 25import android.preference.Preference; 26import android.preference.PreferenceActivity; 27import android.util.Log; 28 29import com.android.settings.R; 30import com.android.settings.applications.InstalledAppDetails; 31import com.android.settings.fuelgauge.BatterySipper; 32import com.android.settings.fuelgauge.BatteryStatsHelper; 33 34import java.util.ArrayList; 35import java.util.HashMap; 36import java.util.List; 37 38/** 39 * Retrieves the information of applications which accessed location recently. 40 */ 41public class RecentLocationApps { 42 private static final String TAG = RecentLocationApps.class.getSimpleName(); 43 44 private static final int RECENT_TIME_INTERVAL_MILLIS = 15 * 60 * 1000; 45 46 private final PreferenceActivity mActivity; 47 private final BatteryStatsHelper mStatsHelper; 48 private final PackageManager mPackageManager; 49 50 // Stores all the packages that requested location within the designated interval 51 // key - package name of the app 52 // value - whether the app has requested high power location 53 54 public RecentLocationApps(PreferenceActivity activity, BatteryStatsHelper sipperUtil) { 55 mActivity = activity; 56 mPackageManager = activity.getPackageManager(); 57 mStatsHelper = sipperUtil; 58 } 59 60 private class UidEntryClickedListener 61 implements Preference.OnPreferenceClickListener { 62 private BatterySipper mSipper; 63 64 public UidEntryClickedListener(BatterySipper sipper) { 65 mSipper = sipper; 66 } 67 68 @Override 69 public boolean onPreferenceClick(Preference preference) { 70 mStatsHelper.startBatteryDetailPage(mActivity, mSipper, false); 71 return true; 72 } 73 } 74 75 private class PackageEntryClickedListener 76 implements Preference.OnPreferenceClickListener { 77 private String mPackage; 78 79 public PackageEntryClickedListener(String packageName) { 80 mPackage = packageName; 81 } 82 83 @Override 84 public boolean onPreferenceClick(Preference preference) { 85 // start new fragment to display extended information 86 Bundle args = new Bundle(); 87 args.putString(InstalledAppDetails.ARG_PACKAGE_NAME, mPackage); 88 mActivity.startPreferencePanel(InstalledAppDetails.class.getName(), args, 89 R.string.application_info_label, null, null, 0); 90 return true; 91 } 92 } 93 94 private Preference createRecentLocationEntry( 95 Drawable icon, 96 CharSequence label, 97 boolean isHighBattery, 98 Preference.OnPreferenceClickListener listener) { 99 Preference pref = new Preference(mActivity); 100 pref.setIcon(icon); 101 pref.setTitle(label); 102 if (isHighBattery) { 103 pref.setSummary(R.string.location_high_battery_use); 104 } else { 105 pref.setSummary(R.string.location_low_battery_use); 106 } 107 pref.setOnPreferenceClickListener(listener); 108 return pref; 109 } 110 111 /** 112 * Stores a BatterySipper object and records whether the sipper has been used. 113 */ 114 private static final class BatterySipperWrapper { 115 private BatterySipper mSipper; 116 private boolean mUsed; 117 118 public BatterySipperWrapper(BatterySipper sipper) { 119 mSipper = sipper; 120 mUsed = false; 121 } 122 123 public BatterySipper batterySipper() { 124 return mSipper; 125 } 126 127 public boolean used() { 128 return mUsed; 129 } 130 131 public void setUsed() { 132 mUsed = true; 133 } 134 } 135 136 /** 137 * Fills a list of applications which queried location recently within 138 * specified time. 139 */ 140 public List<Preference> getAppList() { 141 // Retrieve Uid-based battery blaming info and generate a package to BatterySipper HashMap 142 // for later faster looking up. 143 mStatsHelper.refreshStats(true); 144 List<BatterySipper> usageList = mStatsHelper.getUsageList(); 145 // Key: package Uid. Value: BatterySipperWrapper. 146 HashMap<Integer, BatterySipperWrapper> sipperMap = 147 new HashMap<Integer, BatterySipperWrapper>(usageList.size()); 148 for (BatterySipper sipper: usageList) { 149 int uid = sipper.getUid(); 150 if (uid != 0) { 151 sipperMap.put(uid, new BatterySipperWrapper(sipper)); 152 } 153 } 154 155 // Retrieve a location usage list from AppOps 156 AppOpsManager aoManager = 157 (AppOpsManager) mActivity.getSystemService(Context.APP_OPS_SERVICE); 158 List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps( 159 new int[] { 160 AppOpsManager.OP_MONITOR_LOCATION, 161 AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, 162 }); 163 164 // Process the AppOps list and generate a preference list. 165 ArrayList<Preference> prefs = new ArrayList<Preference>(); 166 long now = System.currentTimeMillis(); 167 for (AppOpsManager.PackageOps ops : appOps) { 168 BatterySipperWrapper wrapper = sipperMap.get(ops.getUid()); 169 Preference pref = getPreferenceFromOps(now, ops, wrapper); 170 if (pref != null) { 171 prefs.add(pref); 172 } 173 } 174 175 return prefs; 176 } 177 178 /** 179 * Creates a Preference entry for the given PackageOps. 180 * 181 * This method examines the time interval of the PackageOps first. If the PackageOps is older 182 * than the designated interval, this method ignores the PackageOps object and returns null. 183 * 184 * When the PackageOps is fresh enough, if the package has a corresponding battery blaming entry 185 * in the Uid-based battery sipper list, this method returns a Preference pointing to the Uid 186 * battery blaming page. If the package doesn't have a battery sipper entry (typically shouldn't 187 * happen), this method returns a Preference pointing to the App Info page for that package. 188 */ 189 private Preference getPreferenceFromOps( 190 long now, 191 AppOpsManager.PackageOps ops, 192 BatterySipperWrapper wrapper) { 193 String packageName = ops.getPackageName(); 194 List<AppOpsManager.OpEntry> entries = ops.getOps(); 195 boolean highBattery = false; 196 boolean normalBattery = false; 197 // Earliest time for a location request to end and still be shown in list. 198 long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS; 199 for (AppOpsManager.OpEntry entry : entries) { 200 if (entry.isRunning() || entry.getTime() >= recentLocationCutoffTime) { 201 switch (entry.getOp()) { 202 case AppOpsManager.OP_MONITOR_LOCATION: 203 normalBattery = true; 204 break; 205 case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION: 206 highBattery = true; 207 break; 208 default: 209 break; 210 } 211 } 212 } 213 214 if (!highBattery && !normalBattery) { 215 if (Log.isLoggable(TAG, Log.VERBOSE)) { 216 Log.v(TAG, packageName + " hadn't used location within the time interval."); 217 } 218 return null; 219 } 220 221 // The package is fresh enough, continue. 222 223 Preference pref = null; 224 if (wrapper != null) { 225 // Contains sipper. Link to Battery Blaming page. 226 227 // We're listing by UID rather than package. Check whether the entry has been used 228 // before to prevent the same UID from showing up twice. 229 if (!wrapper.used()) { 230 BatterySipper sipper = wrapper.batterySipper(); 231 sipper.loadNameAndIcon(); 232 pref = createRecentLocationEntry( 233 sipper.getIcon(), 234 sipper.getLabel(), 235 highBattery, 236 new UidEntryClickedListener(sipper)); 237 wrapper.setUsed(); 238 } 239 } else { 240 // No corresponding sipper. Link to App Info page. 241 242 // This is grouped by package rather than UID, but that's OK because this branch 243 // shouldn't happen in practice. 244 try { 245 ApplicationInfo appInfo = mPackageManager.getApplicationInfo( 246 packageName, PackageManager.GET_META_DATA); 247 // Multiple users can install the same package. Each user gets a different Uid for 248 // the same package. 249 // 250 // Here we retrieve the Uid with package name, that will be the Uid for that package 251 // associated with the current active user. If the Uid differs from the Uid in ops, 252 // that means this entry belongs to another inactive user and we should ignore that. 253 if (appInfo.uid == ops.getUid()) { 254 pref = createRecentLocationEntry( 255 mPackageManager.getApplicationIcon(appInfo), 256 mPackageManager.getApplicationLabel(appInfo), 257 highBattery, 258 new PackageEntryClickedListener(packageName)); 259 } else if (Log.isLoggable(TAG, Log.VERBOSE)) { 260 Log.v(TAG, "package " + packageName + " with Uid " + ops.getUid() + 261 " belongs to another inactive account, ignored."); 262 } 263 } catch (PackageManager.NameNotFoundException e) { 264 Log.wtf(TAG, "Package not found: " + packageName); 265 } 266 } 267 268 return pref; 269 } 270} 271