Sort "Recent Location Requests" by recency.
The list of apps with recent location requests is currently displayed in alphabetical order on device (Settings -> Security & Location -> Location). Sort this list by recency: (1) apps that are currently requesting location are displayed on the top. (2) For apps that are not currently requesting location, the ones with most recent location request finish time come first. Bug: 70350519 Test: Robolectric Test: manual Change-Id: I5c757defcd7645d254c9c47e3c83f7e323247a71
This commit is contained in:
@@ -16,21 +16,21 @@
|
||||
|
||||
package com.android.settingslib.location;
|
||||
|
||||
import android.app.AppGlobals;
|
||||
import android.app.AppOpsManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.util.IconDrawableFactory;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -38,11 +38,13 @@ import java.util.List;
|
||||
*/
|
||||
public class RecentLocationApps {
|
||||
private static final String TAG = RecentLocationApps.class.getSimpleName();
|
||||
private static final String ANDROID_SYSTEM_PACKAGE_NAME = "android";
|
||||
@VisibleForTesting
|
||||
static final String ANDROID_SYSTEM_PACKAGE_NAME = "android";
|
||||
|
||||
private static final int RECENT_TIME_INTERVAL_MILLIS = 15 * 60 * 1000;
|
||||
|
||||
private static final int[] LOCATION_OPS = new int[] {
|
||||
@VisibleForTesting
|
||||
static final int[] LOCATION_OPS = new int[] {
|
||||
AppOpsManager.OP_MONITOR_LOCATION,
|
||||
AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
|
||||
};
|
||||
@@ -59,6 +61,7 @@ public class RecentLocationApps {
|
||||
|
||||
/**
|
||||
* Fills a list of applications which queried location recently within specified time.
|
||||
* Apps are sorted by recency. Apps with more recent location requests are in the front.
|
||||
*/
|
||||
public List<Request> getAppList() {
|
||||
// Retrieve a location usage list from AppOps
|
||||
@@ -91,7 +94,18 @@ public class RecentLocationApps {
|
||||
requests.add(request);
|
||||
}
|
||||
}
|
||||
return requests;
|
||||
}
|
||||
|
||||
public List<Request> getAppListSorted() {
|
||||
List<Request> requests = getAppList();
|
||||
// Sort the list of Requests by recency. Most recent request first.
|
||||
Collections.sort(requests, Collections.reverseOrder(new Comparator<Request>() {
|
||||
@Override
|
||||
public int compare(Request request1, Request request2) {
|
||||
return Long.compare(request1.requestFinishTime, request2.requestFinishTime);
|
||||
}
|
||||
}));
|
||||
return requests;
|
||||
}
|
||||
|
||||
@@ -108,10 +122,12 @@ public class RecentLocationApps {
|
||||
List<AppOpsManager.OpEntry> entries = ops.getOps();
|
||||
boolean highBattery = false;
|
||||
boolean normalBattery = false;
|
||||
long locationRequestFinishTime = 0L;
|
||||
// Earliest time for a location request to end and still be shown in list.
|
||||
long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS;
|
||||
for (AppOpsManager.OpEntry entry : entries) {
|
||||
if (entry.isRunning() || entry.getTime() >= recentLocationCutoffTime) {
|
||||
locationRequestFinishTime = entry.getTime() + entry.getDuration();
|
||||
switch (entry.getOp()) {
|
||||
case AppOpsManager.OP_MONITOR_LOCATION:
|
||||
normalBattery = true;
|
||||
@@ -133,15 +149,13 @@ public class RecentLocationApps {
|
||||
}
|
||||
|
||||
// The package is fresh enough, continue.
|
||||
|
||||
int uid = ops.getUid();
|
||||
int userId = UserHandle.getUserId(uid);
|
||||
|
||||
Request request = null;
|
||||
try {
|
||||
IPackageManager ipm = AppGlobals.getPackageManager();
|
||||
ApplicationInfo appInfo =
|
||||
ipm.getApplicationInfo(packageName, PackageManager.GET_META_DATA, userId);
|
||||
ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
|
||||
packageName, PackageManager.GET_META_DATA, userId);
|
||||
if (appInfo == null) {
|
||||
Log.w(TAG, "Null application info retrieved for package " + packageName
|
||||
+ ", userId " + userId);
|
||||
@@ -158,12 +172,10 @@ public class RecentLocationApps {
|
||||
badgedAppLabel = null;
|
||||
}
|
||||
request = new Request(packageName, userHandle, icon, appLabel, highBattery,
|
||||
badgedAppLabel);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Error while retrieving application info for package " + packageName
|
||||
+ ", userId " + userId, e);
|
||||
badgedAppLabel, locationRequestFinishTime);
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.w(TAG, "package name not found for " + packageName + ", userId " + userId);
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
@@ -174,15 +186,18 @@ public class RecentLocationApps {
|
||||
public final CharSequence label;
|
||||
public final boolean isHighBattery;
|
||||
public final CharSequence contentDescription;
|
||||
public final long requestFinishTime;
|
||||
|
||||
private Request(String packageName, UserHandle userHandle, Drawable icon,
|
||||
CharSequence label, boolean isHighBattery, CharSequence contentDescription) {
|
||||
CharSequence label, boolean isHighBattery, CharSequence contentDescription,
|
||||
long requestFinishTime) {
|
||||
this.packageName = packageName;
|
||||
this.userHandle = userHandle;
|
||||
this.icon = icon;
|
||||
this.label = label;
|
||||
this.isHighBattery = isHighBattery;
|
||||
this.contentDescription = contentDescription;
|
||||
this.requestFinishTime = requestFinishTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
package com.android.settingslib.location;
|
||||
|
||||
import static org.mockito.Matchers.isA;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.AppOpsManager.OpEntry;
|
||||
import android.app.AppOpsManager.PackageOps;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Process;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import com.android.settingslib.SettingsLibRobolectricTestRunner;
|
||||
import com.android.settingslib.TestConfig;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(SettingsLibRobolectricTestRunner.class)
|
||||
@Config(
|
||||
manifest = TestConfig.MANIFEST_PATH,
|
||||
sdk = TestConfig.SDK_VERSION)
|
||||
public class RecentLocationAppsTest {
|
||||
|
||||
private static final int TEST_UID = 1234;
|
||||
private static final long NOW = System.currentTimeMillis();
|
||||
// App running duration in milliseconds
|
||||
private static final int DURATION = 10;
|
||||
private static final long ONE_MIN_AGO = NOW - TimeUnit.MINUTES.toMillis(1);
|
||||
private static final long FOURTEEN_MIN_AGO = NOW - TimeUnit.MINUTES.toMillis(14);
|
||||
private static final long TWENTY_MIN_AGO = NOW - TimeUnit.MINUTES.toMillis(20);
|
||||
private static final String[] TEST_PACKAGE_NAMES =
|
||||
{"package_1MinAgo", "package_14MinAgo", "package_20MinAgo"};
|
||||
|
||||
@Mock
|
||||
private Context mContext;
|
||||
@Mock
|
||||
private PackageManager mPackageManager;
|
||||
@Mock
|
||||
private AppOpsManager mAppOpsManager;
|
||||
@Mock
|
||||
private Resources mResources;
|
||||
@Mock
|
||||
private UserManager mUserManager;
|
||||
private int mTestUserId;
|
||||
private RecentLocationApps mRecentLocationApps;
|
||||
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws NameNotFoundException {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
when(mContext.getPackageManager()).thenReturn(mPackageManager);
|
||||
when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
|
||||
when(mContext.getResources()).thenReturn(mResources);
|
||||
when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
|
||||
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
|
||||
when(mPackageManager.getApplicationLabel(isA(ApplicationInfo.class)))
|
||||
.thenReturn("testApplicationLabel");
|
||||
when(mPackageManager.getUserBadgedLabel(isA(CharSequence.class), isA(UserHandle.class)))
|
||||
.thenReturn("testUserBadgedLabel");
|
||||
mTestUserId = UserHandle.getUserId(TEST_UID);
|
||||
when(mUserManager.getUserProfiles())
|
||||
.thenReturn(Collections.singletonList(new UserHandle(mTestUserId)));
|
||||
|
||||
long[] testRequestTime = {ONE_MIN_AGO, FOURTEEN_MIN_AGO, TWENTY_MIN_AGO};
|
||||
List<PackageOps> appOps = createTestPackageOpsList(TEST_PACKAGE_NAMES, testRequestTime);
|
||||
when(mAppOpsManager.getPackagesForOps(RecentLocationApps.LOCATION_OPS)).thenReturn(appOps);
|
||||
mockTestApplicationInfos(mTestUserId, TEST_PACKAGE_NAMES);
|
||||
|
||||
mRecentLocationApps = new RecentLocationApps(mContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAppList_shouldFilterRecentApps() {
|
||||
List<RecentLocationApps.Request> requests = mRecentLocationApps.getAppList();
|
||||
// Only two of the apps have requested location within 15 min.
|
||||
assertThat(requests).hasSize(2);
|
||||
// Make sure apps are ordered by recency
|
||||
assertThat(requests.get(0).packageName).isEqualTo(TEST_PACKAGE_NAMES[0]);
|
||||
assertThat(requests.get(0).requestFinishTime).isEqualTo(ONE_MIN_AGO + DURATION);
|
||||
assertThat(requests.get(1).packageName).isEqualTo(TEST_PACKAGE_NAMES[1]);
|
||||
assertThat(requests.get(1).requestFinishTime).isEqualTo(FOURTEEN_MIN_AGO + DURATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAppList_shouldNotShowAndroidOS() throws NameNotFoundException {
|
||||
// Add android OS to the list of apps.
|
||||
PackageOps androidSystemPackageOps =
|
||||
createPackageOps(
|
||||
RecentLocationApps.ANDROID_SYSTEM_PACKAGE_NAME,
|
||||
Process.SYSTEM_UID,
|
||||
AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
|
||||
ONE_MIN_AGO,
|
||||
DURATION);
|
||||
long[] testRequestTime =
|
||||
{ONE_MIN_AGO, FOURTEEN_MIN_AGO, TWENTY_MIN_AGO, ONE_MIN_AGO};
|
||||
List<PackageOps> appOps = createTestPackageOpsList(TEST_PACKAGE_NAMES, testRequestTime);
|
||||
appOps.add(androidSystemPackageOps);
|
||||
when(mAppOpsManager.getPackagesForOps(RecentLocationApps.LOCATION_OPS)).thenReturn(appOps);
|
||||
mockTestApplicationInfos(
|
||||
Process.SYSTEM_UID, RecentLocationApps.ANDROID_SYSTEM_PACKAGE_NAME);
|
||||
|
||||
List<RecentLocationApps.Request> requests = mRecentLocationApps.getAppList();
|
||||
// Android OS shouldn't show up in the list of apps.
|
||||
assertThat(requests).hasSize(2);
|
||||
// Make sure apps are ordered by recency
|
||||
assertThat(requests.get(0).packageName).isEqualTo(TEST_PACKAGE_NAMES[0]);
|
||||
assertThat(requests.get(0).requestFinishTime).isEqualTo(ONE_MIN_AGO + DURATION);
|
||||
assertThat(requests.get(1).packageName).isEqualTo(TEST_PACKAGE_NAMES[1]);
|
||||
assertThat(requests.get(1).requestFinishTime).isEqualTo(FOURTEEN_MIN_AGO + DURATION);
|
||||
}
|
||||
|
||||
private void mockTestApplicationInfos(int userId, String... packageNameList)
|
||||
throws NameNotFoundException {
|
||||
for (String packageName : packageNameList) {
|
||||
ApplicationInfo appInfo = new ApplicationInfo();
|
||||
appInfo.packageName = packageName;
|
||||
when(mPackageManager.getApplicationInfoAsUser(
|
||||
packageName, PackageManager.GET_META_DATA, userId)).thenReturn(appInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private List<PackageOps> createTestPackageOpsList(String[] packageNameList, long[] time) {
|
||||
List<PackageOps> packageOpsList = new ArrayList<>();
|
||||
for (int i = 0; i < packageNameList.length ; i++) {
|
||||
PackageOps packageOps = createPackageOps(
|
||||
packageNameList[i],
|
||||
TEST_UID,
|
||||
AppOpsManager.OP_MONITOR_LOCATION,
|
||||
time[i],
|
||||
DURATION);
|
||||
packageOpsList.add(packageOps);
|
||||
}
|
||||
return packageOpsList;
|
||||
}
|
||||
|
||||
private PackageOps createPackageOps(
|
||||
String packageName, int uid, int op, long time, int duration) {
|
||||
return new PackageOps(
|
||||
packageName,
|
||||
uid,
|
||||
Collections.singletonList(createOpEntryWithTime(op, time, duration)));
|
||||
}
|
||||
|
||||
private OpEntry createOpEntryWithTime(int op, long time, int duration) {
|
||||
return new OpEntry(op, AppOpsManager.MODE_ALLOWED, time, 0L, duration, 0, "");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user