Upload
johnny-sung
View
564
Download
2
Embed Size (px)
Citation preview
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