• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2022 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.appselector.view
18 
19 import android.app.ActivityOptions
20 import android.app.ActivityOptions.LaunchCookie
21 import android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
22 import android.app.IActivityTaskManager
23 import android.graphics.Rect
24 import android.view.LayoutInflater
25 import android.view.View
26 import android.view.ViewGroup
27 import android.window.RemoteTransition
28 import androidx.recyclerview.widget.LinearLayoutManager
29 import androidx.recyclerview.widget.RecyclerView
30 import com.android.systemui.Flags.pssAppSelectorRecentsSplitScreen
31 import com.android.systemui.display.naturalBounds
32 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
33 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
34 import com.android.systemui.mediaprojection.appselector.data.RecentTask
35 import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter.RecentTaskClickListener
36 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
37 import com.android.systemui.res.R
38 import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration
39 import com.android.wm.shell.shared.split.SplitBounds
40 import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
41 import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
42 import com.android.wm.shell.splitscreen.SplitScreen
43 import java.util.Optional
44 import javax.inject.Inject
45 
46 /**
47  * Controller that handles view of the recent apps selector in the media projection activity. It is
48  * responsible for creating and updating recent apps view.
49  */
50 @MediaProjectionAppSelectorScope
51 class MediaProjectionRecentsViewController
52 @Inject
53 constructor(
54     private val recentTasksAdapterFactory: RecentTasksAdapter.Factory,
55     private val taskViewSizeProvider: TaskPreviewSizeProvider,
56     private val activityTaskManager: IActivityTaskManager,
57     private val resultHandler: MediaProjectionAppSelectorResultHandler,
58     private val splitScreen: Optional<SplitScreen>,
59 ) : RecentTaskClickListener, TaskPreviewSizeListener {
60 
61     private var views: Views? = null
62     private var lastBoundData: List<RecentTask>? = null
63 
64     val hasRecentTasks: Boolean
65         get() = lastBoundData?.isNotEmpty() ?: false
66 
67     init {
68         taskViewSizeProvider.addCallback(this)
69     }
70 
71     fun createView(parent: ViewGroup): ViewGroup =
72         views?.root
73             ?: createRecentViews(parent)
74                     .also {
75                         views = it
76                         lastBoundData?.let { recents -> bind(recents) }
77                     }
78                     .root
79 
80     fun bind(recentTasks: List<RecentTask>) {
81         views?.apply {
82             if (recentTasks.isEmpty()) {
83                 root.visibility = View.GONE
84                 return
85             }
86 
87             progress.visibility = View.GONE
88             recycler.visibility = View.VISIBLE
89             root.visibility = View.VISIBLE
90 
91             recycler.adapter =
92                 recentTasksAdapterFactory.create(
93                     recentTasks,
94                     this@MediaProjectionRecentsViewController
95                 )
96         }
97 
98         lastBoundData = recentTasks
99     }
100 
101     private fun createRecentViews(parent: ViewGroup): Views {
102         val recentsRoot =
103             LayoutInflater.from(parent.context)
104                     .inflate(R.layout.media_projection_recent_tasks,
105                         parent, /* attachToRoot= */
106                         false)
107                     as ViewGroup
108 
109         val container =
110             recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_container)
111         container.setTaskHeightSize()
112 
113         val progress = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_loader)
114         val recycler =
115             recentsRoot.requireViewById<RecyclerView>(R.id.media_projection_recent_tasks_recycler)
116         recycler.layoutManager =
117             LinearLayoutManager(
118                 parent.context,
119                 LinearLayoutManager.HORIZONTAL,
120                 /* reverseLayout= */ false
121             )
122 
123         val itemDecoration =
124             HorizontalSpacerItemDecoration(
125                 parent.resources.getDimensionPixelOffset(
126                     R.dimen.media_projection_app_selector_recents_padding
127                 )
128             )
129         recycler.addItemDecoration(itemDecoration)
130 
131         return Views(recentsRoot, container, progress, recycler)
132     }
133 
134     private fun RecentTask.isLaunchingInSplitScreen(): Boolean {
135         return splitScreen.isPresent && splitBounds != null
136     }
137 
138     override fun onRecentAppClicked(task: RecentTask, view: View) {
139         val launchCookie = LaunchCookie()
140         val activityOptions = createAnimation(task, view)
141         activityOptions.pendingIntentBackgroundActivityStartMode =
142             MODE_BACKGROUND_ACTIVITY_START_ALLOWED
143         activityOptions.launchDisplayId = task.displayId
144         activityOptions.setLaunchCookie(launchCookie)
145 
146         val taskId = task.taskId
147         val splitBounds = task.splitBounds
148         val handleResult: () -> Unit = { resultHandler.returnSelectedApp(launchCookie, taskId)}
149 
150         if (pssAppSelectorRecentsSplitScreen() &&
151             task.isLaunchingInSplitScreen() &&
152             !task.isForegroundTask) {
153             startSplitScreenTask(view, taskId, splitBounds!!, handleResult, activityOptions)
154         } else {
155             activityTaskManager.startActivityFromRecents(taskId, activityOptions.toBundle())
156             handleResult()
157         }
158     }
159 
160 
161     private fun createAnimation(task: RecentTask, view: View): ActivityOptions =
162         if (task.isForegroundTask) {
163             // When the selected task is in the foreground, the scale up animation doesn't work.
164             // We fallback to the default close animation.
165             ActivityOptions.makeCustomTaskAnimation(
166                 view.context,
167                 /* enterResId= */ 0,
168                 /* exitResId= */ com.android.internal.R.anim.resolver_close_anim,
169                 /* handler = */ null,
170                 /* startedListener = */ null,
171                 /* finishedListener = */ null
172             )
173         } else if (task.isLaunchingInSplitScreen()) {
174             // When the selected task isn't in the foreground, but is launching in split screen,
175             // then we don't need to specify an animation, since we'll already be passing a
176             // manually built remote animation to SplitScreenController
177             ActivityOptions.makeBasic()
178         } else {
179             // The default case is a selected task not in the foreground and launching fullscreen,
180             // so for this we can use the default ActivityOptions animation
181             ActivityOptions.makeScaleUpAnimation(
182                 view,
183                 /* startX= */ 0,
184                 /* startY= */ 0,
185                 view.width,
186                 view.height
187             )
188         }
189 
190     private fun startSplitScreenTask(
191         view: View,
192         taskId: Int,
193         splitBounds: SplitBounds,
194         handleResult: () -> Unit,
195         activityOptions: ActivityOptions,
196     ) {
197         val isLeftTopTask = taskId == splitBounds.leftTopTaskId
198         val task2Id =
199             if (isLeftTopTask) splitBounds.rightBottomTaskId else splitBounds.leftTopTaskId
200         val splitPosition =
201             if (isLeftTopTask) SPLIT_POSITION_TOP_OR_LEFT else SPLIT_POSITION_BOTTOM_OR_RIGHT
202 
203         val animationRunner = RemoteRecentSplitTaskTransitionRunner(taskId, task2Id,
204             view.locationOnScreen, view.context.display.naturalBounds, handleResult)
205         val remoteTransition = RemoteTransition(animationRunner,
206             view.context.iApplicationThread, "startSplitScreenTask")
207 
208         splitScreen.get().startTasks(taskId, activityOptions.toBundle(), task2Id, null,
209             splitPosition, splitBounds.snapPosition, remoteTransition, null)
210     }
211 
212 
213     override fun onTaskSizeChanged(size: Rect) {
214         views?.recentsContainer?.setTaskHeightSize()
215     }
216 
217     private fun View.setTaskHeightSize() {
218         val thumbnailHeight = taskViewSizeProvider.size.height()
219         val itemHeight =
220             thumbnailHeight +
221                     context.resources.getDimensionPixelSize(
222                         R.dimen.media_projection_app_selector_task_icon_size
223                     ) +
224                     context.resources.getDimensionPixelSize(
225                         R.dimen.media_projection_app_selector_task_icon_margin
226                     ) * 2
227 
228         layoutParams = layoutParams.apply { height = itemHeight }
229     }
230 
231     private class Views(
232         val root: ViewGroup,
233         val recentsContainer: View,
234         val progress: View,
235         val recycler: RecyclerView
236     )
237 }
238