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