ShortcutAdapter.java
/*
* Copyright 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 androidx.appsearch.app;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.appsearch.exceptions.AppSearchException;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.util.Preconditions;
/**
* Util methods for Document <-> shortcut conversion.
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class ShortcutAdapter {
private ShortcutAdapter() {
// Hide constructor as utility classes are not meant to be instantiated.
}
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public static final String DEFAULT_DATABASE = "__shortcut_adapter_db__";
/**
* Represents the default namespace which should be used as the
* {@link androidx.appsearch.annotation.Document.Namespace} for documents that
* are meant to be donated as a shortcut through
* {@link androidx.core.content.pm.ShortcutManagerCompat}.
*/
public static final String DEFAULT_NAMESPACE = "__shortcut_adapter_ns__";
private static final String FIELD_NAME = "name";
private static final String SCHEME_APPSEARCH = "appsearch";
private static final String NAMESPACE_CHECK_ERROR_MESSAGE = "Namespace of the document does "
+ "not match androidx.appsearch.app.ShortcutAdapter.DEFAULT_NAMESPACE."
+ "Please use androidx.appsearch.app.ShortcutAdapter.DEFAULT_NAMESPACE as the "
+ "namespace of the document if it will be used to create a shortcut.";
/**
* Converts given document to a {@link ShortcutInfoCompat.Builder}, which can be used to
* construct a shortcut for donation through
* {@link androidx.core.content.pm.ShortcutManagerCompat}. Applicable data in the given
* document will be used to populate corresponding fields in {@link ShortcutInfoCompat.Builder}.
*
* <p>Note: Namespace of the given document is required to be set to {@link #DEFAULT_NAMESPACE}
* if it will be used to create a shortcut; Otherwise an exception would be thrown.
*
* <p>See {@link androidx.appsearch.annotation.Document.Namespace}
*
* <p>Note: The ShortcutID in {@link ShortcutInfoCompat.Builder} will be set to match the id
* of given document. So an unique id across all documents should be chosen if the document
* is to be used to create a shortcut.
*
* <p>see {@link ShortcutInfoCompat#getId()}
* <p>see {@link androidx.appsearch.annotation.Document.Id}
*
* <p>{@link ShortcutInfoCompat.Builder} created this way by default will be set to hidden
* from launcher. If remain hidden, they will not appear in launcher's surfaces (e.g. long
* press menu) nor do they count toward the quota defined in
* {@link androidx.core.content.pm.ShortcutManagerCompat#getMaxShortcutCountPerActivity(Context)}
*
* <p>See {@link ShortcutInfoCompat.Builder#setExcludedFromSurfaces(int)}.
*
* <p>Given document object will be stored in the form of {@link Bundle} in
* {@link ShortcutInfoCompat} and will remain visible to any library that implements
* {@link androidx.core.content.pm.ShortcutInfoChangeListener}. Upon receiving a shortcut
* through its listener, libraries may persist the document in a separate storage, which
* potentially allows the document to be surfaced in assistant and other proactive surfaces.
*
* <p>The document that was stored in {@link ShortcutInfoCompat} is discarded when the
* shortcut is converted into {@link android.content.pm.ShortcutInfo}, meaning that the
* document will not be persisted in the shortcut object itself once the shortcut is
* published. i.e. Any shortcut returned from queries toward
* {@link androidx.core.content.pm.ShortcutManagerCompat} would not carry any document at all.
*
* @param document a document object annotated with
* {@link androidx.appsearch.annotation.Document} that carries structured
* data in a pre-defined format.
* @return a {@link ShortcutInfoCompat.Builder} which can be used to construct a shortcut
* for donation through {@link androidx.core.content.pm.ShortcutManagerCompat}.
* @throws IllegalArgumentException An exception would be thrown if the namespace in the given
* document object does not match {@link #DEFAULT_NAMESPACE}.
* @throws AppSearchException An exception would be thrown if the given document object is not
* annotated with {@link androidx.appsearch.annotation.Document} or
* encountered an unexpected error during the conversion to
* {@link GenericDocument}.
*/
@NonNull
public static ShortcutInfoCompat.Builder createShortcutBuilderFromDocument(
@NonNull final Context context, @NonNull Object document) throws AppSearchException {
Preconditions.checkNotNull(context);
Preconditions.checkNotNull(document);
final GenericDocument doc = GenericDocument.fromDocumentClass(document);
if (!DEFAULT_NAMESPACE.equals(doc.getNamespace())) {
throw new IllegalArgumentException(NAMESPACE_CHECK_ERROR_MESSAGE);
}
final String name = doc.getPropertyString(FIELD_NAME);
return new ShortcutInfoCompat.Builder(context, doc.getId())
.setShortLabel(!TextUtils.isEmpty(name) ? name : doc.getId())
.setIntent(new Intent(Intent.ACTION_VIEW, getDocumentUri(doc)))
.setExcludedFromSurfaces(ShortcutInfoCompat.SURFACE_LAUNCHER)
.setTransientExtras(doc.getBundle());
}
/**
* Extracts {@link GenericDocument} from given {@link ShortcutInfoCompat} if applicable.
* Returns null if document cannot be found in the given shortcut.
* @hide
*/
@Nullable
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public static GenericDocument extractDocument(@NonNull final ShortcutInfoCompat shortcut) {
Preconditions.checkNotNull(shortcut);
final Bundle extras = shortcut.getTransientExtras();
if (extras == null) {
return null;
}
return new GenericDocument(extras);
}
/**
* Returns an uri that uniquely identifies the given document object.
*
* @param document a document object annotated with
* {@link androidx.appsearch.annotation.Document} that carries structured
* data in a pre-defined format.
* @throws AppSearchException if the given document object is not annotated with
* {@link androidx.appsearch.annotation.Document} or encountered an
* unexpected error during the conversion to {@link GenericDocument}.
*/
@NonNull
public static Uri getDocumentUri(@NonNull final Object document)
throws AppSearchException {
Preconditions.checkNotNull(document);
return getDocumentUri(GenericDocument.fromDocumentClass(document));
}
@NonNull
private static Uri getDocumentUri(@NonNull final GenericDocument obj) {
Preconditions.checkNotNull(obj);
return getDocumentUri(obj.getId());
}
/**
* Returns an uri that identifies to the document associated with given document id.
*
* @param id id of the document.
*/
@NonNull
public static Uri getDocumentUri(@NonNull final String id) {
Preconditions.checkNotNull(id);
return new Uri.Builder()
.scheme(SCHEME_APPSEARCH)
.authority(DEFAULT_DATABASE)
.path(DEFAULT_NAMESPACE + "/" + id)
.build();
}
}