• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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             // 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);
98 
99             boolean isAndroidOs =
100                     (uid == android.os.Process.SYSTEM_UID) && ANDROID_SYSTEM_PACKAGE_NAME.equals(
101                             packageName);
102             if (isAndroidOs || !profiles.contains(user)) {
103                 continue;
104             }
105 
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,
112                             user);
113                     if (PermissionChecker.checkPermissionForPreflight(mContext, permission,
114                             PermissionChecker.PID_UNKNOWN, uid, packageName)
115                                     == PermissionChecker.PERMISSION_GRANTED) {
116                         if ((permissionFlags
117                                 & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED)
118                                 == 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             }
131             if (showApp) {
132                 Request request = getRequestFromOps(now, ops);
133                 if (request != null) {
134                     requests.add(request);
135                 }
136             }
137         }
138         return requests;
139     }
140 
141     /**
142      * Gets a list of apps that requested for location recently, sorting by recency.
143      *
144      * @param showSystemApps whether includes system apps in the list.
145      * @return the list of apps that recently requested for location.
146      */
getAppListSorted(boolean showSystemApps)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>() {
151             @Override
152             public int compare(Request request1, Request request2) {
153                 return Long.compare(request1.requestFinishTime, request2.requestFinishTime);
154             }
155         }));
156         return requests;
157     }
158 
159     /**
160      * Creates a Request entry for the given PackageOps.
161      *
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
165      */
getRequestFromOps(long now, AppOpsManager.PackageOps ops)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;
181                         break;
182                     case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION:
183                         highBattery = true;
184                         break;
185                     default:
186                         break;
187                 }
188             }
189         }
190 
191         if (!highBattery && !normalBattery) {
192             if (Log.isLoggable(TAG, Log.VERBOSE)) {
193                 Log.v(TAG, packageName + " hadn't used location within the time interval.");
194             }
195             return null;
196         }
197 
198         // The package is fresh enough, continue.
199         int uid = ops.getUid();
200         int userId = UserHandle.getUserId(uid);
201 
202         Request request = null;
203         try {
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);
209                 return null;
210             }
211 
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;
220             }
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);
225         }
226         return request;
227     }
228 
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;
237 
Request(String packageName, UserHandle userHandle, Drawable icon, CharSequence label, boolean isHighBattery, CharSequence contentDescription, long requestFinishTime)238         public 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;
243             this.icon = icon;
244             this.label = label;
245             this.isHighBattery = isHighBattery;
246             this.contentDescription = contentDescription;
247             this.requestFinishTime = requestFinishTime;
248         }
249     }
250 }
251