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 RecentLocationApps(Context context)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 */ getAppList(boolean showSystemApps)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 */ getAppListSorted(boolean showSystemApps)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 */ getRequestFromOps(long now, AppOpsManager.PackageOps ops)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 Request(String packageName, UserHandle userHandle, Drawable icon, CharSequence label, boolean isHighBattery, CharSequence contentDescription, long requestFinishTime)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