• 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 androidx.test.platform.app.InstrumentationRegistry
36 import kotlin.reflect.KClass
37 
38 open class NotificationTemplateTestBase : AndroidTestCase() {
39 
40     // Used to give time to visually inspect or attach a debugger before the checkViews block
41     protected var waitBeforeCheckingViews: Long = 0
42 
43     override fun setUp() {
44         super.setUp()
45         CtsAppTestUtils.turnScreenOn(InstrumentationRegistry.getInstrumentation(), mContext)
46     }
47 
48     protected fun checkIconView(views: RemoteViews, iconCheck: (ImageView) -> Unit) {
49         checkViews(views) {
50             iconCheck(requireViewByIdName("right_icon"))
51         }
52     }
53 
54     protected fun checkViews(
55         views: RemoteViews,
56         @DimenRes heightDimen: Int? = null,
57         checker: NotificationHostActivity.() -> Unit
58     ) {
59         val activityIntent = Intent(context, NotificationHostActivity::class.java)
60         activityIntent.putExtra(NotificationHostActivity.EXTRA_REMOTE_VIEWS, views)
61         heightDimen?.also {
62             activityIntent.putExtra(NotificationHostActivity.EXTRA_HEIGHT,
63                     context.resources.getDimensionPixelSize(it))
64         }
65         ActivityScenario.launch<NotificationHostActivity>(activityIntent).use { scenario ->
66             scenario.moveToState(Lifecycle.State.RESUMED)
67             if (waitBeforeCheckingViews > 0) {
68                 Thread.sleep(waitBeforeCheckingViews)
69             }
70             scenario.onActivity { activity ->
71                 activity.checker()
72             }
73         }
74     }
75 
76     protected fun createBitmap(width: Int, height: Int): Bitmap =
77             Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).apply {
78                 // IMPORTANT: Pass current DisplayMetrics when creating a Bitmap, so that it
79                 // receives the correct density. Otherwise, the Bitmap may get the default density
80                 // (DisplayMetrics.DENSITY_DEVICE), which in some cases (eg. for apps running in
81                 // compat mode) may be different from the actual density the app is rendered with.
82                 // This would lead to the Bitmap eventually being rendered with different sizes,
83                 // than the ones passed here.
84                 density = context.resources.displayMetrics.densityDpi
85 
86                 eraseColor(Color.GRAY)
87             }
88 
89     protected fun makeCustomContent(): RemoteViews {
90         val customContent = RemoteViews(mContext.packageName, R.layout.simple_list_item_1)
91         val textId = getAndroidRId("text1")
92         customContent.setTextViewText(textId, "Example Text")
93         return customContent
94     }
95 
96     protected fun <T : View> NotificationHostActivity.requireViewByIdName(idName: String): T {
97         val viewId = getAndroidRId(idName)
98         return notificationRoot.findViewById<T>(viewId)
99                 ?: throw NullPointerException("No view with id: android.R.id.$idName ($viewId)")
100     }
101 
102     protected fun <T : View> NotificationHostActivity.findViewByIdName(idName: String): T? =
103             notificationRoot.findViewById<T>(getAndroidRId(idName))
104 
105     /** [Sequence] that yields all of the direct children of this [ViewGroup] */
106     private val ViewGroup.children
107         get() = sequence { for (i in 0 until childCount) yield(getChildAt(i)) }
108 
109     private fun <T : View> collectViews(
110         view: View,
111         type: KClass<T>,
112         mutableList: MutableList<T>,
113         requireVisible: Boolean = true,
114         predicate: (T) -> Boolean
115     ) {
116         if (requireVisible && view.visibility != View.VISIBLE) {
117             return
118         }
119         if (type.java.isInstance(view)) {
120             if (predicate(view as T)) {
121                 mutableList.add(view)
122             }
123         }
124         if (view is ViewGroup) {
125             for (child in view.children) {
126                 collectViews(child, type, mutableList, requireVisible, predicate)
127             }
128         }
129     }
130 
131     protected fun NotificationHostActivity.requireViewWithText(text: String): TextView =
132             findViewWithText(text) ?: throw RuntimeException("Unable to find view with text: $text")
133 
134     protected fun NotificationHostActivity.findViewWithText(text: String): TextView? {
135         val views: MutableList<TextView> = ArrayList()
136         collectViews(notificationRoot, TextView::class, views) { it.text?.toString() == text }
137         when (views.size) {
138             0 -> return null
139             1 -> return views[0]
140             else -> throw RuntimeException("Found multiple views with text: $text")
141         }
142     }
143 
144     private fun getAndroidRes(resType: String, resName: String): Int =
145             mContext.resources.getIdentifier(resName, resType, "android")
146 
147     @IdRes
148     protected fun getAndroidRId(idName: String): Int = getAndroidRes("id", idName)
149 
150     @StringRes
151     protected fun getAndroidRString(stringName: String): Int = getAndroidRes("string", stringName)
152 
153     @BoolRes
154     protected fun getAndroidRBool(boolName: String): Int = getAndroidRes("bool", boolName)
155 
156     @DimenRes
157     protected fun getAndroidRDimen(dimenName: String): Int = getAndroidRes("dimen", dimenName)
158 }
159