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