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.AppOpsManager; 20 import android.content.Context; 21 import android.content.PermissionChecker; 22 import android.content.pm.ApplicationInfo; 23 import android.content.pm.PackageManager; 24 import android.content.pm.PackageManager.NameNotFoundException; 25 import android.graphics.drawable.Drawable; 26 import android.os.UserHandle; 27 import android.os.UserManager; 28 import android.text.format.DateUtils; 29 import android.util.IconDrawableFactory; 30 import android.util.Log; 31 32 import androidx.annotation.VisibleForTesting; 33 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.Comparator; 37 import java.util.List; 38 39 /** 40 * Retrieves the information of applications which accessed location recently. 41 */ 42 public class RecentLocationApps { 43 private static final String TAG = RecentLocationApps.class.getSimpleName(); 44 @VisibleForTesting 45 static final String ANDROID_SYSTEM_PACKAGE_NAME = "android"; 46 47 // Keep last 24 hours of location app information. 48 private static final long RECENT_TIME_INTERVAL_MILLIS = DateUtils.DAY_IN_MILLIS; 49 50 @VisibleForTesting 51 static final int[] LOCATION_REQUEST_OPS = new int[]{ 52 AppOpsManager.OP_MONITOR_LOCATION, 53 AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, 54 }; 55 @VisibleForTesting 56 static final int[] LOCATION_PERMISSION_OPS = new int[]{ 57 AppOpsManager.OP_FINE_LOCATION, 58 AppOpsManager.OP_COARSE_LOCATION, 59 }; 60 61 private final PackageManager mPackageManager; 62 private final Context mContext; 63 private final IconDrawableFactory mDrawableFactory; 64 65 public RecentLocationApps(Context context) { 66 mContext = context; 67 mPackageManager = context.getPackageManager(); 68 mDrawableFactory = IconDrawableFactory.newInstance(context); 69 } 70 71 /** 72 * Fills a list of applications which queried location recently within specified time. 73 * Apps are sorted by recency. Apps with more recent location requests are in the front. 74 */ 75 public List<Request> getAppList(boolean showSystemApps) { 76 // Retrieve a location usage list from AppOps 77 PackageManager pm = mContext.getPackageManager(); 78 // Retrieve a location usage list from AppOps 79 AppOpsManager aoManager = 80 (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); 81 List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(LOCATION_REQUEST_OPS); 82 83 final int appOpsCount = appOps != null ? appOps.size() : 0; 84 85 // Process the AppOps list and generate a preference list. 86 ArrayList<Request> requests = new ArrayList<>(appOpsCount); 87 final long now = System.currentTimeMillis(); 88 final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 89 final List<UserHandle> profiles = um.getUserProfiles(); 90 91 for (int i = 0; i < appOpsCount; ++i) { 92 AppOpsManager.PackageOps ops = appOps.get(i); 93 String packageName = ops.getPackageName(); 94 int uid = ops.getUid(); 95 final UserHandle user = UserHandle.getUserHandleForUid(uid); 96 97 // Don't show apps belonging to background users except managed users. 98 if (!profiles.contains(user)) { 99 continue; 100 } 101 102 // Don't show apps that do not have user sensitive location permissions 103 boolean showApp = true; 104 if (!showSystemApps) { 105 for (int op : LOCATION_PERMISSION_OPS) { 106 final String permission = AppOpsManager.opToPermission(op); 107 final int permissionFlags = pm.getPermissionFlags(permission, packageName, 108 user); 109 if (PermissionChecker.checkPermission(mContext, permission, -1, uid, 110 packageName) 111 == PermissionChecker.PERMISSION_GRANTED) { 112 if ((permissionFlags 113 & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) 114 == 0) { 115 showApp = false; 116 break; 117 } 118 } else { 119 if ((permissionFlags 120 & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) == 0) { 121 showApp = false; 122 break; 123 } 124 } 125 } 126 } 127 if (showApp) { 128 Request request = getRequestFromOps(now, ops); 129 if (request != null) { 130 requests.add(request); 131 } 132 } 133 } 134 return requests; 135 } 136 137 /** 138 * Gets a list of apps that requested for location recently, sorting by recency. 139 * 140 * @param showSystemApps whether includes system apps in the list. 141 * @return the list of apps that recently requested for location. 142 */ 143 public List<Request> getAppListSorted(boolean showSystemApps) { 144 List<Request> requests = getAppList(showSystemApps); 145 // Sort the list of Requests by recency. Most recent request first. 146 Collections.sort(requests, Collections.reverseOrder(new Comparator<Request>() { 147 @Override 148 public int compare(Request request1, Request request2) { 149 return Long.compare(request1.requestFinishTime, request2.requestFinishTime); 150 } 151 })); 152 return requests; 153 } 154 155 /** 156 * Creates a Request entry for the given PackageOps. 157 * 158 * This method examines the time interval of the PackageOps first. If the PackageOps is older 159 * than the designated interval, this method ignores the PackageOps object and returns null. 160 * When the PackageOps is fresh enough, this method returns a Request object for the package 161 */ 162 private Request getRequestFromOps(long now, 163 AppOpsManager.PackageOps ops) { 164 String packageName = ops.getPackageName(); 165 List<AppOpsManager.OpEntry> entries = ops.getOps(); 166 boolean highBattery = false; 167 boolean normalBattery = false; 168 long locationRequestFinishTime = 0L; 169 // Earliest time for a location request to end and still be shown in list. 170 long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS; 171 for (AppOpsManager.OpEntry entry : entries) { 172 if (entry.isRunning() || entry.getTime() >= recentLocationCutoffTime) { 173 locationRequestFinishTime = entry.getTime() + entry.getDuration(); 174 switch (entry.getOp()) { 175 case AppOpsManager.OP_MONITOR_LOCATION: 176 normalBattery = true; 177 break; 178 case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION: 179 highBattery = true; 180 break; 181 default: 182 break; 183 } 184 } 185 } 186 187 if (!highBattery && !normalBattery) { 188 if (Log.isLoggable(TAG, Log.VERBOSE)) { 189 Log.v(TAG, packageName + " hadn't used location within the time interval."); 190 } 191 return null; 192 } 193 194 // The package is fresh enough, continue. 195 int uid = ops.getUid(); 196 int userId = UserHandle.getUserId(uid); 197 198 Request request = null; 199 try { 200 ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser( 201 packageName, PackageManager.GET_META_DATA, userId); 202 if (appInfo == null) { 203 Log.w(TAG, "Null application info retrieved for package " + packageName 204 + ", userId " + userId); 205 return null; 206 } 207 208 final UserHandle userHandle = new UserHandle(userId); 209 Drawable icon = mDrawableFactory.getBadgedIcon(appInfo, userId); 210 CharSequence appLabel = mPackageManager.getApplicationLabel(appInfo); 211 CharSequence badgedAppLabel = mPackageManager.getUserBadgedLabel(appLabel, userHandle); 212 if (appLabel.toString().contentEquals(badgedAppLabel)) { 213 // If badged label is not different from original then no need for it as 214 // a separate content description. 215 badgedAppLabel = null; 216 } 217 request = new Request(packageName, userHandle, icon, appLabel, highBattery, 218 badgedAppLabel, locationRequestFinishTime); 219 } catch (NameNotFoundException e) { 220 Log.w(TAG, "package name not found for " + packageName + ", userId " + userId); 221 } 222 return request; 223 } 224 225 public static class Request { 226 public final String packageName; 227 public final UserHandle userHandle; 228 public final Drawable icon; 229 public final CharSequence label; 230 public final boolean isHighBattery; 231 public final CharSequence contentDescription; 232 public final long requestFinishTime; 233 234 private Request(String packageName, UserHandle userHandle, Drawable icon, 235 CharSequence label, boolean isHighBattery, CharSequence contentDescription, 236 long requestFinishTime) { 237 this.packageName = packageName; 238 this.userHandle = userHandle; 239 this.icon = icon; 240 this.label = label; 241 this.isHighBattery = isHighBattery; 242 this.contentDescription = contentDescription; 243 this.requestFinishTime = requestFinishTime; 244 } 245 } 246 } 247