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

Commit 8134ebff authored by Fabián Kozynski's avatar Fabián Kozynski
Browse files

Add a CurrentTilesInteractor

This will be the layer that will interact with UI (like QSPanel,
QSCustomizer) as well as API (CommandQueue) to retrieve and modify the
list of current tiles. For now it exposes state methods for interop with
Java.

When the flag is off, it won't collect changes in the repository
(to prevent tiles being created twice). QSTileHost will be used.
When the flag is on, QSTileHost won't react to changes, and
CurrentTilesInteractor will be used to track the tiles.

Also, provide a QSHostAdapter that switches between QSTileHost and
CurrentTilesInteractor so the new behavior can be observed without
having to change consumers.

Flag: QS_PIPELINE_NEW_HOST
Test: atest com.android.systemui.qs
Test: atest android.host.systemui CtsTileServiceTestCases
Test: manual, enable flag and observe no difference
Fixes: 275597752

Change-Id: I3bafe78c7f191faca1a9dd6e7e9d22c88c70926f
parent 5be8ccc6
Loading
Loading
Loading
Loading
+6 −4
Original line number Diff line number Diff line
@@ -26,8 +26,6 @@ import com.android.systemui.R;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTileView;
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.util.leak.GarbageMonitor;

import java.util.ArrayList;
@@ -35,7 +33,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.List;

