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.AppGlobals; 20import android.app.AppOpsManager; 21import android.content.Context; 22import android.content.pm.ApplicationInfo; 23import android.content.pm.PackageManager; 24import android.content.pm.IPackageManager; 25import android.content.res.Resources; 26import android.graphics.drawable.Drawable; 27import android.os.Bundle; 28import android.os.Process; 29import android.os.RemoteException; 30import android.os.UserHandle; 31import android.os.UserManager; 32import android.preference.Preference; 33import android.util.Log; 34 35import com.android.settings.DimmableIconPreference; 36import com.android.settings.R; 37import com.android.settings.SettingsActivity; 38import com.android.settings.applications.InstalledAppDetails; 39 40import java.util.ArrayList; 41import java.util.List; 42 43/** 44 * Retrieves the information of applications which accessed location recently. 45 */ 46public class RecentLocationApps { 47 private static final String TAG = RecentLocationApps.class.getSimpleName(); 48 private static final String ANDROID_SYSTEM_PACKAGE_NAME = "android"; 49 50 private static final int RECENT_TIME_INTERVAL_MILLIS = 15 * 60 * 1000; 51 52 private final SettingsActivity mActivity; 53 private final PackageManager mPackageManager; 54 55 public RecentLocationApps(SettingsActivity activity) { 56 mActivity = activity; 57 mPackageManager = activity.getPackageManager(); 58 } 59 60 private class PackageEntryClickedListener 61 implements Preference.OnPreferenceClickListener { 62 private String mPackage; 63 private UserHandle mUserHandle; 64 65 public PackageEntryClickedListener(String packageName, UserHandle userHandle) { 66 mPackage = packageName; 67 mUserHandle = userHandle; 68 } 69 70 @Override 71 public boolean onPreferenceClick(Preference preference) { 72 // start new fragment to display extended information 73 Bundle args = new Bundle(); 74 args.putString(InstalledAppDetails.ARG_PACKAGE_NAME, mPackage); 75 mActivity.startPreferencePanelAsUser(InstalledAppDetails.class.getName(), args, 76 R.string.application_info_label, null, mUserHandle); 77 return true; 78 } 79 } 80 81 private DimmableIconPreference createRecentLocationEntry( 82 Drawable icon, 83 CharSequence label, 84 boolean isHighBattery, 85 CharSequence contentDescription, 86 Preference.OnPreferenceClickListener listener) { 87 DimmableIconPreference pref = new DimmableIconPreference(mActivity, contentDescription); 88 pref.setIcon(icon); 89 pref.setTitle(label); 90 if (isHighBattery) { 91 pref.setSummary(R.string.location_high_battery_use); 92 } else { 93 pref.setSummary(R.string.location_low_battery_use); 94 } 95 pref.setOnPreferenceClickListener(listener); 96 return pref; 97 } 98 99 /** 100 * Fills a list of applications which queried location recently within specified time. 101 */ 102 public List<Preference> getAppList() { 103 // Retrieve a location usage list from AppOps 104 AppOpsManager aoManager = 105 (AppOpsManager) mActivity.getSystemService(Context.APP_OPS_SERVICE); 106 List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(new int[] { 107 AppOpsManager.OP_MONITOR_LOCATION, AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, }); 108 109 // Process the AppOps list and generate a preference list. 110 ArrayList<Preference> prefs = new ArrayList<Preference>(); 111 final long now = System.currentTimeMillis(); 112 final UserManager um = (UserManager) mActivity.getSystemService(Context.USER_SERVICE); 113 final List<UserHandle> profiles = um.getUserProfiles(); 114 115 final int appOpsN = appOps.size(); 116 for (int i = 0; i < appOpsN; ++i) { 117 AppOpsManager.PackageOps ops = appOps.get(i); 118 // Don't show the Android System in the list - it's not actionable for the user. 119 // Also don't show apps belonging to background users except managed users. 120 String packageName = ops.getPackageName(); 121 int uid = ops.getUid(); 122 int userId = UserHandle.getUserId(uid); 123 boolean isAndroidOs = 124 (uid == Process.SYSTEM_UID) && ANDROID_SYSTEM_PACKAGE_NAME.equals(packageName); 125 if (isAndroidOs || !profiles.contains(new UserHandle(userId))) { 126 continue; 127 } 128 Preference preference = getPreferenceFromOps(um, now, ops); 129 if (preference != null) { 130 prefs.add(preference); 131 } 132 } 133 134 return prefs; 135 } 136 137 /** 138 * Creates a Preference entry for the given PackageOps. 139 * 140 * This method examines the time interval of the PackageOps first. If the PackageOps is older 141 * than the designated interval, this method ignores the PackageOps object and returns null. 142 * When the PackageOps is fresh enough, this method returns a Preference pointing to the App 143 * Info page for that package. 144 */ 145 private Preference getPreferenceFromOps(final UserManager um, long now, 146 AppOpsManager.PackageOps ops) { 147 String packageName = ops.getPackageName(); 148 List<AppOpsManager.OpEntry> entries = ops.getOps(); 149 boolean highBattery = false; 150 boolean normalBattery = false; 151 // Earliest time for a location request to end and still be shown in list. 152 long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS; 153 for (AppOpsManager.OpEntry entry : entries) { 154 if (entry.isRunning() || entry.getTime() >= recentLocationCutoffTime) { 155 switch (entry.getOp()) { 156 case AppOpsManager.OP_MONITOR_LOCATION: 157 normalBattery = true; 158 break; 159 case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION: 160 highBattery = true; 161 break; 162 default: 163 break; 164 } 165 } 166 } 167 168 if (!highBattery && !normalBattery) { 169 if (Log.isLoggable(TAG, Log.VERBOSE)) { 170 Log.v(TAG, packageName + " hadn't used location within the time interval."); 171 } 172 return null; 173 } 174 175 // The package is fresh enough, continue. 176 177 int uid = ops.getUid(); 178 int userId = UserHandle.getUserId(uid); 179 180 DimmableIconPreference preference = null; 181 try { 182 IPackageManager ipm = AppGlobals.getPackageManager(); 183 ApplicationInfo appInfo = 184 ipm.getApplicationInfo(packageName, PackageManager.GET_META_DATA, userId); 185 if (appInfo == null) { 186 Log.w(TAG, "Null application info retrieved for package " + packageName 187 + ", userId " + userId); 188 return null; 189 } 190 Resources res = mActivity.getResources(); 191 192 final UserHandle userHandle = new UserHandle(userId); 193 Drawable appIcon = mPackageManager.getApplicationIcon(appInfo); 194 Drawable icon = mPackageManager.getUserBadgedIcon(appIcon, userHandle); 195 CharSequence appLabel = mPackageManager.getApplicationLabel(appInfo); 196 CharSequence badgedAppLabel = mPackageManager.getUserBadgedLabel(appLabel, userHandle); 197 if (appLabel.toString().contentEquals(badgedAppLabel)) { 198 // If badged label is not different from original then no need for it as 199 // a separate content description. 200 badgedAppLabel = null; 201 } 202 preference = createRecentLocationEntry(icon, 203 appLabel, highBattery, badgedAppLabel, 204 new PackageEntryClickedListener(packageName, userHandle)); 205 } catch (RemoteException e) { 206 Log.w(TAG, "Error while retrieving application info for package " + packageName 207 + ", userId " + userId, e); 208 } 209 210 return preference; 211 } 212} 213