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 com.android.permissioncontroller.R 37 import com.android.permissioncontroller.permission.compat.LinkMovementMethodCompat 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 = AnimationUtils.loadInterpolator(mActivity, 126 android.R.interpolator.fast_out_slow_in) 127 TransitionManager.beginDelayedTransition(rootView, growShrinkToNewContentSize) 128 } 129 createViewnull130 override fun createView(): View { 131 val rootView = LayoutInflater.from(mActivity) 132 .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 = rootView.findViewById<Button>(R.id.back_button)!!.apply { 164 setOnClickListener(this@PermissionRationaleViewHandlerImpl) 165 166 // Load the text color from the activity theme rather than the Material Design theme 167 val textColor = getColorStateListForAttr(mActivity, android.R.attr.textColorPrimary)!! 168 setTextColor(textColor) 169 } 170 171 this.rootView = rootView 172 173 // If ui model present, update all children 174 if (groupName != null) { 175 updateAll() 176 } 177 178 return rootView 179 } 180 onClicknull181 override fun onClick(view: View) { 182 val id = view.id 183 184 if (id == R.id.permission_rationale_singleton) { 185 onCancelled() 186 return 187 } 188 189 if (id == R.id.back_button) { 190 onCancelled() 191 } 192 } 193 onBackPressednull194 override fun onBackPressed() { 195 onCancelled() 196 } 197 onCancellednull198 override fun onCancelled() { 199 resultListener.onPermissionRationaleResult(groupName, CANCELLED) 200 } 201 updateTitlenull202 private fun updateTitle() { 203 if (title == null) { 204 titleView!!.visibility = View.GONE 205 } else { 206 titleView!!.text = title 207 titleView!!.visibility = View.VISIBLE 208 } 209 } 210 updateDataSharingSourceMessagenull211 private fun updateDataSharingSourceMessage() { 212 if (dataSharingSourceMessage == null) { 213 dataSharingSourceMessageView!!.visibility = View.GONE 214 } else { 215 dataSharingSourceMessageView!!.text = dataSharingSourceMessage 216 dataSharingSourceMessageView!!.visibility = View.VISIBLE 217 } 218 } 219 updatePurposeTitlenull220 private fun updatePurposeTitle() { 221 if (purposeTitle == null) { 222 purposeTitleView!!.visibility = View.GONE 223 } else { 224 purposeTitleView!!.text = purposeTitle 225 purposeTitleView!!.visibility = View.VISIBLE 226 } 227 } 228 updatePurposeMessagenull229 private fun updatePurposeMessage() { 230 if (purposeMessage == null) { 231 purposeMessageView!!.visibility = View.GONE 232 } else { 233 purposeMessageView!!.text = purposeMessage 234 purposeMessageView!!.visibility = View.VISIBLE 235 } 236 } 237 updateLearnMoreMessagenull238 private fun updateLearnMoreMessage() { 239 if (learnMoreMessage == null) { 240 learnMoreMessageView!!.visibility = View.GONE 241 } else { 242 learnMoreMessageView!!.text = learnMoreMessage 243 learnMoreMessageView!!.visibility = View.VISIBLE 244 } 245 } 246 updateSettingsMessagenull247 private fun updateSettingsMessage() { 248 if (settingsMessage == null) { 249 settingsMessageView!!.visibility = View.GONE 250 } else { 251 settingsMessageView!!.text = settingsMessage 252 settingsMessageView!!.visibility = View.VISIBLE 253 } 254 } 255 256 companion object { 257 private val TAG = PermissionRationaleViewHandlerImpl::class.java.simpleName 258 259 const val ARG_GROUP_NAME = "ARG_GROUP_NAME" 260 const val ARG_TITLE = "ARG_TITLE" 261 const val ARG_DATA_SHARING_SOURCE_MESSAGE = "ARG_DATA_SHARING_SOURCE_MESSAGE" 262 const val ARG_PURPOSE_TITLE = "ARG_PURPOSE_TITLE" 263 const val ARG_PURPOSE_MESSAGE = "ARG_PURPOSE_MESSAGE" 264 const val ARG_LEARN_MORE_MESSAGE = "ARG_LEARN_MORE_MESSAGE" 265 const val ARG_SETTINGS_MESSAGE = "ARG_SETTINGS_MESSAGE" 266 267 // Animation parameters. 268 private const val ANIMATION_DURATION_MILLIS: Long = 200 269 getColorStateListForAttrnull270 fun getColorStateListForAttr(context: Context, attr: Int): ColorStateList? { 271 val typedArray = context.obtainStyledAttributes(intArrayOf(attr)) 272 val colorStateList = typedArray.getColorStateList(0) 273 typedArray.recycle() 274 return colorStateList 275 } 276 } 277 } 278