• 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.pm.ApplicationInfo;
22 import android.content.pm.PackageManager;
23 import android.content.pm.PackageManager.NameNotFoundException;
24 import android.graphics.drawable.Drawable;
25 import android.os.Process;
26 import android.os.UserHandle;
27 import android.os.UserManager;
28 import android.support.annotation.VisibleForTesting;
29 import android.text.format.DateUtils;
30 import android.util.IconDrawableFactory;
31 import android.util.Log;
32 import java.util.ArrayList;
33 import java.util.Collections;
34 import java.util.Comparator;
35 import java.util.List;
36 
37 /**
38  * Retrieves the information of applications which accessed location recently.
39  */
40 public class RecentLocationApps {
41     private static final String TAG = RecentLocationApps.class.getSimpleName();
42     @VisibleForTesting
43     static final String ANDROID_SYSTEM_PACKAGE_NAME = "android";
44 
45     // Keep last 24 hours of location app information.
46     private static final long RECENT_TIME_INTERVAL_MILLIS = DateUtils.DAY_IN_MILLIS;
47 
48     @VisibleForTesting
49     static final int[] LOCATION_OPS = new int[] {
50             AppOpsManager.OP_MONITOR_LOCATION,
51             AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
52     };
53 
54     private final PackageManager mPackageManager;
55     private final Context mContext;
56     private final IconDrawableFactory mDrawableFactory;
57 
RecentLocationApps(Context context)58     public RecentLocationApps(Context context) {
59         mContext = context;
60         mPackageManager = context.getPackageManager();
61         mDrawableFactory = IconDrawableFactory.newInstance(context);
62     }
63 
64     /**
65      * Fills a list of applications which queried location recently within specified time.
66      * Apps are sorted by recency. Apps with more recent location requests are in the front.
67      */
getAppList()68     public List<Request> getAppList() {
69         // Retrieve a location usage list from AppOps
70         AppOpsManager aoManager =
71                 (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
72         List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(LOCATION_OPS);
73 
74         final int appOpsCount = appOps != null ? appOps.size() : 0;
75 
76         // Process the AppOps list and generate a preference list.
77         ArrayList<Request> requests = new ArrayList<>(appOpsCount);
78         final long now = System.currentTimeMillis();
79         final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
80         final List<UserHandle> profiles = um.getUserProfiles();
81 
82         for (int i = 0; i < appOpsCount; ++i) {
83             AppOpsManager.PackageOps ops = appOps.get(i);
84             // Don't show the Android System in the list - it's not actionable for the user.
85             // Also don't show apps belonging to background users except managed users.
86             String packageName = ops.getPackageName();
87             int uid = ops.getUid();
88             int userId = UserHandle.getUserId(uid);
89             boolean isAndroidOs =
90                     (uid == Process.SYSTEM_UID) && ANDROID_SYSTEM_PACKAGE_NAME.equals(packageName);
91             if (isAndroidOs || !profiles.contains(new UserHandle(userId))) {
92                 continue;
93             }
94             Request request = getRequestFromOps(now, ops);
95             if (request != null) {
96                 requests.add(request);
97             }
98         }
99         return requests;
100     }
101 
getAppListSorted()102     public List<Request> getAppListSorted() {
103         List<Request> requests = getAppList();
104         // Sort the list of Requests by recency. Most recent request first.
105         Collections.sort(requests, Collections.reverseOrder(new Comparator<Request>() {
106             @Override
107             public int compare(Request request1, Request request2) {
108                 return Long.compare(request1.requestFinishTime, request2.requestFinishTime);
109             }
110         }));
111         return requests;
112     }
113 
114     /**
115      * Creates a Request entry for the given PackageOps.
116      *
117      * This method examines the time interval of the PackageOps first. If the PackageOps is older
118      * than the designated interval, this method ignores the PackageOps object and returns null.
119      * When the PackageOps is fresh enough, this method returns a Request object for the package
120      */
getRequestFromOps(long now, AppOpsManager.PackageOps ops)121     private Request getRequestFromOps(long now,
122             AppOpsManager.PackageOps ops) {
123         String packageName = ops.getPackageName();
124         List<AppOpsManager.OpEntry> entries = ops.getOps();
125         boolean highBattery = false;
126         boolean normalBattery = false;
127         long locationRequestFinishTime = 0L;
128         // Earliest time for a location request to end and still be shown in list.
129         long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS;
130         for (AppOpsManager.OpEntry entry : entries) {
131             if (entry.isRunning() || entry.getTime() >= recentLocationCutoffTime) {
132                 locationRequestFinishTime = entry.getTime() + entry.getDuration();
133                 switch (entry.getOp()) {
134                     case AppOpsManager.OP_MONITOR_LOCATION:
135                         normalBattery = true;
136                         break;
137                     case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION:
138                         highBattery = true;
139                         break;
140                     default:
141                         break;
142                 }
143             }
144         }
145 
146         if (!highBattery && !normalBattery) {
147             if (Log.isLoggable(TAG, Log.VERBOSE)) {
148                 Log.v(TAG, packageName + " hadn't used location within the time interval.");
149             }
150             return null;
151         }
152 
153         // The package is fresh enough, continue.
154         int uid = ops.getUid();
155         int userId = UserHandle.getUserId(uid);
156 
157         Request request = null;
158         try {
159             ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
160                     packageName, PackageManager.GET_META_DATA, userId);
161             if (appInfo == null) {
162                 Log.w(TAG, "Null application info retrieved for package " + packageName
163                         + ", userId " + userId);
164                 return null;
165             }
166 
167             final UserHandle userHandle = new UserHandle(userId);
168             Drawable icon = mDrawableFactory.getBadgedIcon(appInfo, userId);
169             CharSequence appLabel = mPackageManager.getApplicationLabel(appInfo);
170             CharSequence badgedAppLabel = mPackageManager.getUserBadgedLabel(appLabel, userHandle);
171             if (appLabel.toString().contentEquals(badgedAppLabel)) {
172                 // If badged label is not different from original then no need for it as
173                 // a separate content description.
174                 badgedAppLabel = null;
175             }
176             request = new Request(packageName, userHandle, icon, appLabel, highBattery,
177                     badgedAppLabel, locationRequestFinishTime);
178         } catch (NameNotFoundException e) {
179             Log.w(TAG, "package name not found for " + packageName + ", userId " + userId);
180         }
181         return request;
182     }
183 
184     public static class Request {
185         public final String packageName;
186         public final UserHandle userHandle;
187         public final Drawable icon;
188         public final CharSequence label;
189         public final boolean isHighBattery;
190         public final CharSequence contentDescription;
191         public final long requestFinishTime;
192 
Request(String packageName, UserHandle userHandle, Drawable icon, CharSequence label, boolean isHighBattery, CharSequence contentDescription, long requestFinishTime)193         private Request(String packageName, UserHandle userHandle, Drawable icon,
194                 CharSequence label, boolean isHighBattery, CharSequence contentDescription,
195                 long requestFinishTime) {
196             this.packageName = packageName;
197             this.userHandle = userHandle;
198             this.icon = icon;
199             this.label = label;
200             this.isHighBattery = isHighBattery;
201             this.contentDescription = contentDescription;
202             this.requestFinishTime = requestFinishTime;
203         }
204     }
205 }
206