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

Commit 3e6315cd authored by Jacky Wang's avatar Jacky Wang
Browse files

[Catalyst] Take care of isFlagEnabled in PreferenceHierarchy

If the entry point of sub screen has flag disabled, exclude it from the
hierarchy to avoid applying metadata to UI widget by mistake.

Bug: 332201912
Flag: EXEMPT flag
Test: atest
Change-Id: Ic6bdf9c2b8f1819f3c5871ef607b43f0d824b35f
parent f6a30104
Loading
Loading
Loading
Loading
+28 −12
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.settingslib.metadata
import android.content.Context
import android.os.Bundle
import android.util.Log
import androidx.annotation.VisibleForTesting
import java.util.concurrent.CancellationException
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
@@ -56,7 +57,7 @@ class PreferenceHierarchy : PreferenceHierarchyNode {
     * Each item is either [PreferenceHierarchyNode], [PreferenceHierarchy] or [Deferred] (async sub
     * hierarchy).
     */
    private val children = mutableListOf<Any>()
    @VisibleForTesting internal val children = mutableListOf<Any>()

    internal constructor(context: Context, group: PreferenceGroup) : super(group) {
        this.context = context
@@ -95,7 +96,7 @@ class PreferenceHierarchy : PreferenceHierarchyNode {
     */
    infix fun String.args(args: Bundle) = createPreferenceScreenHierarchy(this, args)

    operator fun PreferenceHierarchyNode.unaryPlus() = also { children.add(it) }
    operator fun PreferenceHierarchyNode.unaryPlus() = also { add(it) }

    /** Specifies preference order in the hierarchy. */
    infix fun PreferenceHierarchyNode.order(order: Int) = apply { this.order = order }
@@ -108,10 +109,26 @@ class PreferenceHierarchy : PreferenceHierarchyNode {
    fun add(metadata: PreferenceMetadata, order: Int? = null) {
        PreferenceHierarchyNode(metadata).also {
            it.order = order
            children.add(it)
            add(it)
        }
    }

    private fun add(node: PreferenceHierarchyNode) {
        if (node.isFlagEnabled()) children.add(node)
    }

    private fun add(index: Int, node: PreferenceHierarchyNode) {
        if (node.isFlagEnabled()) children.add(index, node)
    }

    private fun PreferenceHierarchyNode.isFlagEnabled(): Boolean {
        if ((metadata as? PreferenceScreenMetadata)?.isFlagEnabled(context) == false) {
            Log.w(TAG, "$metadata is not added to hierarchy as it is not enabled")
            return false
        }
        return true
    }

    /**
     * Adds a sub hierarchy with coroutine.
     *
@@ -143,25 +160,25 @@ class PreferenceHierarchy : PreferenceHierarchyNode {
    /** Adds a preference to the hierarchy before given key. */
    fun addBefore(key: String, metadata: PreferenceMetadata) {
        val (hierarchy, index) = findPreference(key) ?: (this to children.size)
        hierarchy.children.add(index, PreferenceHierarchyNode(metadata))
        hierarchy.add(index, PreferenceHierarchyNode(metadata))
    }

    /** Adds a preference group to the hierarchy before given key. */
    fun addGroupBefore(key: String, group: PreferenceGroup): PreferenceHierarchy {
        val (hierarchy, index) = findPreference(key) ?: (this to children.size)
        return PreferenceHierarchy(context, group).also { hierarchy.children.add(index, it) }
        return PreferenceHierarchy(context, group).also { hierarchy.add(index, it) }
    }

    /** Adds a preference to the hierarchy after given key. */
    fun addAfter(key: String, metadata: PreferenceMetadata) {
        val (hierarchy, index) = findPreference(key) ?: (this to children.size - 1)
        hierarchy.children.add(index + 1, PreferenceHierarchyNode(metadata))
        hierarchy.add(index + 1, PreferenceHierarchyNode(metadata))
    }

    /** Adds a preference group to the hierarchy after given key. */
    fun addGroupAfter(key: String, group: PreferenceGroup): PreferenceHierarchy {
        val (hierarchy, index) = findPreference(key) ?: (this to children.size - 1)
        return PreferenceHierarchy(context, group).also { hierarchy.children.add(index + 1, it) }
        return PreferenceHierarchy(context, group).also { hierarchy.add(index + 1, it) }
    }

    /** Manipulates hierarchy on a preference group with given key. */
@@ -181,15 +198,14 @@ class PreferenceHierarchy : PreferenceHierarchyNode {
    }

    /** Adds a preference group to the hierarchy. */
    operator fun PreferenceGroup.unaryPlus() =
        PreferenceHierarchy(context, this).also { children.add(it) }
    operator fun PreferenceGroup.unaryPlus() = PreferenceHierarchy(context, this).also { add(it) }

    /** Adds a preference group and returns its preference hierarchy. */
    @JvmOverloads
    fun addGroup(group: PreferenceGroup, order: Int? = null): PreferenceHierarchy =
        PreferenceHierarchy(context, group).also {
            this.order = order
            children.add(it)
            it.order = order
            add(it)
        }

    /**
@@ -214,7 +230,7 @@ class PreferenceHierarchy : PreferenceHierarchyNode {
    fun addPreferenceScreen(screenKey: String) = addPreferenceScreen(screenKey, null)

    private fun addPreferenceScreen(screenKey: String, args: Bundle?): PreferenceHierarchyNode =
        createPreferenceScreenHierarchy(screenKey, args).also { children.add(it) }
        createPreferenceScreenHierarchy(screenKey, args).also { add(it) }

    private fun createPreferenceScreenHierarchy(screenKey: String, args: Bundle?) =
        PreferenceHierarchyNode(PreferenceScreenRegistry.create(context, screenKey, args)!!)
+2 −0
Original line number Diff line number Diff line
@@ -14,6 +14,8 @@ android_robolectric_test {
        "SettingsLibMetadata",
        "androidx.test.ext.junit",
        "kotlin-parcelize-runtime",
        "mockito-robolectric-prebuilt", // mockito deps order matters!
        "mockito-kotlin2",
        "truth",
    ],
    associates: ["SettingsLibMetadata"],
+77 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.settingslib.metadata

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub

@RunWith(AndroidJUnit4::class)
class PreferenceHierarchyTest {
    private val context: Context = ApplicationProvider.getApplicationContext()

    private val screen = mock<PreferenceScreenMetadata>()
    private val subScreen = mock<PreferenceScreenMetadata>()
    private val preference = mock<PreferenceMetadata> { on { key } doReturn "key" }

    @Test
    fun addScreen_flagDisabled() {
        subScreen.stub { on { isFlagEnabled(context) } doReturn false }
        val hierarchy =
            screen.preferenceHierarchy(context) {
                +subScreen
                +preference
                add(subScreen, 1)
                addBefore(preference.key, subScreen)
                addAfter(preference.key, subScreen)
                addGroup(subScreen)
                addGroupBefore(preference.key, subScreen)
                addGroupAfter(preference.key, subScreen)
            }
        assertThat(hierarchy.children).hasSize(1)
        (hierarchy.children[0] as PreferenceHierarchyNode).apply {
            assertThat(metadata).isSameInstanceAs(preference)
        }
    }

    @Test
    fun addScreen_flagEnabled() {
        subScreen.stub { on { isFlagEnabled(context) } doReturn true }
        val hierarchy = screen.preferenceHierarchy(context) { +subScreen }
        assertThat(hierarchy.children).hasSize(1)
        (hierarchy.children[0] as PreferenceHierarchyNode).apply {
            assertThat(metadata).isSameInstanceAs(subScreen)
        }
    }

    @Test
    fun addScreen_flagEnabled_withOrder() {
        subScreen.stub { on { isFlagEnabled(context) } doReturn true }
        val hierarchy = screen.preferenceHierarchy(context) { +subScreen order 1 }
        assertThat(hierarchy.children).hasSize(1)
        (hierarchy.children[0] as PreferenceHierarchyNode).apply {
            assertThat(metadata).isSameInstanceAs(subScreen)
            assertThat(order).isEqualTo(1)
        }
    }
}