Suivre l'atelier de programmation sur les fiches d'application

Cette page explique comment implémenter une AppCard.

Étape 1 : Ajoutez toutes les importations

  1. Pour ajouter les importations :

    static_libs: [
         …
         "car-app-card",
    ],
    

Étape 2 : Ajoutez SimpleAppCardContentProvider à votre fichier manifeste

  1. Veillez à remplacer android:authorities (en gras) par le nom de votre package.

    <!-- App Card Content Provider that is required to communicate
         relevant App Cards with the system
         android.car.permission.BIND_APP_CARD_PROVIDER is an essential permission
         that will only allow the system to communicate with the
         AppCardContentProvider
         The content provider must also be exported and enabled -->
         <provider android:name=".SimpleAppCardContentProvider"
             android:authorities="com.example.appcard.sample" 
             android:permission="@string/host_permission"
             android:exported="true"
             android:enabled="true">
             <intent-filter>
                <!-- This intent filter will allow the system to find and
                     connect with the application's AppCardContentProvider -->
                 <action android:name="com.android.car.appcard.APP_CARD_PROVIDER" />
                </intent-filter>
         </provider>
    

Étape 3 : Ajoutez SimpleAppCardContentProvider au fichier manifeste

  1. Pour ajouter SimpleAppCardContentProvider au fichier manifeste :

    class SimpleAppCardContentProvider : AppCardContentProvider() {
      /** Must return same authority as defined in manifest */
      override val authority: String = AUTHORITY
    
      /** Setup [AppCardContentProvider] and its constituents */
      override fun onCreate(): Boolean {
        return super.onCreate()
      }
    
      /** Setup an [AppCard] that is being requested */
      override fun onAppCardAdded(id: String, ctx: AppCardContext): AppCard {
        return when (id) {
          APPCARD_ID -> //TODO: create app card
    
          else -> throw IllegalStateException("Unidentified app card ID: $id")
        }
      }
    
      /** List of supported [AppCard] IDs */
       override val appCardIds: List<String> = listOf(APPCARD_ID).toMutableList()
    
      /** Clean up when an [AppCard] is removed */
      override fun onAppCardRemoved(id: String) {
        when (id) {
          APPCARD_ID -> //TODO: create app card
        }
      }
    
      /** Handle an [AppCardContext] change for a particular [AppCard] ID */
      override fun onAppCardContextChanged(
        id: String,
        appCardContext: AppCardContext
      ) {
         when (id) {
          APPCARD_ID -> //TODO: update AppCardContext
        }
      }
    
      companion object {
        private const val AUTHORITY = "com.example.appcard.sample"
        private const val APPCARD_ID = "sampleAppCard"
      }
    }
    

Étape 4 : Créez une AppCard

  1. Pour créer une AppCard :

    override fun onAppCardAdded(id: String, ctx: AppCardContext): AppCard {
      return when (id) {
        APPCARD_ID -> createAppCard(ctx)
    
        else -> throw IllegalStateException("Unidentified app card ID: $id")
      }
    }
    
    private fun createAppCard(appCardContext: AppCardContext): ImageAppCard {
      return ImageAppCard.newBuilder(APPCARD_ID)
        .setPrimaryText("Hello")
        .setSecondaryText("World")
        .setHeader(
          Header.newBuilder("header")
            .setTitle("Code Lab")
            .build()
        )
        .addButton(
          Button.newBuilder(
            "button",
            Button.ButtonType.PRIMARY,
            object : OnClickListener {
              override fun onClick() {
                //no-op
              }
            }
          )
            .setText("Click me!")
            .build()
        )
       .build()
    }
    

Exemple :

Créer une AppCard

Figure 1. Créez une AppCard.

Étape 5 : Activez le clicker de bouton

Pour activer le pointeur :

   private var clickCounter = 0

   private fun createAppCard(appCardContext: AppCardContext): ImageAppCard {
      ...
       .addButton(
        Button.newBuilder(
          "button",
           Button.ButtonType.PRIMARY,
           object : OnClickListener {
             override fun onClick() {
               clickCounter++
               sendAppCardUpdate(createAppCard(appCardContext))
             }
           }
         )
           .setText(
             if (clickCounter == 0) "Click me!" else "Clicked: $clickCounter"
           )
           .build()
         )
      ...
    }

    override fun onAppCardRemoved(id: String) {
      when (id) {
        APPCARD_ID -> clickCounter = 0
      }
    }

Exemple :

Activer le bouton-cliqueur

Figure 2. Activez le clicker de bouton.

