blob: 6fd874989c3584246b0c9ebb5b4acd5d6b44bec5 [file] [log] [blame]
Tony Mantler65eb4ad2015-11-04 15:52:25 -08001/*
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
17package com.android.settingslib.location;
18
Tony Mantler65eb4ad2015-11-04 15:52:25 -080019import android.app.AppOpsManager;
20import android.content.Context;
Lifu Tangb1317572019-05-07 14:40:04 -070021import android.content.PermissionChecker;
Tony Mantler65eb4ad2015-11-04 15:52:25 -080022import android.content.pm.ApplicationInfo;
Tony Mantler65eb4ad2015-11-04 15:52:25 -080023import android.content.pm.PackageManager;
Maggie704c4232017-12-11 18:02:36 -080024import android.content.pm.PackageManager.NameNotFoundException;
Tony Mantler65eb4ad2015-11-04 15:52:25 -080025import android.graphics.drawable.Drawable;
Tony Mantler65eb4ad2015-11-04 15:52:25 -080026import android.os.UserHandle;
27import android.os.UserManager;
Wei Wangec281b12018-05-01 11:21:31 -070028import android.text.format.DateUtils;
Sunny Goyalbab30752017-04-12 15:36:42 -070029import android.util.IconDrawableFactory;
Tony Mantler65eb4ad2015-11-04 15:52:25 -080030import android.util.Log;
Fan Zhangf7802ea2018-08-28 15:15:19 -070031
32import androidx.annotation.VisibleForTesting;
33
Tony Mantler65eb4ad2015-11-04 15:52:25 -080034import java.util.ArrayList;
Maggie704c4232017-12-11 18:02:36 -080035import java.util.Collections;
36import java.util.Comparator;
Tony Mantler65eb4ad2015-11-04 15:52:25 -080037import java.util.List;
38
39/**
40 * Retrieves the information of applications which accessed location recently.
41 */
42public class RecentLocationApps {
43 private static final String TAG = RecentLocationApps.class.getSimpleName();
Maggie704c4232017-12-11 18:02:36 -080044 @VisibleForTesting
45 static final String ANDROID_SYSTEM_PACKAGE_NAME = "android";
Tony Mantler65eb4ad2015-11-04 15:52:25 -080046
Wei Wangec281b12018-05-01 11:21:31 -070047 // Keep last 24 hours of location app information.
48 private static final long RECENT_TIME_INTERVAL_MILLIS = DateUtils.DAY_IN_MILLIS;
Tony Mantler65eb4ad2015-11-04 15:52:25 -080049
Maggie704c4232017-12-11 18:02:36 -080050 @VisibleForTesting
Lifu Tangb1317572019-05-07 14:40:04 -070051 static final int[] LOCATION_REQUEST_OPS = new int[]{
Tony Mantler65eb4ad2015-11-04 15:52:25 -080052 AppOpsManager.OP_MONITOR_LOCATION,
53 AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
54 };
Lifu Tangb1317572019-05-07 14:40:04 -070055 @VisibleForTesting
56 static final int[] LOCATION_PERMISSION_OPS = new int[]{
57 AppOpsManager.OP_FINE_LOCATION,
58 AppOpsManager.OP_COARSE_LOCATION,
59 };
Tony Mantler65eb4ad2015-11-04 15:52:25 -080060
61 private final PackageManager mPackageManager;
62 private final Context mContext;
Sunny Goyalbab30752017-04-12 15:36:42 -070063 private final IconDrawableFactory mDrawableFactory;
Tony Mantler65eb4ad2015-11-04 15:52:25 -080064
65 public RecentLocationApps(Context context) {
66 mContext = context;
67 mPackageManager = context.getPackageManager();
Sunny Goyalbab30752017-04-12 15:36:42 -070068 mDrawableFactory = IconDrawableFactory.newInstance(context);
Tony Mantler65eb4ad2015-11-04 15:52:25 -080069 }
70
71 /**
72 * Fills a list of applications which queried location recently within specified time.
Maggie704c4232017-12-11 18:02:36 -080073 * Apps are sorted by recency. Apps with more recent location requests are in the front.
Tony Mantler65eb4ad2015-11-04 15:52:25 -080074 */
Lifu Tangb1317572019-05-07 14:40:04 -070075 public List<Request> getAppList(boolean showSystemApps) {
76 // Retrieve a location usage list from AppOps
77 PackageManager pm = mContext.getPackageManager();
Tony Mantler65eb4ad2015-11-04 15:52:25 -080078 // Retrieve a location usage list from AppOps
79 AppOpsManager aoManager =
80 (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
Lifu Tangb1317572019-05-07 14:40:04 -070081 List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(LOCATION_REQUEST_OPS);
Tony Mantler65eb4ad2015-11-04 15:52:25 -080082
Tony Mantlerd6cc79092016-02-09 14:12:43 -080083 final int appOpsCount = appOps != null ? appOps.size() : 0;
84
Tony Mantler65eb4ad2015-11-04 15:52:25 -080085 // Process the AppOps list and generate a preference list.
Tony Mantlerd6cc79092016-02-09 14:12:43 -080086 ArrayList<Request> requests = new ArrayList<>(appOpsCount);
Tony Mantler65eb4ad2015-11-04 15:52:25 -080087 final long now = System.currentTimeMillis();
88 final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
89 final List<UserHandle> profiles = um.getUserProfiles();
90
Tony Mantlerd6cc79092016-02-09 14:12:43 -080091 for (int i = 0; i < appOpsCount; ++i) {
Tony Mantler65eb4ad2015-11-04 15:52:25 -080092 AppOpsManager.PackageOps ops = appOps.get(i);
Tony Mantler65eb4ad2015-11-04 15:52:25 -080093 String packageName = ops.getPackageName();
94 int uid = ops.getUid();
Lifu Tangb1317572019-05-07 14:40:04 -070095 final UserHandle user = UserHandle.getUserHandleForUid(uid);
96
97 // Don't show apps belonging to background users except managed users.
98 if (!profiles.contains(user)) {
Tony Mantler65eb4ad2015-11-04 15:52:25 -080099 continue;
100 }
Lifu Tangb1317572019-05-07 14:40:04 -0700101
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 }
Tony Mantler65eb4ad2015-11-04 15:52:25 -0800132 }
133 }
Maggie704c4232017-12-11 18:02:36 -0800134 return requests;
135 }
Tony Mantler65eb4ad2015-11-04 15:52:25 -0800136
Lifu Tangb1317572019-05-07 14:40:04 -0700137 /**
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);
Maggie704c4232017-12-11 18:02:36 -0800145 // 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 }));
Tony Mantler65eb4ad2015-11-04 15:52:25 -0800152 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;
Maggie704c4232017-12-11 18:02:36 -0800168 long locationRequestFinishTime = 0L;
Tony Mantler65eb4ad2015-11-04 15:52:25 -0800169 // 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) {
Maggie704c4232017-12-11 18:02:36 -0800173 locationRequestFinishTime = entry.getTime() + entry.getDuration();
Tony Mantler65eb4ad2015-11-04 15:52:25 -0800174 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.
Tony Mantler65eb4ad2015-11-04 15:52:25 -0800195 int uid = ops.getUid();
196 int userId = UserHandle.getUserId(uid);
197
198 Request request = null;
199 try {
Maggie704c4232017-12-11 18:02:36 -0800200 ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
201 packageName, PackageManager.GET_META_DATA, userId);
Tony Mantler65eb4ad2015-11-04 15:52:25 -0800202 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);
Sunny Goyalbab30752017-04-12 15:36:42 -0700209 Drawable icon = mDrawableFactory.getBadgedIcon(appInfo, userId);
Tony Mantler65eb4ad2015-11-04 15:52:25 -0800210 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,
Maggie704c4232017-12-11 18:02:36 -0800218 badgedAppLabel, locationRequestFinishTime);
219 } catch (NameNotFoundException e) {
220 Log.w(TAG, "package name not found for " + packageName + ", userId " + userId);
Tony Mantler65eb4ad2015-11-04 15:52:25 -0800221 }
Tony Mantler65eb4ad2015-11-04 15:52:25 -0800222 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;
Maggie704c4232017-12-11 18:02:36 -0800232 public final long requestFinishTime;
Tony Mantler65eb4ad2015-11-04 15:52:25 -0800233
234 private Request(String packageName, UserHandle userHandle, Drawable icon,
Maggie704c4232017-12-11 18:02:36 -0800235 CharSequence label, boolean isHighBattery, CharSequence contentDescription,
236 long requestFinishTime) {
Tony Mantler65eb4ad2015-11-04 15:52:25 -0800237 this.packageName = packageName;
238 this.userHandle = userHandle;
239 this.icon = icon;
240 this.label = label;
241 this.isHighBattery = isHighBattery;
242 this.contentDescription = contentDescription;
Maggie704c4232017-12-11 18:02:36 -0800243 this.requestFinishTime = requestFinishTime;
Tony Mantler65eb4ad2015-11-04 15:52:25 -0800244 }
245 }
246}