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