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

Commit 5fae7898 authored by Caitlin Cassidy's avatar Caitlin Cassidy
Browse files

[Media TTT] Define a common controller that will be used for both the

sender device chip and the receiver device chip.

Code for the receiver device chip will be added in future CLs.

Bug: 203800646
Test: manual (verify sender chip still works)
Test: MediaTttChipControllerSenderTest, MediaTttCommandLineHelperTest
Change-Id: I72c7e089c5b587fcdf5827f4e95f9032d96bbfd7
parent e087e129
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -23,8 +23,8 @@ import com.android.systemui.InitController;
import com.android.systemui.SystemUIAppComponentFactory;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
import com.android.systemui.media.taptotransfer.MediaTttChipController;
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
import com.android.systemui.people.PeopleProvider;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
@@ -134,7 +134,7 @@ public interface SysUIComponent {
        });
        getNaturalRotationUnfoldProgressProvider().ifPresent(o -> o.init());
        // No init method needed, just needs to be gotten so that it's created.
        getMediaTttChipController();
        getMediaTttChipControllerSender();
        getMediaTttCommandLineHelper();
        getUnfoldLatencyTracker().init();
    }
@@ -190,7 +190,7 @@ public interface SysUIComponent {
    Optional<NaturalRotationUnfoldProgressProvider> getNaturalRotationUnfoldProgressProvider();

    /** */
    Optional<MediaTttChipController> getMediaTttChipController();
    Optional<MediaTttChipControllerSender> getMediaTttChipControllerSender();

    /** */
    Optional<MediaTttCommandLineHelper> getMediaTttCommandLineHelper();
+5 −5
Original line number Diff line number Diff line
@@ -26,9 +26,9 @@ import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.media.MediaHostStatesManager;
import com.android.systemui.media.taptotransfer.MediaTttChipController;
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.media.taptotransfer.MediaTttFlags;
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.util.concurrency.DelayableExecutor;

@@ -80,7 +80,7 @@ public interface MediaModule {
    /** */
    @Provides
    @SysUISingleton
    static Optional<MediaTttChipController> providesMediaTttChipController(
    static Optional<MediaTttChipControllerSender> providesMediaTttChipControllerSender(
            MediaTttFlags mediaTttFlags,
            Context context,
            WindowManager windowManager,
@@ -89,7 +89,7 @@ public interface MediaModule {
        if (!mediaTttFlags.isMediaTttEnabled()) {
            return Optional.empty();
        }
        return Optional.of(new MediaTttChipController(
        return Optional.of(new MediaTttChipControllerSender(
                context, windowManager, mainExecutor, backgroundExecutor));
    }

@@ -100,12 +100,12 @@ public interface MediaModule {
            MediaTttFlags mediaTttFlags,
            CommandRegistry commandRegistry,
            Context context,
            MediaTttChipController mediaTttChipController,
            MediaTttChipControllerSender mediaTttChipControllerSender,
            @Main DelayableExecutor mainExecutor) {
        if (!mediaTttFlags.isMediaTttEnabled()) {
            return Optional.empty();
        }
        return Optional.of(new MediaTttCommandLineHelper(
                commandRegistry, context, mediaTttChipController, mainExecutor));
                commandRegistry, context, mediaTttChipControllerSender, mainExecutor));
    }
}
+13 −8
Original line number Diff line number Diff line
@@ -23,6 +23,10 @@ import androidx.annotation.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender
import com.android.systemui.media.taptotransfer.sender.MoveCloserToTransfer
import com.android.systemui.media.taptotransfer.sender.TransferInitiated
import com.android.systemui.media.taptotransfer.sender.TransferSucceeded
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -38,7 +42,7 @@ import javax.inject.Inject
class MediaTttCommandLineHelper @Inject constructor(
    commandRegistry: CommandRegistry,
    context: Context,
    private val mediaTttChipController: MediaTttChipController,
    private val mediaTttChipControllerSender: MediaTttChipControllerSender,
    @Main private val mainExecutor: DelayableExecutor,
) {
    private val appIconDrawable =
@@ -54,21 +58,21 @@ class MediaTttCommandLineHelper @Inject constructor(
            val otherDeviceName = args[0]
            when (args[1]) {
                MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME -> {
                    mediaTttChipController.displayChip(
                        MoveCloserToTransfer(otherDeviceName, appIconDrawable)
                    mediaTttChipControllerSender.displayChip(
                        MoveCloserToTransfer(appIconDrawable, otherDeviceName)
                    )
                }
                TRANSFER_INITIATED_COMMAND_NAME -> {
                    val futureTask = FutureTask { fakeUndoRunnable }
                    mediaTttChipController.displayChip(
                        TransferInitiated(otherDeviceName, appIconDrawable, futureTask)
                    mediaTttChipControllerSender.displayChip(
                        TransferInitiated(appIconDrawable, otherDeviceName, futureTask)
                    )
                    mainExecutor.executeDelayed({ futureTask.run() }, FUTURE_WAIT_TIME)

                }
                TRANSFER_SUCCEEDED_COMMAND_NAME -> {
                    mediaTttChipController.displayChip(
                        TransferSucceeded(otherDeviceName, appIconDrawable, fakeUndoRunnable)
                    mediaTttChipControllerSender.displayChip(
                        TransferSucceeded(appIconDrawable, otherDeviceName, fakeUndoRunnable)
                    )
                }
                else -> {
@@ -90,7 +94,7 @@ class MediaTttCommandLineHelper @Inject constructor(

    inner class RemoveChipCommand : Command {
        override fun execute(pw: PrintWriter, args: List<String>) {
            mediaTttChipController.removeChip()
            mediaTttChipControllerSender.removeChip()
        }
        override fun help(pw: PrintWriter) {
            pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_TAG")
@@ -114,3 +118,4 @@ val TRANSFER_INITIATED_COMMAND_NAME = TransferInitiated::class.simpleName!!
val TRANSFER_SUCCEEDED_COMMAND_NAME = TransferSucceeded::class.simpleName!!

private const val FUTURE_WAIT_TIME = 2000L
private const val TAG = "MediaTapToTransferCli"
+11 −0
Original line number Diff line number Diff line
# Media Tap-To-Transfer

This package (and child packages) include code for the media tap-to-transfer feature, which
allows users to easily transfer playing media between devices.

In media transfer, there are two devices: the *sender* and the *receiver*. The sender device will
start and stop media casts to the receiver device. On both devices, System UI will display a chip
informing the user about the media cast occurring.

This package is structured so that the sender code is in the sender package, the receiver code is
in the receiver package, and code that's shared between them is in the common package.
+104 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.media.taptotransfer.common

import android.annotation.LayoutRes
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.PixelFormat
import android.view.Gravity
import android.view.LayoutInflater
import android.view.ViewGroup
import android.view.WindowManager
import com.android.internal.widget.CachingIconView
import com.android.systemui.R

/**
 * A superclass controller that provides common functionality for showing chips on the sender device
 * and the receiver device.
 *
 * Subclasses need to override and implement [updateChipView], which is where they can control what
 * gets displayed to the user.
 */
abstract class MediaTttChipControllerCommon<T : MediaTttChipState>(
    private val context: Context,
    private val windowManager: WindowManager,
    @LayoutRes private val chipLayoutRes: Int
) {
    /** The window layout parameters we'll use when attaching the view to a window. */
    @SuppressLint("WrongConstant") // We're allowed to use TYPE_VOLUME_OVERLAY
    private val windowLayoutParams = WindowManager.LayoutParams().apply {
        width = WindowManager.LayoutParams.WRAP_CONTENT
        height = WindowManager.LayoutParams.WRAP_CONTENT
        gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
        type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
        flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
        title = "Media Tap-To-Transfer Chip View"
        format = PixelFormat.TRANSLUCENT
        setTrustedOverlay()
    }

    /** The chip view currently being displayed. Null if the chip is not being displayed. */
    var chipView: ViewGroup? = null

    /**
     * Displays the chip with the current state.
     *
     * This method handles inflating and attaching the view, then delegates to [updateChipView] to
     * display the correct information in the chip.
     */
    fun displayChip(chipState: T) {
        val oldChipView = chipView
        if (chipView == null) {
            chipView = LayoutInflater
                .from(context)
                .inflate(chipLayoutRes, null) as ViewGroup
        }
        val currentChipView = chipView!!

        updateChipView(chipState, currentChipView)

        // Add view if necessary
        if (oldChipView == null) {
            windowManager.addView(chipView, windowLayoutParams)
        }
    }


    /** Hides the chip. */
    fun removeChip() {
        if (chipView == null) { return }
        windowManager.removeView(chipView)
        chipView = null
    }

    /**
     * A method implemented by subclasses to update [currentChipView] based on [chipState].
     */
    abstract fun updateChipView(chipState: T, currentChipView: ViewGroup)

    /**
     * An internal method to set the icon on the view.
     *
     * This is in the common superclass since both the sender and the receiver show an icon.
     */
    internal fun setIcon(chipState: T, currentChipView: ViewGroup) {
        currentChipView.findViewById<CachingIconView>(R.id.app_icon).apply {
            this.setImageDrawable(chipState.appIconDrawable)
        }
    }
}
Loading