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

詳解 Android Auto - 使い方からそれを支える技術まで -

詳解 Android Auto - 使い方からそれを支える技術まで -

Keishin Yokomaku

February 09, 2018
Tweet

More Decks by Keishin Yokomaku

Other Decks in Technology

Transcript

  1. Keishin Yokomaku 2 ৄղ Android Auto Drivemode, Inc / Engineer

    @KeithYokoma: GitHub / Qiita / Twitter DroidKaigi 2018
  2. Chapters in this talk ▸ Audio Freature Framework ▸ Messaging

    Feature Framework ▸ Wrap-up 10 ৄղ Android Auto DroidKaigi 2018
  3. 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
  4. MediaSession in media app 16 ৄղ Android Auto DroidKaigi 2018

    MediaSession MediaController MediaSession.Token
  5. Playback control flow 17 ৄղ Android Auto DroidKaigi 2018 MediaSession

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

    MediaController MediaPlayer MS.Callback MC.TransportControls
  7. IPC between media app and AndroidAuto 20 ৄղ Android Auto

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

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

    DroidKaigi 2018 MediaSession MediaController MS.Callback MC.TransportControls
  10. ৄղ 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
  11. ৄղ 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
  12. ৄղ 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
  13. ৄղ 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
  14. IPC between media app and AndroidAuto 27 ৄղ Android Auto

    DroidKaigi 2018 MediaSession MediaController MediaSession.Token
  15. ৄղ 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
  16. ৄղ 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
  17. ৄղ 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
  18. ৄղ 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
  19. ৄղ 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
  20. ৄղ 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
  21. ৄղ 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
  22. ৄղ 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
  23. ৄղ 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
  24. ৄղ 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
  25. ৄղ 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
  26. ৄղ 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
  27. ৄղ 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
  28. ৄղ 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
  29. ৄղ 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
  30. ৄղ 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
  31. ৄղ 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
  32. ৄղ 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
  33. ৄղ 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
  34. Activate MediaSession 50 ৄղ Android Auto DroidKaigi 2018 MediaSession MediaSessionManager

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

    MediaSessionService MediaSessionRecord MediaSessionRecord …… MediaSessionRecord
  36. Prepare IPC via MediaController 52 ৄղ Android Auto DroidKaigi 2018

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

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

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

    MediaSession MediaSession.Token ISessionController MediaController ISessionController
  40. Playback control via IPC 56 ৄղ Android Auto DroidKaigi 2018

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

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

    MediaSession MediaSession.Token ISessionController MediaController ISessionController TransportControls play(), pause(), … play(), pause(), MS.Callback onPlay(), …
  43. 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!
  44. What if the media app is dead…? 60 ৄղ Android

    Auto Fatal exception: DeadObjectException DroidKaigi 2018
  45. When remote process is dead 61 ৄղ Android Auto DroidKaigi

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

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

    2018 MediaSession MediaSession.Token ISessionController MediaController ISessionController Process is dead! Callback onSessionDestroy()
  48. ৄղ 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
  49. ৄղ 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
  50. ৄղ 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
  51. 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
  52. ৄղ 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
  53. ৄղ 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
  54. ৄղ 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
  55. ৄղ 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
  56. Playback control IPC on Lock Screen in 4.x 72 ৄղ

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

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

    Android Auto DroidKaigi 2018 RemoteControlClient AudioManager AudioService RemoteControlDisplay LockScreen
  59. Playback control IPC on Lock Screen in 4.4 75 ৄղ

    Android Auto DroidKaigi 2018 RemoteControlClient AudioManager AudioService RemoteController LockScreen NotificationListener Service
  60. Playback control IPC on Lock Screen in 5+ 76 ৄղ

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

    Android Auto DroidKaigi 2018 MediaSession NotificationManager Service MediaController LockScreen MediaStyle Notification NotificationListener Service
  62. Get active sessions for the package 78 ৄղ Android Auto

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

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

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

    DroidKaigi 2018 MediaSessionManager MediaSessionService MediaSessionRecord MediaSessionRecord …… MediaController
  66. ৄղ 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<MediaController> = manager.getActiveSessions(null) // do whatever you want with media controller… } } 82 DroidKaigi 2018
  67. ৄղ 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<MediaController> = manager.getActiveSessions(null) // do whatever you want with media controller… } } 83 DroidKaigi 2018
  68. ৄղ 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<MediaController> = manager.getActiveSessions(null) // do whatever you want with media controller… } } 84 DroidKaigi 2018
  69. ৄղ 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<MediaController> = manager.getActiveSessions(null) // do whatever you want with media controller… } } 85 DroidKaigi 2018
  70. ৄղ 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
  71. ৄղ 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
  72. ৄղ 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
  73. ৄղ 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
  74. ৄղ 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
  75. ৄղ 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
  76. ৄղ 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
  77. Connection verification by 97 ৄղ Android Auto ✔Verify signature of

    the client ✔Verify the client package name DroidKaigi 2018 ✔Verify the client process user id
  78. No Connection verification flow 99 ৄղ Android Auto DroidKaigi 2018

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

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

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

    access? Client has valid signature? Refuse Allow YES No
  82. ৄղ Android Auto List of allowed client packages and signatures

    <?xml version=“1.0” encoding=“utf-8”?> <allowed_callers> <signing_certificate name=“Android Auto” release=“false” package=“com.google.android.projection.gearhead” > Base64 encoded signature here </signing_certificate> </allowed_callers> 103 DroidKaigi 2018
  83. ৄղ Android Auto List of allowed client packages and signatures

    <?xml version=“1.0” encoding=“utf-8”?> <allowed_callers> <signing_certificate name=“Android Auto” release=“false” package=“com.google.android.projection.gearhead” > Base64 encoded signature here </signing_certificate> </allowed_callers> 104 DroidKaigi 2018
  84. ৄղ Android Auto List of allowed client packages and signatures

    <?xml version=“1.0” encoding=“utf-8”?> <allowed_callers> <signing_certificate name=“Android Auto” release=“false” package=“com.google.android.projection.gearhead” > Base64 encoded signature here </signing_certificate> </allowed_callers> 105 DroidKaigi 2018
  85. ৄղ Android Auto Generate your app signature $ keytool -export

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

    -alias androiddebugkey -keystore ~/.android/debug.keystore | base64 Password: xxxx Base64 encoded signature 107 DroidKaigi 2018
  87. ৄղ 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
  88. ৄղ Android Auto Media contents structure 109 DroidKaigi 2018 __ROOT__

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

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

    __FAVORITE__ __RECOMMEND__ __SUBSCRIPTION__ __FAVORITE__/1 __FAVORITE__/2 MediaItem BROWSABLE MediaItem PLAYABLE
  91. Subscribe media content 113 ৄղ Android Auto DroidKaigi 2018 MediaBrowserService

    MediaBrowser MB.SubscriptionCallback# onChildrenLoaded()
  92. ৄղ 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
  93. ৄղ 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
  94. ৄղ 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
  95. ৄղ 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<MediaItem>) { // show media list under the parentId item } } } 117 DroidKaigi 2018
  96. ৄղ 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<MediaItem>) { // show media list under the parentId item } } } 118 DroidKaigi 2018
  97. ৄղ Android Auto On select item: Load the next list

    119 DroidKaigi 2018 __ROOT__ __FAVORITE__ __RECOMMEND__ __SUBSCRIPTION__ __FAVORITE__/1 __FAVORITE__/2 MediaItem BROWSABLE
  98. ৄղ Android Auto On select item: Start playing media content

    120 DroidKaigi 2018 __ROOT__ __FAVORITE__ __RECOMMEND__ __SUBSCRIPTION__ __FAVORITE__/1 __FAVORITE__/2 MediaItem PLAYABLE
  99. ৄղ Android Auto Load media items synchronously class BrowserService :

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

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

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

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

    MediaBrowserServiceCompat() { override fun onLoadChildren( parentId: String, result: Result<List<MediaItem>>) { result.detach() Single.create { val list: List<MediaItem> = // … it.onSuccess(list) }.subscribeOn(schedulers.io).subscribe { list -> result.sendResult(list) } } } 125 DroidKaigi 2018
  104. ৄղ 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
  105. ৄղ 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
  106. ৄղ 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
  107. ৄղ 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
  108. ৄղ 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
  109. ৄղ 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
  110. ৄղ 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<MediaItem>) { // show query result to the list! } } fun onSearchItem(query: String) { browser.search(query, Bundle(), qcb) } } 132 DroidKaigi 2018
  111. ৄղ 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<MediaItem>) { // show query result to the list! } } fun onSearchItem(query: String) { browser.search(query, Bundle(), qcb) } } 133 DroidKaigi 2018
  112. ৄղ 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<MediaItem>) { // show query result to the list! } } fun onSearchItem(query: String) { browser.search(query, Bundle(), qcb) } } 134 DroidKaigi 2018
  113. ৄղ Android Auto Handle search query synchronously class BrowserService :

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

    MediaBrowserServiceCompat() { override fun onSearch( query: String, extras: Bundle, result: Result<List<MediaItem>>) { result.detach() Single.create { val list: List<MediaItem> = // … it.onSuccess(list) }.subscribeOn(schedulers.io).subscribe { list -> result.sendResult(list) } } } 136 DroidKaigi 2018
  115. Wrap up: Audio Feature Framework 137 ৄղ Android Auto DroidKaigi

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

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

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

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

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

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

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

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

    2018 MediaBrowserService MediaBrowser MediaController MediaSession MediaPlayer Actual playback control
  124. 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
  125. NOTIFICATION Overview - Messaging Feature Framework 150 ৄղ Android Auto

    DroidKaigi 2018 NotificationManager#notify(id, n)
  126. Message Notification for Android Auto 151 ৄղ Android Auto DroidKaigi

    2018 NotificationCompat.CarExtender Visual settings(color and icon) Unread/Read status Voice input for reply
  127. ৄղ 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
  128. ৄղ 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
  129. ৄղ 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
  130. ৄղ 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
  131. ৄղ 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
  132. ৄղ 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
  133. ৄղ 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
  134. ৄղ 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
  135. ৄղ 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
  136. ৄղ 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
  137. ৄղ 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
  138. ৄղ 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
  139. ৄղ 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
  140. ৄղ 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
  141. ৄղ 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
  142. ৄղ 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
  143. ৄղ 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
  144. ৄղ 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
  145. ৄղ 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
  146. ৄղ 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
  147. ৄղ 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
  148. ৄղ 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
  149. ৄղ 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
  150. ৄղ 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
  151. ৄղ 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
  152. ৄղ 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
  153. ৄղ 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
  154. ৄղ 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
  155. ৄղ Android Auto Get UnreadConversation data class MyListener : NotificationListenerService

    { override fun onNotificationPosted( sbn: StatusBarNotification) { val conv: Bundle = // …… // Array of Bundles containing messages val messages: Array<Parcelable> = conv.getParcelableArray(“messages”) } } 190 DroidKaigi 2018
  156. ৄղ Android Auto Prepare for sending reply class MyListener :

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

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

    NotificationListenerService { override fun onNotificationPosted( sbn: StatusBarNotification) { val conv: Bundle = // …… val messages: Array<Parcelable> = // …… val input: RemoteInput = // …… val replyPi: PendingIntent = // …… val readPi: PendingIntent = conv.getParcelable(“on_read”) } } 193 DroidKaigi 2018
  159. ৄղ Android Auto Reading incoming message texts class MyListener :

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

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

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

    { override fun onNotificationPosted( sbn: StatusBarNotification) { val readPi: PendingIntent = // …… readPi.send() } } 197 DroidKaigi 2018
  163. ৄղ 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
  164. ৄղ 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
  165. ৄղ 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
  166. ৄղ 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
  167. ৄղ 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
  168. ৄղ 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
  169. ৄղ 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
  170. ৄղ 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
  171. ৄղ 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
  172. ৄղ 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
  173. ৄղ Android Auto Wrap up: Messaging Feature Framework 208 SomeMessagingModel

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

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

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

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

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

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

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

    Notification ListenerService ReadReceiver Set read flag, etc… DroidKaigi 2018
  181. Wrap up: Media playback control 218 ৄղ Android Auto IPC

    with MediaSession / MediaController DroidKaigi 2018
  182. Wrap up: Media browsing 219 ৄղ Android Auto Callback to

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

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

    MediaBrowser Bind to MediaBrowserService DroidKaigi 2018
  185. Wrap up: Replying to a message 224 ৄղ Android Auto

    Broadcast by PendingIntent DroidKaigi 2018
  186. 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
  187. 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
  188. Wrap up: References ▸ API Docs: Messaging features ▸ Notification.CarExtender

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

    ▸ MediaBrowser and MediaBrowserService ▸ MediaSession and MediaController ▸ MediaSessionManager 228 ৄղ Android Auto DroidKaigi 2018
  190. 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