1 /* 2 * Copyright 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.service.quickaccesswallet; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.app.PendingIntent; 23 import android.graphics.drawable.Icon; 24 import android.location.Location; 25 import android.os.Parcel; 26 import android.os.Parcelable; 27 import android.text.TextUtils; 28 29 import com.android.internal.util.Preconditions; 30 31 import java.lang.annotation.Retention; 32 import java.lang.annotation.RetentionPolicy; 33 import java.util.ArrayList; 34 import java.util.List; 35 36 /** 37 * A {@link WalletCard} can represent anything that a user might carry in their wallet -- a credit 38 * card, library card, transit pass, etc. Cards are identified by a String identifier and contain a 39 * card type, card image, card image content description, and a {@link PendingIntent} to be used if 40 * the user clicks on the card. Cards may be displayed with an icon and label, though these are 41 * optional. Non-payment cards will also have a second image that will be displayed when the card is 42 * tapped. 43 */ 44 45 public final class WalletCard implements Parcelable { 46 47 /** 48 * Unknown cards refer to cards whose types are unspecified. 49 */ 50 public static final int CARD_TYPE_UNKNOWN = 0; 51 52 /** 53 * Payment cards refer to credit cards, debit cards or any other cards in the wallet used to 54 * make cash-equivalent payments. 55 */ 56 public static final int CARD_TYPE_PAYMENT = 1; 57 58 /** 59 * Non-payment cards refer to any cards that are not used for cash-equivalent payment, including 60 * event tickets, flights, offers, loyalty cards, gift cards and transit tickets. 61 */ 62 public static final int CARD_TYPE_NON_PAYMENT = 2; 63 64 private final String mCardId; 65 private final int mCardType; 66 private final Icon mCardImage; 67 private final CharSequence mContentDescription; 68 private final PendingIntent mPendingIntent; 69 private final Icon mCardIcon; 70 private final CharSequence mCardLabel; 71 private final Icon mNonPaymentCardSecondaryImage; 72 private List<Location> mCardLocations; 73 WalletCard(Builder builder)74 private WalletCard(Builder builder) { 75 this.mCardId = builder.mCardId; 76 this.mCardType = builder.mCardType; 77 this.mCardImage = builder.mCardImage; 78 this.mContentDescription = builder.mContentDescription; 79 this.mPendingIntent = builder.mPendingIntent; 80 this.mCardIcon = builder.mCardIcon; 81 this.mCardLabel = builder.mCardLabel; 82 this.mNonPaymentCardSecondaryImage = builder.mNonPaymentCardSecondaryImage; 83 this.mCardLocations = builder.mCardLocations; 84 } 85 86 /** 87 * @hide 88 */ 89 @Retention(RetentionPolicy.SOURCE) 90 @IntDef(prefix = {"CARD_TYPE_"}, value = { 91 CARD_TYPE_UNKNOWN, 92 CARD_TYPE_PAYMENT, 93 CARD_TYPE_NON_PAYMENT 94 }) 95 public @interface CardType { 96 } 97 98 @Override describeContents()99 public int describeContents() { 100 return 0; 101 } 102 103 @Override writeToParcel(@onNull Parcel dest, int flags)104 public void writeToParcel(@NonNull Parcel dest, int flags) { 105 dest.writeString(mCardId); 106 dest.writeInt(mCardType); 107 mCardImage.writeToParcel(dest, flags); 108 TextUtils.writeToParcel(mContentDescription, dest, flags); 109 PendingIntent.writePendingIntentOrNullToParcel(mPendingIntent, dest); 110 writeIconIfNonNull(mCardIcon, dest, flags); 111 TextUtils.writeToParcel(mCardLabel, dest, flags); 112 writeIconIfNonNull(mNonPaymentCardSecondaryImage, dest, flags); 113 dest.writeTypedList(mCardLocations, flags); 114 } 115 116 /** Utility function called by writeToParcel 117 */ writeIconIfNonNull(Icon icon, Parcel dest, int flags)118 private void writeIconIfNonNull(Icon icon, Parcel dest, int flags) { 119 if (icon == null) { 120 dest.writeByte((byte) 0); 121 } else { 122 dest.writeByte((byte) 1); 123 icon.writeToParcel(dest, flags); 124 } 125 } 126 readFromParcel(Parcel source)127 private static WalletCard readFromParcel(Parcel source) { 128 String cardId = source.readString(); 129 int cardType = source.readInt(); 130 Icon cardImage = Icon.CREATOR.createFromParcel(source); 131 CharSequence contentDesc = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); 132 PendingIntent pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(source); 133 Icon cardIcon = source.readByte() == 0 ? null : Icon.CREATOR.createFromParcel(source); 134 CharSequence cardLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); 135 Icon nonPaymentCardSecondaryImage = 136 source.readByte() == 0 ? null : Icon.CREATOR.createFromParcel(source); 137 Builder builder = 138 new Builder(cardId, cardType, cardImage, contentDesc, pendingIntent) 139 .setCardIcon(cardIcon) 140 .setCardLabel(cardLabel); 141 if (cardType == CARD_TYPE_NON_PAYMENT) { 142 builder.setNonPaymentCardSecondaryImage(nonPaymentCardSecondaryImage); 143 } 144 List<Location> cardLocations = new ArrayList<>(); 145 source.readTypedList(cardLocations, Location.CREATOR); 146 builder.setCardLocations(cardLocations); 147 148 return builder.build(); 149 } 150 151 @NonNull 152 public static final Creator<WalletCard> CREATOR = 153 new Creator<WalletCard>() { 154 @Override 155 public WalletCard createFromParcel(Parcel source) { 156 return readFromParcel(source); 157 } 158 159 @Override 160 public WalletCard[] newArray(int size) { 161 return new WalletCard[size]; 162 } 163 }; 164 165 /** 166 * The card id must be unique within the list of cards returned. 167 */ 168 @NonNull getCardId()169 public String getCardId() { 170 return mCardId; 171 } 172 173 /** 174 * Returns the card type. 175 */ 176 @NonNull 177 @CardType getCardType()178 public int getCardType() { 179 return mCardType; 180 } 181 182 /** 183 * The visual representation of the card. If the card image Icon is a bitmap, it should have a 184 * width of {@link GetWalletCardsRequest#getCardWidthPx()} and a height of {@link 185 * GetWalletCardsRequest#getCardHeightPx()}. 186 */ 187 @NonNull getCardImage()188 public Icon getCardImage() { 189 return mCardImage; 190 } 191 192 /** 193 * The content description of the card image. 194 */ 195 @NonNull getContentDescription()196 public CharSequence getContentDescription() { 197 return mContentDescription; 198 } 199 200 /** 201 * If the user performs a click on the card, this PendingIntent will be sent. If the device is 202 * locked, the wallet will first request device unlock before sending the pending intent. 203 */ 204 @NonNull getPendingIntent()205 public PendingIntent getPendingIntent() { 206 return mPendingIntent; 207 } 208 209 /** 210 * An icon may be shown alongside the card image to convey information about how the card can be 211 * used, or if some other action must be taken before using the card. For example, an NFC logo 212 * could indicate that the card is NFC-enabled and will be provided to an NFC terminal if the 213 * phone is held in close proximity to the NFC reader. 214 * 215 * <p>If the supplied Icon is backed by a bitmap, it should have width and height 216 * {@link GetWalletCardsRequest#getIconSizePx()}. 217 */ 218 @Nullable getCardIcon()219 public Icon getCardIcon() { 220 return mCardIcon; 221 } 222 223 /** 224 * A card label may be shown alongside the card image to convey information about how the card 225 * can be used, or if some other action must be taken before using the card. For example, an 226 * NFC-enabled card could be labeled "Hold near reader" to inform the user of how to use NFC 227 * cards when interacting with an NFC reader. 228 * 229 * <p>If the provided label is too long to fit on one line, it may be truncated and ellipsized. 230 */ 231 @Nullable getCardLabel()232 public CharSequence getCardLabel() { 233 return mCardLabel; 234 } 235 236 /** 237 * Visual representation of the card when it is tapped. May include additional information 238 * unique to the card, such as a barcode or number. Only valid for CARD_TYPE_NON_PAYMENT. 239 */ 240 @Nullable getNonPaymentCardSecondaryImage()241 public Icon getNonPaymentCardSecondaryImage() { 242 return mNonPaymentCardSecondaryImage; 243 } 244 245 /** List of locations that this card might be useful at. */ 246 @NonNull getCardLocations()247 public List<Location> getCardLocations() { 248 return mCardLocations; 249 } 250 251 /** 252 * Removes locations from card. Should be called if {@link 253 * PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is disabled. 254 * 255 * @hide 256 */ removeCardLocations()257 public void removeCardLocations() { 258 mCardLocations = new ArrayList<>(); 259 } 260 261 /** 262 * Builder for {@link WalletCard} objects. You must provide cardId, cardImage, 263 * contentDescription, and pendingIntent. If the card is opaque and should be shown with 264 * elevation, set hasShadow to true. cardIcon and cardLabel are optional. 265 */ 266 public static final class Builder { 267 private String mCardId; 268 private int mCardType; 269 private Icon mCardImage; 270 private CharSequence mContentDescription; 271 private PendingIntent mPendingIntent; 272 private Icon mCardIcon; 273 private CharSequence mCardLabel; 274 private Icon mNonPaymentCardSecondaryImage; 275 private List<Location> mCardLocations = new ArrayList<>(); 276 277 /** 278 * @param cardId The card id must be non-null and unique within the list of 279 * cards returned. <b>Note: 280 * </b> this card ID should <b>not</b> contain PII (Personally 281 * Identifiable Information, such as username or email address). 282 * @param cardType Integer representing the card type. The card type must be 283 * non-null. 284 * @param cardImage The visual representation of the card. If the card image Icon 285 * is a bitmap, it should have a width of {@link 286 * GetWalletCardsRequest#getCardWidthPx()} and a height of {@link 287 * GetWalletCardsRequest#getCardHeightPx()}. If the card image 288 * does not have these dimensions, it may appear distorted when it 289 * is scaled to fit these dimensions on screen. Bitmaps must be 290 * of type {@link android.graphics.Bitmap.Config#HARDWARE} for 291 * performance reasons. 292 * @param contentDescription The content description of the card image. This field is 293 * required and may not be null or empty. 294 * <b>Note: </b> this message should <b>not</b> contain PII 295 * (Personally Identifiable Information, such as username or email 296 * address). 297 * @param pendingIntent If the user performs a click on the card, this PendingIntent 298 * will be sent. If the device is locked, the wallet will first 299 * request device unlock before sending the pending intent. It is 300 * recommended that the pending intent be immutable (use {@link 301 * PendingIntent#FLAG_IMMUTABLE}). 302 * 303 */ Builder(@onNull String cardId, @NonNull @CardType int cardType, @NonNull Icon cardImage, @NonNull CharSequence contentDescription, @NonNull PendingIntent pendingIntent )304 public Builder(@NonNull String cardId, 305 @NonNull @CardType int cardType, 306 @NonNull Icon cardImage, 307 @NonNull CharSequence contentDescription, 308 @NonNull PendingIntent pendingIntent 309 ) { 310 mCardId = cardId; 311 mCardType = cardType; 312 mCardImage = cardImage; 313 mContentDescription = contentDescription; 314 mPendingIntent = pendingIntent; 315 } 316 317 /** 318 * Called when a card type is not provided, in which case it defaults to CARD_TYPE_UNKNOWN. 319 * Calls {@link Builder#Builder(String, int, Icon, CharSequence, PendingIntent)} 320 */ Builder(@onNull String cardId, @NonNull Icon cardImage, @NonNull CharSequence contentDescription, @NonNull PendingIntent pendingIntent)321 public Builder(@NonNull String cardId, 322 @NonNull Icon cardImage, 323 @NonNull CharSequence contentDescription, 324 @NonNull PendingIntent pendingIntent) { 325 this(cardId, WalletCard.CARD_TYPE_UNKNOWN, cardImage, contentDescription, 326 pendingIntent); 327 } 328 329 /** 330 * An icon may be shown alongside the card image to convey information about how the card 331 * can be used, or if some other action must be taken before using the card. For example, an 332 * NFC logo could indicate that the card is NFC-enabled and will be provided to an NFC 333 * terminal if the phone is held in close proximity to the NFC reader. This field is 334 * optional. 335 * 336 * <p>If the supplied Icon is backed by a bitmap, it should have width and height 337 * {@link GetWalletCardsRequest#getIconSizePx()}. 338 */ 339 @NonNull setCardIcon(@ullable Icon cardIcon)340 public Builder setCardIcon(@Nullable Icon cardIcon) { 341 mCardIcon = cardIcon; 342 return this; 343 } 344 345 /** 346 * A card label may be shown alongside the card image to convey information about how the 347 * card can be used, or if some other action must be taken before using the card. For 348 * example, an NFC-enabled card could be labeled "Hold near reader" to inform the user of 349 * how to use NFC cards when interacting with an NFC reader. This field is optional. 350 * <b>Note: </b> this card label should <b>not</b> contain PII (Personally Identifiable 351 * Information, such as username or email address). If the provided label is too long to fit 352 * on one line, it may be truncated and ellipsized. 353 */ 354 @NonNull setCardLabel(@ullable CharSequence cardLabel)355 public Builder setCardLabel(@Nullable CharSequence cardLabel) { 356 mCardLabel = cardLabel; 357 return this; 358 } 359 360 /** 361 * Visual representation of the card when it is tapped. May include additional information 362 * unique to the card, such as a barcode or number. Only valid for CARD_TYPE_NON_PAYMENT. 363 */ 364 @NonNull setNonPaymentCardSecondaryImage( @ullable Icon nonPaymentCardSecondaryImage)365 public Builder setNonPaymentCardSecondaryImage( 366 @Nullable Icon nonPaymentCardSecondaryImage) { 367 Preconditions.checkState( 368 mCardType == CARD_TYPE_NON_PAYMENT, 369 "This field can only be set on non-payment cards"); 370 mNonPaymentCardSecondaryImage = nonPaymentCardSecondaryImage; 371 return this; 372 } 373 374 /** 375 * Set of locations this card might be useful at. If {@link 376 * PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is enabled, the card might be 377 * shown to the user when a user is near one of these locations. 378 */ 379 @NonNull setCardLocations(@onNull List<Location> cardLocations)380 public Builder setCardLocations(@NonNull List<Location> cardLocations) { 381 Preconditions.checkCollectionElementsNotNull(cardLocations, "cardLocations"); 382 mCardLocations = cardLocations; 383 return this; 384 } 385 386 /** 387 * Builds a new {@link WalletCard} instance. 388 * 389 * @return A built response. 390 */ 391 @NonNull build()392 public WalletCard build() { 393 return new WalletCard(this); 394 } 395 } 396 } 397