First meet with Android Auto

Post on 15-Apr-2017

563 views 2 download

transcript

FIRST MEET WITH

ANDROID AUTOJohnny Sung

2016.05.28 Android Taipei @ Yahoo!

Slides URL: http://goo.gl/EasR9V

MOBILE DEVICES DEVELOPER

Johnny Sunghttps://fb.com/j796160836

https://plus.google.com/+JohnnySung

http://about.me/j796160836

http://www.pioneerelectronics.com/androidauto/

FEATURES & LIMITATIONS

INTRODUCING ANDROID AUTO

http://www.pioneerelectronics.com/androidauto/

DEMO

http://www.greenbot.com/article/2931099/android-auto-review-the-best-way-to-get-google-maps-in-your-car.html

Navigation Tab

Phone Tab

Notification Tab

Music Tab

Vehicle info Tab

Vehicle info detail

***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/

SETUP & INSTALLATION

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.

1. Install Auto Desktop Head Unit emulator from the SDK Manager

https://play.google.com/store/apps/details?id=com.google.android.projection.gearhead

2. Install Android Auto app on phone

A. enable developer mode B. Select Start head unit server from menu.

#!/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

3. Connect your phone to computer via USB.

4. Run scripts

Mac

Android phone

Emulator Commands

▸ day

▸ night

▸ daynight

Day mode

Night mode

For safety reasons,

TOUCHES4operation is limited within

Limited Operations

▸ 11 items per page

▸ 3 level depth

AUDIO APPS

MAKING

FOR ANDROID AUTO

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)

Create MediaBrowserService

<?xml version="1.0" encoding="utf-8"?> <automotiveApp> <uses name="media"/></automotiveApp>

automotive_app_desc.xml(2/3)

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)

Working with MediaSessionpublic 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

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() { } // ...}

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) { }}

Validate caller package@Overridepublic 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

Create Sliding Menus@Overridepublic 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)

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)

Create Sliding Menus (Async)

@Overridepublic 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

Setting Playback StatePlaybackState.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

Show Error MessagePlaybackState.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

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)

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)

VOICE COMMAND

Ok Google,

Ok Google,Listen Jazz music on <YourApp>

MediaSession Callback

private final class MediaSessionCallback extends MediaSession.Callback { @Override public void onPlayFromSearch(final String query, final Bundle extras) { // Perform voice actions }}

MyMediaBrowserService.java

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

Examples

▸ MediaBrowserService ▸ https://github.com/googlesamples/android-MediaBrowserService/

▸ UniversalMusicPlayer ▸ https://github.com/googlesamples/android-UniversalMusicPlayer

MESSAGING APPS

MAKING

FOR ANDROID AUTO

Create MessageReceiversAndroidManifest.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)

automotive_app_desc.xml

<?xml version="1.0" encoding="utf-8"?> <automotiveApp> <uses name="notification"/> </automotiveApp>

Create MessageReceivers (2/2)

MessageReadReceiverpublic 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

MessageReplyReceiverpublic 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

Prepare PendingIntentint conversationId = 1; String name = "Johnny"; String message = "Hello, World!";

// A pending Intent for readsIntent 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)

Prepare PendingIntent// Build a RemoteInput for receiving voice input in a Car NotificationRemoteInput remoteInput = new RemoteInput.Builder(Const.EXTRA_REMOTE_REPLY) .setLabel(getString(R.string.reply)) .build(); // Building a Pending Intent for the reply action to triggerIntent 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)

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

Make a NotificationNotificationCompat.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

Examples

▸ MessagingService ▸ https://github.com/googlesamples/android-MessagingService

DEMO

Q & A