[go: nahoru, domu]

blob: 6e187111d293f81ee869fb575e482e2afcef0aab [file] [log] [blame]
/**
* Copyright (C) 2017 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.core.content.pm;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.PersistableBundle;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.core.app.Person;
import androidx.core.content.LocusIdCompat;
import androidx.core.graphics.drawable.IconCompat;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* Helper for accessing features in {@link ShortcutInfo}.
*/
public class ShortcutInfoCompat {
private static final String EXTRA_PERSON_COUNT = "extraPersonCount";
private static final String EXTRA_PERSON_ = "extraPerson_";
private static final String EXTRA_LOCUS_ID = "extraLocusId";
private static final String EXTRA_LONG_LIVED = "extraLongLived";
Context mContext;
String mId;
Intent[] mIntents;
ComponentName mActivity;
CharSequence mLabel;
CharSequence mLongLabel;
CharSequence mDisabledMessage;
IconCompat mIcon;
boolean mIsAlwaysBadged;
Person[] mPersons;
Set<String> mCategories;
@Nullable
LocusIdCompat mLocusId;
// TODO: Support |auto| when the value of mIsLongLived is not set
boolean mIsLongLived;
int mRank;
ShortcutInfoCompat() { }
/**
* @return {@link ShortcutInfo} object from this compat object.
*/
@RequiresApi(25)
public ShortcutInfo toShortcutInfo() {
ShortcutInfo.Builder builder = new ShortcutInfo.Builder(mContext, mId)
.setShortLabel(mLabel)
.setIntents(mIntents);
if (mIcon != null) {
builder.setIcon(mIcon.toIcon(mContext));
}
if (!TextUtils.isEmpty(mLongLabel)) {
builder.setLongLabel(mLongLabel);
}
if (!TextUtils.isEmpty(mDisabledMessage)) {
builder.setDisabledMessage(mDisabledMessage);
}
if (mActivity != null) {
builder.setActivity(mActivity);
}
if (mCategories != null) {
builder.setCategories(mCategories);
}
builder.setRank(mRank);
if (Build.VERSION.SDK_INT >= 29) {
if (mPersons != null && mPersons.length > 0) {
android.app.Person[] persons = new android.app.Person[mPersons.length];
for (int i = 0; i < persons.length; i++) {
persons[i] = mPersons[i].toAndroidPerson();
}
builder.setPersons(persons);
}
if (mLocusId != null) {
builder.setLocusId(mLocusId.toLocusId());
}
builder.setLongLived(mIsLongLived);
} else {
// ShortcutInfo.Builder#setPersons(...) and ShortcutInfo.Builder#setLongLived(...) are
// introduced in API 29. On older API versions, we store mPersons and mIsLongLived in
// the extras field of ShortcutInfo for backwards compatibility.
builder.setExtras(buildLegacyExtrasBundle());
}
return builder.build();
}
/**
* @hide
*/
@RequiresApi(22)
@RestrictTo(LIBRARY_GROUP_PREFIX)
private PersistableBundle buildLegacyExtrasBundle() {
PersistableBundle bundle = new PersistableBundle();
if (mPersons != null && mPersons.length > 0) {
bundle.putInt(EXTRA_PERSON_COUNT, mPersons.length);
for (int i = 0; i < mPersons.length; i++) {
bundle.putPersistableBundle(EXTRA_PERSON_ + (i + 1),
mPersons[i].toPersistableBundle());
}
}
if (mLocusId != null) {
bundle.putString(EXTRA_LOCUS_ID, mLocusId.getId());
}
bundle.putBoolean(EXTRA_LONG_LIVED, mIsLongLived);
return bundle;
}
Intent addToIntent(Intent outIntent) {
outIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, mIntents[mIntents.length - 1])
.putExtra(Intent.EXTRA_SHORTCUT_NAME, mLabel.toString());
if (mIcon != null) {
Drawable badge = null;
if (mIsAlwaysBadged) {
PackageManager pm = mContext.getPackageManager();
if (mActivity != null) {
try {
badge = pm.getActivityIcon(mActivity);
} catch (PackageManager.NameNotFoundException e) {
// Ignore
}
}
if (badge == null) {
badge = mContext.getApplicationInfo().loadIcon(pm);
}
}
mIcon.addToShortcutIntent(outIntent, badge, mContext);
}
return outIntent;
}
/**
* Returns the ID of a shortcut.
*
* <p>Shortcut IDs are unique within each publisher app and must be stable across
* devices so that shortcuts will still be valid when restored on a different device.
* See {@link android.content.pm.ShortcutManager} for details.
*/
@NonNull
public String getId() {
return mId;
}
/**
* Return the target activity.
*
* <p>This has nothing to do with the activity that this shortcut will launch.
* Launcher apps should show the launcher icon for the returned activity alongside
* this shortcut.
*
* @see Builder#setActivity(ComponentName)
*/
@Nullable
public ComponentName getActivity() {
return mActivity;
}
/**
* Return the short description of a shortcut.
*
* @see Builder#setShortLabel(CharSequence)
*/
@NonNull
public CharSequence getShortLabel() {
return mLabel;
}
/**
* Return the long description of a shortcut.
*
* @see Builder#setLongLabel(CharSequence)
*/
@Nullable
public CharSequence getLongLabel() {
return mLongLabel;
}
/**
* Return the message that should be shown when the user attempts to start a shortcut
* that is disabled.
*
* @see Builder#setDisabledMessage(CharSequence)
*/
@Nullable
public CharSequence getDisabledMessage() {
return mDisabledMessage;
}
/**
* Returns the intent that is executed when the user selects this shortcut.
* If setIntents() was used, then return the last intent in the array.
*
* @see Builder#setIntent(Intent)
*/
@NonNull
public Intent getIntent() {
return mIntents[mIntents.length - 1];
}
/**
* Return the intent set with {@link Builder#setIntents(Intent[])}.
*
* @see Builder#setIntents(Intent[])
*/
@NonNull
public Intent[] getIntents() {
return Arrays.copyOf(mIntents, mIntents.length);
}
/**
* Return the categories set with {@link Builder#setCategories(Set)}.
*
* @see Builder#setCategories(Set)
*/
@Nullable
public Set<String> getCategories() {
return mCategories;
}
/**
* Gets the {@link LocusIdCompat} associated with this shortcut.
*
* <p>Used by the device's intelligence services to correlate objects (such as
* {@link androidx.core.app.NotificationCompat} and
* {@link android.view.contentcapture.ContentCaptureContext}) that are correlated.
*/
@Nullable
public LocusIdCompat getLocusId() {
return mLocusId;
}
/**
* Returns the rank of the shortcut set with {@link Builder#setRank(int)}.
*
* @see Builder#setRank(int)
*/
public int getRank() {
return mRank;
}
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
public IconCompat getIcon() {
return mIcon;
}
/**
* @hide
*/
@RequiresApi(25)
@RestrictTo(LIBRARY_GROUP_PREFIX)
@VisibleForTesting
@Nullable
static Person[] getPersonsFromExtra(@NonNull PersistableBundle bundle) {
if (bundle == null || !bundle.containsKey(EXTRA_PERSON_COUNT)) {
return null;
}
int personsLength = bundle.getInt(EXTRA_PERSON_COUNT);
Person[] persons = new Person[personsLength];
for (int i = 0; i < personsLength; i++) {
persons[i] = Person.fromPersistableBundle(
bundle.getPersistableBundle(EXTRA_PERSON_ + (i + 1)));
}
return persons;
}
/**
* @hide
*/
@RequiresApi(25)
@RestrictTo(LIBRARY_GROUP_PREFIX)
@VisibleForTesting
static boolean getLongLivedFromExtra(@NonNull PersistableBundle bundle) {
if (bundle == null || !bundle.containsKey(EXTRA_LONG_LIVED)) {
return false;
}
return bundle.getBoolean(EXTRA_LONG_LIVED);
}
/**
* @hide
*/
@RequiresApi(25)
@RestrictTo(LIBRARY_GROUP_PREFIX)
@Nullable
static LocusIdCompat getLocusId(@NonNull final ShortcutInfo shortcutInfo) {
if (Build.VERSION.SDK_INT >= 29) {
if (shortcutInfo.getLocusId() == null) return null;
return LocusIdCompat.toLocusIdCompat(shortcutInfo.getLocusId());
} else {
return getLocusIdFromExtra(shortcutInfo.getExtras());
}
}
/**
* @hide
*/
@RequiresApi(25)
@RestrictTo(LIBRARY_GROUP_PREFIX)
@Nullable
private static LocusIdCompat getLocusIdFromExtra(@Nullable PersistableBundle bundle) {
if (bundle == null) return null;
final String locusId = bundle.getString(EXTRA_LOCUS_ID);
return locusId == null ? null : new LocusIdCompat(locusId);
}
/**
* Builder class for {@link ShortcutInfoCompat} objects.
*/
public static class Builder {
private final ShortcutInfoCompat mInfo;
public Builder(@NonNull Context context, @NonNull String id) {
mInfo = new ShortcutInfoCompat();
mInfo.mContext = context;
mInfo.mId = id;
}
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
public Builder(@NonNull ShortcutInfoCompat shortcutInfo) {
mInfo = new ShortcutInfoCompat();
mInfo.mContext = shortcutInfo.mContext;
mInfo.mId = shortcutInfo.mId;
mInfo.mIntents = Arrays.copyOf(shortcutInfo.mIntents, shortcutInfo.mIntents.length);
mInfo.mActivity = shortcutInfo.mActivity;
mInfo.mLabel = shortcutInfo.mLabel;
mInfo.mLongLabel = shortcutInfo.mLongLabel;
mInfo.mDisabledMessage = shortcutInfo.mDisabledMessage;
mInfo.mIcon = shortcutInfo.mIcon;
mInfo.mIsAlwaysBadged = shortcutInfo.mIsAlwaysBadged;
mInfo.mLocusId = shortcutInfo.mLocusId;
mInfo.mIsLongLived = shortcutInfo.mIsLongLived;
mInfo.mRank = shortcutInfo.mRank;
if (shortcutInfo.mPersons != null) {
mInfo.mPersons = Arrays.copyOf(shortcutInfo.mPersons, shortcutInfo.mPersons.length);
}
if (shortcutInfo.mCategories != null) {
mInfo.mCategories = new HashSet<>(shortcutInfo.mCategories);
}
}
/**
* @hide
*/
@RequiresApi(25)
@RestrictTo(LIBRARY_GROUP_PREFIX)
public Builder(@NonNull Context context, @NonNull ShortcutInfo shortcutInfo) {
mInfo = new ShortcutInfoCompat();
mInfo.mContext = context;
mInfo.mId = shortcutInfo.getId();
Intent[] intents = shortcutInfo.getIntents();
mInfo.mIntents = Arrays.copyOf(intents, intents.length);
mInfo.mActivity = shortcutInfo.getActivity();
mInfo.mLabel = shortcutInfo.getShortLabel();
mInfo.mLongLabel = shortcutInfo.getLongLabel();
mInfo.mDisabledMessage = shortcutInfo.getDisabledMessage();
mInfo.mCategories = shortcutInfo.getCategories();
mInfo.mPersons = ShortcutInfoCompat.getPersonsFromExtra(shortcutInfo.getExtras());
mInfo.mLocusId = ShortcutInfoCompat.getLocusId(shortcutInfo);
mInfo.mRank = shortcutInfo.getRank();
}
/**
* Sets the short title of a shortcut.
*
* <p>This is a mandatory field when publishing a new shortcut.
*
* <p>This field is intended to be a concise description of a shortcut.
*
* <p>The recommended maximum length is 10 characters.
*/
@NonNull
public Builder setShortLabel(@NonNull CharSequence shortLabel) {
mInfo.mLabel = shortLabel;
return this;
}
/**
* Sets the text of a shortcut.
*
* <p>This field is intended to be more descriptive than the shortcut title. The launcher
* shows this instead of the short title when it has enough space.
*
* <p>The recommend maximum length is 25 characters.
*/
@NonNull
public Builder setLongLabel(@NonNull CharSequence longLabel) {
mInfo.mLongLabel = longLabel;
return this;
}
/**
* Sets the message that should be shown when the user attempts to start a shortcut that
* is disabled.
*
* @see ShortcutInfo#getDisabledMessage()
*/
@NonNull
public Builder setDisabledMessage(@NonNull CharSequence disabledMessage) {
mInfo.mDisabledMessage = disabledMessage;
return this;
}
/**
* Sets the intent of a shortcut. Alternatively, {@link #setIntents(Intent[])} can be used
* to launch an activity with other activities in the back stack.
*
* <p>This is a mandatory field when publishing a new shortcut.
*
* <p>The given {@code intent} can contain extras, but these extras must contain values
* of primitive types in order for the system to persist these values.
*/
@NonNull
public Builder setIntent(@NonNull Intent intent) {
return setIntents(new Intent[]{intent});
}
/**
* Sets multiple intents instead of a single intent, in order to launch an activity with
* other activities in back stack. Use {@link android.app.TaskStackBuilder} to build
* intents. The last element in the list represents the only intent that doesn't place
* an activity on the back stack.
*/
@NonNull
public Builder setIntents(@NonNull Intent[] intents) {
mInfo.mIntents = intents;
return this;
}
/**
* Sets an icon of a shortcut.
*/
@NonNull
public Builder setIcon(IconCompat icon) {
mInfo.mIcon = icon;
return this;
}
/**
* Sets the {@link LocusIdCompat} associated with this shortcut.
*
* <p>This method should be called when the {@link LocusIdCompat} is used in other places
* (such as {@link androidx.core.app.NotificationCompat} and
* {@link android.view.contentcapture.ContentCaptureContext}) so the device's intelligence
* services can correlate them.
*/
@NonNull
public Builder setLocusId(@Nullable final LocusIdCompat locusId) {
mInfo.mLocusId = locusId;
return this;
}
/**
* Sets the target activity. A shortcut will be shown along with this activity's icon
* on the launcher.
*
* @see ShortcutInfo#getActivity()
* @see ShortcutInfo.Builder#setActivity(ComponentName)
*/
@NonNull
public Builder setActivity(@NonNull ComponentName activity) {
mInfo.mActivity = activity;
return this;
}
/**
* Badges the icon before passing it over to the Launcher.
* <p>
* Launcher automatically badges {@link ShortcutInfo}, so only the legacy shortcut icon,
* {@link Intent.ShortcutIconResource} is badged. This field is ignored when using
* {@link ShortcutInfo} on API 25 and above.
* <p>
* If the shortcut is associated with an activity, the activity icon is used as the badge,
* otherwise application icon is used.
*
* @see #setActivity(ComponentName)
*/
@NonNull
public Builder setAlwaysBadged() {
mInfo.mIsAlwaysBadged = true;
return this;
}
/**
* Associate a person to a shortcut. Alternatively, {@link #setPersons(Person[])} can be
* used to add multiple persons to a shortcut.
*
* <p>This is an optional field when publishing a new shortcut.
*
* @see Person
*/
@NonNull
public Builder setPerson(@NonNull Person person) {
return setPersons(new Person[]{person});
}
/**
* Sets multiple persons instead of a single person.
*/
@NonNull
public Builder setPersons(@NonNull Person[] persons) {
mInfo.mPersons = persons;
return this;
}
/**
* Sets categories for a shortcut. Launcher apps may use this information to categorize
* shortcuts.
*
* @see ShortcutInfo#getCategories()
*/
@NonNull
public Builder setCategories(@NonNull Set<String> categories) {
mInfo.mCategories = categories;
return this;
}
/**
* @deprecated Use {@ink #setLongLived(boolean)) instead.
*/
@Deprecated
@NonNull
public Builder setLongLived() {
mInfo.mIsLongLived = true;
return this;
}
/**
* Sets if a shortcut would be valid even if it has been unpublished/invisible by the app
* (as a dynamic or pinned shortcut). If it is long lived, it can be cached by various
* system services even after it has been unpublished as a dynamic shortcut.
*/
@NonNull
public Builder setLongLived(boolean longLived) {
mInfo.mIsLongLived = longLived;
return this;
}
/**
* Sets rank of a shortcut, which is a non-negative value that's used by the system to sort
* shortcuts. Lower value means higher importance.
*
* @see ShortcutInfo#getRank() for details.
*/
@NonNull
public Builder setRank(int rank) {
mInfo.mRank = rank;
return this;
}
/**
* Creates a {@link ShortcutInfoCompat} instance.
*/
@NonNull
public ShortcutInfoCompat build() {
// Verify the arguments
if (TextUtils.isEmpty(mInfo.mLabel)) {
throw new IllegalArgumentException("Shortcut must have a non-empty label");
}
if (mInfo.mIntents == null || mInfo.mIntents.length == 0) {
throw new IllegalArgumentException("Shortcut must have an intent");
}
return mInfo;
}
}
}