Merge "Refactor recent location access for general usage" into sc-v2-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
0636af24dd
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,7 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settingslib.location;
|
||||
package com.android.settingslib.applications;
|
||||
|
||||
|
||||
import android.app.AppOpsManager;
|
||||
import android.content.Context;
|
||||
@@ -39,14 +40,24 @@ import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Retrieves the information of applications which accessed location recently.
|
||||
* Retrieval of app ops information for the specified ops.
|
||||
*/
|
||||
public class RecentLocationAccesses {
|
||||
private static final String TAG = RecentLocationAccesses.class.getSimpleName();
|
||||
public class RecentAppOpsAccess {
|
||||
@VisibleForTesting
|
||||
static final String ANDROID_SYSTEM_PACKAGE_NAME = "android";
|
||||
static final int[] LOCATION_OPS = new int[]{
|
||||
AppOpsManager.OP_FINE_LOCATION,
|
||||
AppOpsManager.OP_COARSE_LOCATION,
|
||||
};
|
||||
private static final int[] MICROPHONE_OPS = new int[]{
|
||||
AppOpsManager.OP_RECORD_AUDIO,
|
||||
};
|
||||
|
||||
// Keep last 24 hours of location app information.
|
||||
|
||||
private static final String TAG = RecentAppOpsAccess.class.getSimpleName();
|
||||
@VisibleForTesting
|
||||
public static final String ANDROID_SYSTEM_PACKAGE_NAME = "android";
|
||||
|
||||
// Keep last 24 hours of access app information.
|
||||
private static final long RECENT_TIME_INTERVAL_MILLIS = DateUtils.DAY_IN_MILLIS;
|
||||
|
||||
/** The flags for querying ops that are trusted for showing in the UI. */
|
||||
@@ -54,47 +65,55 @@ public class RecentLocationAccesses {
|
||||
| AppOpsManager.OP_FLAG_UNTRUSTED_PROXY
|
||||
| AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
|
||||
|
||||
@VisibleForTesting
|
||||
static final int[] LOCATION_OPS = new int[]{
|
||||
AppOpsManager.OP_FINE_LOCATION,
|
||||
AppOpsManager.OP_COARSE_LOCATION,
|
||||
};
|
||||
|
||||
private final PackageManager mPackageManager;
|
||||
private final Context mContext;
|
||||
private final int[] mOps;
|
||||
private final IconDrawableFactory mDrawableFactory;
|
||||
private final Clock mClock;
|
||||
|
||||
public RecentLocationAccesses(Context context) {
|
||||
this(context, Clock.systemDefaultZone());
|
||||
public RecentAppOpsAccess(Context context, int[] ops) {
|
||||
this(context, Clock.systemDefaultZone(), ops);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
RecentLocationAccesses(Context context, Clock clock) {
|
||||
RecentAppOpsAccess(Context context, Clock clock, int[] ops) {
|
||||
mContext = context;
|
||||
mPackageManager = context.getPackageManager();
|
||||
mOps = ops;
|
||||
mDrawableFactory = IconDrawableFactory.newInstance(context);
|
||||
mClock = clock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills a list of applications which queried location recently within specified time.
|
||||
* Apps are sorted by recency. Apps with more recent location accesses are in the front.
|
||||
* Creates an instance of {@link RecentAppOpsAccess} for location (coarse and fine) access.
|
||||
*/
|
||||
public static RecentAppOpsAccess createForLocation(Context context) {
|
||||
return new RecentAppOpsAccess(context, LOCATION_OPS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of {@link RecentAppOpsAccess} for microphone access.
|
||||
*/
|
||||
public static RecentAppOpsAccess createForMicrophone(Context context) {
|
||||
return new RecentAppOpsAccess(context, MICROPHONE_OPS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills a list of applications which queried for access recently within specified time.
|
||||
* Apps are sorted by recency. Apps with more recent accesses are in the front.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
List<Access> getAppList(boolean showSystemApps) {
|
||||
// Retrieve a location usage list from AppOps
|
||||
PackageManager pm = mContext.getPackageManager();
|
||||
AppOpsManager aoManager =
|
||||
(AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
|
||||
List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(LOCATION_OPS);
|
||||
public List<Access> getAppList(boolean showSystemApps) {
|
||||
// Retrieve a access usage list from AppOps
|
||||
AppOpsManager aoManager = mContext.getSystemService(AppOpsManager.class);
|
||||
List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(mOps);
|
||||
|
||||
final int appOpsCount = appOps != null ? appOps.size() : 0;
|
||||
|
||||
// Process the AppOps list and generate a preference list.
|
||||
ArrayList<Access> accesses = new ArrayList<>(appOpsCount);
|
||||
final long now = mClock.millis();
|
||||
final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
|
||||
final UserManager um = mContext.getSystemService(UserManager.class);
|
||||
final List<UserHandle> profiles = um.getUserProfiles();
|
||||
|
||||
for (int i = 0; i < appOpsCount; ++i) {
|
||||
@@ -111,9 +130,10 @@ public class RecentLocationAccesses {
|
||||
// Don't show apps that do not have user sensitive location permissions
|
||||
boolean showApp = true;
|
||||
if (!showSystemApps) {
|
||||
for (int op : LOCATION_OPS) {
|
||||
for (int op : mOps) {
|
||||
final String permission = AppOpsManager.opToPermission(op);
|
||||
final int permissionFlags = pm.getPermissionFlags(permission, packageName,
|
||||
final int permissionFlags = mPackageManager.getPermissionFlags(permission,
|
||||
packageName,
|
||||
user);
|
||||
if (PermissionChecker.checkPermissionForPreflight(mContext, permission,
|
||||
PermissionChecker.PID_UNKNOWN, uid, packageName)
|
||||
@@ -144,12 +164,11 @@ public class RecentLocationAccesses {
|
||||
return accesses;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a list of apps that accessed location recently, sorting by recency.
|
||||
* Gets a list of apps that accessed the app op recently, sorting by recency.
|
||||
*
|
||||
* @param showSystemApps whether includes system apps in the list.
|
||||
* @return the list of apps that recently accessed location.
|
||||
* @return the list of apps that recently accessed the app op.
|
||||
*/
|
||||
public List<Access> getAppListSorted(boolean showSystemApps) {
|
||||
List<Access> accesses = getAppList(showSystemApps);
|
||||
@@ -174,18 +193,18 @@ public class RecentLocationAccesses {
|
||||
AppOpsManager.PackageOps ops) {
|
||||
String packageName = ops.getPackageName();
|
||||
List<AppOpsManager.OpEntry> entries = ops.getOps();
|
||||
long locationAccessFinishTime = 0L;
|
||||
// Earliest time for a location access to end and still be shown in list.
|
||||
long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS;
|
||||
long accessFinishTime = 0L;
|
||||
// Earliest time for a access to end and still be shown in list.
|
||||
long recentAccessCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS;
|
||||
// Compute the most recent access time from all op entries.
|
||||
for (AppOpsManager.OpEntry entry : entries) {
|
||||
long lastAccessTime = entry.getLastAccessTime(TRUSTED_STATE_FLAGS);
|
||||
if (lastAccessTime > locationAccessFinishTime) {
|
||||
locationAccessFinishTime = lastAccessTime;
|
||||
if (lastAccessTime > accessFinishTime) {
|
||||
accessFinishTime = lastAccessTime;
|
||||
}
|
||||
}
|
||||
// Bail out if the entry is out of date.
|
||||
if (locationAccessFinishTime < recentLocationCutoffTime) {
|
||||
if (accessFinishTime < recentAccessCutoffTime) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -213,13 +232,16 @@ public class RecentLocationAccesses {
|
||||
badgedAppLabel = null;
|
||||
}
|
||||
access = new Access(packageName, userHandle, icon, appLabel, badgedAppLabel,
|
||||
locationAccessFinishTime);
|
||||
accessFinishTime);
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.w(TAG, "package name not found for " + packageName + ", userId " + userId);
|
||||
}
|
||||
return access;
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about when an app last accessed a particular app op.
|
||||
*/
|
||||
public static class Access {
|
||||
public final String packageName;
|
||||
public final UserHandle userHandle;
|
||||
@@ -1,15 +1,34 @@
|
||||
package com.android.settingslib.location;
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settingslib.applications;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.isA;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.AppOpsManager.OpEntry;
|
||||
import android.app.AppOpsManager.PackageOps;
|
||||
import android.content.Context;
|
||||
import android.content.PermissionChecker;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
@@ -19,13 +38,14 @@ import android.os.UserManager;
|
||||
import android.util.LongSparseArray;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadows.ShadowPermissionChecker;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.util.ArrayList;
|
||||
@@ -34,7 +54,8 @@ import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class RecentLocationAccessesTest {
|
||||
@Config(shadows = {ShadowPermissionChecker.class})
|
||||
public class RecentAppOpsAccessesTest {
|
||||
|
||||
private static final int TEST_UID = 1234;
|
||||
private static final long NOW = 1_000_000_000; // Approximately 9/8/2001
|
||||
@@ -54,7 +75,7 @@ public class RecentLocationAccessesTest {
|
||||
private Clock mClock;
|
||||
private Context mContext;
|
||||
private int mTestUserId;
|
||||
private RecentLocationAccesses mRecentLocationAccesses;
|
||||
private RecentAppOpsAccess mRecentAppOpsAccess;
|
||||
|
||||
@Before
|
||||
public void setUp() throws NameNotFoundException {
|
||||
@@ -69,24 +90,37 @@ public class RecentLocationAccessesTest {
|
||||
.thenReturn("testApplicationLabel");
|
||||
when(mPackageManager.getUserBadgedLabel(isA(CharSequence.class), isA(UserHandle.class)))
|
||||
.thenReturn("testUserBadgedLabel");
|
||||
when(mPackageManager.getPermissionFlags(any(), any(), any()))
|
||||
.thenReturn(PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
|
||||
| PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED);
|
||||
for (String testPackageName : TEST_PACKAGE_NAMES) {
|
||||
ShadowPermissionChecker.setResult(
|
||||
testPackageName,
|
||||
Manifest.permission.ACCESS_COARSE_LOCATION,
|
||||
PermissionChecker.PERMISSION_GRANTED);
|
||||
ShadowPermissionChecker.setResult(
|
||||
testPackageName,
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
PermissionChecker.PERMISSION_GRANTED);
|
||||
}
|
||||
mTestUserId = UserHandle.getUserId(TEST_UID);
|
||||
when(mUserManager.getUserProfiles())
|
||||
.thenReturn(Collections.singletonList(new UserHandle(mTestUserId)));
|
||||
|
||||
long[] testRequestTime = {ONE_MIN_AGO, TWENTY_THREE_HOURS_AGO, TWO_DAYS_AGO};
|
||||
List<PackageOps> appOps = createTestPackageOpsList(TEST_PACKAGE_NAMES, testRequestTime);
|
||||
when(mAppOpsManager.getPackagesForOps(RecentLocationAccesses.LOCATION_OPS)).thenReturn(
|
||||
when(mAppOpsManager.getPackagesForOps(RecentAppOpsAccess.LOCATION_OPS)).thenReturn(
|
||||
appOps);
|
||||
mockTestApplicationInfos(mTestUserId, TEST_PACKAGE_NAMES);
|
||||
|
||||
when(mClock.millis()).thenReturn(NOW);
|
||||
mRecentLocationAccesses = new RecentLocationAccesses(mContext, mClock);
|
||||
mRecentAppOpsAccess = new RecentAppOpsAccess(mContext, mClock,
|
||||
RecentAppOpsAccess.LOCATION_OPS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testGetAppList_shouldFilterRecentAccesses() {
|
||||
List<RecentLocationAccesses.Access> requests = mRecentLocationAccesses.getAppList(false);
|
||||
List<RecentAppOpsAccess.Access> requests = mRecentAppOpsAccess.getAppList(false);
|
||||
// Only two of the apps have requested location within 15 min.
|
||||
assertThat(requests).hasSize(2);
|
||||
// Make sure apps are ordered by recency
|
||||
@@ -97,12 +131,11 @@ public class RecentLocationAccessesTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testGetAppList_shouldNotShowAndroidOS() throws NameNotFoundException {
|
||||
// Add android OS to the list of apps.
|
||||
PackageOps androidSystemPackageOps =
|
||||
createPackageOps(
|
||||
RecentLocationAccesses.ANDROID_SYSTEM_PACKAGE_NAME,
|
||||
RecentAppOpsAccess.ANDROID_SYSTEM_PACKAGE_NAME,
|
||||
Process.SYSTEM_UID,
|
||||
AppOpsManager.OP_FINE_LOCATION,
|
||||
ONE_MIN_AGO);
|
||||
@@ -110,12 +143,12 @@ public class RecentLocationAccessesTest {
|
||||
{ONE_MIN_AGO, TWENTY_THREE_HOURS_AGO, TWO_DAYS_AGO, ONE_MIN_AGO};
|
||||
List<PackageOps> appOps = createTestPackageOpsList(TEST_PACKAGE_NAMES, testRequestTime);
|
||||
appOps.add(androidSystemPackageOps);
|
||||
when(mAppOpsManager.getPackagesForOps(RecentLocationAccesses.LOCATION_OPS)).thenReturn(
|
||||
when(mAppOpsManager.getPackagesForOps(RecentAppOpsAccess.LOCATION_OPS)).thenReturn(
|
||||
appOps);
|
||||
mockTestApplicationInfos(
|
||||
Process.SYSTEM_UID, RecentLocationAccesses.ANDROID_SYSTEM_PACKAGE_NAME);
|
||||
Process.SYSTEM_UID, RecentAppOpsAccess.ANDROID_SYSTEM_PACKAGE_NAME);
|
||||
|
||||
List<RecentLocationAccesses.Access> requests = mRecentLocationAccesses.getAppList(true);
|
||||
List<RecentAppOpsAccess.Access> requests = mRecentAppOpsAccess.getAppList(true);
|
||||
// Android OS shouldn't show up in the list of apps.
|
||||
assertThat(requests).hasSize(2);
|
||||
// Make sure apps are ordered by recency
|
||||
@@ -159,7 +192,7 @@ public class RecentLocationAccessesTest {
|
||||
// Slot for background access timestamp.
|
||||
final LongSparseArray<AppOpsManager.NoteOpEvent> accessEvents = new LongSparseArray<>();
|
||||
accessEvents.put(AppOpsManager.makeKey(AppOpsManager.UID_STATE_BACKGROUND,
|
||||
AppOpsManager.OP_FLAG_SELF), new AppOpsManager.NoteOpEvent(time, -1, null));
|
||||
AppOpsManager.OP_FLAG_SELF), new AppOpsManager.NoteOpEvent(time, -1, null));
|
||||
|
||||
return new OpEntry(op, AppOpsManager.MODE_ALLOWED, Collections.singletonMap(null,
|
||||
new AppOpsManager.AttributedOpEntry(op, false, accessEvents, null)));
|
||||
Reference in New Issue
Block a user