summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Pinyao Ting <pinyaoting@google.com> 2021-01-14 09:32:45 -0800
committer Pinyao Ting <pinyaoting@google.com> 2021-02-24 14:55:00 -0800
commit85ad9ee3f7fb4feb747da9933cbdf8bfe26f68f6 (patch)
tree311b631ea89080231334f903ac89075cf26ec75b
parent01af9eec33b37edc1ad8bba4cb9022c2e31bfd73 (diff)
Shortcut integration with AppSearch (Part 1)
This CL includes the initial code change to integrate shortcuts with AppSearch and provides a public api to allow publishers sharing access to shortcuts with other apps. Bug: 151359749 CTS-Coverage-Bug: 180558621 Test: manual Change-Id: I08430dc3d49e4f2588c68c61561fffb2b248f4c9
-rw-r--r--core/api/current.txt1
-rw-r--r--core/java/android/content/pm/IShortcutService.aidl3
-rw-r--r--core/java/android/content/pm/ShortcutManager.java17
-rw-r--r--services/core/java/com/android/server/pm/ShortcutPackage.java156
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java9
-rw-r--r--services/core/java/com/android/server/pm/ShortcutUser.java26
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java134
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java44
8 files changed, 390 insertions, 0 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 0eb3b49ac8b1..5b48dadb000a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12893,6 +12893,7 @@ package android.content.pm {
method public void reportShortcutUsed(String);
method public boolean requestPinShortcut(@NonNull android.content.pm.ShortcutInfo, @Nullable android.content.IntentSender);
method public boolean setDynamicShortcuts(@NonNull java.util.List<android.content.pm.ShortcutInfo>);
+ method public void updateShortcutVisibility(@NonNull String, @Nullable byte[], boolean);
method public boolean updateShortcuts(@NonNull java.util.List<android.content.pm.ShortcutInfo>);
field public static final int FLAG_MATCH_CACHED = 8; // 0x8
field public static final int FLAG_MATCH_DYNAMIC = 2; // 0x2
diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl
index 29a55b7a74da..b34574811bca 100644
--- a/core/java/android/content/pm/IShortcutService.aidl
+++ b/core/java/android/content/pm/IShortcutService.aidl
@@ -78,4 +78,7 @@ interface IShortcutService {
ParceledListSlice getShortcuts(String packageName, int matchFlags, int userId);
void pushDynamicShortcut(String packageName, in ShortcutInfo shortcut, int userId);
+
+ void updateShortcutVisibility(String callingPkg, String packageName, in byte[] certificate,
+ in boolean visible, int userId);
} \ No newline at end of file
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 35c99a13a152..d3bac79aa2b9 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -39,6 +39,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import com.android.internal.annotations.VisibleForTesting;
@@ -771,4 +772,20 @@ public class ShortcutManager {
}
}
+ /**
+ * Granting another app the access to the shortcuts you own. You must provide the package name
+ * and their SHA256 certificate digest in order to granting the access.
+ *
+ * Once granted, the other app can retain a copy of all the shortcuts you own when calling
+ * {@link LauncherApps#getShortcuts(LauncherApps.ShortcutQuery, UserHandle)}.
+ */
+ public void updateShortcutVisibility(@NonNull final String packageName,
+ @Nullable final byte[] certificate, final boolean visible) {
+ try {
+ mService.updateShortcutVisibility(mContext.getPackageName(), packageName, certificate,
+ visible, injectMyUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index bb4ec16be0a8..a604afc22c09 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -19,15 +19,22 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.Person;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchSession;
+import android.app.appsearch.PackageIdentifier;
+import android.app.appsearch.SetSchemaRequest;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.LocusId;
+import android.content.pm.AppSearchPerson;
+import android.content.pm.AppSearchShortcutInfo;
import android.content.pm.PackageInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.content.res.Resources;
import android.graphics.drawable.Icon;
+import android.os.Binder;
import android.os.PersistableBundle;
import android.text.format.Formatter;
import android.util.ArrayMap;
@@ -39,9 +46,11 @@ import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.server.pm.ShortcutService.DumpFilter;
@@ -64,8 +73,12 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Predicate;
/**
@@ -155,6 +168,16 @@ class ShortcutPackage extends ShortcutPackageItem {
private long mLastKnownForegroundElapsedTime;
+ private final Object mLock = new Object();
+
+ /**
+ * All external packages that have gained access to the shortcuts from this package
+ */
+ private final Map<String, PackageIdentifier> mPackageIdentifiers = new ArrayMap<>(0);
+
+ @GuardedBy("mLock")
+ private AppSearchSession mAppSearchSession;
+
private ShortcutPackage(ShortcutUser shortcutUser,
int packageUserId, String packageName, ShortcutPackageInfo spi) {
super(shortcutUser, packageUserId, packageName,
@@ -2140,6 +2163,15 @@ class ShortcutPackage extends ShortcutPackageItem {
}
}
+ void updateVisibility(String packageName, byte[] certificate, boolean visible) {
+ if (visible) {
+ mPackageIdentifiers.put(packageName, new PackageIdentifier(packageName, certificate));
+ } else {
+ mPackageIdentifiers.remove(packageName);
+ }
+ resetAppSearch(null);
+ }
+
private boolean verifyRanksSequential(List<ShortcutInfo> list) {
boolean failed = false;
@@ -2153,4 +2185,128 @@ class ShortcutPackage extends ShortcutPackageItem {
}
return failed;
}
+
+ private void runInAppSearch(
+ Function<SearchSessionObservable, Consumer<AppSearchSession>>... observers) {
+ if (mShortcutUser == null) {
+ Slog.w(TAG, "shortcut user is null");
+ return;
+ }
+ synchronized (mLock) {
+ if (mAppSearchSession != null) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ final SearchSessionObservable upstream =
+ new SearchSessionObservable(mAppSearchSession, latch);
+ for (Function<SearchSessionObservable, Consumer<AppSearchSession>> observer
+ : observers) {
+ upstream.map(observer);
+ }
+ upstream.next();
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ ConcurrentUtils.waitForCountDownNoInterrupt(latch, 500,
+ "timeout accessing shortcut");
+ } else {
+ resetAppSearch(observers);
+ }
+ }
+ }
+
+ private void resetAppSearch(
+ Function<SearchSessionObservable, Consumer<AppSearchSession>>... observers) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AppSearchManager.SearchContext searchContext =
+ new AppSearchManager.SearchContext.Builder()
+ .setDatabaseName(getPackageName()).build();
+ mShortcutUser.runInAppSearch(searchContext, result -> {
+ if (!result.isSuccess()) {
+ Slog.e(TAG, "error getting search session during lazy init, "
+ + result.getErrorMessage());
+ latch.countDown();
+ return;
+ }
+ // TODO: Flatten callback chain with proper async framework
+ final SearchSessionObservable upstream =
+ new SearchSessionObservable(result.getResultValue(), latch)
+ .map(this::setupSchema);
+ if (observers != null) {
+ for (Function<SearchSessionObservable, Consumer<AppSearchSession>> observer
+ : observers) {
+ upstream.map(observer);
+ }
+ }
+ upstream.map(observable -> session -> {
+ mAppSearchSession = session;
+ observable.next();
+ });
+ upstream.next();
+ });
+ ConcurrentUtils.waitForCountDownNoInterrupt(latch, 1500,
+ "timeout accessing shortcut during lazy initialization");
+ }
+
+ /**
+ * creates the schema for shortcut in the database
+ */
+ private Consumer<AppSearchSession> setupSchema(SearchSessionObservable observable) {
+ return session -> {
+ SetSchemaRequest.Builder schemaBuilder = new SetSchemaRequest.Builder()
+ .addSchemas(AppSearchPerson.SCHEMA, AppSearchShortcutInfo.SCHEMA);
+ for (PackageIdentifier pi : mPackageIdentifiers.values()) {
+ schemaBuilder = schemaBuilder
+ .setSchemaTypeVisibilityForPackage(
+ AppSearchPerson.SCHEMA_TYPE, true, pi)
+ .setSchemaTypeVisibilityForPackage(
+ AppSearchShortcutInfo.SCHEMA_TYPE, true, pi);
+ }
+ session.setSchema(schemaBuilder.build(), mShortcutUser.mExecutor, result -> {
+ if (!result.isSuccess()) {
+ observable.error("failed to instantiate app search schema: "
+ + result.getErrorMessage());
+ return;
+ }
+ observable.next();
+ });
+ };
+ }
+
+ /**
+ * TODO: Replace this temporary implementation with proper async framework
+ */
+ private class SearchSessionObservable {
+
+ final AppSearchSession mSession;
+ final CountDownLatch mLatch;
+ final ArrayList<Consumer<AppSearchSession>> mObservers = new ArrayList<>(1);
+
+ SearchSessionObservable(@NonNull final AppSearchSession session,
+ @NonNull final CountDownLatch latch) {
+ mSession = session;
+ mLatch = latch;
+ }
+
+ SearchSessionObservable map(
+ Function<SearchSessionObservable, Consumer<AppSearchSession>> observer) {
+ mObservers.add(observer.apply(this));
+ return this;
+ }
+
+ void next() {
+ if (mObservers.isEmpty()) {
+ mLatch.countDown();
+ return;
+ }
+ mObservers.remove(0).accept(mSession);
+ }
+
+ void error(@Nullable final String errorMessage) {
+ if (errorMessage != null) {
+ Slog.e(TAG, errorMessage);
+ }
+ mLatch.countDown();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 4d8abea8acd4..209a143f665d 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2150,6 +2150,15 @@ public class ShortcutService extends IShortcutService.Stub {
}
@Override
+ public void updateShortcutVisibility(String callingPkg, String packageName, byte[] certificate,
+ boolean visible, int userId) {
+ synchronized (mLock) {
+ getPackageShortcutsForPublisherLocked(callingPkg, userId)
+ .updateVisibility(packageName, certificate, visible);
+ }
+ }
+
+ @Override
public boolean requestPinShortcut(String packageName, ShortcutInfo shortcut,
IntentSender resultIntent, int userId) {
Objects.requireNonNull(shortcut);
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 3e3aa677912b..6cbc47fb59c4 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -18,9 +18,14 @@ package com.android.server.pm;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.AppSearchSession;
import android.content.pm.ShortcutManager;
import android.metrics.LogMaker;
+import android.os.Binder;
import android.os.FileUtils;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.util.ArrayMap;
@@ -32,6 +37,7 @@ import android.util.TypedXmlSerializer;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.server.FgThread;
import com.android.server.pm.ShortcutService.DumpFilter;
import com.android.server.pm.ShortcutService.InvalidFileFormatException;
@@ -45,6 +51,7 @@ import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Objects;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -111,6 +118,8 @@ class ShortcutUser {
}
final ShortcutService mService;
+ final AppSearchManager mAppSearchManager;
+ final Executor mExecutor;
@UserIdInt
private final int mUserId;
@@ -132,6 +141,9 @@ class ShortcutUser {
public ShortcutUser(ShortcutService service, int userId) {
mService = service;
mUserId = userId;
+ mAppSearchManager = service.mContext.createContextAsUser(UserHandle.of(userId), 0)
+ .getSystemService(AppSearchManager.class);
+ mExecutor = FgThread.getExecutor();
}
public int getUserId() {
@@ -693,4 +705,18 @@ class ShortcutUser {
logger.write(logMaker.setType(MetricsEvent.SHORTCUTS_CHANGED_SHORTCUT_COUNT)
.setSubtype(totalSharingShortcutCount));
}
+
+ void runInAppSearch(@NonNull final AppSearchManager.SearchContext searchContext,
+ @NonNull final Consumer<AppSearchResult<AppSearchSession>> callback) {
+ if (mAppSearchManager == null) {
+ Slog.e(TAG, "app search manager is null");
+ return;
+ }
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ mAppSearchManager.createSearchSession(searchContext, mExecutor, callback);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index e46ab6b01f0a..029e9a39ea4b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -45,6 +45,13 @@ import android.app.ActivityManagerInternal;
import android.app.IUidObserver;
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.IAppSearchBatchResultCallback;
+import android.app.appsearch.IAppSearchManager;
+import android.app.appsearch.IAppSearchResultCallback;
+import android.app.appsearch.PackageIdentifier;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ActivityNotFoundException;
@@ -78,6 +85,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.PersistableBundle;
import android.os.Process;
@@ -150,6 +158,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
return mMockUserManager;
case Context.DEVICE_POLICY_SERVICE:
return mMockDevicePolicyManager;
+ case Context.APP_SEARCH_SERVICE:
+ return new AppSearchManager(getTestContext(), mMockAppSearchManager);
case Context.ROLE_SERVICE:
// RoleManager is final and cannot be mocked, so we only override the inject
// accessor methods in ShortcutService.
@@ -159,6 +169,11 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
}
@Override
+ public String getOpPackageName() {
+ return getTestContext().getOpPackageName();
+ }
+
+ @Override
public String getSystemServiceName(Class<?> serviceClass) {
return getTestContext().getSystemServiceName(serviceClass);
}
@@ -601,6 +616,123 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
}
}
+ protected class MockAppSearchManager implements IAppSearchManager {
+
+ protected Map<String, List<PackageIdentifier>> mSchemasPackageAccessible =
+ new ArrayMap<>(1);
+
+ @Override
+ public void setSchema(String packageName, String databaseName, List<Bundle> schemaBundles,
+ List<String> schemasNotPlatformSurfaceable,
+ Map<String, List<Bundle>> schemasPackageAccessibleBundles, boolean forceOverride,
+ int userId, IAppSearchResultCallback callback) throws RemoteException {
+ for (Map.Entry<String, List<Bundle>> entry :
+ schemasPackageAccessibleBundles.entrySet()) {
+ final String key = entry.getKey();
+ final List<PackageIdentifier> packageIdentifiers;
+ if (!mSchemasPackageAccessible.containsKey(key)) {
+ packageIdentifiers = new ArrayList<>(entry.getValue().size());
+ mSchemasPackageAccessible.put(key, packageIdentifiers);
+ } else {
+ packageIdentifiers = mSchemasPackageAccessible.get(key);
+ }
+ for (int i = 0; i < entry.getValue().size(); i++) {
+ packageIdentifiers.add(new PackageIdentifier(entry.getValue().get(i)));
+ }
+ }
+ callback.onResult(AppSearchResult.newSuccessfulResult(null));
+ }
+
+ @Override
+ public void getSchema(String packageName, String databaseName, int userId,
+ IAppSearchResultCallback callback) throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
+ public void putDocuments(String packageName, String databaseName,
+ List<Bundle> documentBundles, int userId, IAppSearchBatchResultCallback callback)
+ throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
+ public void getDocuments(String packageName, String databaseName, String namespace,
+ List<String> uris, Map<String, List<String>> typePropertyPaths, int userId,
+ IAppSearchBatchResultCallback callback) throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
+ public void query(String packageName, String databaseName, String queryExpression,
+ Bundle searchSpecBundle, int userId, IAppSearchResultCallback callback)
+ throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
+ public void globalQuery(String packageName, String queryExpression, Bundle searchSpecBundle,
+ int userId, IAppSearchResultCallback callback) throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
+ public void getNextPage(long nextPageToken, int userId, IAppSearchResultCallback callback)
+ throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
+ public void invalidateNextPageToken(long nextPageToken, int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void reportUsage(String packageName, String databaseName, String namespace,
+ String uri, long usageTimeMillis, int userId, IAppSearchResultCallback callback)
+ throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
+ public void removeByUri(String packageName, String databaseName, String namespace,
+ List<String> uris, int userId, IAppSearchBatchResultCallback callback)
+ throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
+ public void removeByQuery(String packageName, String databaseName, String queryExpression,
+ Bundle searchSpecBundle, int userId, IAppSearchResultCallback callback)
+ throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
+ public void persistToDisk(int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void initialize(int userId, IAppSearchResultCallback callback)
+ throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+
+ private void ignore(IAppSearchResultCallback callback) throws RemoteException {
+ callback.onResult(AppSearchResult.newSuccessfulResult(null));
+ }
+
+ private void ignore(IAppSearchBatchResultCallback callback) throws RemoteException {
+ callback.onResult(new AppSearchBatchResult.Builder().build());
+ }
+ }
+
public static class ShortcutActivity extends Activity {
}
@@ -652,6 +784,7 @@ 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;
@@ -801,6 +934,7 @@ 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);
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java
new file mode 100644
index 000000000000..b17085ee0317
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.pm;
+
+import android.app.appsearch.PackageIdentifier;
+import android.content.pm.AppSearchShortcutInfo;
+
+import java.util.Random;
+
+/**
+ * Tests for {@link android.app.appsearch.AppSearchManager} and relevant APIs in ShortcutManager.
+ *
+ atest -c com.android.server.pm.ShortcutManagerTest12
+ */
+public class ShortcutManagerTest12 extends BaseShortcutManagerTest {
+
+ public void testUpdateShortcutVisibility_updatesShortcutSchema() {
+
+ final byte[] cert = new byte[20];
+ new Random().nextBytes(cert);
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ mManager.updateShortcutVisibility(CALLING_PACKAGE_2, cert, true);
+ assertTrue(mMockAppSearchManager.mSchemasPackageAccessible.containsKey(
+ AppSearchShortcutInfo.SCHEMA_TYPE));
+ assertTrue(mMockAppSearchManager.mSchemasPackageAccessible.get(
+ AppSearchShortcutInfo.SCHEMA_TYPE).get(0).equals(
+ new PackageIdentifier(CALLING_PACKAGE_2, cert)));
+ });
+ }
+}