Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 704c423c authored by Maggie's avatar Maggie
Browse files

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
parent d538bbcb
Loading
Loading
Loading
Loading
+31 −16
Original line number Original line Diff line number Diff line
@@ -16,21 +16,21 @@


package com.android.settingslib.location;
package com.android.settingslib.location;


import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable;
import android.os.Process;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManager;
import android.support.annotation.VisibleForTesting;
import android.util.IconDrawableFactory;
import android.util.IconDrawableFactory;
import android.util.Log;
import android.util.Log;

import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.List;


/**
/**
@@ -38,11 +38,13 @@ import java.util.List;
 */
 */
public class RecentLocationApps {
public class RecentLocationApps {
    private static final String TAG = RecentLocationApps.class.getSimpleName();
    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 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_LOCATION,
            AppOpsManager.OP_MONITOR_HIGH_POWER_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.
     * 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() {
    public List<Request> getAppList() {
        // Retrieve a location usage list from AppOps
        // Retrieve a location usage list from AppOps
@@ -91,7 +94,18 @@ public class RecentLocationApps {
                requests.add(request);
                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;
        return requests;
    }
    }


@@ -108,10 +122,12 @@ public class RecentLocationApps {
        List<AppOpsManager.OpEntry> entries = ops.getOps();
        List<AppOpsManager.OpEntry> entries = ops.getOps();
        boolean highBattery = false;
        boolean highBattery = false;
        boolean normalBattery = false;
        boolean normalBattery = false;
        long locationRequestFinishTime = 0L;
        // Earliest time for a location request to end and still be shown in list.
        // Earliest time for a location request to end and still be shown in list.
        long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS;
        long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS;
        for (AppOpsManager.OpEntry entry : entries) {
        for (AppOpsManager.OpEntry entry : entries) {
            if (entry.isRunning() || entry.getTime() >= recentLocationCutoffTime) {
            if (entry.isRunning() || entry.getTime() >= recentLocationCutoffTime) {
                locationRequestFinishTime = entry.getTime() + entry.getDuration();
                switch (entry.getOp()) {
                switch (entry.getOp()) {
                    case AppOpsManager.OP_MONITOR_LOCATION:
                    case AppOpsManager.OP_MONITOR_LOCATION:
                        normalBattery = true;
                        normalBattery = true;
@@ -133,15 +149,13 @@ public class RecentLocationApps {
        }
        }


        // The package is fresh enough, continue.
        // The package is fresh enough, continue.

        int uid = ops.getUid();
        int uid = ops.getUid();
        int userId = UserHandle.getUserId(uid);
        int userId = UserHandle.getUserId(uid);


        Request request = null;
        Request request = null;
        try {
        try {
            IPackageManager ipm = AppGlobals.getPackageManager();
            ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
            ApplicationInfo appInfo =
                    packageName, PackageManager.GET_META_DATA, userId);
                    ipm.getApplicationInfo(packageName, PackageManager.GET_META_DATA, userId);
            if (appInfo == null) {
            if (appInfo == null) {
                Log.w(TAG, "Null application info retrieved for package " + packageName
                Log.w(TAG, "Null application info retrieved for package " + packageName
                        + ", userId " + userId);
                        + ", userId " + userId);
@@ -158,12 +172,10 @@ public class RecentLocationApps {
                badgedAppLabel = null;
                badgedAppLabel = null;
            }
            }
            request = new Request(packageName, userHandle, icon, appLabel, highBattery,
            request = new Request(packageName, userHandle, icon, appLabel, highBattery,
                    badgedAppLabel);
                    badgedAppLabel, locationRequestFinishTime);
        } catch (RemoteException e) {
        } catch (NameNotFoundException e) {
            Log.w(TAG, "Error while retrieving application info for package " + packageName
            Log.w(TAG, "package name not found for " + packageName + ", userId " + userId);
                    + ", userId " + userId, e);
        }
        }

        return request;
        return request;
    }
    }


@@ -174,15 +186,18 @@ public class RecentLocationApps {
        public final CharSequence label;
        public final CharSequence label;
        public final boolean isHighBattery;
        public final boolean isHighBattery;
        public final CharSequence contentDescription;
        public final CharSequence contentDescription;
        public final long requestFinishTime;


        private Request(String packageName, UserHandle userHandle, Drawable icon,
        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.packageName = packageName;
            this.userHandle = userHandle;
            this.userHandle = userHandle;
            this.icon = icon;
            this.icon = icon;
            this.label = label;
            this.label = label;
            this.isHighBattery = isHighBattery;
            this.isHighBattery = isHighBattery;
            this.contentDescription = contentDescription;
            this.contentDescription = contentDescription;
            this.requestFinishTime = requestFinishTime;
        }
        }
    }
    }
}
}
+162 −0
Original line number Original line Diff line number Diff line
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, "");
    }
}