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
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2149,6 +2149,15 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
verifyStates();
|
||||
}
|
||||
|
||||
@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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -158,6 +168,11 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
|
||||
throw new UnsupportedOperationException("Couldn't find system service: " + name);
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
@@ -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)));
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user