Upgrade to Pro — share decks privately, control downloads, hide ads and more …

First meet with Android Auto

First meet with Android Auto

Introduction of Android Auto features and developing apps with Android Auto
(2016.05.28 Android Taipei @ Yahoo!)

Johnny Sung

May 27, 2016
Tweet

More Decks by Johnny Sung

Other Decks in Programming

Transcript

  1. FIRST MEET WITH ANDROID AUTO Johnny Sung 2016.05.28 Android Taipei

    @ Yahoo! Slides URL: http://goo.gl/EasR9V
  2. ***The Android Auto app is currently available in the following

    countries: Ecuador France Germany Guatemala India Ireland Italy Mexico New Zealand Panama Argentina Australia Austria Bolivia Brazil Canada Chile Colombia Costa Rica Dominican Republic Paraguay Peru Puerto Rico Russia Spain Switzerland United Kingdom United States Uruguay Venezuela https://www.android.com/auto/
  3. EMULATOR SETUP 1. Install Auto Desktop Head Unit emulator from

    the SDK Manager 2. Install Android Auto app on phone A. Tapping the Android Auto toolbar title 10 times to enable developer mode B. Select Start head unit server from the Android Auto menu.
  4. #!/bin/bash adb forward tcp:5277 tcp:5277 $ANDROID_HOME/extras/google/auto/desktop-head-unit EMULATOR SETUP 3. Connect

    your phone to computer via USB. 4. Run scripts StartAndroidAutoDesktopHeadUnit.sh https://developer.android.com/training/auto/start/index.html
  5. Create MediaBrowserService <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 package=“my.package.name">
 <application> <!-- ... --> <meta-data


    android:name="com.google.android.gms.car.application"
 android:resource="@xml/automotive_app_desc"/>
 <service
 android:name=".MyMediaBrowserService"
 android:exported="true">
 <intent-filter>
 <action android:name="android.media.browse.MediaBrowserService"/>
 </intent-filter>
 </service>
 
 </application>
 </manifest> AndroidManifest.xml (1/3)
  6. Create MediaBrowserService @TargetApi(Build.VERSION_CODES.LOLLIPOP)
 public class MyMediaBrowserService extends MediaBrowserService {
 @Nullable


    @Override
 public BrowserRoot onGetRoot(String packageName, int uid, Bundle root) {
 return new BrowserRoot(Const.MEDIA_ID_ROOT, null);
 }
 
 @Override
 public void onLoadChildren(String parentId, Result<List<MediaBrowser.MediaItem>> result) {
 // ...
 }
 } MyMediaBrowserService.java (3/3)
  7. Working with MediaSession public class MyMediaBrowserService extends MediaBrowserService {
 


    private MediaSession mSession;
 
 @Override
 public void onCreate() {
 super.onCreate();
 mSession = new MediaSession(this, "MyMediaBrowserService");
 setSessionToken(mSession.getSessionToken());
 mSession.setCallback(new MediaSessionCallback());
 mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
 MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
 }
 
 @Override
 public void onDestroy() {
 mSession.release();
 }
 
 private final class MediaSessionCallback extends MediaSession.Callback {
 // ...
 }
 } MyMediaBrowserService.java
  8. private final class MediaSessionCallback extends MediaSession.Callback {
 @Override
 public void

    onPlay() {
 }
 @Override
 public void onPause() {
 }
 
 @Override
 public void onStop() {
 }
 
 @Override
 public void onSeekTo(long position) {
 }
 
 @Override
 public void onSkipToNext() {
 }
 
 @Override
 public void onSkipToPrevious() {
 } 
 // ...
 }
  9. private final class MediaSessionCallback extends MediaSession.Callback { // ...
 @Override


    public void onPlayFromMediaId(String mediaId, Bundle extras) {
 }
 
 @Override
 public void onSkipToQueueItem(long queueId) {
 }
 
 @Override
 public void onCustomAction(String action, Bundle extras) {
 }
 
 @Override
 public void onPlayFromSearch(final String query, final Bundle extras) {
 }
 }
  10. Validate caller package @Override
 public BrowserRoot onGetRoot(String packageName, int uid,

    Bundle rootHints) {
 LogHelper.d(TAG, "OnGetRoot: clientPackageName=" + packageName,
 "; clientUid=" + uid + " ; rootHints=", rootHints);
 // To ensure you are not allowing any arbitrary app to browse your app's contents, you need to check the origin:
 if (!mPackageValidator.isCallerAllowed(this, packageName, uid)) {
 // If the request comes from an untrusted package, return null.
 LogHelper.w(TAG, "OnGetRoot: IGNORING request from untrusted package "
 + packageName);
 return null;
 }
 
 return new BrowserRoot(Const.MEDIA_ID_ROOT, null);
 } MyMediaBrowserService.java
  11. Create Sliding Menus @Override
 public void onLoadChildren(final String pId, final

    Result<List<MediaItem>> result) {
 List<MediaItem> mediaItems = new ArrayList<>();
 if ("__ROOT__".equals(pId)) {
 mediaItems.add(new MediaItem(
 new MediaDescription.Builder()
 .setMediaId(Const.MEDIA_ID_ITEM1)
 .setTitle("Item 01")
 .setSubtitle("Some descriptions")
 .setIconUri(Uri.parse( "android.resource://my.package.name/drawable/icon"))
 .build(), MediaItem.FLAG_BROWSABLE
 ));
 mediaItems.add(new MediaItem(
 new MediaDescription.Builder()
 .setMediaId(Const.MEDIA_ID_ITEM2)
 .setTitle("Item 02")
 .setIconUri(Uri.parse( "android.resource://my.package.name/drawable/icon"))
 .build(), MediaItem.FLAG_PLAYABLE
 ));
 result.sendResult(mediaItems);
 }
 } MyMediaBrowserService.java (1/2)
  12. Create Sliding Menus private final class MediaSessionCallback extends MediaSession.Callback {


    
 @Override
 public void onPlayFromMediaId(String mediaId, Bundle extras) {
 if (Const.MEDIA_ID_ITEM2.equals(mediaId)) { // ...
 // Play media // ...
 }
 } // ...
 } MyMediaBrowserService.java (2/2)
  13. Create Sliding Menus (Async) @Override
 public void onLoadChildren(final String parentMediaId,

    final Result<List<MediaItem>> result) {
 result.detach();
 mMusicProvider.retrieveMediaAsync(new MusicProvider.Callback() {
 @Override
 public void onMusicCatalogReady() {
 List<MediaItem> mediaItems = new ArrayList<>();
 // ... // Prepare to create items // ...
 result.sendResult(mediaItems);
 }
 });
 } MyMediaBrowserService.java
  14. Setting Playback State PlaybackState.Builder stateBuilder = new PlaybackState.Builder();
 int playbackState

    = PlaybackState.STATE_PLAYING;
 
 long action = PlaybackState.ACTION_PAUSE;
 action |= PlaybackState.ACTION_SKIP_TO_NEXT;
 action |= PlaybackState.ACTION_SKIP_TO_PREVIOUS;
 stateBuilder.setActions(action);
 
 stateBuilder.setState(playbackState, -1, 1.0f);
 
 mSession.setPlaybackState(stateBuilder.build());
 
 MediaMetadata.Builder metaBuilder = new MediaMetadata.Builder();
 Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
 metaBuilder.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, bitmap);
 
 metaBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST, "Great Artist");
 metaBuilder.putString(MediaMetadata.METADATA_KEY_TITLE, "Song 1");
 mSession.setMetadata(metaBuilder.build());
 
 mSession.setActive(true); MyMediaBrowserService.java
  15. Show Error Message PlaybackState.Builder stateBuilder = new PlaybackState.Builder();
 int playbackState

    = PlaybackState.STATE_ERROR;
 stateBuilder.setState(playbackState, -1, 1.0f);
 stateBuilder.setErrorMessage("Oh no! Something has gone wrong.");
 mSession.setPlaybackState(stateBuilder.build()); MyMediaBrowserService.java
  16. Playing Queue ArrayList<MediaMetadata> mediaMetadatas = new ArrayList<>();
 for (int i

    = 0; i < 5; i++) {
 String coverUrl = "android.resource://my.package.name/drawable/icon";
 MediaMetadata.Builder builder = new MediaMetadata.Builder();
 builder.putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, coverUrl);
 builder.putString(MediaMetadata.METADATA_KEY_ARTIST, "Great artist");
 builder.putString(MediaMetadata.METADATA_KEY_TITLE, "Song " + (i + 1));
 MediaMetadata metadata = builder.build();
 mediaMetadatas.add(metadata);
 } MyMediaBrowserService.java (1/2)
  17. Playing Queue List<MediaSession.QueueItem> queue = convertToQueue(mediaMetadatas);
 mSession.setQueue(queue);
 mSession.setQueueTitle("Now Playing"); private

    static List<MediaSession.QueueItem> convertToQueue(
 Iterable<MediaMetadata> tracks) {
 List<MediaSession.QueueItem> queue = new ArrayList<>();
 int count = 0;
 for (MediaMetadata track : tracks) {
 
 String hierarchyAwareMediaID = "";
 
 MediaMetadata trackCopy = new MediaMetadata.Builder(track)
 .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, hierarchyAwareMediaID)
 .build();
 
 MediaSession.QueueItem item = new MediaSession.QueueItem(
 trackCopy.getDescription(), count++);
 queue.add(item);
 }
 return queue;
 } MyMediaBrowserService.java (2/2)
  18. MediaSession Callback private final class MediaSessionCallback extends MediaSession.Callback { 


    @Override
 public void onPlayFromSearch(final String query, final Bundle extras) { // Perform voice actions
 }
 } MyMediaBrowserService.java
  19. Semantic Analysis GOOGLE KNOWLEDGE GRAPH Play music from Lady Gaga.

    Play Jazz music. Play Starships 
 from Nicki Minaj. Artist Extras Genre Extras Song name Extras
  20. Create MessageReceivers AndroidManifest.xml <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 package="my.package.name">
 
 <application>
 <!-- ...

    -->
 <meta-data android:name="com.google.android.gms.car.application"
 android:resource="@xml/automotive_app_desc"/>
 
 <receiver
 android:name=".MessageReadReceiver"
 android:exported="false">
 <intent-filter>
 <action android:name="my.package.name.ACTION_MESSAGE_READ"/>
 </intent-filter>
 </receiver>
 
 <receiver
 android:name=".MessageReplyReceiver"
 android:exported="false">
 <intent-filter>
 <action android:name="my.package.name.ACTION_MESSAGE_REPLY"/>
 </intent-filter>
 </receiver>
 </application>
 </manifest> (1/2)
  21. MessageReadReceiver public class MessageReadReceiver extends BroadcastReceiver {
 
 @Override
 public

    void onReceive(Context context, Intent intent) {
 int conversationId = intent.getIntExtra(Const.CONVERSATION_ID, -1);
 if (conversationId != -1) {
 // Actions with conversation was read
 }
 }
 } MessageReadReceiver.java
  22. MessageReplyReceiver public class MessageReplyReceiver extends BroadcastReceiver {
 @Override
 public void

    onReceive(Context context, Intent intent) {
 if (Const.REPLY_ACTION.equals(intent.getAction())) {
 int conversationId = intent.getIntExtra(Const.CONVERSATION_ID, -1);
 
 Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
 CharSequence reply = "";
 if (remoteInput != null) {
 reply = remoteInput.getCharSequence(
 Const.EXTRA_REMOTE_REPLY);
 }
 if (conversationId != -1) {
 // Actions for receive reply message
 }
 }
 }
 } MessageReplyReceiver.java
  23. Prepare PendingIntent int conversationId = 1;
 String name = "Johnny";


    String message = "Hello, World!"; // A pending Intent for reads
 Intent readIntent = new Intent()
 .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
 .setAction(Const.READ_ACTION)
 .putExtra(Const.CONVERSATION_ID, conversationId);
 
 PendingIntent readPendingIntent = PendingIntent.getBroadcast(this,
 conversationId,
 readIntent,
 PendingIntent.FLAG_UPDATE_CURRENT); MainActivity.java (1/2)
  24. Prepare PendingIntent // Build a RemoteInput for receiving voice input

    in a Car Notification
 RemoteInput remoteInput = new RemoteInput.Builder(Const.EXTRA_REMOTE_REPLY)
 .setLabel(getString(R.string.reply))
 .build();
 
 // Building a Pending Intent for the reply action to trigger
 Intent replyIntent = new Intent()
 .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
 .setAction(Const.REPLY_ACTION)
 .putExtra(Const.CONVERSATION_ID, conversationId);
 
 PendingIntent replyPendingIntent = PendingIntent.getBroadcast(this,
 conversationId,
 replyIntent,
 PendingIntent.FLAG_UPDATE_CURRENT); MainActivity.java (2/2)
  25. Build CarExtender & UnreadConversion // Create the UnreadConversation and populate

    it with the participant name,
 // read and reply intents.
 NotificationCompat.CarExtender.UnreadConversation.Builder unreadConvBuilder =
 new NotificationCompat.CarExtender.UnreadConversation.Builder(name)
 .setLatestTimestamp(System.currentTimeMillis())
 .setReadPendingIntent(readPendingIntent)
 .setReplyAction(replyPendingIntent, remoteInput)
 .addMessage(message);
 
 NotificationCompat.CarExtender carExtender = new NotificationCompat.CarExtender()
 .setUnreadConversation(unreadConvBuilder.build()); MainActivity.java
  26. Make a Notification NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(
 R.drawable.icon, getString(R.string.reply),

    replyPendingIntent)
 .addRemoteInput(remoteInput)
 .build(); NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
 .setSmallIcon(R.drawable.icon)
 .setLargeIcon(BitmapFactory.decodeResource(
 getResources(), R.drawable.icon_big))
 .setContentText(message)
 .setWhen(System.currentTimeMillis())
 .setContentTitle(name)
 .setContentIntent(readPendingIntent)
 .extend(carExtender)
 .addAction(replyAction);
 
 NotificationManagerCompat manager = NotificationManagerCompat.from(this);
 manager.notify(conversationId, builder.build()); MainActivity.java