1 /*
2  * 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.domain.interactor
18 
19 import android.app.TaskInfo
20 import android.content.Intent
21 import android.util.Log
22 import com.android.systemui.dagger.SysUISingleton
23 import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
24 import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionRepository
25 import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository
26 import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
27 import javax.inject.Inject
28 import kotlinx.coroutines.ExperimentalCoroutinesApi
29 import kotlinx.coroutines.flow.Flow
30 import kotlinx.coroutines.flow.flatMapLatest
31 import kotlinx.coroutines.flow.flowOf
32 import kotlinx.coroutines.flow.map
33 
34 /** Interactor with logic related to task switching in the context of media projection. */
35 @OptIn(ExperimentalCoroutinesApi::class)
36 @SysUISingleton
37 class TaskSwitchInteractor
38 @Inject
39 constructor(
40     mediaProjectionRepository: MediaProjectionRepository,
41     private val tasksRepository: TasksRepository,
42 ) {
43 
44     /**
45      * Emits a stream of changes to the state of task switching, in the context of media projection.
46      */
47     val taskSwitchChanges: Flow<TaskSwitchState> =
48         mediaProjectionRepository.mediaProjectionState.flatMapLatest { projectionState ->
49             Log.d(TAG, "MediaProjectionState -> $projectionState")
50             when (projectionState) {
51                 is MediaProjectionState.SingleTask -> {
52                     val projectedTask = projectionState.task
53                     tasksRepository.foregroundTask.map { foregroundTask ->
54                         if (hasForegroundTaskSwitched(projectedTask, foregroundTask)) {
55                             TaskSwitchState.TaskSwitched(projectedTask, foregroundTask)
56                         } else {
57                             TaskSwitchState.TaskUnchanged
58                         }
59                     }
60                 }
61                 is MediaProjectionState.EntireScreen,
62                 is MediaProjectionState.NotProjecting -> {
63                     flowOf(TaskSwitchState.NotProjectingTask)
64                 }
65             }
66         }
67 
68     /**
69      * Returns whether tasks have been switched.
70      *
71      * Always returns `false` when launcher is in the foreground. The reason is that when going to
72      * recents to switch apps, launcher becomes the new foreground task, and we don't want to show
73      * the notification then.
74      */
75     private fun hasForegroundTaskSwitched(projectedTask: TaskInfo, foregroundTask: TaskInfo) =
76         projectedTask.taskId != foregroundTask.taskId && !foregroundTask.isLauncher
77 
78     private val TaskInfo.isLauncher
79         get() =
80             baseIntent.hasCategory(Intent.CATEGORY_HOME) && baseIntent.action == Intent.ACTION_MAIN
81 
82     companion object {
83         private const val TAG = "TaskSwitchInteractor"
84     }
85 }
86