1 /*
<lambda>null2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.systemui.mediaprojection.taskswitcher.ui
18
19 import android.app.ActivityManager.RunningTaskInfo
20 import android.app.Notification
21 import android.app.NotificationManager
22 import android.app.PendingIntent
23 import android.content.Context
24 import android.content.Intent
25 import android.content.IntentFilter
26 import android.os.Parcelable
27 import android.util.Log
28 import com.android.systemui.broadcast.BroadcastDispatcher
29 import com.android.systemui.dagger.SysUISingleton
30 import com.android.systemui.dagger.qualifiers.Application
31 import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.NotShowing
32 import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.Showing
33 import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
34 import com.android.systemui.res.R
35 import com.android.systemui.util.NotificationChannels
36 import javax.inject.Inject
37 import kotlinx.coroutines.CoroutineScope
38 import kotlinx.coroutines.launch
39
40 /** Coordinator responsible for showing/hiding the task switcher notification. */
41 @SysUISingleton
42 class TaskSwitcherNotificationCoordinator
43 @Inject
44 constructor(
45 private val context: Context,
46 private val notificationManager: NotificationManager,
47 @Application private val applicationScope: CoroutineScope,
48 private val viewModel: TaskSwitcherNotificationViewModel,
49 private val broadcastDispatcher: BroadcastDispatcher,
50 ) {
51
52 fun start() {
53 applicationScope.launch {
54 launch {
55 viewModel.uiState.collect { uiState ->
56 Log.d(TAG, "uiState -> $uiState")
57 when (uiState) {
58 is Showing -> showNotification(uiState)
59 is NotShowing -> hideNotification()
60 }
61 }
62 }
63 launch {
64 broadcastDispatcher
65 .broadcastFlow(IntentFilter(SWITCH_ACTION)) { intent, _ ->
66 intent.requireParcelableExtra<RunningTaskInfo>(EXTRA_ACTION_TASK)
67 }
68 .collect { task: RunningTaskInfo ->
69 Log.d(TAG, "Switch action triggered: $task")
70 viewModel.onSwitchTaskClicked(task)
71 }
72 }
73 launch {
74 broadcastDispatcher
75 .broadcastFlow(IntentFilter(GO_BACK_ACTION)) { intent, _ ->
76 intent.requireParcelableExtra<RunningTaskInfo>(EXTRA_ACTION_TASK)
77 }
78 .collect { task ->
79 Log.d(TAG, "Go back action triggered: $task")
80 viewModel.onGoBackToTaskClicked(task)
81 }
82 }
83 }
84 }
85
86 private fun showNotification(uiState: Showing) {
87 notificationManager.notify(TAG, NOTIFICATION_ID, createNotification(uiState))
88 }
89
90 private fun createNotification(uiState: Showing): Notification {
91 val actionSwitch =
92 Notification.Action.Builder(
93 /* icon = */ null,
94 context.getString(R.string.media_projection_task_switcher_action_switch),
95 createActionPendingIntent(action = SWITCH_ACTION, task = uiState.foregroundTask)
96 )
97 .build()
98
99 val actionBack =
100 Notification.Action.Builder(
101 /* icon = */ null,
102 context.getString(R.string.media_projection_task_switcher_action_back),
103 createActionPendingIntent(action = GO_BACK_ACTION, task = uiState.projectedTask)
104 )
105 .build()
106 return Notification.Builder(context, NotificationChannels.ALERTS)
107 .setSmallIcon(R.drawable.qs_screen_record_icon_on)
108 .setAutoCancel(true)
109 .setContentText(context.getString(R.string.media_projection_task_switcher_text))
110 .addAction(actionSwitch)
111 .addAction(actionBack)
112 .build()
113 }
114
115 private fun hideNotification() {
116 notificationManager.cancel(TAG, NOTIFICATION_ID)
117 }
118
119 private fun createActionPendingIntent(action: String, task: RunningTaskInfo) =
120 PendingIntent.getBroadcast(
121 context,
122 /* requestCode= */ 0,
123 Intent(action).apply { putExtra(EXTRA_ACTION_TASK, task) },
124 /* flags= */ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
125 )
126
127 companion object {
128 private const val TAG = "TaskSwitchNotifCoord"
129 private const val NOTIFICATION_ID = 5566
130
131 private const val EXTRA_ACTION_TASK = "extra_task"
132
133 private const val SWITCH_ACTION = "com.android.systemui.mediaprojection.SWITCH_TASK"
134 private const val GO_BACK_ACTION = "com.android.systemui.mediaprojection.GO_BACK"
135 }
136 }
137
requireParcelableExtranull138 private fun <T : Parcelable> Intent.requireParcelableExtra(key: String) =
139 getParcelableExtra<T>(key)!!
140