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 
18 package com.android.systemui.util
19 
20 import android.app.AlertDialog
21 import android.content.Context
22 import android.content.DialogInterface
23 import java.lang.IllegalArgumentException
24 
25 /**
26  * [AlertDialog] that is easier to test. Due to [AlertDialog] being a class and not an interface,
27  * there are some things that cannot be avoided, like the creation of a [Handler] on the main thread
28  * (and therefore needing a prepared [Looper] in the test).
29  *
30  * It bypasses calls to show, clicks on buttons, cancel and dismiss so it all can happen bounded in
31  * the test. It tries to be as close in behavior as a real [AlertDialog].
32  *
33  * It will only call [onCreate] as part of its lifecycle, but not any of the other lifecycle methods
34  * in [Dialog].
35  *
36  * In order to test clicking on buttons, use [clickButton] instead of calling [View.callOnClick] on
37  * the view returned by [getButton] to bypass the internal [Handler].
38  */
39 class TestableAlertDialog(context: Context) : AlertDialog(context) {
40 
41     private var _onDismissListener: DialogInterface.OnDismissListener? = null
42     private var _onCancelListener: DialogInterface.OnCancelListener? = null
43     private var _positiveButtonClickListener: DialogInterface.OnClickListener? = null
44     private var _negativeButtonClickListener: DialogInterface.OnClickListener? = null
45     private var _neutralButtonClickListener: DialogInterface.OnClickListener? = null
46     private var _onShowListener: DialogInterface.OnShowListener? = null
47     private var _dismissOverride: Runnable? = null
48 
49     private var showing = false
50     private var visible = false
51     private var created = false
52 
shownull53     override fun show() {
54         if (!created) {
55             created = true
56             onCreate(null)
57         }
58         if (isShowing) return
59         showing = true
60         visible = true
61         _onShowListener?.onShow(this)
62     }
63 
hidenull64     override fun hide() {
65         visible = false
66     }
67 
isShowingnull68     override fun isShowing(): Boolean {
69         return visible && showing
70     }
71 
dismissnull72     override fun dismiss() {
73         if (!showing) {
74             return
75         }
76         if (_dismissOverride != null) {
77             _dismissOverride?.run()
78             return
79         }
80         _onDismissListener?.onDismiss(this)
81         showing = false
82     }
83 
cancelnull84     override fun cancel() {
85         _onCancelListener?.onCancel(this)
86         dismiss()
87     }
88 
setOnDismissListenernull89     override fun setOnDismissListener(listener: DialogInterface.OnDismissListener?) {
90         _onDismissListener = listener
91     }
92 
setOnCancelListenernull93     override fun setOnCancelListener(listener: DialogInterface.OnCancelListener?) {
94         _onCancelListener = listener
95     }
96 
setOnShowListenernull97     override fun setOnShowListener(listener: DialogInterface.OnShowListener?) {
98         _onShowListener = listener
99     }
100 
takeCancelAndDismissListenersnull101     override fun takeCancelAndDismissListeners(
102         msg: String?,
103         cancel: DialogInterface.OnCancelListener?,
104         dismiss: DialogInterface.OnDismissListener?
105     ): Boolean {
106         _onCancelListener = cancel
107         _onDismissListener = dismiss
108         return true
109     }
110 
setButtonnull111     override fun setButton(
112         whichButton: Int,
113         text: CharSequence?,
114         listener: DialogInterface.OnClickListener?
115     ) {
116         super.setButton(whichButton, text, listener)
117         when (whichButton) {
118             DialogInterface.BUTTON_POSITIVE -> _positiveButtonClickListener = listener
119             DialogInterface.BUTTON_NEGATIVE -> _negativeButtonClickListener = listener
120             DialogInterface.BUTTON_NEUTRAL -> _neutralButtonClickListener = listener
121             else -> Unit
122         }
123     }
124 
125     /**
126      * Click one of the buttons in the [AlertDialog] and call the corresponding listener.
127      *
128      * Button ids are from [DialogInterface].
129      */
clickButtonnull130     fun clickButton(whichButton: Int) {
131         val listener =
132             when (whichButton) {
133                 DialogInterface.BUTTON_POSITIVE -> _positiveButtonClickListener
134                 DialogInterface.BUTTON_NEGATIVE -> _negativeButtonClickListener
135                 DialogInterface.BUTTON_NEUTRAL -> _neutralButtonClickListener
136                 else -> throw IllegalArgumentException("Wrong button $whichButton")
137             }
138         listener?.onClick(this, whichButton)
139         dismiss()
140     }
141 }
142