• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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