Étape 6 : Mettez à jour l'image d'en-tête

  1. Pour modifier l'image de l'en-tête :

      private fun createAppCard(appCardContext: AppCardContext): ImageAppCard {
        val headerImageSize = appCardContext.imageAppCardContext.getMaxImageSize(Header::class.java)
        val logo = resToBitmap(
         android.R.drawable.ic_menu_compass,
           headerImageSize.width,
           headerImageSize.height
        )
    
     ...
       .setHeader(
         Header.newBuilder("header")
           .setTitle("Code Lab")
           .setImage(
             Image.newBuilder("image")
               .setContentScale(Image.ContentScale.FILL_BOUNDS)
               .setColorFilter(Image.ColorFilter.TINT)
               .setImageData(logo)
               .build()
           )
           .build()
        )
      ...
      }
    
      private fun resToBitmap(res: Int, width: Int, height: Int): Bitmap {
        val drawable = context?.getDrawable(res)
    
        return drawableToBitmap(drawable!!, width, height)
      }
    
      private fun drawableToBitmap(d: Drawable, width: Int, height: Int): Bitmap {
        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
    
        val canvas = Canvas(bitmap)
        val left = 0
        val top = 0
        d.setBounds(left, top, canvas.width, canvas.height)
        d.draw(canvas)
    
        return bitmap
     }
     ```
    
    For example:
    
    ![Change the image in the header](/docs/automotive/unbundled_apps/appcards/images/ceramic-06.png)
    
    **Figure 3.** Change the name to the header.
    
  2. Répétez cette procédure pour ajouter une image à n'importe quel composant compatible.

Étape 7 : Ajouter d'autres interactions

  1. Pour créer une barre de progression avec des commandes :

     private var progressOn = false
    
     private var progressTimer: Timer? = null
    
     private var progressCounter = 0
    
     override fun onAppCardRemoved(id: String) {
       when (id) {
         APPCARD_ID -> {
           clickCounter = 0
           progressCounter = 0
           progressTimer?.cancel()
         }
       }
     }
    
     private fun createAppCard(appCardContext: AppCardContext): ImageAppCard {
       val buttonImageSize = appCardContext.imageAppCardContext.getMaxImageSize(Button::class.java)
       val progressPlayPauseImage = resToBitmap(
         if (progressOn) {
           android.R.drawable.ic_media_pause
         } else {
           android.R.drawable.ic_media_play
         },
         buttonImageSize.width,
         buttonImageSize.height
       )
       ...
         .setProgressBar(
           createProgressBar()
         )
         .addButton(
           Button.newBuilder(
             "progressButton",
             Button.ButtonType.NO_BACKGROUND,
             object : OnClickListener {
               override fun onClick() {
                 progressOn = !progressOn
                 if (progressOn) {
                   progressTimer = Timer()
                   progressTimer?.scheduleAtFixedRate(object : TimerTask() {
                     override fun run() {
                     progressCounter++
                     if (progressCounter > 60) progressCounter = 0
                     sendAppCardComponentUpdate(APPCARD_ID, createProgressBar())
                   }
                 }, SECONDS_TO_MS, SECONDS_TO_MS)
               } else {
                 progressTimer?.cancel()
                 progressTimer = null
               }
               sendAppCardUpdate(createAppCard(appCardContext))
             }
           }
         )
           .setImage(
             Image.newBuilder("buttonImage")
               .setContentScale(Image.ContentScale.FILL_BOUNDS)
               .setColorFilter(Image.ColorFilter.TINT)
               .setImageData(progressPlayPauseImage)
               .build()
           )
           .build()
       )
     ...
     }
    
     private fun createProgressBar(): ProgressBar {
       return ProgressBar.newBuilder(PROGRESS_BAR_ID, 0, 60)
         .setProgress(progressCounter)
         .build()
     }
    
     companion object {
       ...
       private const val PROGRESS_BAR_ID = "progress"
       private const val SECONDS_TO_MS = 1000L
     }
    

Nous avons ajouté un bouton Lire ou Mettre en pause à la barre de progression, qui peut être mis à jour toutes les secondes. En raison des contraintes de taille, voir setText sur le bouton du clicker ajouté pour refléter le nombre de clics, .setText("$clickCounter")

Figure 4. Ajoutez un bouton.

Figure 5. Bouton affiché.

Étape 8 : Lancez l'activité

  1. Si votre application respecte background-starts#exceptions, vous pouvez lancer une activité à partir du onClickListener du bouton.

    class SampleRoutingActivity : AppCompatActivity() {
      override fun onStart() {
        super.onStart()
        val intent = Intent(ACTION_LOCATION_SOURCE_SETTINGS).apply {
          setFlags(FLAG_ACTIVITY_CLEAR_TOP)
        }
        startActivity(intent)
        finish()
      }
    }
    
  2. Pour démarrer l'activité, ajoutez un bouton dans l'AppCard. Si aucune n'existe, ajoutez une activité de routage à votre application :

    override fun onStart() {
    super.onStart()
    val intent = Intent(ACTION_LOCATION_SOURCE_SETTINGS).apply {
      setFlags(FLAG_ACTIVITY_CLEAR_TOP)
    }
    startActivity(intent)
    finish()
     }
    }
    

    Lorsque la classe est appelée, l'utilisateur est redirigé vers les paramètres Localisation. setFlags(FLAG_ACTIVITY_CLEAR_TOP) est appliqué pour s'assurer que l'utilisateur ne peut pas revenir à l'activité d'origine.

  3. Ajoutez un bouton dans l'AppCard pour démarrer l'activité :

     private fun createAppCard(appCardContext: AppCardContext): ImageAppCard {
       val locationImage = resToBitmap(
         android.R.drawable.ic_menu_call,
         buttonImageSize.width,
         buttonImageSize.height
       )
       ...
         .addButton(
           Button.newBuilder(
            "activityButton",
            Button.ButtonType.SECONDARY,
            object : OnClickListener {
              override fun onClick() {
                // no-op
              }
            }
           )
            .setImage(
              Image.newBuilder("locationButtonImage")
                .setContentScale(Image.ContentScale.FILL_BOUNDS)
                .setColorFilter(Image.ColorFilter.TINT)
                .setImageData(locationImage)
                .build()
            )
            .setIntent(
              RoutingActivityIntent
                .newBuilder("com.example.appcard.sample.SampleRoutingActivity")
                .build()
            )
            .build()
          )
        ...
    }
    

En raison de contraintes d'espace, le bouton du sélecteur a été supprimé dans l'hôte AppCard. La carte AppCard se présente comme suit :

Bouton supprimé

Figure 6. AppCard sans bouton de clic.