Slide 1

Slide 1 text

ৄղ Android Auto - ࢖͍ํ͔ΒͦΕΛࢧ͑Δٕज़·Ͱ - Keishin Yokomaku / DroidKaigi 2018

Slide 2

Slide 2 text

Keishin Yokomaku 2 ৄղ Android Auto Drivemode, Inc / Engineer @KeithYokoma: GitHub / Qiita / Twitter DroidKaigi 2018

Slide 3

Slide 3 text

Android Auto 3 ৄղ Android Auto DroidKaigi 2018

Slide 4

Slide 4 text

4 ৄղ Android Auto Photo by Maurizio Pesce: http://bit.ly/2higwKJ

Slide 5

Slide 5 text

5 ৄղ Android Auto Photo by Maurizio Pesce: http://bit.ly/2zsY6BS

Slide 6

Slide 6 text

Features 6 ৄղ Android Auto Audio Messages Calls Navigation DroidKaigi 2018

Slide 7

Slide 7 text

Features 7 ৄղ Android Auto Audio Messages Calls Navigation Voice actions DroidKaigi 2018

Slide 8

Slide 8 text

Features 8 ৄղ Android Auto Audio Messages Calls Navigation Voice actions DroidKaigi 2018

Slide 9

Slide 9 text

How do they work? DroidKaigi 2018

Slide 10

Slide 10 text

Chapters in this talk ▸ Audio Freature Framework ▸ Messaging Feature Framework ▸ Wrap-up 10 ৄղ Android Auto DroidKaigi 2018

Slide 11

Slide 11 text

Audio Feature Framework DroidKaigi 2018

Slide 12

Slide 12 text

Audio: Characters in this framework 12 ৄղ Android Auto DroidKaigi 2018 Apps providing media contents Apps connecting to media app
 (incl. Android Auto and others) Android system service

Slide 13

Slide 13 text

How AndroidAuto works 13 ৄղ Android Auto DroidKaigi 2018

Slide 14

Slide 14 text

How AndroidAuto works 14 ৄղ Android Auto IPC DroidKaigi 2018

Slide 15

Slide 15 text

MediaSession in media app 15 ৄղ Android Auto DroidKaigi 2018 MediaSession

Slide 16

Slide 16 text

MediaSession in media app 16 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaController MediaSession.Token

Slide 17

Slide 17 text

Playback control flow 17 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaController MC.TransportControls MS.Callback

Slide 18

Slide 18 text

Playback control flow 18 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaController MediaPlayer MS.Callback MC.TransportControls

Slide 19

Slide 19 text

MediaController package 19 ৄղ Android Auto ✔android.media.session.MediaController ✘ android.widget.MediaController DroidKaigi 2018

Slide 20

Slide 20 text

IPC between media app and AndroidAuto 20 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaController MediaSession.Token

Slide 21

Slide 21 text

IPC between media app and AndroidAuto 21 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaController MS.Callback MC.TransportControls

Slide 22

Slide 22 text

IPC between media app and AndroidAuto 22 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaController MS.Callback MC.TransportControls

Slide 23

Slide 23 text

