blob: 09107ce6b77ba9490cca76beb4761a54e15ef7fe [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
Svet Ganovaf189e32019-02-15 18:45:29 -080051 /** 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
Lifu Tang60db4e72018-12-10 16:26:11 -080056 @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;
Lifu Tang054cfe52018-12-26 12:57:22 -080065 private final Clock mClock;
Lifu Tang60db4e72018-12-10 16:26:11 -080066
67 public RecentLocationAccesses(Context context) {
Lifu Tang054cfe52018-12-26 12:57:22 -080068 this(context, Clock.systemDefaultZone());
69 }
70
71 @VisibleForTesting
72 RecentLocationAccesses(Context context, Clock clock) {
Lifu Tang60db4e72018-12-10 16:26:11 -080073 mContext = context;
74 mPackageManager = context.getPackageManager();
75 mDrawableFactory = IconDrawableFactory.newInstance(context);
Lifu Tang054cfe52018-12-26 12:57:22 -080076 mClock = clock;
Lifu Tang60db4e72018-12-10 16:26:11 -080077 }
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 */
83 public List<Access> getAppList() {
84 // Retrieve a location usage list from AppOps
85 AppOpsManager aoManager =
86 (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
87 List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(LOCATION_OPS);
88
89 final int appOpsCount = appOps != null ? appOps.size() : 0;
90
91 // Process the AppOps list and generate a preference list.
92 ArrayList<Access> accesses = new ArrayList<>(appOpsCount);
Lifu Tang054cfe52018-12-26 12:57:22 -080093 final long now = mClock.millis();
Lifu Tang60db4e72018-12-10 16:26:11 -080094 final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
95 final List<UserHandle> profiles = um.getUserProfiles();
96
97 for (int i = 0; i < appOpsCount; ++i) {
98 AppOpsManager.PackageOps ops = appOps.get(i);
99 // Don't show the Android System in the list - it's not actionable for the user.
100 // Also don't show apps belonging to background users except managed users.
101 String packageName = ops.getPackageName();
102 int uid = ops.getUid();
103 int userId = UserHandle.getUserId(uid);
104 boolean isAndroidOs =
105 (uid == Process.SYSTEM_UID) && ANDROID_SYSTEM_PACKAGE_NAME.equals(packageName);
106 if (isAndroidOs || !profiles.contains(new UserHandle(userId))) {
107 continue;
108 }
109 Access access = getAccessFromOps(now, ops);
110 if (access != null) {
111 accesses.add(access);
112 }
113 }
114 return accesses;
115 }
116
117 public List<Access> getAppListSorted() {
118 List<Access> accesses = getAppList();
119 // Sort the list of Access by recency. Most recent accesses first.
120 Collections.sort(accesses, Collections.reverseOrder(new Comparator<Access>() {
121 @Override
122 public int compare(Access access1, Access access2) {
123 return Long.compare(access1.accessFinishTime, access2.accessFinishTime);
124 }
125 }));
126 return accesses;
127 }
128
129 /**
130 * Creates a Access entry for the given PackageOps.
131 *
132 * This method examines the time interval of the PackageOps first. If the PackageOps is older
133 * than the designated interval, this method ignores the PackageOps object and returns null.
134 * When the PackageOps is fresh enough, this method returns a Access object for the package
135 */
136 private Access getAccessFromOps(long now,
137 AppOpsManager.PackageOps ops) {
138 String packageName = ops.getPackageName();
139 List<AppOpsManager.OpEntry> entries = ops.getOps();
140 long locationAccessFinishTime = 0L;
141 // Earliest time for a location access to end and still be shown in list.
142 long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS;
143 for (AppOpsManager.OpEntry entry : entries) {
Svet Ganovaf189e32019-02-15 18:45:29 -0800144 locationAccessFinishTime = entry.getLastAccessTime(TRUSTED_STATE_FLAGS);
Lifu Tang60db4e72018-12-10 16:26:11 -0800145 }
146 // Bail out if the entry is out of date.
147 if (locationAccessFinishTime < recentLocationCutoffTime) {
148 return null;
149 }
150
151 // The package is fresh enough, continue.
152 int uid = ops.getUid();
153 int userId = UserHandle.getUserId(uid);
154
155 Access access = null;
156 try {
157 ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
158 packageName, PackageManager.GET_META_DATA, userId);
159 if (appInfo == null) {
160 Log.w(TAG, "Null application info retrieved for package " + packageName
161 + ", userId " + userId);
162 return null;
163 }
164
165 final UserHandle userHandle = new UserHandle(userId);
166 Drawable icon = mDrawableFactory.getBadgedIcon(appInfo, userId);
167 CharSequence appLabel = mPackageManager.getApplicationLabel(appInfo);
168 CharSequence badgedAppLabel = mPackageManager.getUserBadgedLabel(appLabel, userHandle);
169 if (appLabel.toString().contentEquals(badgedAppLabel)) {
170 // If badged label is not different from original then no need for it as
171 // a separate content description.
172 badgedAppLabel = null;
173 }
174 access = new Access(packageName, userHandle, icon, appLabel, badgedAppLabel,
175 locationAccessFinishTime);
176 } catch (NameNotFoundException e) {
177 Log.w(TAG, "package name not found for " + packageName + ", userId " + userId);
178 }
179 return access;
180 }
181
182 public static class Access {
183 public final String packageName;
184 public final UserHandle userHandle;
185 public final Drawable icon;
186 public final CharSequence label;
187 public final CharSequence contentDescription;
188 public final long accessFinishTime;
189
Lifu Tang5509a812018-12-26 16:16:45 -0800190 public Access(String packageName, UserHandle userHandle, Drawable icon,
Lifu Tang60db4e72018-12-10 16:26:11 -0800191 CharSequence label, CharSequence contentDescription,
192 long accessFinishTime) {
193 this.packageName = packageName;
194 this.userHandle = userHandle;
195 this.icon = icon;
196 this.label = label;
197 this.contentDescription = contentDescription;
198 this.accessFinishTime = accessFinishTime;
199 }
200 }
201}