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

Commit 6308c3f6 authored by Bryce Lee's avatar Bryce Lee Committed by Android (Google) Code Review
Browse files

Merge "Dedicated Smartspace handling for Dreams." into tm-dev

parents 577dcdbb f55ef591
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.Recents;
import com.android.systemui.screenshot.dagger.ScreenshotModule;
import com.android.systemui.settings.dagger.SettingsModule;
import com.android.systemui.smartspace.dagger.SmartspaceModule;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -119,6 +120,7 @@ import dagger.Provides;
            SettingsModule.class,
            SettingsUtilModule.class,
            SmartRepliesInflationModule.class,
            SmartspaceModule.class,
            StatusBarPolicyModule.class,
            StatusBarWindowModule.class,
            SysUIConcurrencyModule.class,
+28 −17
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.dreams;

import android.content.Context;
import android.os.Parcelable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -25,7 +26,10 @@ import com.android.systemui.CoreStartable;
import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.dreams.complication.ComplicationLayoutParams;
import com.android.systemui.dreams.complication.ComplicationViewModel;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.dreams.smartspace.DreamsSmartspaceController;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;

import java.util.List;

import javax.inject.Inject;

@@ -39,10 +43,22 @@ public class SmartSpaceComplication implements Complication {
     * SystemUI.
     */
    public static class Registrant extends CoreStartable {
        private final LockscreenSmartspaceController mSmartSpaceController;
        private final DreamsSmartspaceController mSmartSpaceController;
        private final DreamOverlayStateController mDreamOverlayStateController;
        private final SmartSpaceComplication mComplication;

        private final BcSmartspaceDataPlugin.SmartspaceTargetListener mSmartspaceListener =
                new BcSmartspaceDataPlugin.SmartspaceTargetListener() {
            @Override
            public void onSmartspaceTargetsUpdated(List<? extends Parcelable> targets) {
                if (!targets.isEmpty()) {
                    mDreamOverlayStateController.addComplication(mComplication);
                } else {
                    mDreamOverlayStateController.removeComplication(mComplication);
                }
            }
        };

        /**
         * Default constructor for {@link SmartSpaceComplication}.
         */
@@ -50,7 +66,7 @@ public class SmartSpaceComplication implements Complication {
        public Registrant(Context context,
                DreamOverlayStateController dreamOverlayStateController,
                SmartSpaceComplication smartSpaceComplication,
                LockscreenSmartspaceController smartSpaceController) {
                DreamsSmartspaceController smartSpaceController) {
            super(context);
            mDreamOverlayStateController = dreamOverlayStateController;
            mComplication = smartSpaceComplication;
@@ -59,32 +75,27 @@ public class SmartSpaceComplication implements Complication {

        @Override
        public void start() {
            addOrRemoveOverlay();
            mDreamOverlayStateController.addCallback(new DreamOverlayStateController.Callback() {
                @Override
                public void onStateChanged() {
                    addOrRemoveOverlay();
                    if (mDreamOverlayStateController.isOverlayActive()) {
                        mSmartSpaceController.addListener(mSmartspaceListener);
                    } else {
                        mSmartSpaceController.removeListener(mSmartspaceListener);
                    }
            });
        }

        private void addOrRemoveOverlay() {
            if (mDreamOverlayStateController.isPreviewMode()) {
                mDreamOverlayStateController.removeComplication(mComplication);
            } else if (mSmartSpaceController.isEnabled()) {
                mDreamOverlayStateController.addComplication(mComplication);
                }
            });
        }
    }

    private static class SmartSpaceComplicationViewHolder implements ViewHolder {
        private static final int SMARTSPACE_COMPLICATION_WEIGHT = 10;
        private final LockscreenSmartspaceController mSmartSpaceController;
        private final DreamsSmartspaceController mSmartSpaceController;
        private final Context mContext;

        protected SmartSpaceComplicationViewHolder(
                Context context,
                LockscreenSmartspaceController smartSpaceController) {
                DreamsSmartspaceController smartSpaceController) {
            mSmartSpaceController = smartSpaceController;
            mContext = context;
        }
@@ -109,12 +120,12 @@ public class SmartSpaceComplication implements Complication {
        }
    }

    private final LockscreenSmartspaceController mSmartSpaceController;
    private final DreamsSmartspaceController mSmartSpaceController;
    private final Context mContext;

    @Inject
    public SmartSpaceComplication(Context context,
            LockscreenSmartspaceController smartSpaceController) {
            DreamsSmartspaceController smartSpaceController) {
        mContext = context;
        mSmartSpaceController = smartSpaceController;
    }
+219 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.dreams.smartspace

import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceSession
import android.content.Context
import android.graphics.Color
import android.util.Log
import android.view.View
import android.view.ViewGroup
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
import com.android.systemui.smartspace.SmartspacePrecondition
import com.android.systemui.smartspace.SmartspaceTargetFilter
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_DATA_PLUGIN
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_PRECONDITION
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_TARGET_FILTER
import com.android.systemui.smartspace.dagger.SmartspaceViewComponent
import com.android.systemui.util.concurrency.Execution
import java.lang.RuntimeException
import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
import javax.inject.Named

/**
 * Controller for managing the smartspace view on the dream
 */
@SysUISingleton
class DreamsSmartspaceController @Inject constructor(
    private val context: Context,
    private val smartspaceManager: SmartspaceManager,
    private val execution: Execution,
    @Main private val uiExecutor: Executor,
    private val smartspaceViewComponentFactory: SmartspaceViewComponent.Factory,
    @Named(DREAM_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition,
    @Named(DREAM_SMARTSPACE_TARGET_FILTER)
    private val optionalTargetFilter: Optional<SmartspaceTargetFilter>,
    @Named(DREAM_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>
) {
    companion object {
        private const val TAG = "DreamsSmartspaceCtrlr"
    }

    private var session: SmartspaceSession? = null
    private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
    private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null)

    // A shadow copy of listeners is maintained to track whether the session should remain open.
    private var listeners = mutableSetOf<BcSmartspaceDataPlugin.SmartspaceTargetListener>()

    // Smartspace can be used on multiple displays, such as when the user casts their screen
    private var smartspaceViews = mutableSetOf<SmartspaceView>()

    var preconditionListener = object : SmartspacePrecondition.Listener {
        override fun onCriteriaChanged() {
            reloadSmartspace()
        }
    }

    init {
        precondition.addListener(preconditionListener)
    }

    var filterListener = object : SmartspaceTargetFilter.Listener {
        override fun onCriteriaChanged() {
            reloadSmartspace()
        }
    }

    init {
        targetFilter?.addListener(filterListener)
    }

    var stateChangeListener = object : View.OnAttachStateChangeListener {
        override fun onViewAttachedToWindow(v: View) {
            val view = v as SmartspaceView
            // Until there is dream color matching
            view.setPrimaryTextColor(Color.WHITE)
            smartspaceViews.add(view)
            connectSession()
        }

        override fun onViewDetachedFromWindow(v: View) {
            smartspaceViews.remove(v as SmartspaceView)

            if (smartspaceViews.isEmpty()) {
                disconnect()
            }
        }
    }

    private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
        execution.assertIsMainThread()

        val filteredTargets = targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
        plugin?.onTargetsAvailable(filteredTargets)
    }

    /**
     * Constructs the smartspace view and connects it to the smartspace service.
     */
    fun buildAndConnectView(parent: ViewGroup): View? {
        execution.assertIsMainThread()

        if (!precondition.conditionsMet()) {
            throw RuntimeException("Cannot build view when not enabled")
        }

        val view = buildView(parent)
        connectSession()

        return view
    }

    private fun buildView(parent: ViewGroup): View? {
        return if (plugin != null) {
            var view = smartspaceViewComponentFactory.create(parent, plugin, stateChangeListener)
                    .getView()

            if (view is View) {
                return view
            }

            return null
        } else {
            null
        }
    }

    private fun hasActiveSessionListeners(): Boolean {
        return smartspaceViews.isNotEmpty() || listeners.isNotEmpty()
    }

    private fun connectSession() {
        if (plugin == null || session != null || !hasActiveSessionListeners()) {
            return
        }

        if (!precondition.conditionsMet()) {
            return
        }

        // TODO(b/217559844): Replace with "dream" session when available.
        val newSession = smartspaceManager.createSmartspaceSession(
                SmartspaceConfig.Builder(context, "lockscreen").build())
        Log.d(TAG, "Starting smartspace session for dream")
        newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
        this.session = newSession

        plugin.registerSmartspaceEventNotifier {
                e -> session?.notifySmartspaceEvent(e)
        }

        reloadSmartspace()
    }

    /**
     * Disconnects the smartspace view from the smartspace service and cleans up any resources.
     */
    private fun disconnect() {
        if (hasActiveSessionListeners()) return

        execution.assertIsMainThread()

        if (session == null) {
            return
        }

        session?.let {
            it.removeOnTargetsAvailableListener(sessionListener)
            it.close()
        }

        session = null

        plugin?.registerSmartspaceEventNotifier(null)
        plugin?.onTargetsAvailable(emptyList())
        Log.d(TAG, "Ending smartspace session for dream")
    }

    fun addListener(listener: SmartspaceTargetListener) {
        execution.assertIsMainThread()
        plugin?.registerListener(listener)
        listeners.add(listener)

        connectSession()
    }

    fun removeListener(listener: SmartspaceTargetListener) {
        execution.assertIsMainThread()
        plugin?.unregisterListener(listener)
        listeners.remove(listener)
        disconnect()
    }

    private fun reloadSmartspace() {
        session?.requestSmartspaceUpdate()
    }
}
+45 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.smartspace

/**
 * A {@link SmartspacePrecondition} captures the conditions that must be met for Smartspace to be
 * used in a particular setting.
 */
interface SmartspacePrecondition {
    /**
     * A callback for receiving updates when conditions have changed.
     */
    interface Listener {
        fun onCriteriaChanged()
    }

    /**
     * Adds a listener to receive future updates. {@link Listener#onCriteriaChanged} will be called
     * immediately upon adding.
     */
    fun addListener(listener: Listener)

    /**
     * Removes a listener from receiving future updates.
     */
    fun removeListener(listener: Listener)

    /**
     * Returns whether all conditions have been met.
     */
    fun conditionsMet(): Boolean
}
 No newline at end of file
+49 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.smartspace

import android.app.smartspace.SmartspaceTarget

/**
 * {@link SmartspaceTargetFilter} defines a way to locally filter targets from inclusion. This
 * should be used for filtering that isn't available further upstream.
 */
interface SmartspaceTargetFilter {
    /**
     * An interface implemented by clients to receive updates when the filtering criteria changes.
     * When this happens, the client should refresh their target set.
     */
    interface Listener {
        fun onCriteriaChanged()
    }

    /**
     * Adds a listener to receive future updates. {@link Listener#onCriteriaChanged} will be
     * invoked immediately after.
     */
    fun addListener(listener: Listener)

    /**
     * Removes listener from receiving future updates.
     */
    fun removeListener(listener: Listener)

    /**
     * Returns {@code true} if the {@link SmartspaceTarget} should be included in the current
     * target set, {@code false} otherwise.
     */
    fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean
}
 No newline at end of file
Loading