• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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).apply {
72                 // IMPORTANT: Pass current DisplayMetrics when creating a Bitmap, so that it
73                 // receives the correct density. Otherwise, the Bitmap may get the default density
74                 // (DisplayMetrics.DENSITY_DEVICE), which in some cases (eg. for apps running in
75                 // compat mode) may be different from the actual density the app is rendered with.
76                 // This would lead to the Bitmap eventually being rendered with different sizes,
77                 // than the ones passed here.
78                 density = context.resources.displayMetrics.densityDpi
79 
80                 eraseColor(Color.GRAY)
81             }
82 
83     protected fun makeCustomContent(): RemoteViews {
84         val customContent = RemoteViews(mContext.packageName, R.layout.simple_list_item_1)
85         val textId = getAndroidRId("text1")
86         customContent.setTextViewText(textId, "Example Text")
87         return customContent
88     }
89 
90     protected fun <T : View> NotificationHostActivity.requireViewByIdName(idName: String): T {
91         val viewId = getAndroidRId(idName)
92         return notificationRoot.findViewById<T>(viewId)
93                 ?: throw NullPointerException("No view with id: android.R.id.$idName ($viewId)")
94     }
95 
96     protected fun <T : View> NotificationHostActivity.findViewByIdName(idName: String): T? =
97             notificationRoot.findViewById<T>(getAndroidRId(idName))
98 
99     /** [Sequence] that yields all of the direct children of this [ViewGroup] */
100     private val ViewGroup.children
101         get() = sequence { for (i in 0 until childCount) yield(getChildAt(i)) }
102 
103     private fun <T : View> collectViews(
104         view: View,
105         type: KClass<T>,
106         mutableList: MutableList<T>,
107         requireVisible: Boolean = true,
108         predicate: (T) -> Boolean
109     ) {
110         if (requireVisible && view.visibility != View.VISIBLE) {
111             return
112         }
113         if (type.java.isInstance(view)) {
114             if (predicate(view as T)) {
115                 mutableList.add(view)
116             }
117         }
118         if (view is ViewGroup) {
119             for (child in view.children) {
120                 collectViews(child, type, mutableList, requireVisible, predicate)
121             }
122         }
123     }
124 
125     protected fun NotificationHostActivity.requireViewWithText(text: String): TextView =
126             findViewWithText(text) ?: throw RuntimeException("Unable to find view with text: $text")
127 
128     protected fun NotificationHostActivity.findViewWithText(text: String): TextView? {
129         val views: MutableList<TextView> = ArrayList()
130         collectViews(notificationRoot, TextView::class, views) { it.text?.toString() == text }
131         when (views.size) {
132             0 -> return null
133             1 -> return views[0]
134             else -> throw RuntimeException("Found multiple views with text: $text")
135         }
136     }
137 
138     private fun getAndroidRes(resType: String, resName: String): Int =
139             mContext.resources.getIdentifier(resName, resType, "android")
140 
141     @IdRes
142     protected fun getAndroidRId(idName: String): Int = getAndroidRes("id", idName)
143 
144     @StringRes
145     protected fun getAndroidRString(stringName: String): Int = getAndroidRes("string", stringName)
146 
147     @BoolRes
148     protected fun getAndroidRBool(boolName: String): Int = getAndroidRes("bool", boolName)
149 
150     @DimenRes
151     protected fun getAndroidRDimen(dimenName: String) : Int = getAndroidRes("dimen", dimenName)
152 }