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

Commit 3be0a765 authored by Luna Zhang's avatar Luna Zhang Committed by Android (Google) Code Review
Browse files

Merge "Create BluetoothDetailsViewModel and embed bluetooth xml dialog in it." into main

parents 50ca289a 220b3219
Loading
Loading
Loading
Loading
+33 −71
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dump.nano.SystemUIProtoDump
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.qs.QSTile.BooleanState
import com.android.systemui.plugins.qs.TileDetailsViewModel
import com.android.systemui.qs.FakeQSFactory
import com.android.systemui.qs.FakeQSTile
import com.android.systemui.qs.external.CustomTile
@@ -154,7 +155,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
                    TileSpec.create("e"),
                    CUSTOM_TILE_SPEC,
                    TileSpec.create("d"),
                    TileSpec.create("non_existent")
                    TileSpec.create("non_existent"),
                )
            tileSpecRepository.setTiles(USER_INFO_0.id, specs)

@@ -190,11 +191,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
    @Test
    fun logTileCreated() =
        testScope.runTest(USER_INFO_0) {
            val specs =
                listOf(
                    TileSpec.create("a"),
                    CUSTOM_TILE_SPEC,
                )
            val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
            runCurrent()

@@ -204,10 +201,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
    @Test
    fun logTileNotFoundInFactory() =
        testScope.runTest(USER_INFO_0) {
            val specs =
                listOf(
                    TileSpec.create("non_existing"),
                )
            val specs = listOf(TileSpec.create("non_existing"))
            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
            runCurrent()

@@ -218,10 +212,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
    @Test
    fun tileNotAvailableDestroyed_logged() =
        testScope.runTest(USER_INFO_0) {
            val specs =
                listOf(
                    TileSpec.create("e"),
                )
            val specs = listOf(TileSpec.create("e"))
            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
            runCurrent()

@@ -229,7 +220,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
            verify(logger)
                .logTileDestroyed(
                    specs[0],
                    QSPipelineLogger.TileDestroyedReason.NEW_TILE_NOT_AVAILABLE
                    QSPipelineLogger.TileDestroyedReason.NEW_TILE_NOT_AVAILABLE,
                )
        }

@@ -238,11 +229,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
        testScope.runTest(USER_INFO_0) {
            val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))

            val specs =
                listOf(
                    TileSpec.create("a"),
                    TileSpec.create("e"),
                )
            val specs = listOf(TileSpec.create("a"), TileSpec.create("e"))
            tileSpecRepository.setTiles(USER_INFO_0.id, specs)

            assertThat(tiles).isEqualTo(listOf(TileSpec.create("a")))
@@ -266,10 +253,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
        testScope.runTest(USER_INFO_0) {
            val tiles by collectLastValue(underTest.currentTiles)

            val specs =
                listOf(
                    TileSpec.create("a"),
                )
            val specs = listOf(TileSpec.create("a"))

            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
            val originalTileA = tiles!![0].tile
@@ -299,7 +283,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
            verify(logger)
                .logTileDestroyed(
                    TileSpec.create("c"),
                    QSPipelineLogger.TileDestroyedReason.TILE_REMOVED
                    QSPipelineLogger.TileDestroyedReason.TILE_REMOVED,
                )
        }

@@ -325,7 +309,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
            verify(logger)
                .logTileDestroyed(
                    TileSpec.create("a"),
                    QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE
                    QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE,
                )

            assertThat(tiles?.size).isEqualTo(1)
@@ -370,7 +354,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
            verify(logger)
                .logTileDestroyed(
                    specs0[0],
                    QSPipelineLogger.TileDestroyedReason.TILE_NOT_PRESENT_IN_NEW_USER
                    QSPipelineLogger.TileDestroyedReason.TILE_NOT_PRESENT_IN_NEW_USER,
                )
        }

@@ -418,21 +402,12 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
        testScope.runTest(USER_INFO_0) {
            val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
            val spec = TileSpec.create("a")
            val currentSpecs =
                listOf(
                    TileSpec.create("b"),
                    TileSpec.create("c"),
                )
            val currentSpecs = listOf(TileSpec.create("b"), TileSpec.create("c"))
            tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)

            underTest.addTile(spec, position = 1)

            val expectedSpecs =
                listOf(
                    TileSpec.create("b"),
                    spec,
                    TileSpec.create("c"),
                )
            val expectedSpecs = listOf(TileSpec.create("b"), spec, TileSpec.create("c"))
            assertThat(tiles).isEqualTo(expectedSpecs)
        }

@@ -442,11 +417,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
            val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
            val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id))
            val spec = TileSpec.create("a")
            val currentSpecs =
                listOf(
                    TileSpec.create("b"),
                    TileSpec.create("c"),
                )
            val currentSpecs = listOf(TileSpec.create("b"), TileSpec.create("c"))
            tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
            tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs)

