Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5807e5e9 authored by Ibrahim Yilmaz's avatar Ibrahim Yilmaz
Browse files

Create PrecomputedTextView for Notifications

This CL adds PrecomputedTextView and pre computed text version of ImageFloatingTextView to SystemUI.
These TextViews integrated to NotifLayoutInflaterFactory and configured with Feature Flag.
Bug: 289250881
Test: atest TextPrecomputerTest

Change-Id: I05d4814be67d7fce4e4befa766ac5d98709120ce
parent 7283fc8b
Loading
Loading
Loading
Loading
+32 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 com.android.systemui.statusbar.notification.row

import android.content.Context
import android.util.AttributeSet
import android.widget.RemoteViews
import com.android.internal.widget.ImageFloatingTextView

/** Precomputed version of [ImageFloatingTextView] */
@RemoteViews.RemoteView
class PrecomputedImageFloatingTextView
@JvmOverloads
constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
    ImageFloatingTextView(context, attrs, defStyleAttr), TextPrecomputer {

    override fun setTextAsync(text: CharSequence?): Runnable = precompute(this, text)
}
+34 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 com.android.systemui.statusbar.notification.row

import android.content.Context
import android.util.AttributeSet
import android.widget.RemoteViews
import android.widget.TextView

/**
 * A user interface element that uses the PrecomputedText API to display text in a notification,
 * with the help of RemoteViews.
 */
@RemoteViews.RemoteView
class PrecomputedTextView
@JvmOverloads
constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
    TextView(context, attrs, defStyleAttr), TextPrecomputer {

    override fun setTextAsync(text: CharSequence?): Runnable = precompute(this, text)
}
+59 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 com.android.systemui.statusbar.notification.row

import android.text.PrecomputedText
import android.text.Spannable
import android.util.Log
import android.widget.TextView

internal interface TextPrecomputer {
    /**
     * Creates PrecomputedText from given text and returns a runnable which sets precomputed text to
     * the textview on main thread.
     *
     * @param text text to be converted to PrecomputedText
     * @return Runnable that sets precomputed text on the main thread
     */
    fun precompute(
        textView: TextView,
        text: CharSequence?,
        logException: Boolean = true
    ): Runnable {
        val precomputedText: Spannable? =
            text?.let { PrecomputedText.create(it, textView.textMetricsParams) }

        return Runnable {
            try {
                textView.text = precomputedText
            } catch (exception: IllegalArgumentException) {
                if (logException) {
                    Log.wtf(
                        /* tag = */ TAG,
                        /* msg = */ "PrecomputedText setText failed for TextView:$textView",
                        /* tr = */ exception
                    )
                }
                textView.text = text
            }
        }
    }

    private companion object {
        private const val TAG = "TextPrecomputer"
    }
}
+95 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 com.android.systemui.statusbar.notification.row

import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.text.PrecomputedText
import android.text.TextPaint
import android.widget.TextView
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
class TextPrecomputerTest : SysuiTestCase() {

    private lateinit var textPrecomputer: TextPrecomputer

    private lateinit var textView: TextView

    @Before
    fun before() {
        textPrecomputer = object : TextPrecomputer {}
        textView = TextView(mContext)
    }

    @Test
    fun precompute_returnRunnable() {
        // WHEN
        val precomputeResult = textPrecomputer.precompute(textView, TEXT)

        // THEN
        assertThat(precomputeResult).isInstanceOf(Runnable::class.java)
    }

    @Test
    fun precomputeRunnable_anyText_setPrecomputedText() {
        // WHEN
        textPrecomputer.precompute(textView, TEXT).run()

        // THEN
        assertThat(textView.text).isInstanceOf(PrecomputedText::class.java)
    }

    @Test
    fun precomputeRunnable_differentPrecomputedTextConfig_notSetPrecomputedText() {
        // GIVEN
        val precomputedTextRunnable =
            textPrecomputer.precompute(textView, TEXT, logException = false)

        // WHEN
        textView.textMetricsParams = PrecomputedText.Params.Builder(PAINT).build()
        precomputedTextRunnable.run()

        // THEN
        assertThat(textView.text).isInstanceOf(String::class.java)
    }

    @Test
    fun precomputeRunnable_nullText_setNull() {
        // GIVEN
        textView.text = TEXT
        val precomputedTextRunnable = textPrecomputer.precompute(textView, null)

        // WHEN
        precomputedTextRunnable.run()

        // THEN
        assertThat(textView.text).isEqualTo("")
    }

    private companion object {
        private val PAINT = TextPaint()
        private const val TEXT = "Example Notification Test"
    }
}