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