1 /* <lambda>null2 * Copyright (C) 2021 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 package android.app.cts 17 18 import android.R 19 import android.app.stubs.shared.NotificationHostActivity 20 import android.content.Intent 21 import android.graphics.Bitmap 22 import android.graphics.Color 23 import android.test.AndroidTestCase 24 import android.view.View 25 import android.view.ViewGroup 26 import android.widget.ImageView 27 import android.widget.RemoteViews 28 import android.widget.TextView 29 import androidx.annotation.BoolRes 30 import androidx.annotation.DimenRes 31 import androidx.annotation.IdRes 32 import androidx.annotation.StringRes 33 import androidx.lifecycle.Lifecycle 34 import androidx.test.core.app.ActivityScenario 35 import kotlin.reflect.KClass 36 37 open class NotificationTemplateTestBase : AndroidTestCase() { 38 39 // Used to give time to visually inspect or attach a debugger before the checkViews block 40 protected var waitBeforeCheckingViews: Long = 0 41 42 protected fun checkIconView(views: RemoteViews, iconCheck: (ImageView) -> Unit) { 43 checkViews(views) { 44 iconCheck(requireViewByIdName("right_icon")) 45 } 46 } 47 48 protected fun checkViews( 49 views: RemoteViews, 50 @DimenRes heightDimen: Int? = null, 51 checker: NotificationHostActivity.() -> Unit 52 ) { 53 val activityIntent = Intent(context, NotificationHostActivity::class.java) 54 activityIntent.putExtra(NotificationHostActivity.EXTRA_REMOTE_VIEWS, views) 55 heightDimen?.also { 56 activityIntent.putExtra(NotificationHostActivity.EXTRA_HEIGHT, 57 context.resources.getDimensionPixelSize(it)) 58 } 59 ActivityScenario.launch<NotificationHostActivity>(activityIntent).use { scenario -> 60 scenario.moveToState(Lifecycle.State.RESUMED) 61 if (waitBeforeCheckingViews > 0) { 62 Thread.sleep(waitBeforeCheckingViews) 63 } 64 scenario.onActivity { activity -> 65 activity.checker() 66 } 67 } 68 } 69 70 protected fun createBitmap(width: Int, height: Int): Bitmap = 71 Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).also { 72 it.eraseColor(Color.GRAY) 73 } 74 75 protected fun makeCustomContent(): RemoteViews { 76 val customContent = RemoteViews(mContext.packageName, R.layout.simple_list_item_1) 77 val textId = getAndroidRId("text1") 78 customContent.setTextViewText(textId, "Example Text") 79 return customContent 80 } 81 82 protected fun <T : View> NotificationHostActivity.requireViewByIdName(idName: String): T { 83 val viewId = getAndroidRId(idName) 84 return notificationRoot.findViewById<T>(viewId) 85 ?: throw NullPointerException("No view with id: android.R.id.$idName ($viewId)") 86 } 87 88 protected fun <T : View> NotificationHostActivity.findViewByIdName(idName: String): T? = 89 notificationRoot.findViewById<T>(getAndroidRId(idName)) 90 91 /** [Sequence] that yields all of the direct children of this [ViewGroup] */ 92 private val ViewGroup.children 93 get() = sequence { for (i in 0 until childCount) yield(getChildAt(i)) } 94 95 private fun <T : View> collectViews( 96 view: View, 97 type: KClass<T>, 98 mutableList: MutableList<T>, 99 requireVisible: Boolean = true, 100 predicate: (T) -> Boolean 101 ) { 102 if (requireVisible && view.visibility != View.VISIBLE) { 103 return 104 } 105 if (type.java.isInstance(view)) { 106 if (predicate(view as T)) { 107 mutableList.add(view) 108 } 109 } 110 if (view is ViewGroup) { 111 for (child in view.children) { 112 collectViews(child, type, mutableList, requireVisible, predicate) 113 } 114 } 115 } 116 117 protected fun NotificationHostActivity.requireViewWithText(text: String): TextView = 118 findViewWithText(text) ?: throw RuntimeException("Unable to find view with text: $text") 119 120 protected fun NotificationHostActivity.findViewWithText(text: String): TextView? { 121 val views: MutableList<TextView> = ArrayList() 122 collectViews(notificationRoot, TextView::class, views) { it.text?.toString() == text } 123 when (views.size) { 124 0 -> return null 125 1 -> return views[0] 126 else -> throw RuntimeException("Found multiple views with text: $text") 127 } 128 } 129 130 private fun getAndroidRes(resType: String, resName: String): Int = 131 mContext.resources.getIdentifier(resName, resType, "android") 132 133 @IdRes 134 protected fun getAndroidRId(idName: String): Int = getAndroidRes("id", idName) 135 136 @StringRes 137 protected fun getAndroidRString(stringName: String): Int = getAndroidRes("string", stringName) 138 139 @BoolRes 140 protected fun getAndroidRBool(boolName: String): Int = getAndroidRes("bool", boolName) 141 }