public interface QSHost extends PanelInteractor, CustomTileAddedRepository {
public interface QSHost {
    String TILES_SETTING = Settings.Secure.QS_TILES;
    int POSITION_AT_END = -1;

@@ -75,7 +73,11 @@ public interface QSHost extends PanelInteractor, CustomTileAddedRepository {
     * @see QSFactory#createTileView
     */
    QSTileView createTileView(Context themedContext, QSTile tile, boolean collapsedView);
    /** Create a {@link QSTile} of a {@code tileSpec} type. */
    /** Create a {@link QSTile} of a {@code tileSpec} type.
     *
     * This should only be called by classes that need to create one-off instances of tiles.
     * Do not use to create {@code custom} tiles without explicitly taking care of its lifecycle.
     */
    QSTile createTile(String tileSpec);

    /**
+226 −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.qs

import android.content.ComponentName
import android.content.Context
import androidx.annotation.GuardedBy
import com.android.internal.logging.InstanceId
import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.qs.QSTileView
import com.android.systemui.qs.external.TileServiceRequestController
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch

/**
 * Adapter to determine what real class to use for classes that depend on [QSHost].
 *
 * * When [Flags.QS_PIPELINE_NEW_HOST] is off, all calls will be routed to [QSTileHost].
 * * When [Flags.QS_PIPELINE_NEW_HOST] is on, calls regarding the current set of tiles will be
 *   routed to [CurrentTilesInteractor]. Other calls (like [warn]) will still be routed to
 *   [QSTileHost].
 *
 * This routing also includes dumps.
 */
@SysUISingleton
class QSHostAdapter
@Inject
constructor(
    private val qsTileHost: QSTileHost,
    private val interactor: CurrentTilesInteractor,
    private val context: Context,
    private val tileServiceRequestControllerBuilder: TileServiceRequestController.Builder,
    @Application private val scope: CoroutineScope,
    private val featureFlags: FeatureFlags,
    dumpManager: DumpManager,
) : QSHost {

    companion object {
        private const val TAG = "QSTileHost"
    }

    private val useNewHost = featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)

    @GuardedBy("callbacksMap") private val callbacksMap = mutableMapOf<QSHost.Callback, Job>()

    init {
        scope.launch { tileServiceRequestControllerBuilder.create(this@QSHostAdapter).init() }
        // Redirect dump to the correct host (needed for CTS tests)
        dumpManager.registerCriticalDumpable(
            TAG,
            if (useNewHost) interactor else qsTileHost
        )
    }

    override fun getTiles(): Collection<QSTile> {
        return if (useNewHost) {
            interactor.currentQSTiles
        } else {
            qsTileHost.getTiles()
        }
    }

    override fun getSpecs(): List<String> {
        return if (useNewHost) {
            interactor.currentTilesSpecs.map { it.spec }
        } else {
            qsTileHost.getSpecs()
        }
    }

    override fun removeTile(spec: String) {
        if (useNewHost) {
            interactor.removeTiles(listOf(TileSpec.create(spec)))
        } else {
            qsTileHost.removeTile(spec)
        }
    }

    override fun addCallback(callback: QSHost.Callback) {
        if (useNewHost) {
            val job =
                scope.launch {
                    interactor.currentTiles.collect { callback.onTilesChanged() }
                }
            synchronized(callbacksMap) { callbacksMap.put(callback, job) }
        } else {
            qsTileHost.addCallback(callback)
        }
    }

    override fun removeCallback(callback: QSHost.Callback) {
        if (useNewHost) {
            synchronized(callbacksMap) { callbacksMap.get(callback)?.cancel() }
        } else {
            qsTileHost.removeCallback(callback)
        }
    }

    override fun removeTiles(specs: Collection<String>) {
        if (useNewHost) {
            interactor.removeTiles(specs.map(TileSpec::create))
        } else {
            qsTileHost.removeTiles(specs)
        }
    }

    override fun removeTileByUser(component: ComponentName) {
        if (useNewHost) {
            interactor.removeTiles(listOf(TileSpec.create(component)))
        } else {
            qsTileHost.removeTileByUser(component)
        }
    }

    override fun addTile(spec: String, position: Int) {
        if (useNewHost) {
            interactor.addTile(TileSpec.create(spec), position)
        } else {
            qsTileHost.addTile(spec, position)
        }
    }

    override fun addTile(component: ComponentName, end: Boolean) {
        if (useNewHost) {
            interactor.addTile(
                TileSpec.create(component),
                if (end) POSITION_AT_END else 0
            )
        } else {
            qsTileHost.addTile(component, end)
        }
    }

    override fun changeTilesByUser(previousTiles: List<String>, newTiles: List<String>) {
        if (useNewHost) {
            interactor.setTiles(newTiles.map(TileSpec::create))
        } else {
            qsTileHost.changeTilesByUser(previousTiles, newTiles)
        }
    }

    override fun warn(message: String?, t: Throwable?) {
        qsTileHost.warn(message, t)
    }

    override fun getContext(): Context {
        return if (useNewHost) {
            context
        } else {
            qsTileHost.context
        }
    }

    override fun getUserContext(): Context {
        return if (useNewHost) {
            interactor.userContext.value
        } else {
            qsTileHost.userContext
        }
    }

    override fun getUserId(): Int {
        return if (useNewHost) {
            interactor.userId.value
        } else {
            qsTileHost.userId
        }
    }

    override fun getUiEventLogger(): UiEventLogger {
        return qsTileHost.uiEventLogger
    }

    override fun createTileView(
        themedContext: Context?,
        tile: QSTile?,
        collapsedView: Boolean
    ): QSTileView {
        return qsTileHost.createTileView(themedContext, tile, collapsedView)
    }

    override fun createTile(tileSpec: String): QSTile? {
        return qsTileHost.createTile(tileSpec)
    }

    override fun addTile(spec: String) {
        return addTile(spec, QSHost.POSITION_AT_END)
    }

    override fun addTile(tile: ComponentName) {
        return addTile(tile, false)
    }

    override fun indexOf(tileSpec: String): Int {
        return specs.indexOf(tileSpec)
    }

    override fun getNewInstanceId(): InstanceId {
        return qsTileHost.newInstanceId
    }
}
+15 −14
Original line number Diff line number Diff line
@@ -37,8 +37,9 @@ import com.android.systemui.ProtoDumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.nano.SystemUIProtoDump;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.qs.QSFactory;
@@ -48,9 +49,10 @@ import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.external.CustomTileStatePersister;
import com.android.systemui.qs.external.TileLifecycleManager;
import com.android.systemui.qs.external.TileServiceKey;
import com.android.systemui.qs.external.TileServiceRequestController;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.nano.QsTileState;
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.AutoTileManager;
@@ -85,7 +87,8 @@ import javax.inject.Provider;
 * This class also provides the interface for adding/removing/changing tiles.
 */
@SysUISingleton
public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable {
public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable,
        PanelInteractor, CustomTileAddedRepository {
    private static final String TAG = "QSTileHost";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
    private static final int MAX_QS_INSTANCE_ID = 1 << 20;
@@ -99,7 +102,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
    private final ArrayList<String> mTileSpecs = new ArrayList<>();
    private final TunerService mTunerService;
    private final PluginManager mPluginManager;
    private final DumpManager mDumpManager;
    private final QSLogger mQSLogger;
    private final UiEventLogger mUiEventLogger;
    private final InstanceIdSequence mInstanceIdSequence;
@@ -122,9 +124,10 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
    // This is enforced by only cleaning the flag at the end of a successful run of #onTuningChanged
    private boolean mTilesListDirty = true;

    private final TileServiceRequestController mTileServiceRequestController;
    private TileLifecycleManager.Factory mTileLifeCycleManagerFactory;

    private final FeatureFlags mFeatureFlags;

    @Inject
    public QSTileHost(Context context,
            QSFactory defaultFactory,
@@ -132,35 +135,32 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
            PluginManager pluginManager,
            TunerService tunerService,
            Provider<AutoTileManager> autoTiles,
            DumpManager dumpManager,
            Optional<CentralSurfaces> centralSurfacesOptional,
            QSLogger qsLogger,
            UiEventLogger uiEventLogger,
            UserTracker userTracker,
            SecureSettings secureSettings,
            CustomTileStatePersister customTileStatePersister,
            TileServiceRequestController.Builder tileServiceRequestControllerBuilder,
            TileLifecycleManager.Factory tileLifecycleManagerFactory,
            UserFileManager userFileManager
            UserFileManager userFileManager,
            FeatureFlags featureFlags
    ) {
        mContext = context;
        mUserContext = context;
        mTunerService = tunerService;
        mPluginManager = pluginManager;
        mDumpManager = dumpManager;
        mQSLogger = qsLogger;
        mUiEventLogger = uiEventLogger;
        mMainExecutor = mainExecutor;
        mTileServiceRequestController = tileServiceRequestControllerBuilder.create(this);
        mTileLifeCycleManagerFactory = tileLifecycleManagerFactory;
        mUserFileManager = userFileManager;
        mFeatureFlags = featureFlags;

        mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID);
        mCentralSurfacesOptional = centralSurfacesOptional;

        mQsFactories.add(defaultFactory);
        pluginManager.addPluginListener(this, QSFactory.class, true);
        mDumpManager.registerDumpable(TAG, this);
        mUserTracker = userTracker;
        mSecureSettings = secureSettings;
        mCustomTileStatePersister = customTileStatePersister;
@@ -172,7 +172,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
            tunerService.addTunable(this, TILES_SETTING);
            // AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
            mAutoTiles = autoTiles.get();
            mTileServiceRequestController.init();
        });
    }

@@ -186,8 +185,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
        mAutoTiles.destroy();
        mTunerService.removeTunable(this);
        mPluginManager.removePluginListener(this);
        mDumpManager.unregisterDumpable(TAG);
        mTileServiceRequestController.destroy();
    }

    @Override
