AppSearch as persistent layer for shortcut
Currently shortcuts are persisted into xml file upon creation/update.
This CL additionally writes those shortcuts into AppSearch.
Shortcuts persisted in AppSearch will only be removed when its publisher
calls setDynamicShortcuts/disableShortcuts or one of the removal api.
Shortcuts won't get removed as a result of exceeding the limit defined
in getMaxShortcutCountPerActivity().
- setDynamicShortcuts:
1. Turn all dynamic shortcuts in memory into floating shortcuts.
2. Remove orphaned shortcuts (floating shotcuts that are neither cached
nor pinned).
3. Add given shortcuts into memory.
4. Remove all shortcuts from AppSearch in background thread.
5. Schedule a job that writes shortcuts from memory into AppSearch in a
background thread.
- addDynamicShortcuts/updateShortcuts:
1. Add or update shortcuts in memory.
2. Schedule a job that writes shortcuts from memory into AppSearch in a
background thread.
- remove APIs:
1. Removes shortcuts from the system ram synchronously.
2. Removes shortcuts from AppSearch in a background thread.
- reportShortcutUsed:
1. Generates an Usage Event that gets propagated to UsageEventManager.
2. Reports document usage in AppSearch in a background thread.
- applyRestore:
1. Parses backup payload and load shortcuts into memory.
2. Persists shortcuts in memory into xml.
3. Schedule a job that writes shortcuts from memory into AppSearch in a
background thread.
- disableShortcuts
1. Turn correponding dynamic shortcuts in memory into floating shortcuts.
2. Remove orphaned shortcuts (floating shortcuts that are neither cached
nor pinned).
3. If any of the shortcuts are removed as a result of step 2, removes
corresponding shortcuts from AppSearch in a background thread.
- enableShortcuts
1. Given the list of shortcutIds, if there exists a matching floating
shortcut, that floating shortcut will be converted into dynamic shortcut
again. Otherwise the shortcutId is ignored.
2. Since this API only mutates in-memory state of a shortcut that is not
persisted into AppSearch, there will be no behavioral change here.
- requestPinShortcuts:
This api mutates in-memory states of a shortcut, these states are not
persisted in AppSearch, thus there will be no behavioral change.
Bug: 151359749
Test: manually enable feature flag and run ShortcutManagerTest12
Test: atest ShortcutManagerTest1 ShortcutManagerTest2
ShortcutManagerTest3 ShortcutManagerTest4 ShortcutManagerTest5
ShortcutManagerTest6 ShortcutManagerTest7 ShortcutManagerTest8
ShortcutManagerTest9 ShortcutManagerTest10 ShortcutManagerTest11
ShortcutManagerTest12
Test: atest CtsShortcutManagerTestCases
Change-Id: Ibc06fa705cb65c220c4991a1b0247c1a4f318fed
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user