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.permissioncontroller.permission.ui.handheld.v34 18 19 import android.app.Activity 20 import android.content.Context 21 import android.content.res.ColorStateList 22 import android.os.Build 23 import android.os.Bundle 24 import android.transition.ChangeBounds 25 import android.transition.TransitionManager 26 import android.view.Gravity 27 import android.view.LayoutInflater 28 import android.view.View 29 import android.view.View.OnClickListener 30 import android.view.ViewGroup 31 import android.view.animation.AnimationUtils 32 import android.widget.Button 33 import android.widget.LinearLayout 34 import android.widget.TextView 35 import androidx.annotation.RequiresApi 36 import androidx.core.text.method.LinkMovementMethodCompat 37 import com.android.permissioncontroller.R 38 import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleViewHandler 39 import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleViewHandler.Result.Companion.CANCELLED 40 41 /** 42 * Handheld implementation of [PermissionRationaleViewHandler]. Used for managing the presentation 43 * and user interaction of the "permission rationale" user interface. 44 */ 45 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 46 class PermissionRationaleViewHandlerImpl( 47 private val mActivity: Activity, 48 private val resultListener: PermissionRationaleViewHandler.ResultListener, 49 private val shouldShowSettingsSection: Boolean 50 ) : PermissionRationaleViewHandler, OnClickListener { 51 52 private var groupName: String? = null 53 private var title: CharSequence? = null 54 private var dataSharingSourceMessage: CharSequence? = null 55 private var purposeTitle: CharSequence? = null 56 private var purposeMessage: CharSequence? = null 57 private var learnMoreMessage: CharSequence? = null 58 private var settingsMessage: CharSequence? = null 59 60 private var rootView: ViewGroup? = null 61 private var titleView: TextView? = null 62 private var dataSharingSourceMessageView: TextView? = null 63 private var purposeTitleView: TextView? = null 64 private var purposeMessageView: TextView? = null 65 private var learnMoreMessageView: TextView? = null 66 private var settingsMessageView: TextView? = null 67 private var backButton: Button? = null 68 saveInstanceStatenull69 override fun saveInstanceState(outState: Bundle) { 70 outState.putString(ARG_GROUP_NAME, groupName) 71 outState.putCharSequence(ARG_TITLE, title) 72 outState.putCharSequence(ARG_DATA_SHARING_SOURCE_MESSAGE, dataSharingSourceMessage) 73 outState.putCharSequence(ARG_PURPOSE_TITLE, purposeTitle) 74 outState.putCharSequence(ARG_PURPOSE_MESSAGE, purposeMessage) 75 outState.putCharSequence(ARG_LEARN_MORE_MESSAGE, learnMoreMessage) 76 outState.putCharSequence(ARG_SETTINGS_MESSAGE, settingsMessage) 77 } 78 loadInstanceStatenull79 override fun loadInstanceState(savedInstanceState: Bundle) { 80 groupName = savedInstanceState.getString(ARG_GROUP_NAME) 81 title = savedInstanceState.getCharSequence(ARG_TITLE) 82 dataSharingSourceMessage = 83 savedInstanceState.getCharSequence(ARG_DATA_SHARING_SOURCE_MESSAGE) 84 purposeTitle = savedInstanceState.getCharSequence(ARG_PURPOSE_TITLE) 85 purposeMessage = savedInstanceState.getCharSequence(ARG_PURPOSE_MESSAGE) 86 learnMoreMessage = savedInstanceState.getCharSequence(ARG_LEARN_MORE_MESSAGE) 87 settingsMessage = savedInstanceState.getCharSequence(ARG_SETTINGS_MESSAGE) 88 } 89 updateUinull90 override fun updateUi( 91 groupName: String, 92 title: CharSequence, 93 dataSharingSourceMessage: CharSequence, 94 purposeTitle: CharSequence, 95 purposeMessage: CharSequence, 96 learnMoreMessage: CharSequence, 97 settingsMessage: CharSequence 98 ) { 99 this.groupName = groupName 100 this.title = title 101 this.dataSharingSourceMessage = dataSharingSourceMessage 102 this.purposeTitle = purposeTitle 103 this.purposeMessage = purposeMessage 104 this.learnMoreMessage = learnMoreMessage 105 this.settingsMessage = settingsMessage 106 107 // If view already created, update all children 108 if (rootView != null) { 109 updateAll() 110 } 111 } 112 updateAllnull113 private fun updateAll() { 114 updateTitle() 115 updateDataSharingSourceMessage() 116 updatePurposeTitle() 117 updatePurposeMessage() 118 updateLearnMoreMessage() 119 updateSettingsMessage() 120 121 // Animate change in size 122 // Grow or shrink the content container to size of new content 123 val growShrinkToNewContentSize = ChangeBounds() 124 growShrinkToNewContentSize.duration = ANIMATION_DURATION_MILLIS 125 growShrinkToNewContentSize.interpolator = 126 AnimationUtils.loadInterpolator(mActivity, android.R.interpolator.fast_out_slow_in) 127 TransitionManager.beginDelayedTransition(rootView, growShrinkToNewContentSize) 128 } 129 createViewnull130 override fun createView(): View { 131 val rootView = 132 LayoutInflater.from(mActivity).inflate(R.layout.permission_rationale, null) as ViewGroup 133 134 // Uses the vertical gravity of the PermissionGrantSingleton style to position the window 135 val gravity = 136 rootView.requireViewById<LinearLayout>(R.id.permission_rationale_singleton).gravity 137 val verticalGravity = Gravity.VERTICAL_GRAVITY_MASK and gravity 138 mActivity.window.setGravity(Gravity.CENTER_HORIZONTAL or verticalGravity) 139 140 // Cancel dialog 141 rootView.findViewById<View>(R.id.permission_rationale_singleton)!!.setOnClickListener(this) 142 // Swallow click event 143 rootView.findViewById<View>(R.id.permission_rationale_dialog)!!.setOnClickListener(this) 144 145 titleView = rootView.findViewById(R.id.permission_rationale_title) 146 147 dataSharingSourceMessageView = rootView.findViewById(R.id.data_sharing_source_message) 148 dataSharingSourceMessageView!!.movementMethod = LinkMovementMethodCompat.getInstance() 149 150 purposeTitleView = rootView.findViewById(R.id.purpose_title) 151 purposeMessageView = rootView.findViewById(R.id.purpose_message) 152 153 learnMoreMessageView = rootView.findViewById(R.id.learn_more_message) 154 learnMoreMessageView!!.movementMethod = LinkMovementMethodCompat.getInstance() 155 156 settingsMessageView = rootView.findViewById(R.id.settings_message) 157 settingsMessageView!!.movementMethod = LinkMovementMethodCompat.getInstance() 158 159 if (!shouldShowSettingsSection) { 160 val settingsSectionView: ViewGroup? = rootView.findViewById(R.id.settings_section) 161 settingsSectionView?.visibility = View.GONE 162 } 163 backButton = 164 rootView.findViewById<Button>(R.id.back_button)!!.apply { 165 setOnClickListener(this@PermissionRationaleViewHandlerImpl) 166 167 // Load the text color from the activity theme rather than the Material Design theme 168 val textColor = 169 getColorStateListForAttr(mActivity, android.R.attr.textColorPrimary)!! 170 setTextColor(textColor) 171 } 172 173 this.rootView = rootView 174 175 // If ui model present, update all children 176 if (groupName != null) { 177 updateAll() 178 } 179 180 return rootView 181 } 182 onClicknull183 override fun onClick(view: View) { 184 val id = view.id 185 186 if (id == R.id.permission_rationale_singleton) { 187 onCancelled() 188 return 189 } 190 191 if (id == R.id.back_button) { 192 onCancelled() 193 } 194 } 195 onBackPressednull196 override fun onBackPressed() { 197 onCancelled() 198 } 199 onCancellednull200 override fun onCancelled() { 201 resultListener.onPermissionRationaleResult(groupName, CANCELLED) 202 } 203 updateTitlenull204 private fun updateTitle() { 205 if (title == null) { 206 titleView!!.visibility = View.GONE 207 } else { 208 titleView!!.text = title 209 titleView!!.visibility = View.VISIBLE 210 } 211 } 212 updateDataSharingSourceMessagenull213 private fun updateDataSharingSourceMessage() { 214 if (dataSharingSourceMessage == null) { 215 dataSharingSourceMessageView!!.visibility = View.GONE 216 } else { 217 dataSharingSourceMessageView!!.text = dataSharingSourceMessage 218 dataSharingSourceMessageView!!.visibility = View.VISIBLE 219 } 220 } 221 updatePurposeTitlenull222 private fun updatePurposeTitle() { 223 if (purposeTitle == null) { 224 purposeTitleView!!.visibility = View.GONE 225 } else { 226 purposeTitleView!!.text = purposeTitle 227 purposeTitleView!!.visibility = View.VISIBLE 228 } 229 } 230 updatePurposeMessagenull231 private fun updatePurposeMessage() { 232 if (purposeMessage == null) { 233 purposeMessageView!!.visibility = View.GONE 234 } else { 235 purposeMessageView!!.text = purposeMessage 236 purposeMessageView!!.visibility = View.VISIBLE 237 } 238 } 239 updateLearnMoreMessagenull240 private fun updateLearnMoreMessage() { 241 if (learnMoreMessage == null) { 242 learnMoreMessageView!!.visibility = View.GONE 243 } else { 244 learnMoreMessageView!!.text = learnMoreMessage 245 learnMoreMessageView!!.visibility = View.VISIBLE 246 } 247 } 248 updateSettingsMessagenull249 private fun updateSettingsMessage() { 250 if (settingsMessage == null) { 251 settingsMessageView!!.visibility = View.GONE 252 } else { 253 settingsMessageView!!.text = settingsMessage 254 settingsMessageView!!.visibility = View.VISIBLE 255 } 256 } 257 258 companion object { 259 private val TAG = PermissionRationaleViewHandlerImpl::class.java.simpleName 260 261 const val ARG_GROUP_NAME = "ARG_GROUP_NAME" 262 const val ARG_TITLE = "ARG_TITLE" 263 const val ARG_DATA_SHARING_SOURCE_MESSAGE = "ARG_DATA_SHARING_SOURCE_MESSAGE" 264 const val ARG_PURPOSE_TITLE = "ARG_PURPOSE_TITLE" 265 const val ARG_PURPOSE_MESSAGE = "ARG_PURPOSE_MESSAGE" 266 const val ARG_LEARN_MORE_MESSAGE = "ARG_LEARN_MORE_MESSAGE" 267 const val ARG_SETTINGS_MESSAGE = "ARG_SETTINGS_MESSAGE" 268 269 // Animation parameters. 270 private const val ANIMATION_DURATION_MILLIS: Long = 200 271 getColorStateListForAttrnull272 fun getColorStateListForAttr(context: Context, attr: Int): ColorStateList? { 273 val typedArray = context.obtainStyledAttributes(intArrayOf(attr)) 274 val colorStateList = typedArray.getColorStateList(0) 275 typedArray.recycle() 276 return colorStateList 277 } 278 } 279 } 280