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 package com.android.systemui.mediaprojection.permission 17 18 import android.app.AlertDialog 19 import android.content.Context 20 import android.os.Bundle 21 import android.view.Gravity 22 import android.view.LayoutInflater 23 import android.view.View 24 import android.view.ViewGroup 25 import android.view.ViewStub 26 import android.view.WindowManager 27 import android.view.accessibility.AccessibilityNodeInfo 28 import android.widget.AdapterView 29 import android.widget.ArrayAdapter 30 import android.widget.ImageView 31 import android.widget.Spinner 32 import android.widget.TextView 33 import androidx.annotation.CallSuper 34 import androidx.annotation.ColorRes 35 import androidx.annotation.DrawableRes 36 import androidx.annotation.LayoutRes 37 import androidx.annotation.StringRes 38 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger 39 import com.android.systemui.res.R 40 import com.android.systemui.statusbar.phone.DialogDelegate 41 42 /** Base permission dialog for screen share and recording */ 43 abstract class BaseMediaProjectionPermissionDialogDelegate<T : AlertDialog>( 44 private val screenShareOptions: List<ScreenShareOption>, 45 private val appName: String?, 46 private val hostUid: Int, 47 private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger, 48 @DrawableRes private val dialogIconDrawable: Int? = null, 49 @ColorRes private val dialogIconTint: Int? = null, 50 @ScreenShareMode val defaultSelectedMode: Int = screenShareOptions.first().mode, 51 ) : DialogDelegate<T>, AdapterView.OnItemSelectedListener { 52 private lateinit var dialogTitle: TextView 53 private lateinit var startButton: TextView 54 private lateinit var cancelButton: TextView 55 private lateinit var warning: TextView 56 private lateinit var screenShareModeSpinner: Spinner 57 protected lateinit var dialog: AlertDialog 58 private var shouldLogCancel: Boolean = true 59 var selectedScreenShareOption: ScreenShareOption = 60 screenShareOptions.first { it.mode == defaultSelectedMode } 61 62 @CallSuper 63 override fun onStop(dialog: T) { 64 // onStop can be called multiple times and we only want to log once. 65 if (shouldLogCancel) { 66 mediaProjectionMetricsLogger.notifyProjectionRequestCancelled(hostUid) 67 shouldLogCancel = false 68 } 69 } 70 71 @CallSuper 72 override fun onCreate(dialog: T, savedInstanceState: Bundle?) { 73 this.dialog = dialog 74 dialog.window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS) 75 dialog.window?.setGravity(Gravity.CENTER) 76 dialog.setContentView(R.layout.screen_share_dialog) 77 dialogTitle = dialog.requireViewById(R.id.screen_share_dialog_title) 78 warning = dialog.requireViewById(R.id.text_warning) 79 startButton = dialog.requireViewById(android.R.id.button1) 80 cancelButton = dialog.requireViewById(android.R.id.button2) 81 updateIcon() 82 initScreenShareOptions() 83 createOptionsView(getOptionsViewLayoutId()) 84 } 85 86 private fun updateIcon() { 87 val icon = dialog.requireViewById<ImageView>(R.id.screen_share_dialog_icon) 88 if (dialogIconTint != null) { 89 icon.setColorFilter(dialog.context.getColor(dialogIconTint)) 90 } 91 if (dialogIconDrawable != null) { 92 icon.setImageDrawable(dialog.context.getDrawable(dialogIconDrawable)) 93 } 94 } 95 96 private fun initScreenShareOptions() { 97 selectedScreenShareOption = screenShareOptions.first { it.mode == defaultSelectedMode } 98 warning.text = warningText 99 initScreenShareSpinner() 100 } 101 102 private val warningText: String 103 get() = dialog.context.getString(selectedScreenShareOption.warningText, appName) 104 105 private fun initScreenShareSpinner() { 106 val adapter = OptionsAdapter(dialog.context.applicationContext, screenShareOptions) 107 screenShareModeSpinner = dialog.requireViewById(R.id.screen_share_mode_spinner) 108 screenShareModeSpinner.adapter = adapter 109 screenShareModeSpinner.onItemSelectedListener = this 110 111 // disable redundant Touch & Hold accessibility action for Switch Access 112 screenShareModeSpinner.accessibilityDelegate = 113 object : View.AccessibilityDelegate() { 114 override fun onInitializeAccessibilityNodeInfo( 115 host: View, 116 info: AccessibilityNodeInfo 117 ) { 118 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK) 119 super.onInitializeAccessibilityNodeInfo(host, info) 120 } 121 } 122 screenShareModeSpinner.isLongClickable = false 123 val defaultModePosition = screenShareOptions.indexOfFirst { it.mode == defaultSelectedMode } 124 screenShareModeSpinner.setSelection(defaultModePosition, /* animate= */ false) 125 } 126 127 override fun onItemSelected(adapterView: AdapterView<*>?, view: View, pos: Int, id: Long) { 128 selectedScreenShareOption = screenShareOptions[pos] 129 warning.text = warningText 130 } 131 132 override fun onNothingSelected(parent: AdapterView<*>?) {} 133 134 /** Protected methods for the text updates & functionality */ 135 protected fun setDialogTitle(@StringRes stringId: Int) { 136 val title = dialog.context.getString(stringId, appName) 137 dialogTitle.text = title 138 } 139 140 protected fun setStartButtonText(@StringRes stringId: Int) { 141 startButton.setText(stringId) 142 } 143 144 protected fun setStartButtonOnClickListener(listener: View.OnClickListener?) { 145 startButton.setOnClickListener { view -> 146 shouldLogCancel = false 147 listener?.onClick(view) 148 } 149 } 150 151 protected fun setCancelButtonOnClickListener(listener: View.OnClickListener?) { 152 cancelButton.setOnClickListener(listener) 153 } 154 155 // Create additional options that is shown under the share mode spinner 156 // Eg. the audio and tap toggles in SysUI Recorder 157 @LayoutRes protected open fun getOptionsViewLayoutId(): Int? = null 158 159 private fun createOptionsView(@LayoutRes layoutId: Int?) { 160 if (layoutId == null) return 161 val stub = dialog.requireViewById<View>(R.id.options_stub) as ViewStub 162 stub.layoutResource = layoutId 163 stub.inflate() 164 } 165 } 166 167 private class OptionsAdapter( 168 context: Context, 169 private val options: List<ScreenShareOption>, 170 ) : 171 ArrayAdapter<String>( 172 context, 173 R.layout.screen_share_dialog_spinner_text, <lambda>null174 options.map { context.getString(it.spinnerText) } 175 ) { 176 isEnablednull177 override fun isEnabled(position: Int): Boolean { 178 return options[position].spinnerDisabledText == null 179 } 180 getDropDownViewnull181 override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View { 182 val inflater = LayoutInflater.from(parent.context) 183 val view = inflater.inflate(R.layout.screen_share_dialog_spinner_item_text, parent, false) 184 val titleTextView = view.requireViewById<TextView>(android.R.id.text1) 185 val errorTextView = view.requireViewById<TextView>(android.R.id.text2) 186 titleTextView.text = getItem(position) 187 errorTextView.text = options[position].spinnerDisabledText 188 if (isEnabled(position)) { 189 errorTextView.visibility = View.GONE 190 titleTextView.isEnabled = true 191 } else { 192 errorTextView.visibility = View.VISIBLE 193 titleTextView.isEnabled = false 194 } 195 return view 196 } 197 } 198