AndroidのMediaStyle NotificationをMedia以外の目的で使う
公開日: 2020年11月22日最終更新日: 2022年01月28日
AndroidのNotificationにはMediaStyleがあります。
これはNotificationにボタンを置いてMediaの再生をコントロールすることができるものですが、別にMediaSessionと組み合わせなければいけないというわけではありません。
Media用途以外でもサービスの状態(スタート、ストップやモード切替など)をコントロールするために使うことができます。
今回はMediaStyleで作ったNotificationのactionで発行されるIntentを受信してServiceの状態を切り替えます。サンプルコードはhttps://github.com/Gan0803/MediaNotoficationStudyにあります。
サンプルのアプリは起動するとForeground Serviceを作成し、Notificationを表示します。
Notificationにはボタンが1つあり、タップすることで2つの状態(走る、止まる)を切り替えます。
Implementation
最初に、androidx.media.app.NotificationCompat
を使うため、appのbuild.gradleのdependenciesにimplementation "androidx.media:media:1.1.0"
を追加します。
そして、androidx.core.app.NotificationCompat
とandroidx.media.app.NotificationCompat
は名前が重複しているため、import時にas MediaNotificationCompat
と別名を付けて区別します。
Notificationを作るときはsetStyle()
でMediaStyle()
を指定します。NotificationにセットするIntentにはMyReceiverのAction
を設定します。
MyNotificationBuilder.kt
...
import androidx.core.app.NotificationCompat
import androidx.media.app.NotificationCompat as MediaNotificationCompat
class MyNotificationBuilder {
fun build(context: Context, isRunning: Boolean, channelId: String): Notification {
return NotificationCompat.Builder(context, channelId)
.setContentTitle("Notification Study")
.setContentText("Application is active.")
.setSmallIcon(R.drawable.ic_baseline_notifications_24)
.setContentIntent(createPendingIntent(context))
.setTicker("Application is active")
.addAction(createMyAction(context, isRunning))
.setStyle(
MediaNotificationCompat.MediaStyle()
.setShowActionsInCompactView(0)
)
.build()
}
private fun createPendingIntent(context: Context): PendingIntent {
return Intent(context, MainActivity::class.java).let { notificationIntent ->
PendingIntent.getActivity(context, 0, notificationIntent, 0)
}
}
private fun createMyAction(context: Context, isRunning: Boolean): NotificationCompat.Action {
return if (isRunning) {
val pauseIntent = Intent().apply {
action = MyReceiver.ACTION_STOP
}
NotificationCompat.Action(
R.drawable.ic_baseline_directions_run_24,
"Run",
PendingIntent.getBroadcast(context, 0, pauseIntent, 0)
)
} else {
val playIntent = Intent().apply {
action = MyReceiver.ACTION_RUN
}
NotificationCompat.Action(
R.drawable.ic_baseline_emoji_people_24,
"Stop",
PendingIntent.getBroadcast(context, 0, playIntent, 0)
)
}
}
}
Notificationのactionを実行したときに発行されるBroadcast actionをBroadcastReceiverで受け取ります。 このあたりはdevelopersのブロードキャストの受信に説明があります。 そして、受け取ったインテントに応じてコールバックを呼び出します。
MyReceiver.kt
class MyReceiver : BroadcastReceiver() {
companion object {
val TAG: String = this::class.java.simpleName
const val ACTION_STOP = "gan0803.pj.study.medianotificationstudy.action.ACTION_STOP"
const val ACTION_RUN = "gan0803.pj.study.medianotificationstudy.action.ACTION_RUN"
}
private var callback: IMyCallback? = null
override fun onReceive(context: Context?, intent: Intent?) {
val action = intent?.action
Log.d(TAG, "onReceive, action: {$action}")
when (action) {
ACTION_RUN -> {
callback?.onReceiveRun()
}
ACTION_STOP -> {
callback?.onReceiveStop()
}
}
}
fun registerCallback(callback: IMyCallback) {
this.callback = callback
}
interface IMyCallback {
fun onReceiveRun()
fun onReceiveStop()
}
}
サービスではBroadcastReceiverをapplicationのContextで登録し、受け取ったときに呼び出してほしいコールバックも登録しています。これでNotificationからサービスまでが繋がります。
そしてコールバックを受けたときに、NotificationManagerCompat.notify()
を呼んでNotificationを更新します(ボタンのアイコンとIntentのアクションを状態に合わせてt変更しています)。
https://developer.android.com/training/notify-user/build-notification?hl=ja#Updating
MyService.kt
class MyService : Service(), MyReceiver.IMyCallback {
...
private lateinit var myReceiver: MyReceiver
private var isRunning = true
...
private fun init() {
myReceiver = MyReceiver()
myReceiver.registerCallback(this)
val filter = IntentFilter().apply {
addAction(MyReceiver.ACTION_RUN)
addAction(MyReceiver.ACTION_STOP)
}
application.registerReceiver(myReceiver, filter)
}
override fun onCreate() {
super.onCreate()
init()
}
...
override fun onDestroy() {
application.unregisterReceiver(myReceiver)
super.onDestroy()
}
...
private fun buildNotification(): Notification {
return MyNotificationBuilder().build(this, isRunning, channelId)
}
override fun onReceiveRun() {
Log.d(TAG, "onReceivePlay")
isRunning = true
// notificationを更新
with(NotificationManagerCompat.from(this)) {
notify(ONGOING_NOTIFICATION_ID, buildNotification())
}
}
override fun onReceiveStop() {
Log.d(TAG, "onReceivePause")
isRunning = false
// notificationを更新
with(NotificationManagerCompat.from(this)) {
notify(ONGOING_NOTIFICATION_ID, buildNotification())
}
}
}