blob: 47da9fa487585e6b912177682b905a081e45fff1 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.cts
import android.R
import android.app.stubs.shared.NotificationHostActivity
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color
import android.test.AndroidTestCase
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.RemoteViews
import android.widget.TextView
import androidx.annotation.BoolRes
import androidx.annotation.DimenRes
import androidx.annotation.IdRes
import androidx.annotation.StringRes
import androidx.lifecycle.Lifecycle
import androidx.test.core.app.ActivityScenario
import kotlin.reflect.KClass
open class NotificationTemplateTestBase : AndroidTestCase() {
// Used to give time to visually inspect or attach a debugger before the checkViews block
protected var waitBeforeCheckingViews: Long = 0
protected fun checkIconView(views: RemoteViews, iconCheck: (ImageView) -> Unit) {
checkViews(views) {
iconCheck(requireViewByIdName("right_icon"))
}
}
protected fun checkViews(
views: RemoteViews,
@DimenRes heightDimen: Int? = null,
checker: NotificationHostActivity.() -> Unit
) {
val activityIntent = Intent(context, NotificationHostActivity::class.java)
activityIntent.putExtra(NotificationHostActivity.EXTRA_REMOTE_VIEWS, views)
heightDimen?.also {
activityIntent.putExtra(NotificationHostActivity.EXTRA_HEIGHT,
context.resources.getDimensionPixelSize(it))
}
ActivityScenario.launch<NotificationHostActivity>(activityIntent).use { scenario ->
scenario.moveToState(Lifecycle.State.RESUMED)
if (waitBeforeCheckingViews > 0) {
Thread.sleep(waitBeforeCheckingViews)
}
scenario.onActivity { activity ->
activity.checker()
}
}
}
protected fun createBitmap(width: Int, height: Int): Bitmap =
Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).apply {
// IMPORTANT: Pass current DisplayMetrics when creating a Bitmap, so that it
// receives the correct density. Otherwise, the Bitmap may get the default density
// (DisplayMetrics.DENSITY_DEVICE), which in some cases (eg. for apps running in
// compat mode) may be different from the actual density the app is rendered with.
// This would lead to the Bitmap eventually being rendered with different sizes,
// than the ones passed here.
density = context.resources.displayMetrics.densityDpi
eraseColor(Color.GRAY)
}
protected fun makeCustomContent(): RemoteViews {
val customContent = RemoteViews(mContext.packageName, R.layout.simple_list_item_1)
val textId = getAndroidRId("text1")
customContent.setTextViewText(textId, "Example Text")
return customContent
}
protected fun <T : View> NotificationHostActivity.requireViewByIdName(idName: String): T {
val viewId = getAndroidRId(idName)
return notificationRoot.findViewById<T>(viewId)
?: throw NullPointerException("No view with id: android.R.id.$idName ($viewId)")
}
protected fun <T : View> NotificationHostActivity.findViewByIdName(idName: String): T? =
notificationRoot.findViewById<T>(getAndroidRId(idName))
/** [Sequence] that yields all of the direct children of this [ViewGroup] */
private val ViewGroup.children
get() = sequence { for (i in 0 until childCount) yield(getChildAt(i)) }
private fun <T : View> collectViews(
view: View,
type: KClass<T>,
mutableList: MutableList<T>,
requireVisible: Boolean = true,
predicate: (T) -> Boolean
) {
if (requireVisible && view.visibility != View.VISIBLE) {
return
}
if (type.java.isInstance(view)) {
if (predicate(view as T)) {
mutableList.add(view)
}
}
if (view is ViewGroup) {
for (child in view.children) {
collectViews(child, type, mutableList, requireVisible, predicate)
}
}
}
protected fun NotificationHostActivity.requireViewWithText(text: String): TextView =
findViewWithText(text) ?: throw RuntimeException("Unable to find view with text: $text")
protected fun NotificationHostActivity.findViewWithText(text: String): TextView? {
val views: MutableList<TextView> = ArrayList()
collectViews(notificationRoot, TextView::class, views) { it.text?.toString() == text }
when (views.size) {
0 -> return null
1 -> return views[0]
else -> throw RuntimeException("Found multiple views with text: $text")
}
}
private fun getAndroidRes(resType: String, resName: String): Int =
mContext.resources.getIdentifier(resName, resType, "android")
@IdRes
protected fun getAndroidRId(idName: String): Int = getAndroidRes("id", idName)
@StringRes
protected fun getAndroidRString(stringName: String): Int = getAndroidRes("string", stringName)
@BoolRes
protected fun getAndroidRBool(boolName: String): Int = getAndroidRes("bool", boolName)
@DimenRes
protected fun getAndroidRDimen(dimenName: String) : Int = getAndroidRes("dimen", dimenName)
}