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 RecentLocationAccesses(Context context)67 public RecentLocationAccesses(Context context) { 68 this(context, Clock.systemDefaultZone()); 69 } 70 71 @VisibleForTesting RecentLocationAccesses(Context context, Clock clock)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 */ getAppList()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.checkPermissionForPreflight(mContext, permission, 115 PermissionChecker.PID_UNKNOWN, uid, packageName) 116 == PermissionChecker.PERMISSION_GRANTED) { 117 if ((permissionFlags 118 & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) == 0) { 119 showApp = false; 120 break; 121 } 122 } else { 123 if ((permissionFlags 124 & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) == 0) { 125 showApp = false; 126 break; 127 } 128 } 129 } 130 if (showApp) { 131 Access access = getAccessFromOps(now, ops); 132 if (access != null) { 133 accesses.add(access); 134 } 135 } 136 } 137 return accesses; 138 } 139 getAppListSorted()140 public List<Access> getAppListSorted() { 141 List<Access> accesses = getAppList(); 142 // Sort the list of Access by recency. Most recent accesses first. 143 Collections.sort(accesses, Collections.reverseOrder(new Comparator<Access>() { 144 @Override 145 public int compare(Access access1, Access access2) { 146 return Long.compare(access1.accessFinishTime, access2.accessFinishTime); 147 } 148 })); 149 return accesses; 150 } 151 152 /** 153 * Creates a Access entry for the given PackageOps. 154 * 155 * This method examines the time interval of the PackageOps first. If the PackageOps is older 156 * than the designated interval, this method ignores the PackageOps object and returns null. 157 * When the PackageOps is fresh enough, this method returns a Access object for the package 158 */ getAccessFromOps(long now, AppOpsManager.PackageOps ops)159 private Access getAccessFromOps(long now, 160 AppOpsManager.PackageOps ops) { 161 String packageName = ops.getPackageName(); 162 List<AppOpsManager.OpEntry> entries = ops.getOps(); 163 long locationAccessFinishTime = 0L; 164 // Earliest time for a location access to end and still be shown in list. 165 long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS; 166 for (AppOpsManager.OpEntry entry : entries) { 167 locationAccessFinishTime = entry.getLastAccessTime(TRUSTED_STATE_FLAGS); 168 } 169 // Bail out if the entry is out of date. 170 if (locationAccessFinishTime < recentLocationCutoffTime) { 171 return null; 172 } 173 174 // The package is fresh enough, continue. 175 int uid = ops.getUid(); 176 int userId = UserHandle.getUserId(uid); 177 178 Access access = null; 179 try { 180 ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser( 181 packageName, PackageManager.GET_META_DATA, userId); 182 if (appInfo == null) { 183 Log.w(TAG, "Null application info retrieved for package " + packageName 184 + ", userId " + userId); 185 return null; 186 } 187 188 final UserHandle userHandle = new UserHandle(userId); 189 Drawable icon = mDrawableFactory.getBadgedIcon(appInfo, userId); 190 CharSequence appLabel = mPackageManager.getApplicationLabel(appInfo); 191 CharSequence badgedAppLabel = mPackageManager.getUserBadgedLabel(appLabel, userHandle); 192 if (appLabel.toString().contentEquals(badgedAppLabel)) { 193 // If badged label is not different from original then no need for it as 194 // a separate content description. 195 badgedAppLabel = null; 196 } 197 access = new Access(packageName, userHandle, icon, appLabel, badgedAppLabel, 198 locationAccessFinishTime); 199 } catch (NameNotFoundException e) { 200 Log.w(TAG, "package name not found for " + packageName + ", userId " + userId); 201 } 202 return access; 203 } 204 205 public static class Access { 206 public final String packageName; 207 public final UserHandle userHandle; 208 public final Drawable icon; 209 public final CharSequence label; 210 public final CharSequence contentDescription; 211 public final long accessFinishTime; 212 Access(String packageName, UserHandle userHandle, Drawable icon, CharSequence label, CharSequence contentDescription, long accessFinishTime)213 public Access(String packageName, UserHandle userHandle, Drawable icon, 214 CharSequence label, CharSequence contentDescription, 215 long accessFinishTime) { 216 this.packageName = packageName; 217 this.userHandle = userHandle; 218 this.icon = icon; 219 this.label = label; 220 this.contentDescription = contentDescription; 221 this.accessFinishTime = accessFinishTime; 222 } 223 } 224 } 225