@@ -455,12 +426,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {

            assertThat(tiles0).isEqualTo(currentSpecs)

            val expectedSpecs =
                listOf(
                    TileSpec.create("b"),
                    spec,
                    TileSpec.create("c"),
                )
            val expectedSpecs = listOf(TileSpec.create("b"), spec, TileSpec.create("c"))
            assertThat(tiles1).isEqualTo(expectedSpecs)
        }

@@ -515,11 +481,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
            val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
            val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id))
            val currentSpecs =
                listOf(
                    TileSpec.create("a"),
                    TileSpec.create("b"),
                    TileSpec.create("c"),
                )
                listOf(TileSpec.create("a"), TileSpec.create("b"), TileSpec.create("c"))
            tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
            tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs)

@@ -557,11 +519,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
            tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
            runCurrent()

            val newSpecs =
                listOf(
                    otherCustomTileSpec,
                    TileSpec.create("a"),
                )
            val newSpecs = listOf(otherCustomTileSpec, TileSpec.create("a"))

            underTest.setTiles(newSpecs)
            runCurrent()
@@ -615,7 +573,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {

            tileSpecRepository.setTiles(
                USER_INFO_0.id,
                listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
                listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC),
            )
            val newTileA = tiles!![0].tile
            assertThat(tileA).isSameInstanceAs(newTileA)
@@ -650,7 +608,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {

            installedTilesPackageRepository.setInstalledPackagesForUser(
                USER_INFO_0.id,
                setOf(TEST_COMPONENT)
                setOf(TEST_COMPONENT),
            )

            assertThat(tiles!!.size).isEqualTo(3)
@@ -676,10 +634,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
    @Test
    fun changeInPackagesTiles_doesntTriggerUserChange_logged() =
        testScope.runTest(USER_INFO_0) {
            val specs =
                listOf(
                    TileSpec.create("a"),
                )
            val specs = listOf(TileSpec.create("a"))
            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
            runCurrent()
            // Settled on the same list of tiles.
@@ -691,7 +646,6 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
            verify(logger, never()).logTileUserChanged(TileSpec.create("a"), 0)
        }


    @Test
    fun getTileDetails() =
        testScope.runTest(USER_INFO_0) {
@@ -711,11 +665,19 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
            assertThat(tiles!![2].spec).isEqualTo(tileNoDetails)
            (tiles!![2].tile as FakeQSTile).hasDetailsViewModel = false

            assertThat(tiles!![0].tile.detailsViewModel.getTitle()).isEqualTo("a")
            assertThat(tiles!![1].tile.detailsViewModel.getTitle()).isEqualTo("b")
            assertThat(tiles!![2].tile.detailsViewModel).isNull()
        }
            var currentModel: TileDetailsViewModel? = null
            val setCurrentModel = { model: TileDetailsViewModel? -> currentModel = model }
            tiles!![0].tile.getDetailsViewModel(setCurrentModel)
            assertThat(currentModel?.getTitle()).isEqualTo("a")

            currentModel = null
            tiles!![1].tile.getDetailsViewModel(setCurrentModel)
            assertThat(currentModel?.getTitle()).isEqualTo("b")

            currentModel = null
            tiles!![2].tile.getDetailsViewModel(setCurrentModel)
            assertThat(currentModel).isNull()
        }

    private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) {
        this.state = state
@@ -745,7 +707,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
                    customTileAddedRepository.setTileAdded(
                        CUSTOM_TILE_SPEC.componentName,
                        currentUser,
                        true
                        true,
                    )
                }
            in VALID_TILES -> FakeQSTile(currentUser, available = spec !in unavailableTiles)
+25 −1
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import com.android.systemui.plugins.qs.QSTile.Icon;
import com.android.systemui.plugins.qs.QSTile.State;

import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;

