OSDN Git Service

Merge "docs: Add documentation for equals() method" into qt-dev am: 732a127636
[android-x86/frameworks-base.git] / packages / SettingsLib / src / com / android / settingslib / location / RecentLocationApps.java
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
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      */
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      */
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      */
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
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;
243             this.icon = icon;
244             this.label = label;
245             this.isHighBattery = isHighBattery;
246             this.contentDescription = contentDescription;
247             this.requestFinishTime = requestFinishTime;
248         }
249     }
250 }