summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brophy <mbrophy@google.com>2011-06-21 15:40:08 +0100
committerMark Brophy <mbrophy@google.com>2011-07-12 11:39:25 +0100
commit01ff60258654b87d12c219035a637746cf1fdbf7 (patch)
treed04f54d650d69359403438417329531cf005f73a
parent42227d505a6fabb1374f84fc7d6f3e2469903584 (diff)
downloadApplicationsProvider-01ff60258654b87d12c219035a637746cf1fdbf7.tar.gz
Return the last access time of apps to QSB.
When we get a request for results from global search, results will be sorted by last access time, and the new field SearchManager.SUGGEST_COLUMN_LAST_ACCESS_HINT will be set. The launch times are no longer updated every 6 hours. Now we update the in-memory database when the request from QSB sets a "refresh" parameter. DEPENDS ON: I80e9b127410ed0d528515d2256787f30a953e9b0 Change-Id: Ie45e4fa52ffe43d4e7f3fa06221f3c9e039f2bb1
-rw-r--r--src/com/android/providers/applications/ApplicationsProvider.java175
-rw-r--r--tests/src/com/android/providers/applications/ApplicationsProviderForTesting.java24
-rw-r--r--tests/src/com/android/providers/applications/ApplicationsProviderTest.java120
-rw-r--r--tests/src/com/android/providers/applications/MockActivityManager.java16
4 files changed, 205 insertions, 130 deletions
diff --git a/src/com/android/providers/applications/ApplicationsProvider.java b/src/com/android/providers/applications/ApplicationsProvider.java
index d6a4f00..1177aa8 100644
--- a/src/com/android/providers/applications/ApplicationsProvider.java
+++ b/src/com/android/providers/applications/ApplicationsProvider.java
@@ -17,6 +17,7 @@
package com.android.providers.applications;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.os.PkgUsageStats;
import android.app.ActivityManager;
import android.app.AlarmManager;
@@ -62,7 +63,7 @@ import com.google.common.annotations.VisibleForTesting;
* {@link android.provider.Applications} should be updated.
*
* TODO: this provider should be moved to the Launcher, which contains similar logic to keep an up
- * to date list of installed applications. Alternatively, Launcher could be updated to use this
+ * to date list of installed applications. Alternatively, Launcher could be updated to use this
* provider.
*/
public class ApplicationsProvider extends ContentProvider {
@@ -81,11 +82,6 @@ public class ApplicationsProvider extends ContentProvider {
// Messages for mHandler
private static final int MSG_UPDATE_ALL = 0;
- private static final int MSG_UPDATE_APP_LAUNCH_COUNTS = 1;
-
- // A request to update application launch counts.
- private static final String INTENT_UPDATE_LAUNCH_COUNTS =
- ApplicationsProvider.class.getName() + ".UPDATE_LAUNCH_COUNTS";
public static final String _ID = "_id";
public static final String NAME = "name";
@@ -94,6 +90,10 @@ public class ApplicationsProvider extends ContentProvider {
public static final String CLASS = "class";
public static final String ICON = "icon";
public static final String LAUNCH_COUNT = "launch_count";
+ public static final String LAST_RESUME_TIME = "last_resume_time";
+
+ // A query parameter to refresh application statistics. Used by QSB.
+ public static final String REFRESH_STATS = "refresh";
private static final String APPLICATIONS_TABLE = "applications";
@@ -102,7 +102,9 @@ public class ApplicationsProvider extends ContentProvider {
+ " applicationsLookup.source = " + APPLICATIONS_TABLE + "." + _ID;
private static final HashMap<String, String> sSearchSuggestionsProjectionMap =
- buildSuggestionsProjectionMap();
+ buildSuggestionsProjectionMap(false);
+ private static final HashMap<String, String> sGlobalSearchSuggestionsProjectionMap =
+ buildSuggestionsProjectionMap(true);
private static final HashMap<String, String> sSearchProjectionMap =
buildSearchProjectionMap();
@@ -123,11 +125,6 @@ public class ApplicationsProvider extends ContentProvider {
*/
private static final long UPDATE_DELAY_MILLIS = 1000L;
- /**
- * Application launch counts will be updated every 6 hours.
- */
- private static final long LAUNCH_COUNT_UPDATE_INTERVAL = AlarmManager.INTERVAL_HOUR * 6;
-
private static UriMatcher buildUriMatcher() {
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI(Applications.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY,
@@ -169,20 +166,6 @@ public class ApplicationsProvider extends ContentProvider {
}
};
- // Broadcast receiver receiving "update application launch counts" requests
- // fired by the AlarmManager at regular intervals.
- private BroadcastReceiver mLaunchCountUpdateReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (INTENT_UPDATE_LAUNCH_COUNTS.equals(action)) {
- if (DBG) Log.d(TAG, "Launch count update requested");
- mHandler.removeMessages(MSG_UPDATE_APP_LAUNCH_COUNTS);
- Message.obtain(mHandler, MSG_UPDATE_APP_LAUNCH_COUNTS).sendToTarget();
- }
- }
- };
-
@Override
public boolean onCreate() {
createDatabase();
@@ -197,7 +180,6 @@ public class ApplicationsProvider extends ContentProvider {
mHandler = new UpdateHandler(thread.getLooper());
// Kick off first apps update
postUpdateAll();
- scheduleRegularLaunchCountUpdates();
return true;
}
@@ -213,9 +195,6 @@ public class ApplicationsProvider extends ContentProvider {
case MSG_UPDATE_ALL:
updateApplicationsList(null);
break;
- case MSG_UPDATE_APP_LAUNCH_COUNTS:
- updateLaunchCounts();
- break;
default:
Log.e(TAG, "Unknown message: " + msg.what);
break;
@@ -235,27 +214,6 @@ public class ApplicationsProvider extends ContentProvider {
mHandler.sendMessageDelayed(msg, UPDATE_DELAY_MILLIS);
}
- @VisibleForTesting
- protected void scheduleRegularLaunchCountUpdates() {
- // Set up a recurring event that sends an intent caught by the
- // mLaunchCountUpdateReceiver event handler. This event handler
- // will update application launch counts in the ApplicationsProvider's
- // database.
- getContext().registerReceiver(
- mLaunchCountUpdateReceiver,
- new IntentFilter(INTENT_UPDATE_LAUNCH_COUNTS));
-
- PendingIntent updateLaunchCountsIntent = PendingIntent.getBroadcast(
- getContext(), 0, new Intent(INTENT_UPDATE_LAUNCH_COUNTS),
- PendingIntent.FLAG_CANCEL_CURRENT);
-
- // Schedule the recurring event.
- AlarmManager alarmManager =
- (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
- alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, LAUNCH_COUNT_UPDATE_INTERVAL,
- LAUNCH_COUNT_UPDATE_INTERVAL, updateLaunchCountsIntent);
- }
-
// ----------
// END ASYC UPDATE CODE
// ----------
@@ -273,10 +231,11 @@ public class ApplicationsProvider extends ContentProvider {
PACKAGE + " TEXT," +
CLASS + " TEXT," +
ICON + " TEXT," +
- LAUNCH_COUNT + " INTEGER DEFAULT 0" +
+ LAUNCH_COUNT + " INTEGER DEFAULT 0," +
+ LAST_RESUME_TIME + " INTEGER DEFAULT 0" +
");");
// Needed for efficient update and remove
- mDb.execSQL("CREATE INDEX applicationsComponentIndex ON " + APPLICATIONS_TABLE + " ("
+ mDb.execSQL("CREATE INDEX applicationsComponentIndex ON " + APPLICATIONS_TABLE + " ("
+ PACKAGE + "," + CLASS + ");");
// Maps token from the app name to records in the applications table
mDb.execSQL("CREATE TABLE applicationsLookup (" +
@@ -289,18 +248,18 @@ public class ApplicationsProvider extends ContentProvider {
"source" +
");");
// Triggers to keep the applicationsLookup table up to date
- mDb.execSQL("CREATE TRIGGER applicationsLookup_update UPDATE OF " + NAME + " ON " +
+ mDb.execSQL("CREATE TRIGGER applicationsLookup_update UPDATE OF " + NAME + " ON " +
APPLICATIONS_TABLE + " " +
"BEGIN " +
"DELETE FROM applicationsLookup WHERE source = new." + _ID + ";" +
"SELECT _TOKENIZE('applicationsLookup', new." + _ID + ", new." + NAME + ", ' ', 1);" +
"END");
- mDb.execSQL("CREATE TRIGGER applicationsLookup_insert AFTER INSERT ON " +
+ mDb.execSQL("CREATE TRIGGER applicationsLookup_insert AFTER INSERT ON " +
APPLICATIONS_TABLE + " " +
"BEGIN " +
"SELECT _TOKENIZE('applicationsLookup', new." + _ID + ", new." + NAME + ", ' ', 1);" +
"END");
- mDb.execSQL("CREATE TRIGGER applicationsLookup_delete DELETE ON " +
+ mDb.execSQL("CREATE TRIGGER applicationsLookup_delete DELETE ON " +
APPLICATIONS_TABLE + " " +
"BEGIN " +
"DELETE FROM applicationsLookup WHERE source = old." + _ID + ";" +
@@ -350,6 +309,9 @@ public class ApplicationsProvider extends ContentProvider {
if (uri.getPathSegments().size() > 1) {
query = uri.getLastPathSegment().toLowerCase();
}
+ if (uri.getQueryParameter(REFRESH_STATS) != null) {
+ updateUsageStats();
+ }
return getSuggestions(query, projectionIn);
}
case SHORTCUT_REFRESH: {
@@ -372,12 +334,15 @@ public class ApplicationsProvider extends ContentProvider {
}
private Cursor getSuggestions(String query, String[] projectionIn) {
- // No zero-query suggestions except for global search, to avoid leaking info about apps
- // that have been used.
- if (TextUtils.isEmpty(query) && !canRankByLaunchCount()) {
+ Map<String, String> projectionMap = sSearchSuggestionsProjectionMap;
+ // No zero-query suggestions or launch times except for global search,
+ // to avoid leaking info about apps that have been used.
+ if (hasGlobalSearchPermission()) {
+ projectionMap = sGlobalSearchSuggestionsProjectionMap;
+ } else if (TextUtils.isEmpty(query)) {
return null;
}
- return searchApplications(query, projectionIn, sSearchSuggestionsProjectionMap);
+ return searchApplications(query, projectionIn, projectionMap);
}
/**
@@ -414,8 +379,8 @@ public class ApplicationsProvider extends ContentProvider {
if (!zeroQuery) {
qb.appendWhere(buildTokenFilter(query));
} else {
- if (canRankByLaunchCount()) {
- qb.appendWhere(LAUNCH_COUNT + " > 0");
+ if (hasGlobalSearchPermission()) {
+ qb.appendWhere(LAST_RESUME_TIME + " > 0");
}
}
// don't return duplicates when there are two matching tokens for an app
@@ -437,8 +402,8 @@ public class ApplicationsProvider extends ContentProvider {
orderBy.append("MIN(token_index) != 0, ");
}
- if (canRankByLaunchCount()) {
- orderBy.append(LAUNCH_COUNT + " DESC, ");
+ if (hasGlobalSearchPermission()) {
+ orderBy.append(LAST_RESUME_TIME + " DESC, ");
}
orderBy.append(NAME);
@@ -452,12 +417,12 @@ public class ApplicationsProvider extends ContentProvider {
// NOTE: Query parameters won't work here since the SQL compiler
// needs to parse the actual string to know that it can use the
// index to do a prefix scan.
- DatabaseUtils.appendEscapedSQLString(filter,
+ DatabaseUtils.appendEscapedSQLString(filter,
DatabaseUtils.getHexCollationKey(filterParam) + "*");
return filter.toString();
}
- private static HashMap<String, String> buildSuggestionsProjectionMap() {
+ private static HashMap<String, String> buildSuggestionsProjectionMap(boolean forGlobalSearch) {
HashMap<String, String> map = new HashMap<String, String>();
addProjection(map, Applications.ApplicationColumns._ID, _ID);
addProjection(map, SearchManager.SUGGEST_COLUMN_TEXT_1, NAME);
@@ -469,6 +434,10 @@ public class ApplicationsProvider extends ContentProvider {
addProjection(map, SearchManager.SUGGEST_COLUMN_ICON_2, "NULL");
addProjection(map, SearchManager.SUGGEST_COLUMN_SHORTCUT_ID,
PACKAGE + " || '/' || " + CLASS);
+ if (forGlobalSearch) {
+ addProjection(map, SearchManager.SUGGEST_COLUMN_LAST_ACCESS_HINT,
+ LAST_RESUME_TIME);
+ }
return map;
}
@@ -496,10 +465,10 @@ public class ApplicationsProvider extends ContentProvider {
* @param packageName Name of package whose activities to update.
* If {@code null}, all packages are updated.
*/
- private void updateApplicationsList(String packageName) {
+ private synchronized void updateApplicationsList(String packageName) {
if (DBG) Log.d(TAG, "Updating database (packageName = " + packageName + ")...");
-
- DatabaseUtils.InsertHelper inserter =
+
+ DatabaseUtils.InsertHelper inserter =
new DatabaseUtils.InsertHelper(mDb, APPLICATIONS_TABLE);
int nameCol = inserter.getColumnIndex(NAME);
int descriptionCol = inserter.getColumnIndex(DESCRIPTION);
@@ -507,8 +476,9 @@ public class ApplicationsProvider extends ContentProvider {
int classCol = inserter.getColumnIndex(CLASS);
int iconCol = inserter.getColumnIndex(ICON);
int launchCountCol = inserter.getColumnIndex(LAUNCH_COUNT);
+ int lastResumeTimeCol = inserter.getColumnIndex(LAST_RESUME_TIME);
- Map<String, Integer> launchCounts = fetchLaunchCounts();
+ Map<String, PkgUsageStats> usageStats = fetchUsageStats();
mDb.beginTransaction();
try {
@@ -533,9 +503,14 @@ public class ApplicationsProvider extends ContentProvider {
}
String activityPackageName = info.activityInfo.applicationInfo.packageName;
- Integer launchCount = launchCounts.get(activityPackageName);
- if (launchCount == null) {
- launchCount = 0;
+ PkgUsageStats stats = usageStats.get(activityPackageName);
+ int launchCount = 0;
+ long lastResumeTime = 0;
+ if (stats != null) {
+ launchCount = stats.launchCount;
+ if (stats.componentResumeTimes.containsKey(activityClassName)) {
+ lastResumeTime = stats.componentResumeTimes.get(activityClassName);
+ }
}
String icon = getActivityIconUri(info.activityInfo);
@@ -546,6 +521,7 @@ public class ApplicationsProvider extends ContentProvider {
inserter.bind(classCol, activityClassName);
inserter.bind(iconCol, icon);
inserter.bind(launchCountCol, launchCount);
+ inserter.bind(lastResumeTimeCol, lastResumeTime);
inserter.execute();
}
mDb.setTransactionSuccessful();
@@ -562,24 +538,37 @@ public class ApplicationsProvider extends ContentProvider {
}
@VisibleForTesting
- protected void updateLaunchCounts() {
- Map<String, Integer> launchCounts = fetchLaunchCounts();
+ protected synchronized void updateUsageStats() {
+ if (DBG) Log.d(TAG, "Update application usage stats.");
+ Map<String, PkgUsageStats> usageStats = fetchUsageStats();
mDb.beginTransaction();
try {
- for (String packageName : launchCounts.keySet()) {
- ContentValues updatedValues = new ContentValues();
- updatedValues.put(LAUNCH_COUNT, launchCounts.get(packageName));
+ for (Map.Entry<String, PkgUsageStats> statsEntry : usageStats.entrySet()) {
+ ContentValues updatedLaunchCount = new ContentValues();
+ String packageName = statsEntry.getKey();
+ PkgUsageStats stats = statsEntry.getValue();
+ updatedLaunchCount.put(LAUNCH_COUNT, stats.launchCount);
- mDb.update(APPLICATIONS_TABLE, updatedValues,
+ mDb.update(APPLICATIONS_TABLE, updatedLaunchCount,
PACKAGE + " = ?", new String[] { packageName });
+
+ for (Map.Entry<String, Long> crtEntry: stats.componentResumeTimes.entrySet()) {
+ ContentValues updatedLastResumeTime = new ContentValues();
+ String componentName = crtEntry.getKey();
+ updatedLastResumeTime.put(LAST_RESUME_TIME, crtEntry.getValue());
+
+ mDb.update(APPLICATIONS_TABLE, updatedLastResumeTime,
+ PACKAGE + " = ? AND " + CLASS + " = ?",
+ new String[] { packageName, componentName });
+ }
}
mDb.setTransactionSuccessful();
} finally {
mDb.endTransaction();
}
- if (DBG) Log.d(TAG, "Finished updating application launch counts in database.");
+ if (DBG) Log.d(TAG, "Finished updating application usage stats in database.");
}
private String getActivityIconUri(ActivityInfo activityInfo) {
@@ -648,17 +637,25 @@ public class ApplicationsProvider extends ContentProvider {
}
@VisibleForTesting
- protected Map<String, Integer> fetchLaunchCounts() {
+ protected Map<String, PkgUsageStats> fetchUsageStats() {
try {
ActivityManager activityManager = (ActivityManager)
getContext().getSystemService(Context.ACTIVITY_SERVICE);
- Map<String, Integer> allPackageLaunchCounts = activityManager.getAllPackageLaunchCounts();
- return allPackageLaunchCounts;
+ if (activityManager != null) {
+ Map<String, PkgUsageStats> stats = new HashMap<String, PkgUsageStats>();
+ PkgUsageStats[] pkgUsageStats = activityManager.getAllPackageUsageStats();
+ if (pkgUsageStats != null) {
+ for (PkgUsageStats pus : pkgUsageStats) {
+ stats.put(pus.packageName, pus);
+ }
+ }
+ return stats;
+ }
} catch (Exception e) {
- Log.w(TAG, "Could not fetch launch counts", e);
- return new HashMap<String, Integer>();
+ Log.w(TAG, "Could not fetch usage stats", e);
}
+ return new HashMap<String, PkgUsageStats>();
}
@VisibleForTesting
@@ -667,10 +664,10 @@ public class ApplicationsProvider extends ContentProvider {
}
@VisibleForTesting
- protected boolean canRankByLaunchCount() {
- // Only the global search system is allowed to rank apps by launch count.
- // Without this restriction the ApplicationsProvider could leak
- // information about the user's behavior to applications.
+ protected boolean hasGlobalSearchPermission() {
+ // Only the global-search system is allowed to see the usage stats of
+ // applications. Without this restriction the ApplicationsProvider
+ // could leak information about the user's behavior to applications.
return (PackageManager.PERMISSION_GRANTED ==
getContext().checkCallingPermission(android.Manifest.permission.GLOBAL_SEARCH));
}
diff --git a/tests/src/com/android/providers/applications/ApplicationsProviderForTesting.java b/tests/src/com/android/providers/applications/ApplicationsProviderForTesting.java
index fa8645c..90b6711 100644
--- a/tests/src/com/android/providers/applications/ApplicationsProviderForTesting.java
+++ b/tests/src/com/android/providers/applications/ApplicationsProviderForTesting.java
@@ -17,7 +17,9 @@
package com.android.providers.applications;
import android.content.pm.PackageManager;
+import com.android.internal.os.PkgUsageStats;
+import java.util.HashMap;
import java.util.Map;
/**
@@ -29,7 +31,7 @@ public class ApplicationsProviderForTesting extends ApplicationsProvider {
private MockActivityManager mMockActivityManager;
- private boolean mCanRankByLaunchCount;
+ private boolean mHasGlobalSearchPermission;
@Override
protected PackageManager getPackageManager() {
@@ -41,24 +43,24 @@ public class ApplicationsProviderForTesting extends ApplicationsProvider {
}
@Override
- protected Map<String, Integer> fetchLaunchCounts() {
- return mMockActivityManager.getAllPackageLaunchCounts();
+ protected Map<String, PkgUsageStats> fetchUsageStats() {
+ Map<String, PkgUsageStats> stats = new HashMap<String, PkgUsageStats>();
+ for (PkgUsageStats pus : mMockActivityManager.getAllPackageUsageStats()) {
+ stats.put(pus.packageName, pus);
+ }
+ return stats;
}
protected void setMockActivityManager(MockActivityManager mockActivityManager) {
mMockActivityManager = mockActivityManager;
}
- protected void setCanRankByLaunchCount(boolean canRankByLaunchCount) {
- mCanRankByLaunchCount = canRankByLaunchCount;
+ protected void setHasGlobalSearchPermission(boolean hasGlobalSearchPermission) {
+ mHasGlobalSearchPermission = hasGlobalSearchPermission;
}
@Override
- protected boolean canRankByLaunchCount() {
- return mCanRankByLaunchCount;
- }
-
- @Override
- protected void scheduleRegularLaunchCountUpdates() {
+ protected boolean hasGlobalSearchPermission() {
+ return mHasGlobalSearchPermission;
}
}
diff --git a/tests/src/com/android/providers/applications/ApplicationsProviderTest.java b/tests/src/com/android/providers/applications/ApplicationsProviderTest.java
index 77bc064..d512590 100644
--- a/tests/src/com/android/providers/applications/ApplicationsProviderTest.java
+++ b/tests/src/com/android/providers/applications/ApplicationsProviderTest.java
@@ -16,9 +16,11 @@
package com.android.providers.applications;
+import android.app.SearchManager;
import android.content.ComponentName;
import android.content.Context;
import android.database.Cursor;
+import android.net.Uri;
import android.provider.Applications;
import android.test.ProviderTestCase2;
import android.test.suitebuilder.annotation.LargeTest;
@@ -94,6 +96,7 @@ public class ApplicationsProviderTest extends ProviderTestCase2<ApplicationsProv
mockPackageManager.addPackage("AlphabeticB", new ComponentName("b", "b.BView"));
mockPackageManager.addPackage("AlphabeticC", new ComponentName("c", "c.CView"));
mockPackageManager.addPackage("AlphabeticD", new ComponentName("d", "d.DView"));
+ mockPackageManager.addPackage("AlphabeticD2", new ComponentName("d", "d.DView2"));
}
public void testSearch_singleResult() {
@@ -109,24 +112,25 @@ public class ApplicationsProviderTest extends ProviderTestCase2<ApplicationsProv
}
public void testSearch_orderingIsAlphabeticByDefault() {
- testSearch("alphabetic", "AlphabeticA", "AlphabeticB", "AlphabeticC", "AlphabeticD");
+ testSearch("alphabetic", "AlphabeticA", "AlphabeticB", "AlphabeticC", "AlphabeticD",
+ "AlphabeticD2");
}
public void testSearch_emptySearchQueryReturnsEverything() {
testSearch("",
- "AlphabeticA", "AlphabeticB", "AlphabeticC", "AlphabeticD",
+ "AlphabeticA", "AlphabeticB", "AlphabeticC", "AlphabeticD", "AlphabeticD2",
"Ebay", "Email", "Fakeapp");
}
- public void testSearch_appsAreRankedByLaunchCountOnStartup() throws Exception {
- mMockActivityManager.addLaunchCount("d", 3);
- mMockActivityManager.addLaunchCount("b", 1);
- // Missing launch count for "a".
- mMockActivityManager.addLaunchCount("c", 0);
+ public void testSearch_appsAreRankedByUsageTimeOnStartup() throws Exception {
+ mMockActivityManager.addLastResumeTime("d", "d.DView", 3);
+ mMockActivityManager.addLastResumeTime("b", "b.BView", 1);
+ // Missing usage time for "a".
+ mMockActivityManager.addLastResumeTime("c", "c.CView", 0);
// Launch count database is populated on startup.
mProvider = createNewProvider(getMockContext());
- mProvider.setCanRankByLaunchCount(true);
+ mProvider.setHasGlobalSearchPermission(true);
initProvider(mProvider);
// Override the previous provider with the new instance in the
@@ -135,24 +139,56 @@ public class ApplicationsProviderTest extends ProviderTestCase2<ApplicationsProv
// New ranking: D, B, A, C (first by launch count, then
// - if the launch counts of two apps are equal - alphabetically)
- testSearch("alphabetic", "AlphabeticD", "AlphabeticB", "AlphabeticA", "AlphabeticC");
+ testSearch("alphabetic", "AlphabeticD", "AlphabeticB", "AlphabeticA", "AlphabeticC",
+ "AlphabeticD2");
}
- public void testSearch_appsAreRankedByLaunchCountAfterScheduledUpdate() {
- mProvider.setCanRankByLaunchCount(true);
+ public void testSearch_appsAreRankedByResumeTimeAfterUpdate() {
+ mProvider.setHasGlobalSearchPermission(true);
- mMockActivityManager.addLaunchCount("d", 3);
- mMockActivityManager.addLaunchCount("b", 1);
+ mMockActivityManager.addLastResumeTime("d", "d.DView", 3);
+ mMockActivityManager.addLastResumeTime("b", "b.BView", 1);
// Missing launch count for "a".
- mMockActivityManager.addLaunchCount("c", 0);
+ mMockActivityManager.addLastResumeTime("c", "c.CView", 0);
- // Fetch new data from launch count provider (in the real instance this
- // is scheduled).
- mProvider.updateLaunchCounts();
+ // Fetch new data from usage stat provider (in the real instance this
+ // is triggered by a zero-query from global search).
+ mProvider.updateUsageStats();
// New ranking: D, B, A, C (first by launch count, then
// - if the launch counts of two apps are equal - alphabetically)
- testSearch("alphabetic", "AlphabeticD", "AlphabeticB", "AlphabeticA", "AlphabeticC");
+ testSearch("alphabetic", "AlphabeticD", "AlphabeticB", "AlphabeticA", "AlphabeticC",
+ "AlphabeticD2");
+ }
+
+ public void testSearch_noLastAccessTimesWithoutPermission() {
+ mProvider.setHasGlobalSearchPermission(false);
+ mMockActivityManager.addLastResumeTime("d", "d.DView", 1);
+ mMockActivityManager.addLastResumeTime("b", "b.BView", 2);
+ mMockActivityManager.addLastResumeTime("c", "c.CView", 3);
+
+ assertNull(getGlobalSearchCursor("", false));
+
+ Cursor cursor = getGlobalSearchCursor("alphabetic", false);
+ assertEquals(-1, cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_LAST_ACCESS_HINT));
+ }
+
+ public void testSearch_lastAccessTimes() {
+ mProvider.setHasGlobalSearchPermission(true);
+ mMockActivityManager.addLastResumeTime("d", "d.DView", 1);
+ mMockActivityManager.addLastResumeTime("b", "b.BView", 2);
+ mMockActivityManager.addLastResumeTime("c", "c.CView", 3);
+
+ testLastAccessTimes("", true, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0);
+
+ testLastAccessTimes("alphabetic", true, 3, 2, 1, 0, 0);
+
+ // Update the last resume time of "c".
+ mMockActivityManager.addLastResumeTime("c", "c.CView", 5);
+ // Without a refresh, we should see the same results as before.
+ testLastAccessTimes("alphabetic", false, 3, 2, 1, 0, 0);
+ // If we refresh, we should see the change.
+ testLastAccessTimes("alphabetic", true, 5, 2, 1, 0, 0);
}
/**
@@ -162,19 +198,20 @@ public class ApplicationsProviderTest extends ProviderTestCase2<ApplicationsProv
*/
public void testSearch_notAllowedToRankByLaunchCount() {
// Simulate non-privileged calling application.
- mProvider.setCanRankByLaunchCount(false);
+ mProvider.setHasGlobalSearchPermission(false);
- mMockActivityManager.addLaunchCount("d", 3);
- mMockActivityManager.addLaunchCount("b", 1);
- mMockActivityManager.addLaunchCount("a", 0);
- mMockActivityManager.addLaunchCount("c", 0);
+ mMockActivityManager.addLastResumeTime("d", "d.DView", 3);
+ mMockActivityManager.addLastResumeTime("b", "b.BView", 1);
+ mMockActivityManager.addLastResumeTime("a", "a.AView", 0);
+ mMockActivityManager.addLastResumeTime("c", "c.CView", 0);
// Fetch new data from launch count provider.
- mProvider.updateLaunchCounts();
+ mProvider.updateUsageStats();
// Launch count information mustn't be leaked - ranking is still
// alphabetic.
- testSearch("alphabetic", "AlphabeticA", "AlphabeticB", "AlphabeticC", "AlphabeticD");
+ testSearch("alphabetic", "AlphabeticA", "AlphabeticB", "AlphabeticC", "AlphabeticD",
+ "AlphabeticD2");
}
private void testSearch(String searchQuery, String... expectedResultsInOrder) {
@@ -205,6 +242,39 @@ public class ApplicationsProviderTest extends ProviderTestCase2<ApplicationsProv
}
}
+ private Cursor getGlobalSearchCursor(String searchQuery, boolean refresh) {
+ Uri.Builder uriBuilder = Applications.CONTENT_URI.buildUpon();
+ uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY).appendPath(searchQuery);
+ if (refresh) {
+ uriBuilder.appendQueryParameter(ApplicationsProvider.REFRESH_STATS, "");
+ }
+ return getMockContentResolver().query(uriBuilder.build(), null, null, null, null);
+ }
+
+ private void testLastAccessTimes(String searchQuery, boolean refresh,
+ int... expectedLastAccessTimesInOrder) {
+ Cursor cursor = getGlobalSearchCursor(searchQuery, refresh);
+
+ assertNotNull(cursor);
+ assertFalse(cursor.isClosed());
+
+ verifyLastAccessTimes(cursor, expectedLastAccessTimesInOrder);
+
+ cursor.close();
+ }
+
+ private void verifyLastAccessTimes(Cursor cursor, int... expectedLastAccessTimesInOrder) {
+ cursor.moveToFirst();
+ int lastAccessTimeIndex = cursor.getColumnIndex(
+ SearchManager.SUGGEST_COLUMN_LAST_ACCESS_HINT);
+ // Verify that the actual results match the expected ones.
+ for (int i = 0; i < cursor.getCount(); i++) {
+ assertEquals("Wrong last-access time at position " + i,
+ expectedLastAccessTimesInOrder[i], cursor.getInt(lastAccessTimeIndex));
+ cursor.moveToNext();
+ }
+ }
+
private ApplicationsProviderForTesting createNewProvider(Context context) throws Exception {
ApplicationsProviderForTesting newProviderInstance =
ApplicationsProviderForTesting.class.newInstance();
diff --git a/tests/src/com/android/providers/applications/MockActivityManager.java b/tests/src/com/android/providers/applications/MockActivityManager.java
index 63deacf..5ac5cf3 100644
--- a/tests/src/com/android/providers/applications/MockActivityManager.java
+++ b/tests/src/com/android/providers/applications/MockActivityManager.java
@@ -15,6 +15,8 @@
*/
package com.android.providers.applications;
+import com.android.internal.os.PkgUsageStats;
+
import java.util.HashMap;
import java.util.Map;
@@ -24,13 +26,17 @@ import java.util.Map;
*/
public class MockActivityManager {
- private Map<String, Integer> mPackageLaunchCounts = new HashMap<String, Integer>();
+ private Map<String, PkgUsageStats> mPackageUsageStats = new HashMap<String, PkgUsageStats>();
- public void addLaunchCount(String packageName, int launchCount) {
- mPackageLaunchCounts.put(packageName, launchCount);
+ public void addLastResumeTime(String packageName, String componentName, long lastResumeTime) {
+ if (!mPackageUsageStats.containsKey(packageName)) {
+ PkgUsageStats stats = new PkgUsageStats(packageName, 1, 0, new HashMap<String, Long>());
+ mPackageUsageStats.put(packageName, stats);
+ }
+ mPackageUsageStats.get(packageName).componentResumeTimes.put(componentName, lastResumeTime);
}
- public Map<String, Integer> getAllPackageLaunchCounts() {
- return mPackageLaunchCounts;
+ public PkgUsageStats[] getAllPackageUsageStats() {
+ return mPackageUsageStats.values().toArray(new PkgUsageStats[mPackageUsageStats.size()]);
}
}