@ProvidesInterface(version = QSTile.VERSION)
@@ -121,10 +122,33 @@ public interface QSTile {
     */
    boolean isListening();

    /**
     * Get this tile's {@link TileDetailsViewModel} through a callback.
     *
     * Please only override this method if the tile can't get its {@link TileDetailsViewModel}
     * synchronously and thus need a callback to defer it.
     *
     * @return a boolean indicating whether this tile has a {@link TileDetailsViewModel}. The tile's
     * {@link TileDetailsViewModel} will be passed to the callback. Please always return true when
     * overriding this method. Return false will make the tile display its dialog instead of details
     * view, and it will not wait for the callback to be returned before proceeding to show the
     * dialog.
     */
    default boolean getDetailsViewModel(Consumer<TileDetailsViewModel> callback) {
        TileDetailsViewModel tileDetailsViewModel = getDetailsViewModel();
        callback.accept(tileDetailsViewModel);
        return tileDetailsViewModel != null;
    }

    /**
     * Return this tile's {@link TileDetailsViewModel} to be used to render the TileDetailsView.
     *
     * Please only override this method if the tile doesn't need a callback to set its
     * {@link TileDetailsViewModel}.
     */
    default TileDetailsViewModel getDetailsViewModel() { return null; }
    default TileDetailsViewModel getDetailsViewModel() {
        return null;
    }

    @ProvidesInterface(version = Callback.VERSION)
    interface Callback {
+56 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.bluetooth.qsdialog

import android.view.LayoutInflater
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import com.android.systemui.plugins.qs.TileDetailsViewModel
import com.android.systemui.res.R

class BluetoothDetailsViewModel(onLongClick: () -> Unit) : TileDetailsViewModel() {
    private val _onLongClick = onLongClick

    @Composable
    override fun GetContentView() {
        AndroidView(
            modifier = Modifier.fillMaxWidth().fillMaxHeight(),
            factory = { context ->
                // Inflate with the existing dialog xml layout
                LayoutInflater.from(context).inflate(R.layout.bluetooth_tile_dialog, null)
                // TODO: b/378513956 - Implement the bluetooth details view
            },
        )
    }

    override fun clickOnSettingsButton() {
        _onLongClick()
    }

    override fun getTitle(): String {
        // TODO: b/378513956 Update the placeholder text
        return "Bluetooth"
    }

    override fun getSubTitle(): String {
        // TODO: b/378513956 Update the placeholder text
        return "Tap to connect or disconnect a device"
    }
}
+10 −10
Original line number Diff line number Diff line
@@ -23,11 +23,9 @@ import com.android.systemui.plugins.qs.TileDetailsViewModel
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow

@SysUISingleton
class DetailsViewModel
@Inject constructor(val currentTilesInteractor: CurrentTilesInteractor) {
class DetailsViewModel @Inject constructor(val currentTilesInteractor: CurrentTilesInteractor) {

    /**
     * The current active [TileDetailsViewModel]. If it's `null`, it means the qs overlay is not
@@ -38,6 +36,7 @@ class DetailsViewModel

    /**
     * Update the active [TileDetailsViewModel] to `null`.
     *
     * @see activeTileDetails
     */
    fun closeDetailedView() {
@@ -45,8 +44,9 @@ class DetailsViewModel
    }

    /**
     * Update the active [TileDetailsViewModel] to the `spec`'s corresponding view model.
     * Return if the [TileDetailsViewModel] is successfully found.
     * Update the active [TileDetailsViewModel] to the `spec`'s corresponding view model. Return if
     * the [TileDetailsViewModel] is successfully handled.
     *
     * @see activeTileDetails
     */
    fun onTileClicked(spec: TileSpec?): Boolean {
@@ -55,11 +55,11 @@ class DetailsViewModel
            return false
        }

        _activeTileDetails.value = currentTilesInteractor
            .currentQSTiles
            .firstOrNull { it.tileSpec == spec.spec }
            ?.detailsViewModel
        val currentTile =
            currentTilesInteractor.currentQSTiles.firstOrNull { it.tileSpec == spec.spec }

        return _activeTileDetails.value != null
        return currentTile?.getDetailsViewModel { detailsViewModel ->
            _activeTileDetails.value = detailsViewModel
        } ?: false
    }
}
+20 −2
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.satellite.SatelliteDialogUtils;
import com.android.systemui.animation.Expandable;
import com.android.systemui.bluetooth.qsdialog.BluetoothDetailsViewModel;
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -51,6 +52,7 @@ import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.plugins.qs.TileDetailsViewModel;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
@@ -63,6 +65,7 @@ import kotlinx.coroutines.Job;

import java.util.List;
import java.util.concurrent.Executor;
import java.util.function.Consumer;

import javax.inject.Inject;

@@ -121,6 +124,21 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {

    @Override
    protected void handleClick(@Nullable Expandable expandable) {
        handleClickWithSatelliteCheck(() -> handleClickEvent(expandable));
    }

    @Override
    public boolean getDetailsViewModel(Consumer<TileDetailsViewModel> callback) {
        handleClickWithSatelliteCheck(() ->
                callback.accept(new BluetoothDetailsViewModel(() -> {
                    longClick(null);
                    return null;
                }))
        );
        return true;
    }

    private void handleClickWithSatelliteCheck(Runnable clickCallback) {
        if (com.android.internal.telephony.flags.Flags.oemEnabledSatelliteFlag()) {
            if (mClickJob != null && !mClickJob.isCompleted()) {
                return;
@@ -130,12 +148,12 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
                        if (!isAllowClick) {
                            return null;
                        }
                        handleClickEvent(expandable);
                        clickCallback.run();
                        return null;
                    });
            return;
        }
        handleClickEvent(expandable);
        clickCallback.run();
    }

    private void handleClickEvent(@Nullable Expandable expandable) {
Loading