diff options
13 files changed, 383 insertions, 390 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt new file mode 100644 index 000000000000..2719cd29009e --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt @@ -0,0 +1,365 @@ +/* + * 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.wm.shell.common.pip + +import android.annotation.DrawableRes +import android.annotation.StringRes +import android.app.PendingIntent +import android.app.RemoteAction +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.graphics.drawable.Icon +import android.media.MediaMetadata +import android.media.session.MediaController +import android.media.session.MediaSession +import android.media.session.MediaSessionManager +import android.media.session.PlaybackState +import android.os.Handler +import android.os.HandlerExecutor +import android.os.UserHandle +import com.android.wm.shell.R +import com.android.wm.shell.pip.PipUtils +import java.util.function.Consumer + +/** + * Interfaces with the [MediaSessionManager] to compose the right set of actions to show (only + * if there are no actions from the PiP activity itself). The active media controller is only set + * when there is a media session from the top PiP activity. + */ +class PipMediaController(private val mContext: Context, private val mMainHandler: Handler) { + /** + * A listener interface to receive notification on changes to the media actions. + */ + interface ActionListener { + /** + * Called when the media actions changed. + */ + fun onMediaActionsChanged(actions: List<RemoteAction?>?) + } + + /** + * A listener interface to receive notification on changes to the media metadata. + */ + interface MetadataListener { + /** + * Called when the media metadata changed. + */ + fun onMediaMetadataChanged(metadata: MediaMetadata?) + } + + /** + * A listener interface to receive notification on changes to the media session token. + */ + interface TokenListener { + /** + * Called when the media session token changed. + */ + fun onMediaSessionTokenChanged(token: MediaSession.Token?) + } + + private val mHandlerExecutor: HandlerExecutor = HandlerExecutor(mMainHandler) + private val mMediaSessionManager: MediaSessionManager? + private var mMediaController: MediaController? = null + private val mPauseAction: RemoteAction + private val mPlayAction: RemoteAction + private val mNextAction: RemoteAction + private val mPrevAction: RemoteAction + private val mMediaActionReceiver: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (mMediaController == null) { + // no active media session, bail early. + return + } + when (intent.action) { + ACTION_PLAY -> mMediaController!!.transportControls.play() + ACTION_PAUSE -> mMediaController!!.transportControls.pause() + ACTION_NEXT -> mMediaController!!.transportControls.skipToNext() + ACTION_PREV -> mMediaController!!.transportControls.skipToPrevious() + } + } + } + private val mPlaybackChangedListener: MediaController.Callback = + object : MediaController.Callback() { + override fun onPlaybackStateChanged(state: PlaybackState?) { + notifyActionsChanged() + } + + override fun onMetadataChanged(metadata: MediaMetadata?) { + notifyMetadataChanged(metadata) + } + } + private val mSessionsChangedListener = + MediaSessionManager.OnActiveSessionsChangedListener { controllers: List<MediaController>? -> + resolveActiveMediaController(controllers) + } + private val mActionListeners = ArrayList<ActionListener>() + private val mMetadataListeners = ArrayList<MetadataListener>() + private val mTokenListeners = ArrayList<TokenListener>() + + init { + val mediaControlFilter = IntentFilter() + mediaControlFilter.addAction(ACTION_PLAY) + mediaControlFilter.addAction(ACTION_PAUSE) + mediaControlFilter.addAction(ACTION_NEXT) + mediaControlFilter.addAction(ACTION_PREV) + mContext.registerReceiverForAllUsers( + mMediaActionReceiver, mediaControlFilter, + SYSTEMUI_PERMISSION, mMainHandler, Context.RECEIVER_EXPORTED + ) + + // Creates the standard media buttons that we may show. + mPauseAction = getDefaultRemoteAction( + R.string.pip_pause, + R.drawable.pip_ic_pause_white, ACTION_PAUSE + ) + mPlayAction = getDefaultRemoteAction( + R.string.pip_play, + R.drawable.pip_ic_play_arrow_white, ACTION_PLAY + ) + mNextAction = getDefaultRemoteAction( + R.string.pip_skip_to_next, + R.drawable.pip_ic_skip_next_white, ACTION_NEXT + ) + mPrevAction = getDefaultRemoteAction( + R.string.pip_skip_to_prev, + R.drawable.pip_ic_skip_previous_white, ACTION_PREV + ) + mMediaSessionManager = mContext.getSystemService( + MediaSessionManager::class.java + ) + } + + /** + * Handles when an activity is pinned. + */ + fun onActivityPinned() { + // Once we enter PiP, try to find the active media controller for the top most activity + resolveActiveMediaController( + mMediaSessionManager!!.getActiveSessionsForUser( + null, + UserHandle.CURRENT + ) + ) + } + + /** + * Adds a new media action listener. + */ + fun addActionListener(listener: ActionListener) { + if (!mActionListeners.contains(listener)) { + mActionListeners.add(listener) + listener.onMediaActionsChanged(mediaActions) + } + } + + /** + * Removes a media action listener. + */ + fun removeActionListener(listener: ActionListener) { + listener.onMediaActionsChanged(emptyList<RemoteAction>()) + mActionListeners.remove(listener) + } + + /** + * Adds a new media metadata listener. + */ + fun addMetadataListener(listener: MetadataListener) { + if (!mMetadataListeners.contains(listener)) { + mMetadataListeners.add(listener) + listener.onMediaMetadataChanged(mediaMetadata) + } + } + + /** + * Removes a media metadata listener. + */ + fun removeMetadataListener(listener: MetadataListener) { + listener.onMediaMetadataChanged(null) + mMetadataListeners.remove(listener) + } + + /** + * Adds a new token listener. + */ + fun addTokenListener(listener: TokenListener) { + if (!mTokenListeners.contains(listener)) { + mTokenListeners.add(listener) + listener.onMediaSessionTokenChanged(token) + } + } + + /** + * Removes a token listener. + */ + fun removeTokenListener(listener: TokenListener) { + listener.onMediaSessionTokenChanged(null) + mTokenListeners.remove(listener) + } + + private val token: MediaSession.Token? + get() = if (mMediaController == null) { + null + } else mMediaController!!.sessionToken + private val mediaMetadata: MediaMetadata? + get() = if (mMediaController != null) mMediaController!!.metadata else null + + private val mediaActions: List<RemoteAction?> + /** + * Gets the set of media actions currently available. + */ + get() { + if (mMediaController == null) { + return emptyList<RemoteAction>() + } + // Cache the PlaybackState since it's a Binder call. + // Safe because mMediaController is guaranteed non-null here. + val playbackState: PlaybackState = mMediaController!!.playbackState + ?: return emptyList<RemoteAction>() + val mediaActions = ArrayList<RemoteAction?>() + val isPlaying = playbackState.isActive + val actions = playbackState.actions + + // Prev action + mPrevAction.isEnabled = + actions and PlaybackState.ACTION_SKIP_TO_PREVIOUS != 0L + mediaActions.add(mPrevAction) + + // Play/pause action + if (!isPlaying && actions and PlaybackState.ACTION_PLAY != 0L) { + mediaActions.add(mPlayAction) + } else if (isPlaying && actions and PlaybackState.ACTION_PAUSE != 0L) { + mediaActions.add(mPauseAction) + } + + // Next action + mNextAction.isEnabled = + actions and PlaybackState.ACTION_SKIP_TO_NEXT != 0L + mediaActions.add(mNextAction) + return mediaActions + } + + /** @return Default [RemoteAction] sends broadcast back to SysUI. + */ + private fun getDefaultRemoteAction( + @StringRes titleAndDescription: Int, + @DrawableRes icon: Int, + action: String + ): RemoteAction { + val titleAndDescriptionStr = mContext.getString(titleAndDescription) + val intent = Intent(action) + intent.setPackage(mContext.packageName) + return RemoteAction( + Icon.createWithResource(mContext, icon), + titleAndDescriptionStr, titleAndDescriptionStr, + PendingIntent.getBroadcast( + mContext, 0 /* requestCode */, intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + ) + } + + /** + * Re-registers the session listener for the current user. + */ + fun registerSessionListenerForCurrentUser() { + mMediaSessionManager!!.removeOnActiveSessionsChangedListener(mSessionsChangedListener) + mMediaSessionManager.addOnActiveSessionsChangedListener( + null, UserHandle.CURRENT, + mHandlerExecutor, mSessionsChangedListener + ) + } + + /** + * Tries to find and set the active media controller for the top PiP activity. + */ + private fun resolveActiveMediaController(controllers: List<MediaController>?) { + if (controllers != null) { + val topActivity = PipUtils.getTopPipActivity(mContext).first + if (topActivity != null) { + for (i in controllers.indices) { + val controller = controllers[i] + if (controller.packageName == topActivity.packageName) { + setActiveMediaController(controller) + return + } + } + } + } + setActiveMediaController(null) + } + + /** + * Sets the active media controller for the top PiP activity. + */ + private fun setActiveMediaController(controller: MediaController?) { + if (controller != mMediaController) { + if (mMediaController != null) { + mMediaController!!.unregisterCallback(mPlaybackChangedListener) + } + mMediaController = controller + controller?.registerCallback(mPlaybackChangedListener, mMainHandler) + notifyActionsChanged() + notifyMetadataChanged(mediaMetadata) + notifyTokenChanged(token) + + // TODO(winsonc): Consider if we want to close the PIP after a timeout (like on TV) + } + } + + /** + * Notifies all listeners that the actions have changed. + */ + private fun notifyActionsChanged() { + if (mActionListeners.isNotEmpty()) { + val actions = mediaActions + mActionListeners.forEach( + Consumer { l: ActionListener -> l.onMediaActionsChanged(actions) }) + } + } + + /** + * Notifies all listeners that the metadata have changed. + */ + private fun notifyMetadataChanged(metadata: MediaMetadata?) { + if (mMetadataListeners.isNotEmpty()) { + mMetadataListeners.forEach(Consumer { l: MetadataListener -> + l.onMediaMetadataChanged( + metadata + ) + }) + } + } + + private fun notifyTokenChanged(token: MediaSession.Token?) { + if (mTokenListeners.isNotEmpty()) { + mTokenListeners.forEach(Consumer { l: TokenListener -> + l.onMediaSessionTokenChanged( + token + ) + }) + } + } + + companion object { + private const val SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF" + private const val ACTION_PLAY = "com.android.wm.shell.pip.PLAY" + private const val ACTION_PAUSE = "com.android.wm.shell.pip.PAUSE" + private const val ACTION_NEXT = "com.android.wm.shell.pip.NEXT" + private const val ACTION_PREV = "com.android.wm.shell.pip.PREV" + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 8df89bc17621..aafd9fdddb7f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -57,6 +57,7 @@ import com.android.wm.shell.common.annotations.ShellAnimationThread; import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.annotations.ShellSplashscreenThread; +import com.android.wm.shell.common.pip.PipMediaController; import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.compatui.CompatUIConfiguration; import com.android.wm.shell.compatui.CompatUIController; @@ -332,6 +333,13 @@ public abstract class WMShellBaseModule { return new PipUiEventLogger(uiEventLogger, packageManager); } + @WMSingleton + @Provides + static PipMediaController providePipMediaController(Context context, + @ShellMainThread Handler mainHandler) { + return new PipMediaController(context, mainHandler); + } + // // Bubbles (optional feature) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java index 5fd4f24a0f0d..24ef44a7c0f4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java @@ -32,6 +32,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.pip.PhoneSizeSpecSource; import com.android.wm.shell.common.pip.PipAppOpsListener; +import com.android.wm.shell.common.pip.PipMediaController; import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.dagger.WMShellBaseModule; @@ -42,7 +43,6 @@ import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipDisplayLayoutState; -import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java index 55a810ae5c68..c4ca5013afb6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java @@ -17,11 +17,8 @@ package com.android.wm.shell.dagger.pip; import android.content.Context; -import android.os.Handler; -import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.dagger.WMSingleton; -import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import dagger.Module; @@ -33,14 +30,6 @@ import dagger.Provides; */ @Module public abstract class Pip1SharedModule { - // Needs handler for registering broadcast receivers - @WMSingleton - @Provides - static PipMediaController providePipMediaController(Context context, - @ShellMainThread Handler mainHandler) { - return new PipMediaController(context, mainHandler); - } - @WMSingleton @Provides static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java index 4e332becb2f9..a6ff9ecf7f4f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java @@ -30,6 +30,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.pip.LegacySizeSpecSource; import com.android.wm.shell.common.pip.PipAppOpsListener; +import com.android.wm.shell.common.pip.PipMediaController; import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.dagger.WMShellBaseModule; @@ -37,7 +38,6 @@ import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipDisplayLayoutState; -import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java deleted file mode 100644 index ddffb5bdacde..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java +++ /dev/null @@ -1,369 +0,0 @@ -/* - * Copyright (C) 2020 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.wm.shell.pip; - -import static android.app.PendingIntent.FLAG_IMMUTABLE; -import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; - -import android.annotation.DrawableRes; -import android.annotation.StringRes; -import android.annotation.SuppressLint; -import android.app.PendingIntent; -import android.app.RemoteAction; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.graphics.drawable.Icon; -import android.media.MediaMetadata; -import android.media.session.MediaController; -import android.media.session.MediaSession; -import android.media.session.MediaSessionManager; -import android.media.session.PlaybackState; -import android.os.Handler; -import android.os.HandlerExecutor; -import android.os.UserHandle; - -import androidx.annotation.Nullable; - -import com.android.wm.shell.R; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Interfaces with the {@link MediaSessionManager} to compose the right set of actions to show (only - * if there are no actions from the PiP activity itself). The active media controller is only set - * when there is a media session from the top PiP activity. - */ -public class PipMediaController { - private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF"; - - private static final String ACTION_PLAY = "com.android.wm.shell.pip.PLAY"; - private static final String ACTION_PAUSE = "com.android.wm.shell.pip.PAUSE"; - private static final String ACTION_NEXT = "com.android.wm.shell.pip.NEXT"; - private static final String ACTION_PREV = "com.android.wm.shell.pip.PREV"; - - /** - * A listener interface to receive notification on changes to the media actions. - */ - public interface ActionListener { - /** - * Called when the media actions changed. - */ - void onMediaActionsChanged(List<RemoteAction> actions); - } - - /** - * A listener interface to receive notification on changes to the media metadata. - */ - public interface MetadataListener { - /** - * Called when the media metadata changed. - */ - void onMediaMetadataChanged(MediaMetadata metadata); - } - - /** - * A listener interface to receive notification on changes to the media session token. - */ - public interface TokenListener { - /** - * Called when the media session token changed. - */ - void onMediaSessionTokenChanged(MediaSession.Token token); - } - - private final Context mContext; - private final Handler mMainHandler; - private final HandlerExecutor mHandlerExecutor; - - private final MediaSessionManager mMediaSessionManager; - private MediaController mMediaController; - - private RemoteAction mPauseAction; - private RemoteAction mPlayAction; - private RemoteAction mNextAction; - private RemoteAction mPrevAction; - - private final BroadcastReceiver mMediaActionReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (mMediaController == null || mMediaController.getTransportControls() == null) { - // no active media session, bail early. - return; - } - switch (intent.getAction()) { - case ACTION_PLAY: - mMediaController.getTransportControls().play(); - break; - case ACTION_PAUSE: - mMediaController.getTransportControls().pause(); - break; - case ACTION_NEXT: - mMediaController.getTransportControls().skipToNext(); - break; - case ACTION_PREV: - mMediaController.getTransportControls().skipToPrevious(); - break; - } - } - }; - - private final MediaController.Callback mPlaybackChangedListener = - new MediaController.Callback() { - @Override - public void onPlaybackStateChanged(PlaybackState state) { - notifyActionsChanged(); - } - - @Override - public void onMetadataChanged(@Nullable MediaMetadata metadata) { - notifyMetadataChanged(metadata); - } - }; - - private final MediaSessionManager.OnActiveSessionsChangedListener mSessionsChangedListener = - this::resolveActiveMediaController; - - private final ArrayList<ActionListener> mActionListeners = new ArrayList<>(); - private final ArrayList<MetadataListener> mMetadataListeners = new ArrayList<>(); - private final ArrayList<TokenListener> mTokenListeners = new ArrayList<>(); - - public PipMediaController(Context context, Handler mainHandler) { - mContext = context; - mMainHandler = mainHandler; - mHandlerExecutor = new HandlerExecutor(mMainHandler); - if (!PipUtils.isPip2ExperimentEnabled()) { - IntentFilter mediaControlFilter = new IntentFilter(); - mediaControlFilter.addAction(ACTION_PLAY); - mediaControlFilter.addAction(ACTION_PAUSE); - mediaControlFilter.addAction(ACTION_NEXT); - mediaControlFilter.addAction(ACTION_PREV); - mContext.registerReceiverForAllUsers(mMediaActionReceiver, mediaControlFilter, - SYSTEMUI_PERMISSION, mainHandler, Context.RECEIVER_EXPORTED); - } - - // Creates the standard media buttons that we may show. - mPauseAction = getDefaultRemoteAction(R.string.pip_pause, - R.drawable.pip_ic_pause_white, ACTION_PAUSE); - mPlayAction = getDefaultRemoteAction(R.string.pip_play, - R.drawable.pip_ic_play_arrow_white, ACTION_PLAY); - mNextAction = getDefaultRemoteAction(R.string.pip_skip_to_next, - R.drawable.pip_ic_skip_next_white, ACTION_NEXT); - mPrevAction = getDefaultRemoteAction(R.string.pip_skip_to_prev, - R.drawable.pip_ic_skip_previous_white, ACTION_PREV); - - mMediaSessionManager = context.getSystemService(MediaSessionManager.class); - } - - /** - * Handles when an activity is pinned. - */ - public void onActivityPinned() { - // Once we enter PiP, try to find the active media controller for the top most activity - resolveActiveMediaController(mMediaSessionManager.getActiveSessionsForUser(null, - UserHandle.CURRENT)); - } - - /** - * Adds a new media action listener. - */ - public void addActionListener(ActionListener listener) { - if (!mActionListeners.contains(listener)) { - mActionListeners.add(listener); - listener.onMediaActionsChanged(getMediaActions()); - } - } - - /** - * Removes a media action listener. - */ - public void removeActionListener(ActionListener listener) { - listener.onMediaActionsChanged(Collections.emptyList()); - mActionListeners.remove(listener); - } - - /** - * Adds a new media metadata listener. - */ - public void addMetadataListener(MetadataListener listener) { - if (!mMetadataListeners.contains(listener)) { - mMetadataListeners.add(listener); - listener.onMediaMetadataChanged(getMediaMetadata()); - } - } - - /** - * Removes a media metadata listener. - */ - public void removeMetadataListener(MetadataListener listener) { - listener.onMediaMetadataChanged(null); - mMetadataListeners.remove(listener); - } - - /** - * Adds a new token listener. - */ - public void addTokenListener(TokenListener listener) { - if (!mTokenListeners.contains(listener)) { - mTokenListeners.add(listener); - listener.onMediaSessionTokenChanged(getToken()); - } - } - - /** - * Removes a token listener. - */ - public void removeTokenListener(TokenListener listener) { - listener.onMediaSessionTokenChanged(null); - mTokenListeners.remove(listener); - } - - private MediaSession.Token getToken() { - if (mMediaController == null) { - return null; - } - return mMediaController.getSessionToken(); - } - - private MediaMetadata getMediaMetadata() { - return mMediaController != null ? mMediaController.getMetadata() : null; - } - - /** - * Gets the set of media actions currently available. - */ - // This is due to using PlaybackState#isActive, which is added in API 31. - // It can be removed when min_sdk of the app is set to 31 or greater. - @SuppressLint("NewApi") - private List<RemoteAction> getMediaActions() { - // Cache the PlaybackState since it's a Binder call. - final PlaybackState playbackState; - if (mMediaController == null - || (playbackState = mMediaController.getPlaybackState()) == null) { - return Collections.emptyList(); - } - - ArrayList<RemoteAction> mediaActions = new ArrayList<>(); - boolean isPlaying = playbackState.isActive(); - long actions = playbackState.getActions(); - - // Prev action - mPrevAction.setEnabled((actions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0); - mediaActions.add(mPrevAction); - - // Play/pause action - if (!isPlaying && ((actions & PlaybackState.ACTION_PLAY) != 0)) { - mediaActions.add(mPlayAction); - } else if (isPlaying && ((actions & PlaybackState.ACTION_PAUSE) != 0)) { - mediaActions.add(mPauseAction); - } - - // Next action - mNextAction.setEnabled((actions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0); - mediaActions.add(mNextAction); - return mediaActions; - } - - /** @return Default {@link RemoteAction} sends broadcast back to SysUI. */ - private RemoteAction getDefaultRemoteAction(@StringRes int titleAndDescription, - @DrawableRes int icon, String action) { - final String titleAndDescriptionStr = mContext.getString(titleAndDescription); - final Intent intent = new Intent(action); - intent.setPackage(mContext.getPackageName()); - return new RemoteAction(Icon.createWithResource(mContext, icon), - titleAndDescriptionStr, titleAndDescriptionStr, - PendingIntent.getBroadcast(mContext, 0 /* requestCode */, intent, - FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE)); - } - - /** - * Re-registers the session listener for the current user. - */ - public void registerSessionListenerForCurrentUser() { - mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionsChangedListener); - mMediaSessionManager.addOnActiveSessionsChangedListener(null, UserHandle.CURRENT, - mHandlerExecutor, mSessionsChangedListener); - } - - /** - * Tries to find and set the active media controller for the top PiP activity. - */ - private void resolveActiveMediaController(List<MediaController> controllers) { - if (controllers != null) { - final ComponentName topActivity = PipUtils.getTopPipActivity(mContext).first; - if (topActivity != null) { - for (int i = 0; i < controllers.size(); i++) { - final MediaController controller = controllers.get(i); - if (controller.getPackageName().equals(topActivity.getPackageName())) { - setActiveMediaController(controller); - return; - } - } - } - } - setActiveMediaController(null); - } - - /** - * Sets the active media controller for the top PiP activity. - */ - private void setActiveMediaController(MediaController controller) { - if (controller != mMediaController) { - if (mMediaController != null) { - mMediaController.unregisterCallback(mPlaybackChangedListener); - } - mMediaController = controller; - if (controller != null) { - controller.registerCallback(mPlaybackChangedListener, mMainHandler); - } - notifyActionsChanged(); - notifyMetadataChanged(getMediaMetadata()); - notifyTokenChanged(getToken()); - - // TODO(winsonc): Consider if we want to close the PIP after a timeout (like on TV) - } - } - - /** - * Notifies all listeners that the actions have changed. - */ - private void notifyActionsChanged() { - if (!mActionListeners.isEmpty()) { - List<RemoteAction> actions = getMediaActions(); - mActionListeners.forEach(l -> l.onMediaActionsChanged(actions)); - } - } - - /** - * Notifies all listeners that the metadata have changed. - */ - private void notifyMetadataChanged(MediaMetadata metadata) { - if (!mMetadataListeners.isEmpty()) { - mMetadataListeners.forEach(l -> l.onMediaMetadataChanged(metadata)); - } - } - - private void notifyTokenChanged(MediaSession.Token token) { - if (!mTokenListeners.isEmpty()) { - mTokenListeners.forEach(l -> l.onMediaSessionTokenChanged(token)); - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index c93d85016df6..cc182ba89985 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -38,10 +38,10 @@ import android.view.WindowManagerGlobal; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SystemWindows; +import com.android.wm.shell.common.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipMediaController.ActionListener; import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.pip.PipBoundsState; -import com.android.wm.shell.pip.PipMediaController; -import com.android.wm.shell.pip.PipMediaController.ActionListener; import com.android.wm.shell.pip.PipMenuController; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index f25110ac3257..5c65d7839f07 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -76,6 +76,7 @@ import com.android.wm.shell.common.TabletopModeController; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.pip.PipAppOpsListener; +import com.android.wm.shell.common.pip.PipMediaController; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.pip.IPip; @@ -87,7 +88,6 @@ import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipDisplayLayoutState; import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface; -import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java index 3b44f10ebe62..7dc400c0faeb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java @@ -35,7 +35,7 @@ import android.content.Context; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; -import com.android.wm.shell.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipMediaController; import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index 5e583c20061f..0816dc74dfa9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -48,11 +48,11 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.pip.PipAppOpsListener; +import com.android.wm.shell.common.pip.PipMediaController; import com.android.wm.shell.pip.PinnedStackListenerForwarder; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipDisplayLayoutState; -import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java index f22ee595e6c9..39c7a4b220a7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java @@ -36,7 +36,7 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ImageUtils; import com.android.wm.shell.R; -import com.android.wm.shell.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipMediaController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index 05c6ba9f0897..911f5e165ef1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -56,12 +56,12 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TabletopModeController; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.pip.PipAppOpsListener; +import com.android.wm.shell.common.pip.PipMediaController; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipDisplayLayoutState; -import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java index 02e6b8c71663..c40cd4069cab 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java @@ -37,7 +37,7 @@ import android.testing.TestableLooper; import android.util.Log; import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipMediaController; import org.junit.Before; import org.junit.Test; |