Merge "AppSearch as persistent layer for shortcut"

This commit is contained in:
TreeHugger Robot
2021-11-11 17:03:46 +00:00
committed by Android (Google) Code Review
6 changed files with 514 additions and 783 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -467,7 +467,7 @@ class ShortcutRequestPinProcessor {
launcher.attemptToRestoreIfNeededAndSave();
if (launcher.hasPinned(original)) {
if (DEBUG) {
Slog.d(TAG, "Shortcut " + original + " already pinned."); // This too.
Slog.d(TAG, "Shortcut " + original + " already pinned."); // This too.
}
return true;
}
@@ -517,7 +517,8 @@ class ShortcutRequestPinProcessor {
if (DEBUG) {
Slog.d(TAG, "Removing " + shortcutId + " as dynamic");
}
ps.deleteDynamicWithId(shortcutId, /*ignoreInvisible=*/ false);
ps.deleteDynamicWithId(shortcutId, /*ignoreInvisible=*/ false,
/*wasPushedOut=*/ false);
}
ps.adjustRanks(); // Shouldn't be needed, but just in case.

View File

@@ -1965,13 +1965,12 @@ public class ShortcutService extends IShortcutService.Stub {
ArrayList<ShortcutInfo> cachedOrPinned = new ArrayList<>();
ps.findAll(cachedOrPinned,
AppSearchShortcutInfo.QUERY_IS_VISIBLE_CACHED_OR_PINNED,
(ShortcutInfo si) -> si.isVisibleToPublisher()
&& si.isDynamic() && (si.isCached() || si.isPinned()),
ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
// First, remove all un-pinned and non-cached; dynamic shortcuts
removedShortcuts = ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true);
removedShortcuts = ps.deleteAllDynamicShortcuts();
// Then, add/update all. We need to make sure to take over "pinned" flag.
for (int i = 0; i < size; i++) {
@@ -2381,7 +2380,8 @@ public class ShortcutService extends IShortcutService.Stub {
if (!ps.isShortcutExistsAndVisibleToPublisher(id)) {
continue;
}
ShortcutInfo removed = ps.deleteDynamicWithId(id, /*ignoreInvisible=*/ true);
ShortcutInfo removed = ps.deleteDynamicWithId(id, /*ignoreInvisible=*/ true,
/*wasPushedOut*/ false);
if (removed == null) {
if (changedShortcuts == null) {
changedShortcuts = new ArrayList<>(1);
@@ -2412,11 +2412,10 @@ public class ShortcutService extends IShortcutService.Stub {
userId);
// Dynamic shortcuts that are either cached or pinned will not get deleted.
ps.findAll(changedShortcuts,
AppSearchShortcutInfo.QUERY_IS_VISIBLE_CACHED_OR_PINNED,
(ShortcutInfo si) -> si.isVisibleToPublisher()
&& si.isDynamic() && (si.isCached() || si.isPinned()),
ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
removedShortcuts = ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true);
removedShortcuts = ps.deleteAllDynamicShortcuts();
changedShortcuts = prepareChangedShortcuts(
changedShortcuts, null, removedShortcuts, ps);
}
@@ -2475,10 +2474,8 @@ public class ShortcutService extends IShortcutService.Stub {
| (matchPinned ? ShortcutInfo.FLAG_PINNED : 0)
| (matchManifest ? ShortcutInfo.FLAG_MANIFEST : 0)
| (matchCached ? ShortcutInfo.FLAG_CACHED_ALL : 0);
final String query = AppSearchShortcutInfo.QUERY_IS_VISIBLE_TO_PUBLISHER + " "
+ createQuery(matchDynamic, matchPinned, matchManifest, matchCached);
return getShortcutsWithQueryLocked(
packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, query,
packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
(ShortcutInfo si) ->
si.isVisibleToPublisher()
&& (si.getFlags() & shortcutFlags) != 0);
@@ -2542,13 +2539,12 @@ public class ShortcutService extends IShortcutService.Stub {
@GuardedBy("mLock")
private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName,
@UserIdInt int userId, int cloneFlags, @NonNull final String query,
@NonNull Predicate<ShortcutInfo> filter) {
@UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> filter) {
final ArrayList<ShortcutInfo> ret = new ArrayList<>();
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.findAll(ret, query, filter, cloneFlags);
ps.findAll(ret, filter, cloneFlags);
return new ParceledListSlice<>(setReturnedByServer(ret));
}
@@ -2955,27 +2951,14 @@ public class ShortcutService extends IShortcutService.Stub {
((queryFlags & ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER) != 0);
queryFlags |= (getPinnedByAnyLauncher ? ShortcutQuery.FLAG_MATCH_PINNED : 0);
final boolean matchPinnedOnly =
((queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0)
&& ((queryFlags & ShortcutQuery.FLAG_MATCH_CACHED) == 0)
&& ((queryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) == 0)
&& ((queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) == 0);
final Predicate<ShortcutInfo> filter = getFilterFromQuery(ids, locusIds, changedSince,
componentName, queryFlags, getPinnedByAnyLauncher);
if (matchPinnedOnly) {
p.findAllPinned(ret, filter, cloneFlag, callingPackage, launcherUserId,
getPinnedByAnyLauncher);
} else if (ids != null && !ids.isEmpty()) {
if (ids != null && !ids.isEmpty()) {
p.findAllByIds(ret, ids, filter, cloneFlag, callingPackage, launcherUserId,
getPinnedByAnyLauncher);
} else {
final boolean matchDynamic = (queryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) != 0;
final boolean matchPinned = (queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0;
final boolean matchManifest = (queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) != 0;
final boolean matchCached = (queryFlags & ShortcutQuery.FLAG_MATCH_CACHED) != 0;
p.findAll(ret, createQuery(matchDynamic, matchPinned, matchManifest, matchCached),
filter, cloneFlag, callingPackage, launcherUserId, getPinnedByAnyLauncher);
p.findAll(ret, filter, cloneFlag, callingPackage, launcherUserId,
getPinnedByAnyLauncher);
}
}
@@ -3090,8 +3073,7 @@ public class ShortcutService extends IShortcutService.Stub {
if (sp != null) {
// List the shortcuts that are pinned only, these will get removed.
removedShortcuts = new ArrayList<>();
sp.findAll(removedShortcuts, AppSearchShortcutInfo.QUERY_IS_VISIBLE_PINNED_ONLY,
(ShortcutInfo si) -> si.isVisibleToPublisher()
sp.findAll(removedShortcuts, (ShortcutInfo si) -> si.isVisibleToPublisher()
&& si.isPinned() && !si.isCached() && !si.isDynamic()
&& !si.isDeclaredInManifest(),
ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO,
@@ -3183,8 +3165,7 @@ public class ShortcutService extends IShortcutService.Stub {
if (doCache) {
if (si.isLongLived()) {
sp.mutateShortcut(si.getId(), si,
shortcut -> shortcut.addFlags(cacheFlags));
si.addFlags(cacheFlags);
if (changedShortcuts == null) {
changedShortcuts = new ArrayList<>(1);
}
@@ -3195,21 +3176,20 @@ public class ShortcutService extends IShortcutService.Stub {
}
} else {
ShortcutInfo removed = null;
sp.mutateShortcut(si.getId(), si, shortcut ->
shortcut.clearFlags(cacheFlags));
si.clearFlags(cacheFlags);
if (!si.isDynamic() && !si.isCached()) {
removed = sp.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true);
}
if (removed != null) {
if (removedShortcuts == null) {
removedShortcuts = new ArrayList<>(1);
}
removedShortcuts.add(removed);
} else {
if (removed == null) {
if (changedShortcuts == null) {
changedShortcuts = new ArrayList<>(1);
}
changedShortcuts.add(si);
} else {
if (removedShortcuts == null) {
removedShortcuts = new ArrayList<>(1);
}
removedShortcuts.add(removed);
}
}
}
@@ -5084,8 +5064,7 @@ public class ShortcutService extends IShortcutService.Stub {
synchronized (mLock) {
final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId);
if (pkg == null) return;
pkg.mutateShortcut(shortcutId, null, cb);
cb.accept(pkg.findShortcutById(shortcutId));
}
}

View File

@@ -186,7 +186,7 @@ class ShortcutUser {
final ShortcutPackage removed = mPackages.remove(packageName);
if (removed != null) {
removed.removeShortcuts();
removed.removeAllShortcutsAsync();
}
mService.cleanupBitmapsForPackage(mUserId, packageName);
@@ -577,7 +577,7 @@ class ShortcutUser {
Log.w(TAG, "Shortcuts for package " + sp.getPackageName() + " are being restored."
+ " Existing non-manifeset shortcuts will be overwritten.");
}
sp.restoreParsedShortcuts();
sp.removeAllShortcutsAsync();
addPackage(sp);
restoredPackages[0]++;
restoredShortcuts[0] += sp.getShortcutCount();
@@ -714,6 +714,7 @@ class ShortcutUser {
.setSubtype(totalSharingShortcutCount));
}
@NonNull
AndroidFuture<AppSearchSession> getAppSearch(
@NonNull final AppSearchManager.SearchContext searchContext) {
final AndroidFuture<AppSearchSession> future = new AndroidFuture<>();

View File

@@ -46,18 +46,6 @@ import android.app.IUidObserver;
import android.app.PendingIntent;
import android.app.Person;
import android.app.admin.DevicePolicyManager;
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.PackageIdentifier;
import android.app.appsearch.SearchResultPage;
import android.app.appsearch.SetSchemaResponse;
import android.app.appsearch.aidl.AppSearchBatchResultParcel;
import android.app.appsearch.aidl.AppSearchResultParcel;
import android.app.appsearch.aidl.IAppSearchBatchResultCallback;
import android.app.appsearch.aidl.IAppSearchManager;
import android.app.appsearch.aidl.IAppSearchResultCallback;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ActivityNotFoundException;
@@ -92,9 +80,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
@@ -106,6 +92,7 @@ import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import com.android.internal.infra.AndroidFuture;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.LauncherAppsService.LauncherAppsImpl;
@@ -168,7 +155,6 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
case Context.DEVICE_POLICY_SERVICE:
return mMockDevicePolicyManager;
case Context.APP_SEARCH_SERVICE:
return new AppSearchManager(this, mMockAppSearchManager);
case Context.ROLE_SERVICE:
// RoleManager is final and cannot be mocked, so we only override the inject
// accessor methods in ShortcutService.
@@ -647,260 +633,6 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
}
}
protected class MockAppSearchManager implements IAppSearchManager {
protected Map<String, List<PackageIdentifier>> mSchemasVisibleToPackages =
new ArrayMap<>(1);
private Map<String, Map<String, GenericDocument>> mDocumentMap = new ArrayMap<>(1);
private String getKey(UserHandle userHandle, String databaseName) {
return userHandle.getIdentifier() + "@" + databaseName;
}
@Override
public void setSchema(String packageName, String databaseName, List<Bundle> schemaBundles,
List<String> schemasNotDisplayedBySystem,
Map<String, List<Bundle>> schemasVisibleToPackagesBundles, boolean forceOverride,
int version, UserHandle userHandle, long binderCallStartTimeMillis,
IAppSearchResultCallback callback) throws RemoteException {
for (Map.Entry<String, List<Bundle>> entry :
schemasVisibleToPackagesBundles.entrySet()) {
final String key = entry.getKey();
final List<PackageIdentifier> packageIdentifiers;
if (!mSchemasVisibleToPackages.containsKey(key)) {
packageIdentifiers = new ArrayList<>(entry.getValue().size());
mSchemasVisibleToPackages.put(key, packageIdentifiers);
} else {
packageIdentifiers = mSchemasVisibleToPackages.get(key);
}
for (int i = 0; i < entry.getValue().size(); i++) {
packageIdentifiers.add(new PackageIdentifier(entry.getValue().get(i)));
}
}
final SetSchemaResponse response = new SetSchemaResponse.Builder().build();
callback.onResult(
new AppSearchResultParcel(
AppSearchResult.newSuccessfulResult(response.getBundle())));
}
@Override
public void getSchema(String packageName, String databaseName, UserHandle userHandle,
IAppSearchResultCallback callback) throws RemoteException {
ignore(callback);
}
@Override
public void getNamespaces(String packageName, String databaseName, UserHandle userHandle,
IAppSearchResultCallback callback) throws RemoteException {
ignore(callback);
}
@Override
public void putDocuments(String packageName, String databaseName,
List<Bundle> documentBundles, UserHandle userHandle, long binderCallStartTimeMillis,
IAppSearchBatchResultCallback callback)
throws RemoteException {
final List<GenericDocument> docs = new ArrayList<>(documentBundles.size());
for (Bundle bundle : documentBundles) {
docs.add(new GenericDocument(bundle));
}
final AppSearchBatchResult.Builder<String, Void> builder =
new AppSearchBatchResult.Builder<>();
final String key = getKey(userHandle, databaseName);
Map<String, GenericDocument> docMap = mDocumentMap.get(key);
for (GenericDocument doc : docs) {
builder.setSuccess(doc.getId(), null);
if (docMap == null) {
docMap = new ArrayMap<>(1);
mDocumentMap.put(key, docMap);
}
docMap.put(doc.getId(), doc);
}
callback.onResult(new AppSearchBatchResultParcel<>(builder.build()));
}
@Override
public void getDocuments(String packageName, String databaseName, String namespace,
List<String> ids, Map<String, List<String>> typePropertyPaths,
UserHandle userHandle, long binderCallStartTimeMillis,
IAppSearchBatchResultCallback callback) throws RemoteException {
final AppSearchBatchResult.Builder<String, Bundle> builder =
new AppSearchBatchResult.Builder<>();
final String key = getKey(userHandle, databaseName);
if (!mDocumentMap.containsKey(key)) {
for (String id : ids) {
builder.setFailure(id, AppSearchResult.RESULT_NOT_FOUND,
key + " not found when getting: " + id);
}
} else {
final Map<String, GenericDocument> docs = mDocumentMap.get(key);
for (String id : ids) {
if (docs.containsKey(id)) {
builder.setSuccess(id, docs.get(id).getBundle());
} else {
builder.setFailure(id, AppSearchResult.RESULT_NOT_FOUND,
"shortcut not found: " + id);
}
}
}
callback.onResult(new AppSearchBatchResultParcel<>(builder.build()));
}
@Override
public void query(String packageName, String databaseName, String queryExpression,
Bundle searchSpecBundle, UserHandle userHandle, long binderCallStartTimeMillis,
IAppSearchResultCallback callback)
throws RemoteException {
final String key = getKey(userHandle, databaseName);
if (!mDocumentMap.containsKey(key)) {
final Bundle page = new Bundle();
page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 1);
page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, new ArrayList<>());
callback.onResult(
new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(page)));
return;
}
final List<GenericDocument> documents = new ArrayList<>(mDocumentMap.get(key).values());
final Bundle page = new Bundle();
page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 0);
final ArrayList<Bundle> resultBundles = new ArrayList<>();
for (GenericDocument document : documents) {
final Bundle resultBundle = new Bundle();
resultBundle.putBundle("document", document.getBundle());
resultBundle.putString("packageName", packageName);
resultBundle.putString("databaseName", databaseName);
resultBundle.putParcelableArrayList("matches", new ArrayList<>());
resultBundles.add(resultBundle);
}
page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, resultBundles);
callback.onResult(
new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(page)));
}
@Override
public void globalQuery(String packageName, String queryExpression, Bundle searchSpecBundle,
UserHandle userHandle, long binderCallStartTimeMillis,
IAppSearchResultCallback callback) throws RemoteException {
ignore(callback);
}
@Override
public void getNextPage(String packageName, long nextPageToken, UserHandle userHandle,
IAppSearchResultCallback callback) throws RemoteException {
final Bundle page = new Bundle();
page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 1);
page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, new ArrayList<>());
callback.onResult(
new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(page)));
}
@Override
public void invalidateNextPageToken(String packageName, long nextPageToken,
UserHandle userHandle) throws RemoteException {
}
@Override
public void writeQueryResultsToFile(String packageName, String databaseName,
ParcelFileDescriptor fileDescriptor, String queryExpression,
Bundle searchSpecBundle, UserHandle userHandle, IAppSearchResultCallback callback)
throws RemoteException {
ignore(callback);
}
@Override
public void putDocumentsFromFile(String packageName, String databaseName,
ParcelFileDescriptor fileDescriptor, UserHandle userHandle,
IAppSearchResultCallback callback)
throws RemoteException {
ignore(callback);
}
@Override
public void reportUsage(String packageName, String databaseName, String namespace,
String documentId, long usageTimestampMillis, boolean systemUsage,
UserHandle userHandle,
IAppSearchResultCallback callback)
throws RemoteException {
ignore(callback);
}
@Override
public void removeByDocumentId(String packageName, String databaseName, String namespace,
List<String> ids, UserHandle userHandle, long binderCallStartTimeMillis,
IAppSearchBatchResultCallback callback)
throws RemoteException {
final AppSearchBatchResult.Builder<String, Void> builder =
new AppSearchBatchResult.Builder<>();
final String key = getKey(userHandle, databaseName);
if (!mDocumentMap.containsKey(key)) {
for (String id : ids) {
builder.setFailure(id, AppSearchResult.RESULT_NOT_FOUND,
"package " + key + " not found when removing " + id);
}
} else {
final Map<String, GenericDocument> docs = mDocumentMap.get(key);
for (String id : ids) {
if (docs.containsKey(id)) {
docs.remove(id);
builder.setSuccess(id, null);
} else {
builder.setFailure(id, AppSearchResult.RESULT_NOT_FOUND,
"shortcut not found when removing " + id);
}
}
}
callback.onResult(new AppSearchBatchResultParcel<>(builder.build()));
}
@Override
public void removeByQuery(String packageName, String databaseName, String queryExpression,
Bundle searchSpecBundle, UserHandle userHandle, long binderCallStartTimeMillis,
IAppSearchResultCallback callback)
throws RemoteException {
final String key = getKey(userHandle, databaseName);
if (!mDocumentMap.containsKey(key)) {
callback.onResult(
new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(null)));
return;
}
mDocumentMap.get(key).clear();
callback.onResult(
new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(null)));
}
@Override
public void getStorageInfo(String packageName, String databaseName, UserHandle userHandle,
IAppSearchResultCallback callback) throws RemoteException {
ignore(callback);
}
@Override
public void persistToDisk(String packageName, UserHandle userHandle,
long binderCallStartTimeMillis) throws RemoteException {
}
@Override
public void initialize(String packageName, UserHandle userHandle,
long binderCallStartTimeMillis, IAppSearchResultCallback callback)
throws RemoteException {
ignore(callback);
}
@Override
public IBinder asBinder() {
return null;
}
private void removeShortcuts() {
mDocumentMap.clear();
}
private void ignore(IAppSearchResultCallback callback) throws RemoteException {
callback.onResult(
new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(null)));
}
}
public static class ShortcutActivity extends Activity {
}
@@ -952,7 +684,6 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
protected PackageManagerInternal mMockPackageManagerInternal;
protected UserManager mMockUserManager;
protected DevicePolicyManager mMockDevicePolicyManager;
protected MockAppSearchManager mMockAppSearchManager;
protected UserManagerInternal mMockUserManagerInternal;
protected UsageStatsManagerInternal mMockUsageStatsManagerInternal;
protected ActivityManagerInternal mMockActivityManagerInternal;
@@ -1102,7 +833,6 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
mMockPackageManagerInternal = mock(PackageManagerInternal.class);
mMockUserManager = mock(UserManager.class);
mMockDevicePolicyManager = mock(DevicePolicyManager.class);
mMockAppSearchManager = new MockAppSearchManager();
mMockUserManagerInternal = mock(UserManagerInternal.class);
mMockUsageStatsManagerInternal = mock(UsageStatsManagerInternal.class);
mMockActivityManagerInternal = mock(ActivityManagerInternal.class);
@@ -1314,9 +1044,6 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
shutdownServices();
mMockAppSearchManager.removeShortcuts();
mMockAppSearchManager = null;
super.tearDown();
}
@@ -2235,6 +1962,18 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
return p == null ? null : p.getAllShareTargetsForTest();
}
protected void resetPersistedShortcuts() {
final ShortcutPackage p = mService.getPackageShortcutForTest(
getCallingPackage(), getCallingUserId());
p.removeAllShortcutsAsync();
}
protected void getPersistedShortcut(AndroidFuture<List<ShortcutInfo>> cb) {
final ShortcutPackage p = mService.getPackageShortcutForTest(
getCallingPackage(), getCallingUserId());
p.getTopShortcutsFromPersistence(cb);
}
/**
* @return the number of shortcuts stored internally for the caller that can be used as a share
* target in the ShareSheet. Such shortcuts have a matching category with at least one of the
@@ -2425,8 +2164,6 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
deleteAllSavedFiles();
mMockAppSearchManager.removeShortcuts();
initService();
mService.applyRestore(payload, USER_0);

View File

@@ -18,9 +18,20 @@ package com.android.server.pm;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
import android.app.PendingIntent;
import android.content.pm.ShortcutInfo;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import com.android.internal.infra.AndroidFuture;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Tests for {@link android.app.appsearch.AppSearchManager} and relevant APIs in ShortcutManager.
*
@@ -28,6 +39,21 @@ import android.os.UserHandle;
*/
public class ShortcutManagerTest12 extends BaseShortcutManagerTest {
@Override
protected void setUp() throws Exception {
super.setUp();
mService.updateConfigurationLocked(
ShortcutService.ConfigConstants.KEY_MAX_SHORTCUTS + "=5,"
+ ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1");
}
@Override
protected void tearDown() throws Exception {
setCaller(CALLING_PACKAGE_1, USER_0);
mService.getPackageShortcutForTest(CALLING_PACKAGE_1, USER_0).removeAllShortcutsAsync();
super.tearDown();
}
public void testGetShortcutIntents_ReturnsMutablePendingIntents() throws RemoteException {
setDefaultLauncher(USER_0, LAUNCHER_1);
@@ -41,4 +67,201 @@ public class ShortcutManagerTest12 extends BaseShortcutManagerTest {
assertNotNull(intent);
});
}
public void testSetDynamicShortcuts_PersistsShortcutsToDisk() throws RemoteException {
if (!mService.isAppSearchEnabled()) {
return;
}
setCaller(CALLING_PACKAGE_1, USER_0);
// Verifies setDynamicShortcuts persists shortcuts into AppSearch
mManager.setDynamicShortcuts(list(
makeShortcut("s1"),
makeShortcut("s2"),
makeShortcut("s3")
));
List<ShortcutInfo> shortcuts = getAllPersistedShortcuts();
assertNotNull(shortcuts);
assertEquals(3, shortcuts.size());
Set<String> shortcutIds =
shortcuts.stream().map(ShortcutInfo::getId).collect(Collectors.toSet());
assertTrue(shortcutIds.contains("s1"));
assertTrue(shortcutIds.contains("s2"));
assertTrue(shortcutIds.contains("s3"));
// Verifies removeAllDynamicShortcuts removes shortcuts from persistence layer
mManager.removeAllDynamicShortcuts();
shortcuts = getAllPersistedShortcuts();
assertNotNull(shortcuts);
assertTrue(shortcuts.isEmpty());
}
public void testAddDynamicShortcuts_PersistsShortcutsToDisk() {
if (!mService.isAppSearchEnabled()) {
return;
}
setCaller(CALLING_PACKAGE_1, USER_0);
mManager.setDynamicShortcuts(list(
makeShortcut("s1"),
makeShortcut("s2"),
makeShortcut("s3")
));
// Verifies addDynamicShortcuts persists shortcuts into AppSearch
mManager.addDynamicShortcuts(list(makeShortcut("s4"), makeShortcut("s5")));
final List<ShortcutInfo> shortcuts = getAllPersistedShortcuts();
assertNotNull(shortcuts);
assertEquals(5, shortcuts.size());
final Set<String> shortcutIds =
shortcuts.stream().map(ShortcutInfo::getId).collect(Collectors.toSet());
assertTrue(shortcutIds.contains("s1"));
assertTrue(shortcutIds.contains("s2"));
assertTrue(shortcutIds.contains("s3"));
assertTrue(shortcutIds.contains("s4"));
assertTrue(shortcutIds.contains("s5"));
}
public void testPushDynamicShortcuts_PersistsShortcutsToDisk() {
if (!mService.isAppSearchEnabled()) {
return;
}
setCaller(CALLING_PACKAGE_1, USER_0);
mManager.setDynamicShortcuts(list(
makeShortcut("s1"),
makeShortcut("s2"),
makeShortcut("s3"),
makeShortcut("s4"),
makeShortcut("s5")
));
List<ShortcutInfo> shortcuts = getAllPersistedShortcuts();
assertNotNull(shortcuts);
assertEquals(5, shortcuts.size());
Set<String> shortcutIds =
shortcuts.stream().map(ShortcutInfo::getId).collect(Collectors.toSet());
assertTrue(shortcutIds.contains("s1"));
assertTrue(shortcutIds.contains("s2"));
assertTrue(shortcutIds.contains("s3"));
assertTrue(shortcutIds.contains("s4"));
assertTrue(shortcutIds.contains("s5"));
// Verifies pushDynamicShortcuts further persists shortcuts into AppSearch without
// removing previous shortcuts when max number of shortcuts is reached.
mManager.pushDynamicShortcut(makeShortcut("s6"));
shortcuts = getAllPersistedShortcuts();
assertNotNull(shortcuts);
assertEquals(6, shortcuts.size());
shortcutIds = shortcuts.stream().map(ShortcutInfo::getId).collect(Collectors.toSet());
assertTrue(shortcutIds.contains("s1"));
assertTrue(shortcutIds.contains("s2"));
assertTrue(shortcutIds.contains("s3"));
assertTrue(shortcutIds.contains("s4"));
assertTrue(shortcutIds.contains("s5"));
assertTrue(shortcutIds.contains("s6"));
}
public void testRemoveDynamicShortcuts_RemovesShortcutsFromDisk() {
if (!mService.isAppSearchEnabled()) {
return;
}
setCaller(CALLING_PACKAGE_1, USER_0);
mManager.setDynamicShortcuts(list(
makeShortcut("s1"),
makeShortcut("s2"),
makeShortcut("s3"),
makeShortcut("s4"),
makeShortcut("s5")
));
// Verifies removeDynamicShortcuts removes shortcuts from persistence layer
mManager.removeDynamicShortcuts(list("s1"));
final List<ShortcutInfo> shortcuts = getAllPersistedShortcuts();
assertNotNull(shortcuts);
assertEquals(4, shortcuts.size());
final Set<String> shortcutIds =
shortcuts.stream().map(ShortcutInfo::getId).collect(Collectors.toSet());
assertTrue(shortcutIds.contains("s2"));
assertTrue(shortcutIds.contains("s3"));
assertTrue(shortcutIds.contains("s4"));
assertTrue(shortcutIds.contains("s5"));
}
public void testRemoveLongLivedShortcuts_RemovesShortcutsFromDisk() {
if (!mService.isAppSearchEnabled()) {
return;
}
setCaller(CALLING_PACKAGE_1, USER_0);
mManager.setDynamicShortcuts(list(
makeShortcut("s1"),
makeShortcut("s2"),
makeShortcut("s3"),
makeShortcut("s4"),
makeShortcut("s5")
));
mManager.removeDynamicShortcuts(list("s2"));
final List<ShortcutInfo> shortcuts = getAllPersistedShortcuts();
assertNotNull(shortcuts);
assertEquals(4, shortcuts.size());
final Set<String> shortcutIds =
shortcuts.stream().map(ShortcutInfo::getId).collect(Collectors.toSet());
assertTrue(shortcutIds.contains("s1"));
assertTrue(shortcutIds.contains("s3"));
assertTrue(shortcutIds.contains("s4"));
assertTrue(shortcutIds.contains("s5"));
}
public void testDisableShortcuts_RemovesShortcutsFromDisk() {
if (!mService.isAppSearchEnabled()) {
return;
}
setCaller(CALLING_PACKAGE_1, USER_0);
mManager.setDynamicShortcuts(list(
makeShortcut("s1"),
makeShortcut("s2"),
makeShortcut("s3"),
makeShortcut("s4"),
makeShortcut("s5")
));
// Verifies disableShortcuts removes shortcuts from persistence layer
mManager.disableShortcuts(list("s3"));
final List<ShortcutInfo> shortcuts = getAllPersistedShortcuts();
assertNotNull(shortcuts);
assertEquals(4, shortcuts.size());
final Set<String> shortcutIds =
shortcuts.stream().map(ShortcutInfo::getId).collect(Collectors.toSet());
assertTrue(shortcutIds.contains("s1"));
assertTrue(shortcutIds.contains("s2"));
assertTrue(shortcutIds.contains("s4"));
assertTrue(shortcutIds.contains("s5"));
}
public void testUpdateShortcuts_UpdateShortcutsOnDisk() {
if (!mService.isAppSearchEnabled()) {
return;
}
setCaller(CALLING_PACKAGE_1, USER_0);
mManager.setDynamicShortcuts(list(
makeShortcut("s1"),
makeShortcut("s2"),
makeShortcut("s3"),
makeShortcut("s4"),
makeShortcut("s5")
));
// Verifies disableShortcuts removes shortcuts from persistence layer
mManager.updateShortcuts(list(makeShortcutWithShortLabel("s3", "custom")));
final List<ShortcutInfo> shortcuts = getAllPersistedShortcuts();
assertNotNull(shortcuts);
assertEquals(5, shortcuts.size());
final Map<String, ShortcutInfo> map = shortcuts.stream()
.collect(Collectors.toMap(ShortcutInfo::getId, Function.identity()));
assertTrue(map.containsKey("s3"));
assertEquals("custom", map.get("s3").getShortLabel());
}
private List<ShortcutInfo> getAllPersistedShortcuts() {
try {
SystemClock.sleep(500);
final AndroidFuture<List<ShortcutInfo>> future = new AndroidFuture<>();
getPersistedShortcut(future);
return future.get(10, TimeUnit.SECONDS);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}