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
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
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
ৄղ 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
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
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
ৄղ 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
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