1 /* 2 * Copyright (C) 2018 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.time.Clock; 35 import java.util.ArrayList; 36 import java.util.Collections; 37 import java.util.Comparator; 38 import java.util.List; 39 40 /** 41 * Retrieves the information of applications which accessed location recently. 42 */ 43 public class RecentLocationAccesses { 44 private static final String TAG = RecentLocationAccesses.class.getSimpleName(); 45 @VisibleForTesting 46 static final String ANDROID_SYSTEM_PACKAGE_NAME = "android"; 47 48 // Keep last 24 hours of location app information. 49 private static final long RECENT_TIME_INTERVAL_MILLIS = DateUtils.DAY_IN_MILLIS; 50 51 /** The flags for querying ops that are trusted for showing in the UI. */ 52 public static final int TRUSTED_STATE_FLAGS = AppOpsManager.OP_FLAG_SELF 53 | AppOpsManager.OP_FLAG_UNTRUSTED_PROXY 54 | AppOpsManager.OP_FLAG_TRUSTED_PROXIED; 55 56 @VisibleForTesting 57 static final int[] LOCATION_OPS = new int[]{ 58 AppOpsManager.OP_FINE_LOCATION, 59 AppOpsManager.OP_COARSE_LOCATION, 60 }; 61 62 private final PackageManager mPackageManager; 63 private final Context mContext; 64 private final IconDrawableFactory mDrawableFactory; 65 private final Clock mClock; 66 67 public RecentLocationAccesses(Context context) { 68 this(context, Clock.systemDefaultZone()); 69 } 70 71 @VisibleForTesting 72 RecentLocationAccesses(Context context, Clock clock) { 73 mContext = context; 74 mPackageManager = context.getPackageManager(); 75 mDrawableFactory = IconDrawableFactory.newInstance(context); 76 mClock = clock; 77 } 78 79 /** 80 * Fills a list of applications which queried location recently within specified time. 81 * Apps are sorted by recency. Apps with more recent location accesses are in the front. 82 */ 83 public List<Access> getAppList() { 84 // Retrieve a location usage list from AppOps 85 PackageManager pm = mContext.getPackageManager(); 86 AppOpsManager aoManager = 87 (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); 88 List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(LOCATION_OPS); 89 90 final int appOpsCount = appOps != null ? appOps.size() : 0; 91 92 // Process the AppOps list and generate a preference list. 93 ArrayList<Access> accesses = new ArrayList<>(appOpsCount); 94 final long now = mClock.millis(); 95 final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 96 final List<UserHandle> profiles = um.getUserProfiles(); 97 98 for (int i = 0; i < appOpsCount; ++i) { 99 AppOpsManager.PackageOps ops = appOps.get(i); 100 String packageName = ops.getPackageName(); 101 int uid = ops.getUid(); 102 UserHandle user = UserHandle.getUserHandleForUid(uid); 103 104 // Don't show apps belonging to background users except managed users. 105 if (!profiles.contains(user)) { 106 continue; 107 } 108 109 // Don't show apps that do not have user sensitive location permissions 110 boolean showApp = true; 111 for (int op : LOCATION_OPS) { 112 final String permission = AppOpsManager.opToPermission(op); 113 final int permissionFlags = pm.getPermissionFlags(permission, packageName, user); 114 if (PermissionChecker.checkPermission(mContext, permission, -1, uid, packageName) 115 == PermissionChecker.PERMISSION_GRANTED) { 116 if ((permissionFlags 117 & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) == 0) { 118 showApp = false; 119 break; 120 } 121 } else { 122 if ((permissionFlags 123 & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) == 0) { 124 showApp = false; 125 break; 126 } 127 } 128 } 129 if (showApp) { 130 Access access = getAccessFromOps(now, ops); 131 if (access != null) { 132 accesses.add(access); 133 } 134 } 135 } 136 return accesses; 137 } 138 139 public List<Access> getAppListSorted() { 140 List<Access> accesses = getAppList(); 141 // Sort the list of Access by recency. Most recent accesses first. 142 Collections.sort(accesses, Collections.reverseOrder(new Comparator<Access>() { 143 @Override 144 public int compare(Access access1, Access access2) { 145 return Long.compare(access1.accessFinishTime, access2.accessFinishTime); 146 } 147 })); 148 return accesses; 149 } 150 151 /** 152 * Creates a Access entry for the given PackageOps. 153 * 154 * This method examines the time interval of the PackageOps first. If the PackageOps is older 155 * than the designated interval, this method ignores the PackageOps object and returns null. 156 * When the PackageOps is fresh enough, this method returns a Access object for the package 157 */ 158 private Access getAccessFromOps(long now, 159 AppOpsManager.PackageOps ops) { 160 String packageName = ops.getPackageName(); 161 List<AppOpsManager.OpEntry> entries = ops.getOps(); 162 long locationAccessFinishTime = 0L; 163 // Earliest time for a location access to end and still be shown in list. 164 long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS; 165 for (AppOpsManager.OpEntry entry : entries) { 166 locationAccessFinishTime = entry.getLastAccessTime(TRUSTED_STATE_FLAGS); 167 } 168 // Bail out if the entry is out of date. 169 if (locationAccessFinishTime < recentLocationCutoffTime) { 170 return null; 171 } 172 173 // The package is fresh enough, continue. 174 int uid = ops.getUid(); 175 int userId = UserHandle.getUserId(uid); 176 177 Access access = null; 178 try { 179 ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser( 180 packageName, PackageManager.GET_META_DATA, userId); 181 if (appInfo == null) { 182 Log.w(TAG, "Null application info retrieved for package " + packageName 183 + ", userId " + userId); 184 return null; 185 } 186 187 final UserHandle userHandle = new UserHandle(userId); 188 Drawable icon = mDrawableFactory.getBadgedIcon(appInfo, userId); 189 CharSequence appLabel = mPackageManager.getApplicationLabel(appInfo); 190 CharSequence badgedAppLabel = mPackageManager.getUserBadgedLabel(appLabel, userHandle); 191 if (appLabel.toString().contentEquals(badgedAppLabel)) { 192 // If badged label is not different from original then no need for it as 193 // a separate content description. 194 badgedAppLabel = null; 195 } 196 access = new Access(packageName, userHandle, icon, appLabel, badgedAppLabel, 197 locationAccessFinishTime); 198 } catch (NameNotFoundException e) { 199 Log.w(TAG, "package name not found for " + packageName + ", userId " + userId); 200 } 201 return access; 202 } 203 204 public static class Access { 205 public final String packageName; 206 public final UserHandle userHandle; 207 public final Drawable icon; 208 public final CharSequence label; 209 public final CharSequence contentDescription; 210 public final long accessFinishTime; 211 212 public Access(String packageName, UserHandle userHandle, Drawable icon, 213 CharSequence label, CharSequence contentDescription, 214 long accessFinishTime) { 215 this.packageName = packageName; 216 this.userHandle = userHandle; 217 this.icon = icon; 218 this.label = label; 219 this.contentDescription = contentDescription; 220 this.accessFinishTime = accessFinishTime; 221 } 222 } 223 } 224