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