• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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