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