@@ -300,6 +297,10 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
        if (!TILES_SETTING.equals(key)) {
            return;
        }
        // Do not process tiles if the flag is enabled.
        if (mFeatureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
            return;
        }
        if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
            newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
        }
+4 −3
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.qs.dagger
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QSHostAdapter
import com.android.systemui.qs.QSTileHost
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedSharedPrefsRepository
@@ -31,7 +32,7 @@ import dagger.Provides
@Module
interface QSHostModule {

    @Binds fun provideQsHost(controllerImpl: QSTileHost): QSHost
    @Binds fun provideQsHost(controllerImpl: QSHostAdapter): QSHost

    @Module
    companion object {
@@ -39,7 +40,7 @@ interface QSHostModule {
        @JvmStatic
        fun providePanelInteractor(
            featureFlags: FeatureFlags,
            qsHost: QSHost,
            qsHost: QSTileHost,
            panelInteractorImpl: PanelInteractorImpl
        ): PanelInteractor {
            return if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
@@ -53,7 +54,7 @@ interface QSHostModule {
        @JvmStatic
        fun provideCustomTileAddedRepository(
            featureFlags: FeatureFlags,
            qsHost: QSHost,
            qsHost: QSTileHost,
            customTileAddedRepository: CustomTileAddedSharedPrefsRepository
        ): CustomTileAddedRepository {
            return if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
+7 −0
Original line number Diff line number Diff line
@@ -22,6 +22,8 @@ import com.android.systemui.log.LogBufferFactory
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractorImpl
import com.android.systemui.qs.pipeline.prototyping.PrototypeCoreStartable
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import dagger.Binds
@@ -37,6 +39,11 @@ abstract class QSPipelineModule {
    @Binds
    abstract fun provideTileSpecRepository(impl: TileSpecSettingsRepository): TileSpecRepository

    @Binds
    abstract fun bindCurrentTilesInteractor(
        impl: CurrentTilesInteractorImpl
    ): CurrentTilesInteractor

    @Binds
    @IntoMap
    @ClassKey(PrototypeCoreStartable::class)
Loading