blob: 4ac3ce436f824edfb2cef4b6975b751b32eb08e1 [file] [log] [blame]
Lifu Tang60db4e72018-12-10 16:26:11 -08001/*
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
17package com.android.settingslib.location;
18
19import android.app.AppOpsManager;
20import android.content.Context;
21import android.content.pm.ApplicationInfo;
22import android.content.pm.PackageManager;
23import android.content.pm.PackageManager.NameNotFoundException;
24import android.graphics.drawable.Drawable;
25import android.os.Process;
26import android.os.UserHandle;
27import android.os.UserManager;
28import android.text.format.DateUtils;
29import android.util.IconDrawableFactory;
30import android.util.Log;
31
32import androidx.annotation.VisibleForTesting;
33
Lifu Tang054cfe52018-12-26 12:57:22 -080034import java.time.Clock;
Lifu Tang60db4e72018-12-10 16:26:11 -080035import java.util.ArrayList;
36import java.util.Collections;
37import java.util.Comparator;
38import java.util.List;
39
40/**
41 * Retrieves the information of applications which accessed location recently.
42 */
43public 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 @VisibleForTesting
52 static final int[] LOCATION_OPS = new int[]{
53 AppOpsManager.OP_FINE_LOCATION,
54 AppOpsManager.OP_COARSE_LOCATION,
55 };
56
57 private final PackageManager mPackageManager;
58 private final Context mContext;
59 private final IconDrawableFactory mDrawableFactory;
Lifu Tang054cfe52018-12-26 12:57:22 -080060 private final Clock mClock;
Lifu Tang60db4e72018-12-10 16:26:11 -080061
62 public RecentLocationAccesses(Context context) {
Lifu Tang054cfe52018-12-26 12:57:22 -080063 this(context, Clock.systemDefaultZone());
64 }
65
66 @VisibleForTesting
67 RecentLocationAccesses(Context context, Clock clock) {
Lifu Tang60db4e72018-12-10 16:26:11 -080068 mContext = context;
69 mPackageManager = context.getPackageManager();
70 mDrawableFactory = IconDrawableFactory.newInstance(context);
Lifu Tang054cfe52018-12-26 12:57:22 -080071 mClock = clock;
Lifu Tang60db4e72018-12-10 16:26:11 -080072 }
73
74 /**
75 * Fills a list of applications which queried location recently within specified time.
76 * Apps are sorted by recency. Apps with more recent location accesses are in the front.
77 */
78 public List<Access> getAppList() {
79 // Retrieve a location usage list from AppOps
80 AppOpsManager aoManager =
81 (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
82 List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(LOCATION_OPS);
83
84 final int appOpsCount = appOps != null ? appOps.size() : 0;
85
86 // Process the AppOps list and generate a preference list.
87 ArrayList<Access> accesses = new ArrayList<>(appOpsCount);
Lifu Tang054cfe52018-12-26 12:57:22 -080088 final long now = mClock.millis();
Lifu Tang60db4e72018-12-10 16:26:11 -080089 final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
90 final List<UserHandle> profiles = um.getUserProfiles();
91
92 for (int i = 0; i < appOpsCount; ++i) {
93 AppOpsManager.PackageOps ops = appOps.get(i);
94 // Don't show the Android System in the list - it's not actionable for the user.
95 // Also don't show apps belonging to background users except managed users.
96 String packageName = ops.getPackageName();
97 int uid = ops.getUid();
98 int userId = UserHandle.getUserId(uid);
99 boolean isAndroidOs =
100 (uid == Process.SYSTEM_UID) && ANDROID_SYSTEM_PACKAGE_NAME.equals(packageName);
101 if (isAndroidOs || !profiles.contains(new UserHandle(userId))) {
102 continue;
103 }
104 Access access = getAccessFromOps(now, ops);
105 if (access != null) {
106 accesses.add(access);
107 }
108 }
109 return accesses;
110 }
111
112 public List<Access> getAppListSorted() {
113 List<Access> accesses = getAppList();
114 // Sort the list of Access by recency. Most recent accesses first.
115 Collections.sort(accesses, Collections.reverseOrder(new Comparator<Access>() {
116 @Override
117 public int compare(Access access1, Access access2) {
118 return Long.compare(access1.accessFinishTime, access2.accessFinishTime);
119 }
120 }));
121 return accesses;
122 }
123
124 /**
125 * Creates a Access entry for the given PackageOps.
126 *
127 * This method examines the time interval of the PackageOps first. If the PackageOps is older
128 * than the designated interval, this method ignores the PackageOps object and returns null.
129 * When the PackageOps is fresh enough, this method returns a Access object for the package
130 */
131 private Access getAccessFromOps(long now,
132 AppOpsManager.PackageOps ops) {
133 String packageName = ops.getPackageName();
134 List<AppOpsManager.OpEntry> entries = ops.getOps();
135 long locationAccessFinishTime = 0L;
136 // Earliest time for a location access to end and still be shown in list.
137 long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS;
138 for (AppOpsManager.OpEntry entry : entries) {
139 locationAccessFinishTime = Math.max(entry.getLastAccessBackgroundTime(),
140 entry.getLastAccessForegroundTime());
141 }
142 // Bail out if the entry is out of date.
143 if (locationAccessFinishTime < recentLocationCutoffTime) {
144 return null;
145 }
146
147 // The package is fresh enough, continue.
148 int uid = ops.getUid();
149 int userId = UserHandle.getUserId(uid);
150
151 Access access = null;
152 try {
153 ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
154 packageName, PackageManager.GET_META_DATA, userId);
155 if (appInfo == null) {
156 Log.w(TAG, "Null application info retrieved for package " + packageName
157 + ", userId " + userId);
158 return null;
159 }
160
161 final UserHandle userHandle = new UserHandle(userId);
162 Drawable icon = mDrawableFactory.getBadgedIcon(appInfo, userId);
163 CharSequence appLabel = mPackageManager.getApplicationLabel(appInfo);
164 CharSequence badgedAppLabel = mPackageManager.getUserBadgedLabel(appLabel, userHandle);
165 if (appLabel.toString().contentEquals(badgedAppLabel)) {
166 // If badged label is not different from original then no need for it as
167 // a separate content description.
168 badgedAppLabel = null;
169 }
170 access = new Access(packageName, userHandle, icon, appLabel, badgedAppLabel,
171 locationAccessFinishTime);
172 } catch (NameNotFoundException e) {
173 Log.w(TAG, "package name not found for " + packageName + ", userId " + userId);
174 }
175 return access;
176 }
177
178 public static class Access {
179 public final String packageName;
180 public final UserHandle userHandle;
181 public final Drawable icon;
182 public final CharSequence label;
183 public final CharSequence contentDescription;
184 public final long accessFinishTime;
185
Lifu Tang5509a812018-12-26 16:16:45 -0800186 public Access(String packageName, UserHandle userHandle, Drawable icon,
Lifu Tang60db4e72018-12-10 16:26:11 -0800187 CharSequence label, CharSequence contentDescription,
188 long accessFinishTime) {
189 this.packageName = packageName;
190 this.userHandle = userHandle;
191 this.icon = icon;
192 this.label = label;
193 this.contentDescription = contentDescription;
194 this.accessFinishTime = accessFinishTime;
195 }
196 }
197}