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

Commit 88ca0d1f authored by Maggie Wang's avatar Maggie Wang Committed by Android (Google) Code Review
Browse files

Merge "Sort "Recent Location Requests" by recency."

parents c4fe9726 704c423c
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, "");
    }
}