Merge "Refactor recent location access for general usage" into sc-v2-dev

This commit is contained in:
Jordan Jozwiak
2021-08-17 21:40:29 +00:00
committed by Android (Google) Code Review
2 changed files with 106 additions and 51 deletions

View File

@@ -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;

View File

@@ -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)));