1 /* 2 * 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.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED 21 import android.app.IActivityTaskManager 22 import android.graphics.Rect 23 import android.os.Binder 24 import android.view.LayoutInflater 25 import android.view.View 26 import android.view.ViewGroup 27 import androidx.recyclerview.widget.LinearLayoutManager 28 import androidx.recyclerview.widget.RecyclerView 29 import com.android.systemui.R 30 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler 31 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope 32 import com.android.systemui.mediaprojection.appselector.data.RecentTask 33 import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter.RecentTaskClickListener 34 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener 35 import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration 36 import javax.inject.Inject 37 38 /** 39 * Controller that handles view of the recent apps selector in the media projection activity. It is 40 * responsible for creating and updating recent apps view. 41 */ 42 @MediaProjectionAppSelectorScope 43 class MediaProjectionRecentsViewController 44 @Inject 45 constructor( 46 private val recentTasksAdapterFactory: RecentTasksAdapter.Factory, 47 private val taskViewSizeProvider: TaskPreviewSizeProvider, 48 private val activityTaskManager: IActivityTaskManager, 49 private val resultHandler: MediaProjectionAppSelectorResultHandler, 50 ) : RecentTaskClickListener, TaskPreviewSizeListener { 51 52 private var views: Views? = null 53 private var lastBoundData: List<RecentTask>? = null 54 55 val hasRecentTasks: Boolean 56 get() = lastBoundData?.isNotEmpty() ?: false 57 58 init { 59 taskViewSizeProvider.addCallback(this) 60 } 61 62 fun createView(parent: ViewGroup): ViewGroup = 63 views?.root 64 ?: createRecentViews(parent) 65 .also { 66 views = it 67 lastBoundData?.let { recents -> bind(recents) } 68 } 69 .root 70 71 fun bind(recentTasks: List<RecentTask>) { 72 views?.apply { 73 if (recentTasks.isEmpty()) { 74 root.visibility = View.GONE 75 return 76 } 77 78 progress.visibility = View.GONE 79 recycler.visibility = View.VISIBLE 80 root.visibility = View.VISIBLE 81 82 recycler.adapter = 83 recentTasksAdapterFactory.create( 84 recentTasks, 85 this@MediaProjectionRecentsViewController 86 ) 87 } 88 89 lastBoundData = recentTasks 90 } 91 92 private fun createRecentViews(parent: ViewGroup): Views { 93 val recentsRoot = 94 LayoutInflater.from(parent.context) 95 .inflate(R.layout.media_projection_recent_tasks, parent, /* attachToRoot= */ false) 96 as ViewGroup 97 98 val container = 99 recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_container) 100 container.setTaskHeightSize() 101 102 val progress = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_loader) 103 val recycler = 104 recentsRoot.requireViewById<RecyclerView>(R.id.media_projection_recent_tasks_recycler) 105 recycler.layoutManager = 106 LinearLayoutManager( 107 parent.context, 108 LinearLayoutManager.HORIZONTAL, 109 /* reverseLayout= */ false 110 ) 111 112 val itemDecoration = 113 HorizontalSpacerItemDecoration( 114 parent.resources.getDimensionPixelOffset( 115 R.dimen.media_projection_app_selector_recents_padding 116 ) 117 ) 118 recycler.addItemDecoration(itemDecoration) 119 120 return Views(recentsRoot, container, progress, recycler) 121 } 122 123 override fun onRecentAppClicked(task: RecentTask, view: View) { 124 val launchCookie = Binder() 125 val activityOptions = 126 ActivityOptions.makeScaleUpAnimation( 127 view, 128 /* startX= */ 0, 129 /* startY= */ 0, 130 view.width, 131 view.height 132 ) 133 activityOptions.setPendingIntentBackgroundActivityStartMode( 134 MODE_BACKGROUND_ACTIVITY_START_ALLOWED 135 ) 136 activityOptions.launchCookie = launchCookie 137 138 activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle()) 139 resultHandler.returnSelectedApp(launchCookie) 140 } 141 142 override fun onTaskSizeChanged(size: Rect) { 143 views?.recentsContainer?.setTaskHeightSize() 144 } 145 146 private fun View.setTaskHeightSize() { 147 val thumbnailHeight = taskViewSizeProvider.size.height() 148 val itemHeight = 149 thumbnailHeight + 150 context.resources.getDimensionPixelSize( 151 R.dimen.media_projection_app_selector_task_icon_size 152 ) + 153 context.resources.getDimensionPixelSize( 154 R.dimen.media_projection_app_selector_task_icon_margin 155 ) * 2 156 157 layoutParams = layoutParams.apply { height = itemHeight } 158 } 159 160 private class Views( 161 val root: ViewGroup, 162 val recentsContainer: View, 163 val progress: View, 164 val recycler: RecyclerView 165 ) 166 } 167