• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2015 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
18 
19 import android.Manifest.permission.ACCESS_COARSE_LOCATION
20 import android.Manifest.permission.ACCESS_FINE_LOCATION
21 import android.app.Activity
22 import android.content.res.Configuration
23 import android.graphics.Color
24 import android.graphics.ImageDecoder
25 import android.graphics.Paint
26 import android.graphics.Path
27 import android.graphics.PixelFormat
28 import android.graphics.PorterDuff
29 import android.graphics.PorterDuffXfermode
30 import android.graphics.drawable.AnimatedImageDrawable
31 import android.graphics.drawable.Drawable
32 import android.graphics.drawable.Icon
33 import android.os.Bundle
34 import android.os.UserHandle
35 import android.text.method.LinkMovementMethod
36 import android.transition.ChangeBounds
37 import android.transition.TransitionManager
38 import android.util.SparseIntArray
39 import android.view.LayoutInflater
40 import android.view.View
41 import android.view.View.OnClickListener
42 import android.view.ViewGroup
43 import android.view.WindowManager.LayoutParams
44 import android.view.accessibility.AccessibilityNodeInfo
45 import android.view.animation.AnimationUtils
46 import android.widget.Button
47 import android.widget.ImageView
48 import android.widget.RadioButton
49 import android.widget.RadioGroup
50 import android.widget.TextView
51 import com.android.permissioncontroller.R
52 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ALWAYS_BUTTON
53 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_BUTTON
54 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_FOREGROUND_BUTTON
55 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ONE_TIME_BUTTON
56 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.COARSE_RADIO_BUTTON
57 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_AND_DONT_ASK_AGAIN_BUTTON
58 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_BUTTON
59 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_BOTH_LOCATIONS
60 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_COARSE_LOCATION_ONLY
61 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_FINE_LOCATION_ONLY
62 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.FINE_RADIO_BUTTON
63 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.LOCATION_ACCURACY_LAYOUT
64 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NEXT_BUTTON
65 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NEXT_LOCATION_DIALOG
66 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON
67 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_BUTTON
68 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON
69 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_BUTTON
70 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler
71 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.CANCELED
72 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED
73 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN
74 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ALWAYS
75 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY
76 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ONE_TIME
77 
78 class GrantPermissionsViewHandlerImpl(
79     private val mActivity: Activity,
80     private val mAppPackageName: String,
81     private val mUserHandle: UserHandle
82 ) : GrantPermissionsViewHandler, OnClickListener {
83 
84     private val LOCATION_ACCURACY_DIALOGS = listOf(DIALOG_WITH_BOTH_LOCATIONS,
85             DIALOG_WITH_FINE_LOCATION_ONLY, DIALOG_WITH_COARSE_LOCATION_ONLY)
86     private val LOCATION_ACCURACY_IMAGE_DIAMETER = mActivity.resources.getDimension(
87             R.dimen.location_accuracy_image_size).toInt()
88 
89     private var resultListener: GrantPermissionsViewHandler.ResultListener? = null
90 
91     // Configuration of the current dialog
92     private var groupName: String? = null
93     private var groupCount: Int = 0
94     private var groupIndex: Int = 0
95     private var groupIcon: Icon? = null
96     private var groupMessage: CharSequence? = null
97     private var detailMessage: CharSequence? = null
98     private val buttonVisibilities = BooleanArray(NEXT_BUTTON) { false }
99     private val locationVisibilities = BooleanArray(NEXT_LOCATION_DIALOG) { false }
100     private var selectedPrecision: Int = 0
101     private var isLocationPermissionDialogActionClicked: Boolean = false
102     private var coarseRadioButton: RadioButton? = null
103     private var fineRadioButton: RadioButton? = null
104     private var coarseOffDrawable: AnimatedImageDrawable? = null
105     private var coarseOnDrawable: AnimatedImageDrawable? = null
106     private var fineOffDrawable: AnimatedImageDrawable? = null
107     private var fineOnDrawable: AnimatedImageDrawable? = null
108 
109     // Views
110     private var iconView: ImageView? = null
111     private var messageView: TextView? = null
112     private var detailMessageView: TextView? = null
113     private var buttons: Array<Button?> = arrayOfNulls(NEXT_BUTTON)
114     private var locationViews: Array<View?> = arrayOfNulls(NEXT_LOCATION_DIALOG)
115     private var rootView: ViewGroup? = null
116 
117     override fun setResultListener(
118         listener: GrantPermissionsViewHandler.ResultListener
119     ): GrantPermissionsViewHandlerImpl {
120         resultListener = listener
121         return this
122     }
123 
124     override fun saveInstanceState(arguments: Bundle) {
125         arguments.putString(ARG_GROUP_NAME, groupName)
126         arguments.putInt(ARG_GROUP_COUNT, groupCount)
127         arguments.putInt(ARG_GROUP_INDEX, groupIndex)
128         arguments.putParcelable(ARG_GROUP_ICON, groupIcon)
129         arguments.putCharSequence(ARG_GROUP_MESSAGE, groupMessage)
130         arguments.putCharSequence(ARG_GROUP_DETAIL_MESSAGE, detailMessage)
131         arguments.putBooleanArray(ARG_DIALOG_BUTTON_VISIBILITIES, buttonVisibilities)
132         arguments.putBooleanArray(ARG_DIALOG_LOCATION_VISIBILITIES, locationVisibilities)
133         arguments.putInt(ARG_DIALOG_SELECTED_PRECISION, selectedPrecision)
134     }
135 
136     override fun loadInstanceState(savedInstanceState: Bundle) {
137         groupName = savedInstanceState.getString(ARG_GROUP_NAME)
138         groupMessage = savedInstanceState.getCharSequence(ARG_GROUP_MESSAGE)
139         groupIcon = savedInstanceState.getParcelable(ARG_GROUP_ICON)
140         groupCount = savedInstanceState.getInt(ARG_GROUP_COUNT)
141         groupIndex = savedInstanceState.getInt(ARG_GROUP_INDEX)
142         detailMessage = savedInstanceState.getCharSequence(ARG_GROUP_DETAIL_MESSAGE)
143         setButtonVisibilities(savedInstanceState.getBooleanArray(ARG_DIALOG_BUTTON_VISIBILITIES))
144         setLocationVisibilities(savedInstanceState.getBooleanArray(
145             ARG_DIALOG_LOCATION_VISIBILITIES))
146         selectedPrecision = savedInstanceState.getInt(ARG_DIALOG_SELECTED_PRECISION)
147 
148         updateAll()
149     }
150 
151     override fun updateUi(
152         groupName: String,
153         groupCount: Int,
154         groupIndex: Int,
155         icon: Icon?,
156         message: CharSequence?,
157         detailMessage: CharSequence?,
158         buttonVisibilities: BooleanArray,
159         locationVisibilities: BooleanArray
160     ) {
161 
162         this.groupName = groupName
163         this.groupCount = groupCount
164         this.groupIndex = groupIndex
165         groupIcon = icon
166         groupMessage = message
167         this.detailMessage = detailMessage
168         setButtonVisibilities(buttonVisibilities)
169         setLocationVisibilities(locationVisibilities)
170 
171         // If this is a second (or later) permission and the views exist, then animate.
172         if (iconView != null) {
173             updateAll()
174         }
175     }
176 
177     private fun updateAll() {
178         updateDescription()
179         updateDetailDescription()
180         updateButtons()
181         updateLocationVisibilities()
182 
183         //      Animate change in size
184         //      Grow or shrink the content container to size of new content
185         val growShrinkToNewContentSize = ChangeBounds()
186         growShrinkToNewContentSize.duration = ANIMATION_DURATION_MILLIS
187         growShrinkToNewContentSize.interpolator = AnimationUtils.loadInterpolator(mActivity,
188             android.R.interpolator.fast_out_slow_in)
189         TransitionManager.beginDelayedTransition(rootView, growShrinkToNewContentSize)
190     }
191 
192     override fun createView(): View {
193         // Make this activity be Non-IME target to prevent hiding keyboard flicker when it show up.
194         mActivity.window.addFlags(LayoutParams.FLAG_ALT_FOCUSABLE_IM)
195         val rootView = LayoutInflater.from(mActivity)
196             .inflate(R.layout.grant_permissions, null) as ViewGroup
197         this.rootView = rootView
198 
199         val h = mActivity.resources.displayMetrics.heightPixels
200         rootView.minimumHeight = h
201         // Cancel dialog
202         rootView.findViewById<View>(R.id.grant_singleton)!!.setOnClickListener(this)
203         // Swallow click event
204         rootView.findViewById<View>(R.id.grant_dialog)!!.setOnClickListener(this)
205 
206         messageView = rootView.findViewById(R.id.permission_message)
207         detailMessageView = rootView.findViewById(R.id.detail_message)
208         detailMessageView!!.movementMethod = LinkMovementMethod.getInstance()
209         iconView = rootView.findViewById(R.id.permission_icon)
210 
211         val buttons = arrayOfNulls<Button>(NEXT_BUTTON)
212         val numButtons = BUTTON_RES_ID_TO_NUM.size()
213         for (i in 0 until numButtons) {
214             val button = rootView.findViewById<Button>(BUTTON_RES_ID_TO_NUM.keyAt(i))
215             button!!.setOnClickListener(this)
216             buttons[BUTTON_RES_ID_TO_NUM.valueAt(i)] = button
217         }
218         this.buttons = buttons
219 
220         val locationViews = arrayOfNulls<View>(NEXT_LOCATION_DIALOG)
221         for (i in 0 until LOCATION_RES_ID_TO_NUM.size()) {
222             val locationView = rootView.findViewById<View>(LOCATION_RES_ID_TO_NUM.keyAt(i))
223             locationViews[LOCATION_RES_ID_TO_NUM.valueAt(i)] = locationView
224         }
225         initializeAnimatedImages()
226 
227         // Set location accuracy radio buttons' click listeners
228         coarseRadioButton = locationViews[COARSE_RADIO_BUTTON] as RadioButton
229         fineRadioButton = locationViews[FINE_RADIO_BUTTON] as RadioButton
230         coarseRadioButton!!.setOnClickListener(this)
231         fineRadioButton!!.setOnClickListener(this)
232         this.locationViews = locationViews
233 
234         if (groupName != null) {
235             updateAll()
236         }
237 
238         return rootView
239     }
240 
241     private fun initializeAnimatedImages() {
242         val isDarkMode = (mActivity.resources.configuration.uiMode and
243                 Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
244         val coarseOffDrawableId = if (isDarkMode) R.drawable.coarse_off_dark
245             else R.drawable.coarse_off_light
246         val coarseOnDrawableId = if (isDarkMode) R.drawable.coarse_on_dark
247             else R.drawable.coarse_on_light
248         val fineOffDrawableId = if (isDarkMode) R.drawable.fine_off_dark
249             else R.drawable.fine_off_light
250         val fineOnDrawableId = if (isDarkMode) R.drawable.fine_on_dark else R.drawable.fine_on_light
251 
252         coarseOffDrawable = getDrawableFromId(coarseOffDrawableId) as AnimatedImageDrawable
253         coarseOnDrawable = getDrawableFromId(coarseOnDrawableId) as AnimatedImageDrawable
254         fineOffDrawable = getDrawableFromId(fineOffDrawableId) as AnimatedImageDrawable
255         fineOnDrawable = getDrawableFromId(fineOnDrawableId) as AnimatedImageDrawable
256     }
257 
258     private fun getDrawableFromId(drawableId: Int): Drawable {
259         val source = ImageDecoder.createSource(mActivity.resources, drawableId)
260         return ImageDecoder.decodeDrawable(source) { decoder, _, _ ->
261             decoder.setTargetSize(LOCATION_ACCURACY_IMAGE_DIAMETER,
262                     LOCATION_ACCURACY_IMAGE_DIAMETER)
263             decoder.setPostProcessor { canvas ->
264                 // This will crop the image to circle image.
265                 val path = Path()
266                 path.fillType = Path.FillType.INVERSE_EVEN_ODD
267                 val width: Int = canvas.width
268                 val height: Int = canvas.height
269                 path.addRoundRect(0f, 0f, width.toFloat(), height.toFloat(),
270                         width.toFloat() / 2, height.toFloat() / 2, Path.Direction.CW)
271                 val paint = Paint()
272                 paint.isAntiAlias = true
273                 paint.color = Color.TRANSPARENT
274                 paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC)
275                 canvas.drawPath(path, paint)
276                 PixelFormat.TRANSLUCENT
277             }
278         }
279     }
280 
281     override fun updateWindowAttributes(outLayoutParams: LayoutParams) {
282         // No-op
283     }
284 
285     private fun setButtonVisibilities(visibilities: BooleanArray?) {
286         for (i in buttonVisibilities.indices) {
287             buttonVisibilities[i] = if (visibilities != null && i < visibilities.size) {
288                 visibilities[i]
289             } else {
290                 false
291             }
292         }
293     }
294 
295     private fun setLocationVisibilities(visibilities: BooleanArray?) {
296         for (i in locationVisibilities.indices) {
297             locationVisibilities[i] = if (visibilities != null && i < visibilities.size) {
298                 visibilities[i]
299             } else {
300                 false
301             }
302         }
303     }
304 
305     private fun updateDescription() {
306         if (groupIcon != null) {
307             iconView!!.setImageDrawable(groupIcon!!.loadDrawable(mActivity))
308         }
309         messageView!!.text = groupMessage
310     }
311 
312     private fun updateDetailDescription() {
313         if (detailMessage == null) {
314             detailMessageView!!.visibility = View.GONE
315         } else {
316             detailMessageView!!.text = detailMessage
317             detailMessageView!!.visibility = View.VISIBLE
318         }
319     }
320 
321     private fun updateButtons() {
322         for (i in 0 until BUTTON_RES_ID_TO_NUM.size()) {
323             val pos = BUTTON_RES_ID_TO_NUM.valueAt(i)
324             buttons[pos]?.visibility = if (buttonVisibilities[pos]) {
325                 View.VISIBLE
326             } else {
327                 View.GONE
328             }
329             if (pos == ALLOW_FOREGROUND_BUTTON && buttonVisibilities[pos] &&
330                     locationVisibilities[LOCATION_ACCURACY_LAYOUT] &&
331                     locationVisibilities[DIALOG_WITH_FINE_LOCATION_ONLY]) {
332                 buttons[pos]?.text = mActivity.resources.getString(
333                         R.string.grant_dialog_button_change_to_precise_location)
334             }
335             if ((pos == DENY_BUTTON || pos == DENY_AND_DONT_ASK_AGAIN_BUTTON) &&
336                     locationVisibilities[LOCATION_ACCURACY_LAYOUT] &&
337                     locationVisibilities[DIALOG_WITH_FINE_LOCATION_ONLY]) {
338                 buttons[pos]?.text = mActivity.resources.getString(
339                         R.string.grant_dialog_button_keey_approximate_location)
340             }
341             buttons[pos]?.requestLayout()
342         }
343     }
344 
345     private fun updateLocationVisibilities() {
346         if (locationVisibilities[LOCATION_ACCURACY_LAYOUT]) {
347             if (isLocationPermissionDialogActionClicked) {
348                 return
349             }
350             locationViews[LOCATION_ACCURACY_LAYOUT]?.visibility = View.VISIBLE
351             for (i in LOCATION_ACCURACY_DIALOGS) {
352                 locationViews[i]?.visibility = if (locationVisibilities[i]) {
353                     View.VISIBLE
354                 } else {
355                     View.GONE
356                 }
357             }
358             if (locationVisibilities[DIALOG_WITH_BOTH_LOCATIONS]) {
359                 coarseRadioButton?.visibility = View.VISIBLE
360                 fineRadioButton?.visibility = View.VISIBLE
361                 if (selectedPrecision == 0) {
362                     fineRadioButton?.isChecked = locationVisibilities[FINE_RADIO_BUTTON]
363                     coarseRadioButton?.isChecked = locationVisibilities[COARSE_RADIO_BUTTON]
364                 } else {
365                     fineRadioButton?.isChecked = selectedPrecision == FINE_RADIO_BUTTON
366                     coarseRadioButton?.isChecked = selectedPrecision == COARSE_RADIO_BUTTON
367                 }
368                 if (coarseRadioButton?.isChecked == true) {
369                     runLocationAccuracyAnimation(false)
370                 } else if (fineRadioButton?.isChecked == true) {
371                     runLocationAccuracyAnimation(true)
372                 }
373             } else if (locationVisibilities[DIALOG_WITH_COARSE_LOCATION_ONLY]) {
374                 (locationViews[DIALOG_WITH_COARSE_LOCATION_ONLY] as ImageView).setImageDrawable(
375                         coarseOnDrawable)
376                 coarseOnDrawable?.start()
377             } else if (locationVisibilities[DIALOG_WITH_FINE_LOCATION_ONLY]) {
378                 (locationViews[DIALOG_WITH_FINE_LOCATION_ONLY] as ImageView).setImageDrawable(
379                         fineOnDrawable)
380                 fineOnDrawable?.start()
381             }
382         } else {
383             locationViews[LOCATION_ACCURACY_LAYOUT]?.visibility = View.GONE
384             for (i in LOCATION_ACCURACY_DIALOGS) {
385                 locationVisibilities[i] = false
386                 locationViews[i]?.visibility = View.GONE
387             }
388         }
389     }
390 
391     private fun runLocationAccuracyAnimation(isFineSelected: Boolean) {
392         if (isFineSelected) {
393             coarseOnDrawable?.stop()
394             fineOffDrawable?.stop()
395             coarseRadioButton?.setCompoundDrawablesWithIntrinsicBounds(null, coarseOffDrawable,
396                     null, null)
397             fineRadioButton?.setCompoundDrawablesWithIntrinsicBounds(null, fineOnDrawable,
398                     null, null)
399             coarseOffDrawable?.start()
400             fineOnDrawable?.start()
401         } else {
402             coarseOffDrawable?.stop()
403             fineOnDrawable?.stop()
404             coarseRadioButton?.setCompoundDrawablesWithIntrinsicBounds(null, coarseOnDrawable,
405                     null, null)
406             fineRadioButton?.setCompoundDrawablesWithIntrinsicBounds(null, fineOffDrawable,
407                     null, null)
408             coarseOnDrawable?.start()
409             fineOffDrawable?.start()
410         }
411     }
412 
413     override fun onClick(view: View) {
414         val id = view.id
415 
416         if (id == R.id.permission_location_accuracy_radio_fine) {
417             (locationViews[FINE_RADIO_BUTTON] as RadioButton).isChecked = true
418             selectedPrecision = FINE_RADIO_BUTTON
419             runLocationAccuracyAnimation(true)
420             return
421         }
422 
423         if (id == R.id.permission_location_accuracy_radio_coarse) {
424             (locationViews[COARSE_RADIO_BUTTON] as RadioButton).isChecked = true
425             selectedPrecision = COARSE_RADIO_BUTTON
426             runLocationAccuracyAnimation(false)
427             return
428         }
429 
430         if (locationVisibilities[LOCATION_ACCURACY_LAYOUT]) {
431             isLocationPermissionDialogActionClicked = true
432         }
433 
434         if (id == R.id.grant_singleton) {
435             if (resultListener != null) {
436                 resultListener!!.onPermissionGrantResult(groupName, CANCELED)
437             } else {
438                 mActivity.finishAfterTransition()
439             }
440             return
441         }
442 
443         var affectedForegroundPermissions: List<String>? = null
444         if (locationVisibilities[DIALOG_WITH_BOTH_LOCATIONS]) {
445             when ((locationViews[DIALOG_WITH_BOTH_LOCATIONS] as RadioGroup).checkedRadioButtonId) {
446                 R.id.permission_location_accuracy_radio_coarse ->
447                     affectedForegroundPermissions = listOf(ACCESS_COARSE_LOCATION)
448                 R.id.permission_location_accuracy_radio_fine ->
449                     affectedForegroundPermissions = listOf(ACCESS_FINE_LOCATION,
450                         ACCESS_COARSE_LOCATION)
451             }
452         } else if (locationVisibilities[DIALOG_WITH_FINE_LOCATION_ONLY]) {
453             affectedForegroundPermissions = listOf(ACCESS_FINE_LOCATION)
454         } else if (locationVisibilities[DIALOG_WITH_COARSE_LOCATION_ONLY]) {
455             affectedForegroundPermissions = listOf(ACCESS_COARSE_LOCATION)
456         }
457 
458         when (BUTTON_RES_ID_TO_NUM.get(id, -1)) {
459             ALLOW_BUTTON -> if (resultListener != null) {
460                 view.performAccessibilityAction(
461                     AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null)
462                 resultListener!!.onPermissionGrantResult(groupName, affectedForegroundPermissions,
463                     GRANTED_ALWAYS)
464             }
465             ALLOW_FOREGROUND_BUTTON -> if (resultListener != null) {
466                 view.performAccessibilityAction(
467                     AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null)
468                 resultListener!!.onPermissionGrantResult(groupName, affectedForegroundPermissions,
469                     GRANTED_FOREGROUND_ONLY)
470             }
471             ALLOW_ALWAYS_BUTTON -> if (resultListener != null) {
472                 view.performAccessibilityAction(
473                     AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null)
474                 resultListener!!.onPermissionGrantResult(groupName, affectedForegroundPermissions,
475                     GRANTED_ALWAYS)
476             }
477             ALLOW_ONE_TIME_BUTTON -> if (resultListener != null) {
478                 view.performAccessibilityAction(
479                     AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null)
480                 resultListener!!.onPermissionGrantResult(groupName, affectedForegroundPermissions,
481                     GRANTED_ONE_TIME)
482             }
483             DENY_BUTTON, NO_UPGRADE_BUTTON, NO_UPGRADE_OT_BUTTON -> if (resultListener != null) {
484                 view.performAccessibilityAction(
485                     AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null)
486                 resultListener!!.onPermissionGrantResult(groupName, affectedForegroundPermissions,
487                     DENIED)
488             }
489             DENY_AND_DONT_ASK_AGAIN_BUTTON, NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON,
490             NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON -> if (resultListener != null) {
491                 view.performAccessibilityAction(
492                     AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null)
493                 resultListener!!.onPermissionGrantResult(groupName, affectedForegroundPermissions,
494                     DENIED_DO_NOT_ASK_AGAIN)
495             }
496         }
497     }
498 
499     override fun onBackPressed() {
500         if (resultListener == null) {
501             mActivity.finishAfterTransition()
502             return
503         }
504         resultListener?.onPermissionGrantResult(groupName, CANCELED)
505     }
506 
507     companion object {
508 
509         const val ARG_GROUP_NAME = "ARG_GROUP_NAME"
510         const val ARG_GROUP_COUNT = "ARG_GROUP_COUNT"
511         const val ARG_GROUP_INDEX = "ARG_GROUP_INDEX"
512         const val ARG_GROUP_ICON = "ARG_GROUP_ICON"
513         const val ARG_GROUP_MESSAGE = "ARG_GROUP_MESSAGE"
514         private const val ARG_GROUP_DETAIL_MESSAGE = "ARG_GROUP_DETAIL_MESSAGE"
515         private const val ARG_DIALOG_BUTTON_VISIBILITIES = "ARG_DIALOG_BUTTON_VISIBILITIES"
516         private const val ARG_DIALOG_LOCATION_VISIBILITIES = "ARG_DIALOG_LOCATION_VISIBILITIES"
517         private const val ARG_DIALOG_SELECTED_PRECISION = "ARG_DIALOG_SELECTED_PRECISION"
518 
519         // Animation parameters.
520         private const val SWITCH_TIME_MILLIS: Long = 75
521         private const val ANIMATION_DURATION_MILLIS: Long = 200
522 
523         private val BUTTON_RES_ID_TO_NUM = SparseIntArray()
524         private val LOCATION_RES_ID_TO_NUM = SparseIntArray()
525 
526         init {
527             BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_button, ALLOW_BUTTON)
528             BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_foreground_only_button,
529                 ALLOW_FOREGROUND_BUTTON)
530             BUTTON_RES_ID_TO_NUM.put(R.id.permission_deny_button, DENY_BUTTON)
531             BUTTON_RES_ID_TO_NUM.put(R.id.permission_deny_and_dont_ask_again_button,
532                 DENY_AND_DONT_ASK_AGAIN_BUTTON)
533             BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_one_time_button,
534                 ALLOW_ONE_TIME_BUTTON)
535             BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_button,
536                 NO_UPGRADE_BUTTON)
537             BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_and_dont_ask_again_button,
538                 NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON)
539             BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_one_time_button,
540                 NO_UPGRADE_OT_BUTTON)
541             BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_one_time_and_dont_ask_again_button,
542                 NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON)
543 
544             LOCATION_RES_ID_TO_NUM.put(R.id.permission_location_accuracy, LOCATION_ACCURACY_LAYOUT)
545             LOCATION_RES_ID_TO_NUM.put(R.id.permission_location_accuracy_radio_fine,
546                 FINE_RADIO_BUTTON)
547             LOCATION_RES_ID_TO_NUM.put(R.id.permission_location_accuracy_radio_coarse,
548                 COARSE_RADIO_BUTTON)
549             LOCATION_RES_ID_TO_NUM.put(R.id.permission_location_accuracy_radio_group,
550                 DIALOG_WITH_BOTH_LOCATIONS)
551             LOCATION_RES_ID_TO_NUM.put(R.id.permission_location_accuracy_fine_only,
552                 DIALOG_WITH_FINE_LOCATION_ONLY)
553             LOCATION_RES_ID_TO_NUM.put(R.id.permission_location_accuracy_coarse_only,
554                 DIALOG_WITH_COARSE_LOCATION_ONLY)
555         }
556     }
557 }
558