• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.wm.shell.apptoweb
18 
19 import android.app.ActivityManager.RunningTaskInfo
20 import android.content.Context
21 import android.content.pm.PackageManager.NameNotFoundException
22 import android.content.pm.verify.domain.DomainVerificationManager
23 import android.graphics.Bitmap
24 import android.graphics.PixelFormat
25 import android.util.Slog
26 import android.view.LayoutInflater
27 import android.view.SurfaceControl
28 import android.view.SurfaceControlViewHost
29 import android.view.WindowManager
30 import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
31 import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
32 import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
33 import android.view.WindowlessWindowManager
34 import android.widget.ImageView
35 import android.widget.RadioButton
36 import android.widget.TextView
37 import android.window.TaskConstants
38 import com.android.wm.shell.R
39 import com.android.wm.shell.common.DisplayController
40 import com.android.wm.shell.shared.annotations.ShellBackgroundThread
41 import com.android.wm.shell.shared.annotations.ShellMainThread
42 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer
43 import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader
44 import java.util.function.Supplier
45 import kotlinx.coroutines.CoroutineScope
46 import kotlinx.coroutines.Job
47 import kotlinx.coroutines.MainCoroutineDispatcher
48 import kotlinx.coroutines.isActive
49 import kotlinx.coroutines.launch
50 import kotlinx.coroutines.withContext
51 
52 
53 /**
54  * Window manager for the open by default settings dialog
55  */
56 internal class OpenByDefaultDialog(
57     private val context: Context,
58     private val taskInfo: RunningTaskInfo,
59     private val taskSurface: SurfaceControl,
60     private val displayController: DisplayController,
61     private val taskResourceLoader: WindowDecorTaskResourceLoader,
62     private val surfaceControlTransactionSupplier: Supplier<SurfaceControl.Transaction>,
63     @ShellMainThread private val mainDispatcher: MainCoroutineDispatcher,
64     @ShellBackgroundThread private val bgScope: CoroutineScope,
65     private val listener: DialogLifecycleListener,
66 ) {
67     private lateinit var dialog: OpenByDefaultDialogView
68     private lateinit var viewHost: SurfaceControlViewHost
69     private lateinit var dialogSurfaceControl: SurfaceControl
70     private var dialogContainer: AdditionalViewHostViewContainer? = null
71     private lateinit var appIconView: ImageView
72     private lateinit var appNameView: TextView
73 
74     private lateinit var openInAppButton: RadioButton
75     private lateinit var openInBrowserButton: RadioButton
76 
77     private val domainVerificationManager =
78         context.getSystemService(DomainVerificationManager::class.java)!!
79     private val packageName = taskInfo.baseActivity?.packageName!!
80 
81     private var loadAppInfoJob: Job? = null
82 
83     init {
84         createDialog()
85         initializeRadioButtons()
<lambda>null86         loadAppInfoJob = bgScope.launch {
87             if (!isActive) return@launch
88             val name = taskResourceLoader.getName(taskInfo)
89             val icon = taskResourceLoader.getHeaderIcon(taskInfo)
90             withContext(mainDispatcher.immediate) {
91                 if (!isActive) return@withContext
92                 bindAppInfo(icon, name)
93             }
94         }
95     }
96 
97     /** Creates an open by default settings dialog. */
createDialognull98     fun createDialog() {
99         val t = SurfaceControl.Transaction()
100         val taskBounds = taskInfo.configuration.windowConfiguration.bounds
101 
102         dialog = LayoutInflater.from(context)
103             .inflate(
104                 R.layout.open_by_default_settings_dialog,
105                 null /* root */
106             ) as OpenByDefaultDialogView
107         appIconView = dialog.requireViewById(R.id.application_icon)
108         appNameView = dialog.requireViewById(R.id.application_name)
109 
110         val display = displayController.getDisplay(taskInfo.displayId)
111         val builder: SurfaceControl.Builder = SurfaceControl.Builder()
112         dialogSurfaceControl = builder
113             .setName("Open by Default Dialog of Task=" + taskInfo.taskId)
114             .setContainerLayer()
115             .setParent(taskSurface)
116             .setCallsite("OpenByDefaultDialog#createDialog")
117             .build()
118         t.setPosition(dialogSurfaceControl, 0f, 0f)
119             .setWindowCrop(dialogSurfaceControl, taskBounds.width(), taskBounds.height())
120             .setLayer(dialogSurfaceControl, TaskConstants.TASK_CHILD_LAYER_SETTINGS_DIALOG)
121             .show(dialogSurfaceControl)
122         val lp = WindowManager.LayoutParams(
123             taskBounds.width(),
124             taskBounds.height(),
125             TYPE_APPLICATION_PANEL,
126             FLAG_NOT_FOCUSABLE or FLAG_NOT_TOUCH_MODAL,
127             PixelFormat.TRANSLUCENT)
128         lp.title = "Open by default settings dialog of task=" + taskInfo.taskId
129         lp.setTrustedOverlay()
130         val windowManager = WindowlessWindowManager(
131             taskInfo.configuration,
132             dialogSurfaceControl, null /* hostInputToken */
133         )
134         viewHost = SurfaceControlViewHost(context, display, windowManager, "Dialog").apply {
135             setView(dialog, lp)
136             rootSurfaceControl.applyTransactionOnDraw(t)
137         }
138         dialogContainer = AdditionalViewHostViewContainer(
139             dialogSurfaceControl, viewHost, surfaceControlTransactionSupplier)
140 
141         dialog.setDismissOnClickListener{
142             closeMenu()
143         }
144 
145         dialog.setConfirmButtonClickListener {
146             setDefaultLinkHandlingSetting()
147             closeMenu()
148         }
149 
150         listener.onDialogCreated()
151     }
152 
initializeRadioButtonsnull153     private fun initializeRadioButtons() {
154         openInAppButton = dialog.requireViewById(R.id.open_in_app_button)
155         openInBrowserButton = dialog.requireViewById(R.id.open_in_browser_button)
156 
157         val userState =
158             getDomainVerificationUserState(domainVerificationManager, packageName) ?: return
159         val openInApp = userState.isLinkHandlingAllowed
160         openInAppButton.isChecked = openInApp
161         openInBrowserButton.isChecked = !openInApp
162     }
163 
setDefaultLinkHandlingSettingnull164     private fun setDefaultLinkHandlingSetting() {
165         try {
166             domainVerificationManager.setDomainVerificationLinkHandlingAllowed(
167                 packageName, openInAppButton.isChecked)
168         } catch (e: NameNotFoundException) {
169             Slog.e(
170                 TAG,
171                 "Failed to change link handling policy due to the package name is not found: " + e
172             )
173         }
174     }
175 
closeMenunull176     private fun closeMenu() {
177         loadAppInfoJob?.cancel()
178         dialogContainer?.releaseView()
179         dialogContainer = null
180         listener.onDialogDismissed()
181     }
182 
bindAppInfonull183      private fun bindAppInfo(
184         appIconBitmap: Bitmap,
185         appName: CharSequence
186     ) {
187         appIconView.setImageBitmap(appIconBitmap)
188         appNameView.text = appName
189     }
190 
191     /**
192      * Relayout the dialog to the new task bounds.
193      */
relayoutnull194     fun relayout(
195         taskInfo: RunningTaskInfo,
196     ) {
197         val t = surfaceControlTransactionSupplier.get()
198         val taskBounds = taskInfo.configuration.windowConfiguration.bounds
199         t.setWindowCrop(dialogSurfaceControl, taskBounds.width(), taskBounds.height())
200         viewHost.rootSurfaceControl.applyTransactionOnDraw(t)
201         viewHost.relayout(taskBounds.width(), taskBounds.height())
202     }
203 
204     /**
205      * Defines interface for classes that can listen to lifecycle events of open by default settings
206      * dialog.
207      */
208     interface DialogLifecycleListener {
209         /** Called when open by default dialog view has been created. */
onDialogCreatednull210         fun onDialogCreated()
211 
212         /** Called when open by default dialog view has been released. */
213         fun onDialogDismissed()
214     }
215 
216     companion object {
217         private const val TAG = "OpenByDefaultDialog"
218     }
219 }
220