ৄղ Android Auto Creating MediaSession class MusicService : Service() { lateinit var session: MediaSessionCompat override fun onCreate() { session = MediaSessionCompat(this, “music”) session.setCallback(object : Callback() { override fun onPlay() { // start playing music! } // …… }) session.isActive = true } } 23 DroidKaigi 2018

Slide 24

Slide 24 text

ৄղ Android Auto class MusicService : Service() { lateinit var session: MediaSessionCompat override fun onCreate() { session = MediaSessionCompat(this, “music”) session.setCallback(object : Callback() { override fun onPlay() { // start playing music! } // …… }) session.isActive = true } } 24 DroidKaigi 2018 Creating MediaSession

Slide 25

Slide 25 text

ৄղ Android Auto Implement Callback methods class MusicService : Service() { lateinit var session: MediaSessionCompat override fun onCreate() { session = MediaSessionCompat(this, “music”) session.setCallback(object : Callback() { override fun onPlay() { // start playing music! } // …… }) session.isActive = true } } 25 DroidKaigi 2018

Slide 26

Slide 26 text

ৄղ Android Auto Activate MediaSession class MusicService : Service() { lateinit var session: MediaSessionCompat override fun onCreate() { session = MediaSessionCompat(this, “music”) session.setCallback(object : Callback() { override fun onPlay() { // start playing music! } // …… }) session.isActive = true } } 26 DroidKaigi 2018

Slide 27

Slide 27 text

IPC between media app and AndroidAuto 27 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaController MediaSession.Token

Slide 28

Slide 28 text

ৄղ Android Auto Emit MediaSession.Token via Binder class MusicService : Service() { lateinit var session: MediaSessionCompat private val binder: MusicBinder = MusicService() override fun onBind(intent: Intent) = binder fun getToken() = session.sessionToken inner class MusicBinder : Binder() { MusicService getService() = this@MusicService } } 28 DroidKaigi 2018

Slide 29

Slide 29 text

ৄղ Android Auto Emit MediaSession.Token via Binder class MusicService : Service() { lateinit var session: MediaSessionCompat private val binder: MusicBinder = MusicService() override fun onBind(intent: Intent) = binder fun getToken() = session.sessionToken inner class MusicBinder : Binder() { MusicService getService() = this@MusicService } } 29 DroidKaigi 2018

Slide 30

Slide 30 text

ৄղ Android Auto Emit MediaSession.Token via Binder class MusicService : Service() { lateinit var session: MediaSessionCompat private val binder: MusicBinder = MusicService() override fun onBind(intent: Intent) = binder fun getToken() = session.sessionToken inner class MusicBinder : Binder() { MusicService getService() = this@MusicService } } 30 DroidKaigi 2018

Slide 31

Slide 31 text

ৄղ Android Auto Emit MediaSession.Token via Binder class MusicService : Service() { lateinit var session: MediaSessionCompat private val binder: MusicBinder = MusicService() override fun onBind(intent: Intent) = binder fun getToken() = session.sessionToken inner class MusicBinder : Binder() { MusicService getService() = this@MusicService } } 31 DroidKaigi 2018 MediaSession.Token : Parcelable

Slide 32

Slide 32 text

ৄղ Android Auto Create MediaController from Token class MusicActivity : Activity() { var service: MusicService? = null var bound: Boolean = false val conn: ServiceConnection = // … override fun onCreate(savedState: Bundle) { super.onCreate(savedState) val intent = Intent(this, MusicService::class) bindService(intent, conn, BIND_AUTO_CREATE) } } 32 DroidKaigi 2018

Slide 33

Slide 33 text

ৄղ Android Auto Create MediaController from Token class MusicActivity : Activity() { var service: MusicService? = null var bound: Boolean = false val conn: ServiceConnection = // … override fun onCreate(savedState: Bundle) { super.onCreate(savedState) val intent = Intent(this, MusicService::class) bindService(intent, conn, BIND_AUTO_CREATE) } } 33 DroidKaigi 2018

Slide 34

Slide 34 text

ৄղ Android Auto Create MediaController from Token class MusicActivity : Activity() { var service: MusicService? = null var bound: Boolean = false val conn: ServiceConnection = // … override fun onDestroy() { if (bound) { unbindService(conn) bound = false } super.onDestroy() } } 34 DroidKaigi 2018

Slide 35

Slide 35 text

ৄղ Android Auto Create MediaController from Token class MusicActivity : Activity() { var service: MusicService? = null var bound: Boolean = false val conn: ServiceConnection = // … override fun onDestroy() { if (bound) { unbindService(conn) bound = false } super.onDestroy() } } 35 DroidKaigi 2018

Slide 36

Slide 36 text

ৄղ Android Auto Create MediaController from Token class MusicActivity : Activity() { var service: MusicService? = null var bound: Boolean = false val conn = object : ServiceConnection() { override fun onServiceConnected( cn: ComponentName, s: IBinder) { val binder = s as MusicBinder service = binder.getService() bound = true } } } 36 DroidKaigi 2018

Slide 37

Slide 37 text

ৄղ Android Auto Create MediaController from Token class MusicActivity : Activity() { var service: MusicService? = null var bound: Boolean = false val conn = object : ServiceConnection() { override fun onServiceConnected( cn: ComponentName, s: IBinder) { val binder = s as MusicBinder service = binder.getService() bound = true } } } 37 DroidKaigi 2018

Slide 38

Slide 38 text

ৄղ Android Auto Create MediaController from Token class MusicActivity : Activity() { var service: MusicService? = null var bound: Boolean = false val conn = object : ServiceConnection() { override fun onServiceConnected( cn: ComponentName, s: IBinder) { val binder = s as MusicBinder service = binder.getService() bound = true } } } 38 DroidKaigi 2018

Slide 39

Slide 39 text

ৄղ Android Auto Create MediaController from Token class MusicActivity : Activity() { var service: MusicService? = null var bound: Boolean = false val conn = object : ServiceConnection() { override fun onServiceConnected( cn: ComponentName, s: IBinder) { val binder = s as MusicBinder service = binder.getService() bound = true } } } 39 DroidKaigi 2018

Slide 40

Slide 40 text

ৄղ Android Auto Create MediaController from Token class MusicActivity : Activity() { var service: MusicService? = null var bound: Boolean = false val conn = object : ServiceConnection() { override fun onServiceConnected( cn: ComponentName, s: IBinder) { val binder = s as MusicBinder service = binder.getService() bound = true } } } 40 DroidKaigi 2018

Slide 41

Slide 41 text

ৄղ Android Auto Create MediaController from Token class MusicActivity : Activity() { var service: MusicService? = null var bound: Boolean = false var mediaController: MediaController? = null fun buildMediaController() { ɹ service?.let { mediaController = MediaControllerCompat( this@MusicActivity, it.getToken()) mediaController.prepare() } } } 41 DroidKaigi 2018

Slide 42

Slide 42 text

ৄղ Android Auto Create MediaController from Token class MusicActivity : Activity() { var service: MusicService? = null var bound: Boolean = false var mediaController: MediaController? = null fun buildMediaController() { ɹ service?.let { mediaController = MediaControllerCompat( this@MusicActivity, it.getToken()) mediaController.prepare() } } } 42 DroidKaigi 2018

Slide 43

Slide 43 text

ৄղ Android Auto Create MediaController from Token class MusicActivity : Activity() { var service: MusicService? = null var bound: Boolean = false var mediaController: MediaController? = null fun buildMediaController() { ɹ service?.let { mediaController = MediaControllerCompat( this@MusicActivity, it.getToken()) mediaController.prepare() } } } 43 DroidKaigi 2018

Slide 44

Slide 44 text

ৄղ Android Auto Create MediaController from Token class MusicActivity : Activity() { var service: MusicService? = null var bound: Boolean = false var mediaController: MediaController? = null fun buildMediaController() { ɹ service?.let { mediaController = MediaControllerCompat( this@MusicActivity, it.getToken()) mediaController.prepare() } } } 44 DroidKaigi 2018

Slide 45

Slide 45 text

ৄղ Android Auto Playback control via MC.TransportControls class MusicActivity : Activity() { var mediaController: MediaController? // … fun onPlayClick() { mediaController.transportControls.play() } fun onPauseClick() { mediaController.transportControls.pause() } } 45 DroidKaigi 2018

Slide 46

Slide 46 text

ৄղ Android Auto Playback control via MC.TransportControls class MusicActivity : Activity() { var mediaController: MediaController? // … fun onPlayClick() { mediaController.transportControls.play() } fun onPauseClick() { mediaController.transportControls.pause() } } 46 DroidKaigi 2018

Slide 47

Slide 47 text

Activate MediaSession 47 ৄղ Android Auto DroidKaigi 2018 MediaSession

Slide 48

Slide 48 text

Activate MediaSession 48 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaSessionManager

Slide 49

Slide 49 text

Activate MediaSession 49 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaSessionManager MediaSessionService MediaSessionRecord ……

Slide 50

Slide 50 text

Activate MediaSession 50 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaSessionManager MediaSessionService MediaSessionRecord MediaSessionRecord ……

Slide 51

Slide 51 text

Activate MediaSession 51 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaSessionManager MediaSessionService MediaSessionRecord MediaSessionRecord …… MediaSessionRecord

Slide 52

Slide 52 text

Prepare IPC via MediaController 52 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaSession.Token MSR.getController()

Slide 53

Slide 53 text

Prepare IPC via MediaController 53 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaSession.Token ISessionController

Slide 54

Slide 54 text

Prepare IPC via MediaController 54 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaSession.Token ISessionController MediaController

Slide 55

Slide 55 text

Prepare IPC via MediaController 55 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaSession.Token ISessionController MediaController ISessionController

Slide 56

Slide 56 text

Playback control via IPC 56 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaSession.Token ISessionController MediaController ISessionController TransportControls play(), pause(), …

Slide 57

Slide 57 text

Playback control via IPC 57 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaSession.Token ISessionController MediaController ISessionController TransportControls play(), pause(), … play(), pause(),

Slide 58

Slide 58 text

Playback control via IPC 58 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaSession.Token ISessionController MediaController ISessionController TransportControls play(), pause(), … play(), pause(), MS.Callback onPlay(), …

Slide 59

Slide 59 text

What if the media app is dead…? 59 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaSession.Token ISessionController MediaController ISessionController TransportControls play(), pause(), … play(), pause(), Process is dead!

Slide 60

Slide 60 text

What if the media app is dead…? 60 ৄղ Android Auto Fatal exception: DeadObjectException DroidKaigi 2018

Slide 61

Slide 61 text

When remote process is dead 61 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaSession.Token ISessionController MediaController ISessionController Process is dead!

Slide 62

Slide 62 text

When remote process is dead 62 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaSession.Token ISessionController MediaController ISessionController Process is dead! binderDied() ⇣
 onDestroy()

Slide 63

Slide 63 text

When remote process is dead 63 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaSession.Token ISessionController MediaController ISessionController Process is dead! Callback onSessionDestroy()

Slide 64

Slide 64 text

ৄղ Android Auto Detect remote process death class MusicActivity : Activity() { var binder: IBinder? = null val recipient: IBinder.DeathRecipient = // … val conn = object : ServiceConnection() { override fun onServiceConnected( cn: ComponentName, b: IBinder) { binder = b b.linkToDeath(recipient, 0) } } } 64 DroidKaigi 2018

Slide 65

Slide 65 text

ৄղ Android Auto Detect remote process death class MusicActivity : Activity() { var binder: IBinder? = null val recipient: IBinder.DeathRecipient = // … val conn = object : ServiceConnection() { override fun onServiceConnected( cn: ComponentName, b: IBinder) { binder = b b.linkToDeath(recipient, 0) } } } 65 DroidKaigi 2018

Slide 66

Slide 66 text

ৄղ Android Auto Detect remote process death class MusicActivity : Activity() { var binder: IBinder? = null val recipient = object : IBinder.DeathRecipient { override fun binderDied() { binder?.unlinkToDeath(this@DeathRecipient) } } val conn: ServiceConnection = // … } 66 DroidKaigi 2018

Slide 67

Slide 67 text

IBinder.DeathRecipient vs ServiceConnection ▸ IBinder.DeathRecipient#binderDied ▸ Monitor remote process death ▸ ServiceConnection#onServiceDisconnected ▸ Called from IBinder.DeathRecipient#binderDied ▸ Only called when service is bound to remote 67 ৄղ Android Auto DroidKaigi 2018

Slide 68

Slide 68 text

ৄղ Android Auto Get notified from media app class MusicActivity : Activity() { var service: MusicService? = null var mediaController: MediaController? = null val callback: MediaController.Callback = //… fun buildMediaController() { ɹ service?.let { mediaController = // … mediaController.registerCallback(callback) } } } 68 DroidKaigi 2018

Slide 69

Slide 69 text

ৄղ Android Auto Get notified from media app class MusicActivity : Activity() { var service: MusicService? = null var mediaController: MediaController? = null val callback: MediaController.Callback = object : MediaController.Callback() { fun onSessionDestroy() { // no more interaction from here! } } } 69 DroidKaigi 2018

Slide 70

Slide 70 text

ৄղ Android Auto Get notified from media app class MusicActivity : Activity() { var service: MusicService? = null var mediaController: MediaController? = null val callback: MediaController.Callback = object : MediaController.Callback() { fun onPlaybackStateChanged( state: PlaybackStateCompat) { // get new playback state! } } } 70 DroidKaigi 2018

Slide 71

Slide 71 text

ৄղ Android Auto Get notified from media app class MusicActivity : Activity() { var service: MusicService? = null var mediaController: MediaController? = null val callback: MediaController.Callback = object : MediaController.Callback() { fun onMetadataChanged( data: MediaMetadataCompat) { // get new media information! } } } 71 DroidKaigi 2018

Slide 72

Slide 72 text

Playback control IPC on Lock Screen in 4.x 72 ৄղ Android Auto DroidKaigi 2018 RemoteControlClient AudioManager AudioService LockScreen

Slide 73

Slide 73 text

Playback control IPC on Lock Screen in 4.x 73 ৄղ Android Auto DroidKaigi 2018 RemoteControlClient AudioManager AudioService RemoteControlDisplay LockScreen

Slide 74

Slide 74 text

Playback control IPC on Lock Screen in 4.x 74 ৄղ Android Auto DroidKaigi 2018 RemoteControlClient AudioManager AudioService RemoteControlDisplay LockScreen

Slide 75

Slide 75 text

Playback control IPC on Lock Screen in 4.4 75 ৄղ Android Auto DroidKaigi 2018 RemoteControlClient AudioManager AudioService RemoteController LockScreen NotificationListener Service

Slide 76

Slide 76 text

Playback control IPC on Lock Screen in 5+ 76 ৄղ Android Auto DroidKaigi 2018 MediaSession NotificationManager Service MediaController LockScreen MediaStyle Notification NotificationListener Service

Slide 77

Slide 77 text

Playback control IPC on Lock Screen in 5+ 77 ৄղ Android Auto DroidKaigi 2018 MediaSession NotificationManager Service MediaController LockScreen MediaStyle Notification NotificationListener Service

Slide 78

Slide 78 text

Get active sessions for the package 78 ৄղ Android Auto DroidKaigi 2018 MediaSessionManager MediaSessionService MediaSessionRecord MediaSessionRecord …… getActiveSessions()

Slide 79

Slide 79 text

Get active sessions for the package 79 ৄղ Android Auto DroidKaigi 2018 MediaSessionManager MediaSessionService MediaSessionRecord MediaSessionRecord …… getSessions()

Slide 80

Slide 80 text

Get active sessions for the package 80 ৄղ Android Auto DroidKaigi 2018 MediaSessionManager MediaSessionService MediaSessionRecord MediaSessionRecord …… ISessionController

Slide 81

Slide 81 text

Get active sessions for the package 81 ৄղ Android Auto DroidKaigi 2018 MediaSessionManager MediaSessionService MediaSessionRecord MediaSessionRecord …… MediaController

Slide 82

Slide 82 text

ৄղ Android Auto Get notified from media app class MusicActivity : Activity() { override fun onCreate(savedState: Bundle) { super.onCreate(savedState) val manager: MediaSessionManager = context.getSystemService( MEDIA_SESSION_SERVICE) val controllers: List = manager.getActiveSessions(null) // do whatever you want with media controller… } } 82 DroidKaigi 2018

Slide 83

Slide 83 text

ৄղ Android Auto Get notified from media app class MusicActivity : Activity() { override fun onCreate(savedState: Bundle) { super.onCreate(savedState) val manager: MediaSessionManager = context.getSystemService( MEDIA_SESSION_SERVICE) val controllers: List = manager.getActiveSessions(null) // do whatever you want with media controller… } } 83 DroidKaigi 2018

Slide 84

Slide 84 text

ৄղ Android Auto Get notified from media app class MusicActivity : Activity() { override fun onCreate(savedState: Bundle) { super.onCreate(savedState) val manager: MediaSessionManager = context.getSystemService( MEDIA_SESSION_SERVICE) val controllers: List = manager.getActiveSessions(null) // do whatever you want with media controller… } } 84 DroidKaigi 2018

Slide 85

Slide 85 text

ৄղ Android Auto Get notified from media app class MusicActivity : Activity() { override fun onCreate(savedState: Bundle) { super.onCreate(savedState) val manager: MediaSessionManager = context.getSystemService( MEDIA_SESSION_SERVICE) val controllers: List = manager.getActiveSessions(null) // do whatever you want with media controller… } } 85 DroidKaigi 2018

Slide 86

Slide 86 text

How AndroidAuto works: MediaBrowser 86 ৄղ Android Auto IPC DroidKaigi 2018

Slide 87

Slide 87 text

MediaBrowser and MediaBrowserService 87 ৄղ Android Auto DroidKaigi 2018 MediaBrowserService MediaBrowser

Slide 88

Slide 88 text

Connect to MediaBrowserService 88 ৄղ Android Auto DroidKaigi 2018 MediaBrowserService MediaBrowser connect()

Slide 89

Slide 89 text

Connect to MediaBrowserService 89 ৄղ Android Auto DroidKaigi 2018 MediaBrowserService MediaBrowser MB.ConnectionCallback# onConnected()

Slide 90

Slide 90 text

ৄղ Android Auto Connect to MediaBrowserService class MusicActivity : Activity() { lateinit var browser: MediaBrowserCompat val service = ComponentName( this, BrowserService::class) val cb: MediaBrowserCompat.ConnectionCallback = … override fun onCreate(savedState: Bundle) { super.onCreate(savedState) browser = MediaBrowserCompat( this, service, cb, Bundle()) browser.connect() } } 90 DroidKaigi 2018

Slide 91

Slide 91 text

ৄղ Android Auto Connect to MediaBrowserService class MusicActivity : Activity() { lateinit var browser: MediaBrowserCompat val service = ComponentName( this, BrowserService::class) val cb: MediaBrowserCompat.ConnectionCallback = … override fun onCreate(savedState: Bundle) { super.onCreate(savedState) browser = MediaBrowserCompat( this, service, cb, Bundle()) browser.connect() } } 91 DroidKaigi 2018

Slide 92

Slide 92 text

ৄղ Android Auto Connect to MediaBrowserService class MusicActivity : Activity() { lateinit var browser: MediaBrowserCompat val service = ComponentName( this, BrowserService::class) val cb: MediaBrowserCompat.ConnectionCallback = … override fun onCreate(savedState: Bundle) { super.onCreate(savedState) browser = MediaBrowserCompat( this, service, cb, Bundle()) browser.connect() } } 92 DroidKaigi 2018

Slide 93

Slide 93 text

ৄղ Android Auto Connect to MediaBrowserService class MusicActivity : Activity() { lateinit var browser: MediaBrowserCompat val cb = object : MBC.ConnectionCallback { override fun onConnected() { // you can start subscribing/searching // media contents after this callback! } override fun onConnectionFailed() { // cannot connect to remote service! } } } 93 DroidKaigi 2018

Slide 94

Slide 94 text

ৄղ Android Auto Connect to MediaBrowserService class MusicActivity : Activity() { lateinit var browser: MediaBrowserCompat val cb = object : MBC.ConnectionCallback { override fun onConnected() { // you can start subscribing/searching // media contents after this callback! } override fun onConnectionFailed() { // cannot connect to remote service! } } } 94 DroidKaigi 2018

Slide 95

Slide 95 text

ৄղ Android Auto Connect to MediaBrowserService class MusicActivity : Activity() { lateinit var browser: MediaBrowserCompat val cb = object : MBC.ConnectionCallback { override fun onConnected() { // you can start subscribing/searching // media contents after this callback! } override fun onConnectionFailed() { // cannot connect to remote service! } } } 95 DroidKaigi 2018

Slide 96

Slide 96 text

ৄղ Android Auto Verify connection from MediaBrowser class BrowserService : MediaBrowserServiceCompat() { override fun onGetRoot( clientPkg: String, clientUid: Int, hints: Bundle?): BrowserRoot? { // called on connect // return null if refusing connection } } 96 DroidKaigi 2018

Slide 97

Slide 97 text

Connection verification by 97 ৄղ Android Auto ✔Verify signature of the client ✔Verify the client package name DroidKaigi 2018 ✔Verify the client process user id

Slide 98

Slide 98 text

Connection verification flow 98 ৄղ Android Auto DroidKaigi 2018 Allow access? Client is self? Next Step Allow YES No

Slide 99

Slide 99 text

No Connection verification flow 99 ৄղ Android Auto DroidKaigi 2018 Allow access? Client is self? Next Step Allow clientUid == Process.myUid() YES

Slide 100

Slide 100 text

No Connection verification flow 100 ৄղ Android Auto DroidKaigi 2018 Allow access? Client is system? Next Step Allow YES

Slide 101

Slide 101 text

No Connection verification flow 101 ৄղ Android Auto DroidKaigi 2018 Allow access? Client is system? Next Step Allow clientUid == Process.SYSTEM_UID YES

Slide 102

Slide 102 text

Connection verification flow 102 ৄղ Android Auto DroidKaigi 2018 Allow access? Client has valid signature? Refuse Allow YES No

Slide 103

Slide 103 text

ৄղ Android Auto List of allowed client packages and signatures Base64 encoded signature here 103 DroidKaigi 2018

Slide 104

Slide 104 text

ৄղ Android Auto List of allowed client packages and signatures Base64 encoded signature here 104 DroidKaigi 2018

Slide 105

Slide 105 text

ৄղ Android Auto List of allowed client packages and signatures Base64 encoded signature here 105 DroidKaigi 2018

Slide 106

Slide 106 text

ৄղ Android Auto Generate your app signature $ keytool -export -alias androiddebugkey -keystore ~/.android/debug.keystore | base64 Password: xxxx Base64 encoded signature 106 DroidKaigi 2018

Slide 107

Slide 107 text

ৄղ Android Auto Generate your app signature $ keytool -export -alias androiddebugkey -keystore ~/.android/debug.keystore | base64 Password: xxxx Base64 encoded signature 107 DroidKaigi 2018

Slide 108

Slide 108 text

ৄղ Android Auto Verify connection from MediaBrowser class BrowserService : MediaBrowserServiceCompat() { override fun onGetRoot( clientPkg: String, clientUid: Int, hints: Bundle?): BrowserRoot? { val validPackage: Boolean = // validate caller! if (validPackage) { return MBSC.BrowserRoot(“__ROOT__”, null) } return null } } 108 DroidKaigi 2018

Slide 109

Slide 109 text

ৄղ Android Auto Media contents structure 109 DroidKaigi 2018 __ROOT__ __FAVORITE__ __RECOMMEND__ __SUBSCRIPTION__ __FAVORITE__/1 __FAVORITE__/2

Slide 110

Slide 110 text

ৄղ Android Auto Media contents structure 110 DroidKaigi 2018 __ROOT__ __FAVORITE__ __RECOMMEND__ __SUBSCRIPTION__ __FAVORITE__/1 __FAVORITE__/2 BrowserRoot

Slide 111

Slide 111 text

ৄղ Android Auto Media contents structure 111 DroidKaigi 2018 __ROOT__ __FAVORITE__ __RECOMMEND__ __SUBSCRIPTION__ __FAVORITE__/1 __FAVORITE__/2 MediaItem BROWSABLE MediaItem PLAYABLE

Slide 112

Slide 112 text

Subscribe media content 112 ৄղ Android Auto DroidKaigi 2018 MediaBrowserService MediaBrowser subscribe(id, callback)

Slide 113

Slide 113 text

Subscribe media content 113 ৄղ Android Auto DroidKaigi 2018 MediaBrowserService MediaBrowser MB.SubscriptionCallback# onChildrenLoaded()

Slide 114

Slide 114 text

ৄղ Android Auto Load top level items class MusicActivity : Activity() { lateinit var browser: MediaBrowserCompat val scb: MBC.SubscriptionCallback = // … fun loadRootItems() { val rootId: String = browser.getRoot() browser.subscribe(rootId, scb) } } 114 DroidKaigi 2018

Slide 115

Slide 115 text

ৄղ Android Auto Load top level items class MusicActivity : Activity() { lateinit var browser: MediaBrowserCompat val scb: MBC.SubscriptionCallback = // … fun loadRootItems() { val rootId: String = browser.getRoot() browser.subscribe(rootId, scb) } } 115 DroidKaigi 2018

Slide 116

Slide 116 text

ৄղ Android Auto Load top level items class MusicActivity : Activity() { lateinit var browser: MediaBrowserCompat val scb: MBC.SubscriptionCallback = // … fun loadRootItems() { val rootId: String = browser.getRoot() browser.subscribe(rootId, scb) } } 116 DroidKaigi 2018

Slide 117

Slide 117 text

ৄղ Android Auto Load top level items class MusicActivity : Activity() { lateinit var browser: MediaBrowserCompat val scb = object : MBC.SubscriptionCallback() { override fun onChildrenLoaded( parentId: String, list: List) { // show media list under the parentId item } } } 117 DroidKaigi 2018

Slide 118

Slide 118 text

ৄղ Android Auto Load top level items class MusicActivity : Activity() { lateinit var browser: MediaBrowserCompat val scb = object : MBC.SubscriptionCallback() { override fun onChildrenLoaded( parentId: String, list: List) { // show media list under the parentId item } } } 118 DroidKaigi 2018

Slide 119

Slide 119 text

ৄղ Android Auto On select item: Load the next list 119 DroidKaigi 2018 __ROOT__ __FAVORITE__ __RECOMMEND__ __SUBSCRIPTION__ __FAVORITE__/1 __FAVORITE__/2 MediaItem BROWSABLE

Slide 120

Slide 120 text

ৄղ Android Auto On select item: Start playing media content 120 DroidKaigi 2018 __ROOT__ __FAVORITE__ __RECOMMEND__ __SUBSCRIPTION__ __FAVORITE__/1 __FAVORITE__/2 MediaItem PLAYABLE

Slide 121

Slide 121 text

ৄղ Android Auto Load media items synchronously class BrowserService : MediaBrowserServiceCompat() { override fun onLoadChildren( parentId: String, result: Result>) { val list: List = // load media items result.sendResult(list) } } 121 DroidKaigi 2018

Slide 122

Slide 122 text

ৄղ Android Auto Load media items synchronously class BrowserService : MediaBrowserServiceCompat() { override fun onLoadChildren( parentId: String, result: Result>) { val list: List = // load media items result.sendResult(list) } } 122 DroidKaigi 2018

Slide 123

Slide 123 text

ৄղ Android Auto Load media items asynchronously class BrowserService : MediaBrowserServiceCompat() { override fun onLoadChildren( parentId: String, result: Result>) { result.detach() Single.create { val list: List = // … it.onSuccess(list) }.subscribeOn(schedulers.io).subscribe { list -> result.sendResult(list) } } } 123 DroidKaigi 2018

Slide 124

Slide 124 text

ৄղ Android Auto Load media items asynchronously class BrowserService : MediaBrowserServiceCompat() { override fun onLoadChildren( parentId: String, result: Result>) { result.detach() Single.create { val list: List = // … it.onSuccess(list) }.subscribeOn(schedulers.io).subscribe { list -> result.sendResult(list) } } } 124 DroidKaigi 2018

Slide 125

Slide 125 text

ৄղ Android Auto Load media items asynchronously class BrowserService : MediaBrowserServiceCompat() { override fun onLoadChildren( parentId: String, result: Result>) { result.detach() Single.create { val list: List = // … it.onSuccess(list) }.subscribeOn(schedulers.io).subscribe { list -> result.sendResult(list) } } } 125 DroidKaigi 2018

Slide 126

Slide 126 text

ৄղ Android Auto Handle item selection class MusicActivity : Activity() { lateinit var browser: MediaBrowserCompat val scb: MBC.SubscriptionCallback = // … fun onItemSelected(item: MediaItem) { if (item.isBrowsable()) { // start loading more item under the item } else if (item.isPlayable() { // start playing item } } } 126 DroidKaigi 2018

Slide 127

Slide 127 text

ৄղ Android Auto Handle item selection class MusicActivity : Activity() { lateinit var browser: MediaBrowserCompat val scb: MBC.SubscriptionCallback = // … fun onItemSelected(item: MediaItem) { if (item.isBrowsable()) { // start loading more item under the item } else if (item.isPlayable() { // start playing item } } } 127 DroidKaigi 2018

Slide 128

Slide 128 text

ৄղ Android Auto Handle item selection: Load more! class MusicActivity : Activity() { lateinit var browser: MediaBrowserCompat val scb: MBC.SubscriptionCallback = // … fun onItemSelected(item: MediaItem) { if (item.isBrowsable()) { // start loading more item under the item } else if (item.isPlayable() { // start playing item } } } 128 DroidKaigi 2018

Slide 129

Slide 129 text

ৄղ Android Auto Handle item selection: Start playing! class MusicActivity : Activity() { lateinit var browser: MediaBrowserCompat val scb: MBC.SubscriptionCallback = // … fun onItemSelected(item: MediaItem) { if (item.isBrowsable()) { // start loading more item under the item } else if (item.isPlayable() { // start playing item } } } 129 DroidKaigi 2018

Slide 130

Slide 130 text

ৄղ Android Auto Handle item selection: Load more! class MusicActivity : Activity() { lateinit var browser: MediaBrowserCompat val scb: MBC.SubscriptionCallback = // … fun onItemSelected(item: MediaItem) { if (item.isBrowsable()) { browser.subscribe(item.mediaId, scb) } } } 130 DroidKaigi 2018

Slide 131

Slide 131 text

ৄղ Android Auto Handle item selection: Start playing! class MusicActivity : Activity() { lateinit var browser: MediaBrowserCompat var controller: MediaController? = null fun onItemSelected(item: MediaItem) { if (item.isPlayable() { val token = browser.sessionToken controller = MediaController(this, token) controller.transportControls.playFromMediaId( item.mediaId, Bundle()) } } } 131 DroidKaigi 2018

Slide 132

Slide 132 text

ৄղ Android Auto Search items class MusicActivity : Activity() { lateinit var browser: MediaBrowserCompat val qcb = object : MBC.SearchCallback() { override fun onSearchResult(query: String, extra: Bundle, res: List) { // show query result to the list! } } fun onSearchItem(query: String) { browser.search(query, Bundle(), qcb) } } 132 DroidKaigi 2018

Slide 133

Slide 133 text

ৄղ Android Auto Search items class MusicActivity : Activity() { lateinit var browser: MediaBrowserCompat val qcb = object : MBC.SearchCallback() { override fun onSearchResult(query: String, extra: Bundle, res: List) { // show query result to the list! } } fun onSearchItem(query: String) { browser.search(query, Bundle(), qcb) } } 133 DroidKaigi 2018

Slide 134

Slide 134 text

ৄղ Android Auto Search items class MusicActivity : Activity() { lateinit var browser: MediaBrowserCompat val qcb = object : MBC.SearchCallback() { override fun onSearchResult(query: String, extra: Bundle, res: List) { // show query result to the list! } } fun onSearchItem(query: String) { browser.search(query, Bundle(), qcb) } } 134 DroidKaigi 2018

Slide 135

Slide 135 text

ৄղ Android Auto Handle search query synchronously class BrowserService : MediaBrowserServiceCompat() { override fun onSearch( query: String, extras: Bundle, result: Result>) { val list: List = // … result.sendResult(list) } } 135 DroidKaigi 2018

Slide 136

Slide 136 text

ৄղ Android Auto Handle search query asynchronously class BrowserService : MediaBrowserServiceCompat() { override fun onSearch( query: String, extras: Bundle, result: Result>) { result.detach() Single.create { val list: List = // … it.onSuccess(list) }.subscribeOn(schedulers.io).subscribe { list -> result.sendResult(list) } } } 136 DroidKaigi 2018

Slide 137

Slide 137 text

Wrap up: Audio Feature Framework 137 ৄղ Android Auto DroidKaigi 2018 MediaBrowserService MediaBrowser MediaController MediaSession MediaPlayer

Slide 138

Slide 138 text

Wrap up: Audio Feature Framework 138 ৄղ Android Auto DroidKaigi 2018 MediaBrowserService MediaBrowser MediaController MediaSession MediaPlayer Create new session

Slide 139

Slide 139 text

Wrap up: Audio Feature Framework 139 ৄղ Android Auto DroidKaigi 2018 MediaBrowserService MediaBrowser MediaController MediaSession MediaPlayer Provide session token

Slide 140

Slide 140 text

Wrap up: Audio Feature Framework 140 ৄղ Android Auto DroidKaigi 2018 MediaBrowserService MediaBrowser MediaController MediaSession MediaPlayer Connect to service

Slide 141

Slide 141 text

Wrap up: Audio Feature Framework 141 ৄղ Android Auto DroidKaigi 2018 MediaBrowserService MediaBrowser MediaController MediaSession MediaPlayer Provide media info and token

Slide 142

Slide 142 text

Wrap up: Audio Feature Framework 142 ৄղ Android Auto DroidKaigi 2018 MediaBrowserService MediaBrowser MediaController MediaSession MediaPlayer Refuse connection if not OK

Slide 143

Slide 143 text

Wrap up: Audio Feature Framework 143 ৄղ Android Auto DroidKaigi 2018 MediaBrowserService MediaBrowser MediaController MediaSession MediaPlayer Create controller from token

Slide 144

Slide 144 text

Wrap up: Audio Feature Framework 144 ৄղ Android Auto DroidKaigi 2018 MediaBrowserService MediaBrowser MediaController MediaSession MediaPlayer Playback control via TransportControls

Slide 145

Slide 145 text

Wrap up: Audio Feature Framework 145 ৄղ Android Auto DroidKaigi 2018 MediaBrowserService MediaBrowser MediaController MediaSession MediaPlayer Actual playback control

Slide 146

Slide 146 text

Messaging Feature Framework DroidKaigi 2018

Slide 147

Slide 147 text

Messaging: Characters in this framework 147 ৄղ Android Auto DroidKaigi 2018 Messenger apps Apps listening to incoming messages
 (incl. Android Auto and others) Android system service

Slide 148

Slide 148 text

Overview - Messaging Feature Framework 148 ৄղ Android Auto DroidKaigi 2018

Slide 149

Slide 149 text

NOTIFICATION Overview - Messaging Feature Framework 149 ৄղ Android Auto DroidKaigi 2018

Slide 150

Slide 150 text

NOTIFICATION Overview - Messaging Feature Framework 150 ৄղ Android Auto DroidKaigi 2018 NotificationManager#notify(id, n)

Slide 151

Slide 151 text

Message Notification for Android Auto 151 ৄղ Android Auto DroidKaigi 2018 NotificationCompat.CarExtender Visual settings(color and icon) Unread/Read status Voice input for reply

Slide 152

Slide 152 text

ৄղ Android Auto Building Notification for Android Auto val n = NotificationCompat.Builder(context) … .extend(CarExtender() .setColor(R.color.car_notification) .setLargeIcon(iconBitmap) .setUnreadConversation( CarExtender.UnreadConversation.Builder(“test”) .addMessage(“This is a message.”) .setReadPendingIntent(readPi) .setReplyAction(replyPi, remoteInput) .setLatestTimestamp(timestamp) .build()) ).build() 152 DroidKaigi 2018

Slide 153

Slide 153 text

ৄղ Android Auto Building Notification for Android Auto val n = NotificationCompat.Builder(context) … .extend(CarExtender() .setColor(R.color.car_notification) .setLargeIcon(iconBitmap) .setUnreadConversation( CarExtender.UnreadConversation.Builder(“test”) .addMessage(“This is a message.”) .setReadPendingIntent(readPi) .setReplyAction(replyPi, remoteInput) .setLatestTimestamp(timestamp) .build()) ).build() 153 DroidKaigi 2018

Slide 154

Slide 154 text

ৄղ Android Auto Building Notification for Android Auto val n = NotificationCompat.Builder(context) … .extend(CarExtender() .setColor(R.color.car_notification) .setLargeIcon(iconBitmap) .setUnreadConversation( CarExtender.UnreadConversation.Builder(“test”) .addMessage(“This is a message.”) .setReadPendingIntent(readPi) .setReplyAction(replyPi, remoteInput) .setLatestTimestamp(timestamp) .build()) ).build() 154 DroidKaigi 2018

Slide 155

Slide 155 text

ৄղ Android Auto Building Notification for Android Auto val n = NotificationCompat.Builder(context) … .extend(CarExtender() .setColor(R.color.car_notification) .setLargeIcon(iconBitmap) .setUnreadConversation( CarExtender.UnreadConversation.Builder(“test”) .addMessage(“This is a message.”) .setReadPendingIntent(readPi) .setReplyAction(replyPi, remoteInput) .setLatestTimestamp(timestamp) .build()) ).build() 155 DroidKaigi 2018

Slide 156

Slide 156 text

ৄղ Android Auto Building Notification for Android Auto val n = NotificationCompat.Builder(context) … .extend(CarExtender() .setColor(R.color.car_notification) .setLargeIcon(iconBitmap) .setUnreadConversation( CarExtender.UnreadConversation.Builder(“test”) .addMessage(“This is a message.”) .setReadPendingIntent(readPi) .setReplyAction(replyPi, remoteInput) .setLatestTimestamp(timestamp) .build()) ).build() 156 DroidKaigi 2018

Slide 157

Slide 157 text

ৄղ Android Auto Building Notification for Android Auto val n = NotificationCompat.Builder(context) … .extend(CarExtender() .setColor(R.color.car_notification) .setLargeIcon(iconBitmap) .setUnreadConversation( CarExtender.UnreadConversation.Builder(“test”) .addMessage(“This is a message.”) .setReadPendingIntent(readPi) .setReplyAction(replyPi, remoteInput) .setLatestTimestamp(timestamp) .build()) ).build() 157 DroidKaigi 2018

Slide 158

Slide 158 text

ৄղ Android Auto Building Notification for Android Auto val n = NotificationCompat.Builder(context) … .extend(CarExtender() .setColor(R.color.car_notification) .setLargeIcon(iconBitmap) .setUnreadConversation( CarExtender.UnreadConversation.Builder(“test”) .addMessage(“This is a message.”) .setReadPendingIntent(readPi) .setReplyAction(replyPi, remoteInput) .setLatestTimestamp(timestamp) .build()) ).build() 158 DroidKaigi 2018

Slide 159

Slide 159 text

ৄղ Android Auto Building Notification for Android Auto val n = NotificationCompat.Builder(context) … .extend(CarExtender() .setColor(R.color.car_notification) .setLargeIcon(iconBitmap) .setUnreadConversation( CarExtender.UnreadConversation.Builder(“test”) .addMessage(“This is a message.”) .setReadPendingIntent(readPi) .setReplyAction(replyPi, remoteInput) .setLatestTimestamp(timestamp) .build()) ).build() 159 DroidKaigi 2018

Slide 160

Slide 160 text

ৄղ Android Auto Building Notification for Android Auto val n = NotificationCompat.Builder(context) … .extend(CarExtender() .setColor(R.color.car_notification) .setLargeIcon(iconBitmap) .setUnreadConversation( CarExtender.UnreadConversation.Builder(“test”) .addMessage(“This is a message.”) .setReadPendingIntent(readPi) .setReplyAction(replyPi, remoteInput) .setLatestTimestamp(timestamp) .build()) ).build() 160 DroidKaigi 2018

Slide 161

Slide 161 text

ৄղ Android Auto Read/Unread status update val conversationId: Int = //ϝοηʔδͷ΍ΓऔΓΛද͢ID val intent = Intent(“your.app.action.READ_MESSAGE”) .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGE) .putExtra(“conversation_id”, conversationId); val readPi = PendingIntent.getBroadcast( applicationContext, conversationId, intent, PendingIntent.FLAG_UPDATE_CURRENT) 161 DroidKaigi 2018

Slide 162

Slide 162 text

ৄղ Android Auto Read/Unread status update val conversationId: Int = //ϝοηʔδͷ΍ΓऔΓΛද͢ID val intent = Intent(“your.app.action.READ_MESSAGE”) .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGE) .putExtra(“conversation_id”, conversationId); val readPi = PendingIntent.getBroadcast( applicationContext, conversationId, intent, PendingIntent.FLAG_UPDATE_CURRENT) 162 DroidKaigi 2018

Slide 163

Slide 163 text

ৄղ Android Auto Read/Unread status update val conversationId: Int = //ϝοηʔδͷ΍ΓऔΓΛද͢ID val intent = Intent(“your.app.action.READ_MESSAGE”) .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGE) .putExtra(“conversation_id”, conversationId); val readPi = PendingIntent.getBroadcast( applicationContext, conversationId, intent, PendingIntent.FLAG_UPDATE_CURRENT) 163 DroidKaigi 2018

Slide 164

Slide 164 text

ৄղ Android Auto Read/Unread status update val conversationId: Int = //ϝοηʔδͷ΍ΓऔΓΛද͢ID val intent = Intent(“your.app.action.READ_MESSAGE”) .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGE) .putExtra(“conversation_id”, conversationId); val readPi = PendingIntent.getBroadcast( applicationContext, conversationId, intent, PendingIntent.FLAG_UPDATE_CURRENT) 164 DroidKaigi 2018

Slide 165

Slide 165 text

ৄղ Android Auto Reply by voice input in Android Auto val conversationId: Int = //ϝοηʔδͷ΍ΓऔΓΛද͢ID val input = RemoteInput.Builder(“key_voice_reply”) … val intent = Intent(“your.app.action.REPLY_MESSAGE”) … val replyPi = PendingIntent.getBroadcast( applicationContext, conversationId, intent, PendingIntent.FLAG_UPDATE_CURRENT) 165 DroidKaigi 2018

Slide 166

Slide 166 text

ৄղ Android Auto Reply by voice input in Android Auto val conversationId: Int = //ϝοηʔδͷ΍ΓऔΓΛද͢ID val input = RemoteInput.Builder(“key_voice_reply”) … val intent = Intent(“your.app.action.REPLY_MESSAGE”) … val replyPi = PendingIntent.getBroadcast( applicationContext, conversationId, intent, PendingIntent.FLAG_UPDATE_CURRENT) 166 DroidKaigi 2018

Slide 167

Slide 167 text

ৄղ Android Auto Reply by voice input in Android Auto val conversationId: Int = //ϝοηʔδͷ΍ΓऔΓΛද͢ID val input = RemoteInput.Builder(“key_voice_reply”) .setLabel(“Reply by voice”) .build() val intent = Intent(“your.app.action.REPLY_MESSAGE”) … val replyPi = PendingIntent.getBroadcast( applicationContext, conversationId, intent, PendingIntent.FLAG_UPDATE_CURRENT) 167 DroidKaigi 2018

Slide 168

Slide 168 text

ৄղ Android Auto Reply by voice input in Android Auto val conversationId: Int = //ϝοηʔδͷ΍ΓऔΓΛද͢ID val input = RemoteInput.Builder(“key_voice_reply”) … val intent = Intent(“your.app.action.REPLY_MESSAGE”) .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) .putExtra(“conversation_id”, conversationId) val replyPi = PendingIntent.getBroadcast( applicationContext, conversationId, intent, PendingIntent.FLAG_UPDATE_CURRENT) 168 DroidKaigi 2018

Slide 169

Slide 169 text

ৄղ Android Auto Reply by voice input in Android Auto val conversationId: Int = //ϝοηʔδͷ΍ΓऔΓΛද͢ID val input = RemoteInput.Builder(“key_voice_reply”) … val intent = Intent(“your.app.action.REPLY_MESSAGE”) … val replyPi = PendingIntent.getBroadcast( applicationContext, conversationId, intent, PendingIntent.FLAG_UPDATE_CURRENT) 169 DroidKaigi 2018

Slide 170

Slide 170 text

ৄղ Android Auto Receive voice input result from Android Auto class ReplyReceiver : BroadcastReceiver() { override fun onReceive(c: Context, i: Intent) { val conversationId = i.getIntExtra(“conversation_id”) val result = RemoteInput.getResultsFromIntent(i) result?.let { val text = it.getCharSequence(“key_voice_reply”) … // sending reply } } } 170 DroidKaigi 2018

Slide 171

Slide 171 text

ৄղ Android Auto Receive voice input result from Android Auto class ReplyReceiver : BroadcastReceiver() { override fun onReceive(c: Context, i: Intent) { val conversationId = i.getIntExtra(“conversation_id”) val result = RemoteInput.getResultsFromIntent(i) result?.let { val text = it.getCharSequence(“key_voice_reply”) … // sending reply } } } 171 DroidKaigi 2018

Slide 172

Slide 172 text

ৄղ Android Auto Receive voice input result from Android Auto class ReplyReceiver : BroadcastReceiver() { override fun onReceive(c: Context, i: Intent) { val conversationId = i.getIntExtra(“conversation_id”) val result = RemoteInput.getResultsFromIntent(i) result?.let { val text = it.getCharSequence(“key_voice_reply”) … // sending reply } } } 172 DroidKaigi 2018

Slide 173

Slide 173 text

NOTIFICATION Overview - Messaging Feature Framework 173 ৄղ Android Auto DroidKaigi 2018 ?

Slide 174

Slide 174 text

NOTIFICATION Overview - Messaging Feature Framework 174 ৄղ Android Auto DroidKaigi 2018 NotificationListenerService

Slide 175

Slide 175 text

Special permission for accessing notifications 175 ৄղ Android Auto DroidKaigi 2018

Slide 176

Slide 176 text

Special permission for accessing notifications 176 ৄղ Android Auto DroidKaigi 2018

Slide 177

Slide 177 text

Special permission for accessing notifications 177 ৄղ Android Auto DroidKaigi 2018

Slide 178

Slide 178 text

NotificationListenerService 178 ৄղ Android Auto DroidKaigi 2018

Slide 179

Slide 179 text

NotificationListenerService 179 ৄղ Android Auto DroidKaigi 2018 NotificationManager NotificationManagerService NotificationListenerService IPC IPC

Slide 180

Slide 180 text

NotificationListenerService 180 ৄղ Android Auto DroidKaigi 2018 NotificationManager NotificationManagerService NotificationListenerService notify IPC IPC

Slide 181

Slide 181 text

NotificationListenerService 181 ৄղ Android Auto DroidKaigi 2018 NotificationManager NotificationManagerService NotificationListenerService enqueueNotificationWithTag IPC IPC

Slide 182

Slide 182 text

NotificationListenerService 182 ৄղ Android Auto DroidKaigi 2018 NotificationManager NotificationManagerService NotificationListenerService onNotificationPosted IPC IPC

Slide 183

Slide 183 text

ৄղ Android Auto Reading car-extended notification class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val n: Notification = sbn.getNotification() val extras: Bundle = n.extras val extensions: Bundle = extras.getBundle( “android.car.EXTENSIONS”) } } 183 DroidKaigi 2018

Slide 184

Slide 184 text

ৄղ Android Auto Reading car-extended notification class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val n: Notification = sbn.getNotification() val extras: Bundle = n.extras val extensions: Bundle = extras.getBundle( “android.car.EXTENSIONS”) } } 184 DroidKaigi 2018

Slide 185

Slide 185 text

ৄղ Android Auto Reading car-extended notification class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val n: Notification = sbn.getNotification() val extras: Bundle = n.extras val extensions: Bundle = extras.getBundle( “android.car.EXTENSIONS”) } } 185 DroidKaigi 2018

Slide 186

Slide 186 text

ৄղ Android Auto Reading car-extended notification class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val n: Notification = sbn.getNotification() val extras: Bundle = n.extras val extensions: Bundle = extras.getBundle( “android.car.EXTENSIONS”) } } 186 DroidKaigi 2018

Slide 187

Slide 187 text

ৄղ Android Auto Reading car-extended notification class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val n: Notification = sbn.getNotification() val extras: Bundle = n.extras val extensions: Bundle = extras.getBundle( “android.car.EXTENSIONS”) } } 187 DroidKaigi 2018

Slide 188

Slide 188 text

ৄղ Android Auto Reading car-extended notification class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val n: Notification = sbn.getNotification() val extras: Bundle = n.extras val extensions: Bundle = extras.getBundle( “android.car.EXTENSIONS”) } } 188 DroidKaigi 2018

Slide 189

Slide 189 text

ৄղ Android Auto Get UnreadConversation data class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val extensions: Bundle = // …… // UnreadConversation val conv: Bundle = extensions.getBundle( “car_conversation”) } } 189 DroidKaigi 2018

Slide 190

Slide 190 text

ৄղ Android Auto Get UnreadConversation data class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val conv: Bundle = // …… // Array of Bundles containing messages val messages: Array = conv.getParcelableArray(“messages”) } } 190 DroidKaigi 2018

Slide 191

Slide 191 text

ৄղ Android Auto Prepare for sending reply class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val conv: Bundle = // …… val messages: Array = // …… // Reply message container val input: RemoteInput = conv.getParcelable(“remote_input”) } } 191 DroidKaigi 2018

Slide 192

Slide 192 text

ৄղ Android Auto Prepare for sending reply class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val conv: Bundle = // …… val messages: Array = // …… val input: RemoteInput = // …… val replyPi: PendingIntent = conv.getParcelable(“on_reply”) } } 192 DroidKaigi 2018

Slide 193

Slide 193 text

ৄղ Android Auto Prepare for sending reply class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val conv: Bundle = // …… val messages: Array = // …… val input: RemoteInput = // …… val replyPi: PendingIntent = // …… val readPi: PendingIntent = conv.getParcelable(“on_read”) } } 193 DroidKaigi 2018

Slide 194

Slide 194 text

ৄղ Android Auto Reading incoming message texts class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val messages: Array = // …… val list: List = messages.filter { it is Bundle }.map { (it as Bundle).getString(“text”) } } } 194 DroidKaigi 2018

Slide 195

Slide 195 text

ৄղ Android Auto Reading incoming message texts class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val messages: Array = // …… val list: List = messages.filter { it is Bundle }.map { (it as Bundle).getString(“text”) } } } 195 DroidKaigi 2018

Slide 196

Slide 196 text

ৄղ Android Auto Reading incoming message texts class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val messages: Array = // …… val list: List = messages.filter { it is Bundle }.map { (it as Bundle).getString(“text”) } } } 196 DroidKaigi 2018

Slide 197

Slide 197 text

ৄղ Android Auto Finish reading texts class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val readPi: PendingIntent = // …… readPi.send() } } 197 DroidKaigi 2018

Slide 198

Slide 198 text

ৄղ Android Auto Sending reply message class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val replyMessage: String = “reply body” val input: RemoteInput = conv.getParcelable(“remote_input”) val key = input.getResultKey() val intent: Intent = Intent() val bundle: Bundle = Bundle() bundle.putString(key, replyMessage) } } 198 DroidKaigi 2018

Slide 199

Slide 199 text

ৄղ Android Auto Sending reply message class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val replyMessage: String = “reply body” val input: RemoteInput = conv.getParcelable(“remote_input”) val key = input.getResultKey() val intent: Intent = Intent() val bundle: Bundle = Bundle() bundle.putString(key, replyMessage) } } 199 DroidKaigi 2018

Slide 200

Slide 200 text

ৄղ Android Auto Sending reply message class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val replyMessage: String = “reply body” val input: RemoteInput = conv.getParcelable(“remote_input”) val key = input.getResultKey() val intent: Intent = Intent() val bundle: Bundle = Bundle() bundle.putString(key, replyMessage) } } 200 DroidKaigi 2018

Slide 201

Slide 201 text

ৄղ Android Auto Sending reply message class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val replyMessage: String = “reply body” val input: RemoteInput = conv.getParcelable(“remote_input”) val key = input.getResultKey() val intent: Intent = Intent() val bundle: Bundle = Bundle() bundle.putString(key, replyMessage) } } 201 DroidKaigi 2018

Slide 202

Slide 202 text

ৄղ Android Auto Sending reply message class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val key = input.getResultKey() val intent: Intent = Intent() val bundle: Bundle = Bundle() val replyInput: RemoteInput = RemoteInput.Builder(key).build() RemoteInput.addResultsToIntent( arrayOf(replyInput), intent, bundle) } } 202 DroidKaigi 2018

Slide 203

Slide 203 text

ৄղ Android Auto Sending reply message class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val key = input.getResultKey() val intent: Intent = Intent() val bundle: Bundle = Bundle() val replyInput: RemoteInput = RemoteInput.Builder(key).build() RemoteInput.addResultsToIntent( arrayOf(replyInput), intent, bundle) } } 203 DroidKaigi 2018

Slide 204

Slide 204 text

ৄղ Android Auto Sending reply message class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val key = input.getResultKey() val intent: Intent = Intent() val bundle: Bundle = Bundle() val replyInput: RemoteInput = RemoteInput.Builder(key).build() RemoteInput.addResultsToIntent( arrayOf(replyInput), intent, bundle) } } 204 DroidKaigi 2018

Slide 205

Slide 205 text

ৄղ Android Auto Sending reply message class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val key = input.getResultKey() val intent: Intent = Intent() val replyPi: PendingIntent = // …… replyPi.send(context, 0, intent) } } 205 DroidKaigi 2018

Slide 206

Slide 206 text

ৄղ Android Auto Sending reply message class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val key = input.getResultKey() val intent: Intent = Intent() val replyPi: PendingIntent = // …… replyPi.send(context, 0, intent) } } 206 DroidKaigi 2018

Slide 207

Slide 207 text

ৄղ Android Auto Sending reply message class MyListener : NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val key = input.getResultKey() val intent: Intent = Intent() val replyPi: PendingIntent = // …… replyPi.send(context, 0, intent) } } 207 DroidKaigi 2018

Slide 208

Slide 208 text

ৄղ Android Auto Wrap up: Messaging Feature Framework 208 SomeMessagingModel Notification ListenerService ReplyReceiver DroidKaigi 2018

Slide 209

Slide 209 text

ৄղ Android Auto Wrap up: Messaging Feature Framework 209 SomeMessagingModel Notification ListenerService ReplyReceiver Notification with PendingIntent DroidKaigi 2018

Slide 210

Slide 210 text

ৄղ Android Auto Wrap up: Messaging Feature Framework 210 SomeMessagingModel Notification ListenerService ReplyReceiver PendingIntent#send DroidKaigi 2018

Slide 211

Slide 211 text

ৄղ Android Auto Wrap up: Messaging Feature Framework 211 SomeMessagingModel Notification ListenerService ReplyReceiver Send reply message DroidKaigi 2018

Slide 212

Slide 212 text

ৄղ Android Auto Wrap up: Messaging Feature Framework 212 SomeMessagingModel Notification ListenerService ReadReceiver DroidKaigi 2018

Slide 213

Slide 213 text

ৄղ Android Auto Wrap up: Messaging Feature Framework 213 SomeMessagingModel Notification ListenerService ReadReceiver Notification with PendingIntent DroidKaigi 2018

Slide 214

Slide 214 text

ৄղ Android Auto Wrap up: Messaging Feature Framework 214 SomeMessagingModel Notification ListenerService ReadReceiver PendingIntent#send DroidKaigi 2018

Slide 215

Slide 215 text

ৄղ Android Auto Wrap up: Messaging Feature Framework 215 SomeMessagingModel Notification ListenerService ReadReceiver Set read flag, etc… DroidKaigi 2018

Slide 216

Slide 216 text

Wrap up DroidKaigi 2018

Slide 217

Slide 217 text

Wrap up: Overview 217 ৄղ Android Auto DroidKaigi 2018

Slide 218

Slide 218 text

Wrap up: Media playback control 218 ৄղ Android Auto IPC with MediaSession / MediaController DroidKaigi 2018

Slide 219

Slide 219 text

Wrap up: Media browsing 219 ৄղ Android Auto Callback to MediaBrowser Bind to MediaBrowserService DroidKaigi 2018

Slide 220

Slide 220 text

Wrap up: Media browsing 220 ৄղ Android Auto Callback to MediaBrowser Bind to MediaBrowserService DroidKaigi 2018

Slide 221

Slide 221 text

Wrap up: Media browsing 221 ৄղ Android Auto Callback to MediaBrowser Bind to MediaBrowserService DroidKaigi 2018

Slide 222

Slide 222 text

Wrap up: Incoming message 222 ৄղ Android Auto Notification DroidKaigi 2018

Slide 223

Slide 223 text

Wrap up: Incoming message 223 ৄղ Android Auto Notification Notification DroidKaigi 2018

Slide 224

Slide 224 text

Wrap up: Replying to a message 224 ৄղ Android Auto Broadcast by PendingIntent DroidKaigi 2018

Slide 225

Slide 225 text

Wrap up: Behind the scenes of Android Auto ▸ Interprocess Communication(IPC) ▸ The Binder ▸ Android Auto is based on the wrapped classes ▸ MediaSession and MediaController ▸ MediaBrowser and MediaBrowserService ▸ Google Assistant is also based on these classes! 225 ৄղ Android Auto DroidKaigi 2018

Slide 226

Slide 226 text

Wrap up: References ▸ More about IPC in Android ▸ Inside Binder ▸ https://www.slideshare.net/l_b__/binderandroid ▸ Deep Dive into Android IPC/Binder Framework ▸ Android Binder 226 ৄղ Android Auto DroidKaigi 2018

Slide 227

Slide 227 text

Wrap up: References ▸ API Docs: Messaging features ▸ Notification.CarExtender ▸ Notification.CarExtender.UnreadConversation ▸ NotificationListenerService 227 ৄղ Android Auto DroidKaigi 2018

Slide 228

Slide 228 text

Wrap up: References ▸ API Docs: Media features ▸ Notification.MediaStyle ▸ MediaBrowser and MediaBrowserService ▸ MediaSession and MediaController ▸ MediaSessionManager 228 ৄղ Android Auto DroidKaigi 2018

Slide 229

Slide 229 text

Wrap up: Sample Project ▸ Media Browser sample ▸ github/googlesamples/MediaBrowserService ▸ github/googlesamples/UniversalMusicPlayer ▸ TechBooster ▸ C90ΞϯυϩΠυΞΧσϛΞ for Audio Framework ▸ C91ͳͳ͍ΖAndroid for Messaging Framework 229 ৄղ Android Auto DroidKaigi 2018

Slide 230

Slide 230 text

ৄղ Android Auto - ࢖͍ํ͔ΒͦΕΛࢧ͑Δٕज़·Ͱ - Keishin Yokomaku / DroidKaigi 2018

Slide 231

Slide 231 text

We are hiring! 231 Drivemode DroidKaigi 2018