2 * Copyright (C) 2015 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settingslib.location;
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;
32 import androidx.annotation.VisibleForTesting;
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.Comparator;
37 import java.util.List;
40 * Retrieves the information of applications which accessed location recently.
42 public class RecentLocationApps {
43 private static final String TAG = RecentLocationApps.class.getSimpleName();
45 static final String ANDROID_SYSTEM_PACKAGE_NAME = "android";
47 // Keep last 24 hours of location app information.
48 private static final long RECENT_TIME_INTERVAL_MILLIS = DateUtils.DAY_IN_MILLIS;
51 static final int[] LOCATION_REQUEST_OPS = new int[]{
52 AppOpsManager.OP_MONITOR_LOCATION,
53 AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
56 static final int[] LOCATION_PERMISSION_OPS = new int[]{
57 AppOpsManager.OP_FINE_LOCATION,
58 AppOpsManager.OP_COARSE_LOCATION,
61 private final PackageManager mPackageManager;
62 private final Context mContext;
63 private final IconDrawableFactory mDrawableFactory;
65 public RecentLocationApps(Context context) {
67 mPackageManager = context.getPackageManager();
68 mDrawableFactory = IconDrawableFactory.newInstance(context);
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.
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);
83 final int appOpsCount = appOps != null ? appOps.size() : 0;
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();
91 for (int i = 0; i < appOpsCount; ++i) {
92 AppOpsManager.PackageOps ops = appOps.get(i);
93 // Don't show the Android System in the list - it's not actionable for the user.
94 // Also don't show apps belonging to background users except managed users.
95 String packageName = ops.getPackageName();
96 int uid = ops.getUid();
97 final UserHandle user = UserHandle.getUserHandleForUid(uid);
100 (uid == android.os.Process.SYSTEM_UID) && ANDROID_SYSTEM_PACKAGE_NAME.equals(
102 if (isAndroidOs || !profiles.contains(user)) {
106 // Don't show apps that do not have user sensitive location permissions
107 boolean showApp = true;
108 if (!showSystemApps) {
109 for (int op : LOCATION_PERMISSION_OPS) {
110 final String permission = AppOpsManager.opToPermission(op);
111 final int permissionFlags = pm.getPermissionFlags(permission, packageName,
113 if (PermissionChecker.checkPermissionForPreflight(mContext, permission,
114 PermissionChecker.PID_UNKNOWN, uid, packageName)
115 == PermissionChecker.PERMISSION_GRANTED) {
117 & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED)
124 & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) == 0) {
132 Request request = getRequestFromOps(now, ops);
133 if (request != null) {
134 requests.add(request);
142 * Gets a list of apps that requested for location recently, sorting by recency.
144 * @param showSystemApps whether includes system apps in the list.
145 * @return the list of apps that recently requested for location.
147 public List<Request> getAppListSorted(boolean showSystemApps) {
148 List<Request> requests = getAppList(showSystemApps);
149 // Sort the list of Requests by recency. Most recent request first.
150 Collections.sort(requests, Collections.reverseOrder(new Comparator<Request>() {
152 public int compare(Request request1, Request request2) {
153 return Long.compare(request1.requestFinishTime, request2.requestFinishTime);
160 * Creates a Request entry for the given PackageOps.
162 * This method examines the time interval of the PackageOps first. If the PackageOps is older
163 * than the designated interval, this method ignores the PackageOps object and returns null.
164 * When the PackageOps is fresh enough, this method returns a Request object for the package
166 private Request getRequestFromOps(long now,
167 AppOpsManager.PackageOps ops) {
168 String packageName = ops.getPackageName();
169 List<AppOpsManager.OpEntry> entries = ops.getOps();
170 boolean highBattery = false;
171 boolean normalBattery = false;
172 long locationRequestFinishTime = 0L;
173 // Earliest time for a location request to end and still be shown in list.
174 long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS;
175 for (AppOpsManager.OpEntry entry : entries) {
176 if (entry.isRunning() || entry.getTime() >= recentLocationCutoffTime) {
177 locationRequestFinishTime = entry.getTime() + entry.getDuration();
178 switch (entry.getOp()) {
179 case AppOpsManager.OP_MONITOR_LOCATION:
180 normalBattery = true;
182 case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION:
191 if (!highBattery && !normalBattery) {
192 if (Log.isLoggable(TAG, Log.VERBOSE)) {
193 Log.v(TAG, packageName + " hadn't used location within the time interval.");
198 // The package is fresh enough, continue.
199 int uid = ops.getUid();
200 int userId = UserHandle.getUserId(uid);
202 Request request = null;
204 ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
205 packageName, PackageManager.GET_META_DATA, userId);
206 if (appInfo == null) {
207 Log.w(TAG, "Null application info retrieved for package " + packageName
208 + ", userId " + userId);
212 final UserHandle userHandle = new UserHandle(userId);
213 Drawable icon = mDrawableFactory.getBadgedIcon(appInfo, userId);
214 CharSequence appLabel = mPackageManager.getApplicationLabel(appInfo);
215 CharSequence badgedAppLabel = mPackageManager.getUserBadgedLabel(appLabel, userHandle);
216 if (appLabel.toString().contentEquals(badgedAppLabel)) {
217 // If badged label is not different from original then no need for it as
218 // a separate content description.
219 badgedAppLabel = null;
221 request = new Request(packageName, userHandle, icon, appLabel, highBattery,
222 badgedAppLabel, locationRequestFinishTime);
223 } catch (NameNotFoundException e) {
224 Log.w(TAG, "package name not found for " + packageName + ", userId " + userId);
229 public static class Request {
230 public final String packageName;
231 public final UserHandle userHandle;
232 public final Drawable icon;
233 public final CharSequence label;
234 public final boolean isHighBattery;
235 public final CharSequence contentDescription;
236 public final long requestFinishTime;
238 private Request(String packageName, UserHandle userHandle, Drawable icon,
239 CharSequence label, boolean isHighBattery, CharSequence contentDescription,
240 long requestFinishTime) {
241 this.packageName = packageName;
242 this.userHandle = userHandle;
245 this.isHighBattery = isHighBattery;
246 this.contentDescription = contentDescription;
247 this.requestFinishTime = requestFinishTime;