Loading libs/WindowManager/Shell/res/layout/tv_pip_controls.xml +0 −10 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ --> <!-- Layout for {@link com.android.wm.shell.pip.tv.PipControlsView}. --> <merge xmlns:android="http://schemas.android.com/apk/res/android"> <com.android.wm.shell.pip.tv.PipControlButtonView android:id="@+id/full_button" android:layout_width="@dimen/picture_in_picture_button_width" Loading @@ -31,13 +30,4 @@ android:layout_marginStart="@dimen/picture_in_picture_button_start_margin" android:src="@drawable/pip_ic_close_white" android:text="@string/pip_close" /> <com.android.wm.shell.pip.tv.PipControlButtonView android:id="@+id/play_pause_button" android:layout_width="@dimen/picture_in_picture_button_width" android:layout_height="wrap_content" android:layout_marginStart="@dimen/picture_in_picture_button_start_margin" android:src="@drawable/pip_ic_pause_white" android:text="@string/pip_pause" android:visibility="gone" /> </merge> libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java +0 −13 Original line number Diff line number Diff line Loading @@ -24,7 +24,6 @@ import android.content.pm.ActivityInfo; import android.graphics.Rect; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.pip.tv.PipController; import java.io.PrintWriter; import java.util.function.Consumer; Loading @@ -33,12 +32,6 @@ import java.util.function.Consumer; * Interface to engage picture in picture feature. */ public interface Pip { /** * Registers a {@link PipController.MediaListener} to PipController. */ default void addMediaListener(PipController.MediaListener listener) { } /** * Closes PIP (PIPed activity and PIP system UI). */ Loading Loading @@ -144,12 +137,6 @@ public interface Pip { default void onTaskStackChanged() { } /** * Removes a {@link PipController.MediaListener} from PipController. */ default void removeMediaListener(PipController.MediaListener listener) { } /** * Resize the Pip to the appropriate size for the input state. * Loading libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java +10 −104 Original line number Diff line number Diff line Loading @@ -38,9 +38,6 @@ import android.content.pm.ParceledListSlice; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; import android.media.session.MediaController; import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; import android.os.Debug; import android.os.Handler; import android.os.RemoteException; Loading @@ -55,6 +52,7 @@ import com.android.wm.shell.pip.PinnedStackListenerForwarder; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipBoundsHandler; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipTaskOrganizer; import java.util.ArrayList; Loading Loading @@ -110,22 +108,19 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac private final PipBoundsState mPipBoundsState; private final PipBoundsHandler mPipBoundsHandler; private final PipTaskOrganizer mPipTaskOrganizer; private final PipMediaController mPipMediaController; private IActivityTaskManager mActivityTaskManager; private MediaSessionManager mMediaSessionManager; private int mState = STATE_NO_PIP; private int mResumeResizePinnedStackRunnableState = STATE_NO_PIP; private final Handler mHandler = new Handler(); private List<Listener> mListeners = new ArrayList<>(); private List<MediaListener> mMediaListeners = new ArrayList<>(); private Rect mPipBounds; private Rect mDefaultPipBounds = new Rect(); private Rect mMenuModePipBounds; private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED; private int mPipTaskId = TASK_ID_NO_PIP; private int mPinnedStackId = INVALID_STACK_ID; private ComponentName mPipComponentName; private MediaController mPipMediaController; private String[] mLastPackagesResourceGranted; private PipNotification mPipNotification; private ParceledListSlice<RemoteAction> mCustomActions; Loading Loading @@ -168,17 +163,13 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac } } }; private final MediaSessionManager.OnActiveSessionsChangedListener mActiveMediaSessionListener = controllers -> updateMediaController(controllers); private final PinnedStackListenerForwarder.PinnedStackListener mPinnedStackListener = new PipControllerPinnedStackListener(); @Override public void registerSessionListenerForCurrentUser() { // TODO Need confirm if TV have to re-registers when switch user mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveMediaSessionListener); mMediaSessionManager.addOnActiveSessionsChangedListener(mActiveMediaSessionListener, null, UserHandle.USER_CURRENT, null); mPipMediaController.registerSessionListenerForCurrentUser(); } /** Loading Loading @@ -232,12 +223,13 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac PipBoundsState pipBoundsState, PipBoundsHandler pipBoundsHandler, PipTaskOrganizer pipTaskOrganizer, WindowManagerShellWrapper windowManagerShellWrapper ) { PipMediaController pipMediaController, WindowManagerShellWrapper windowManagerShellWrapper) { mContext = context; mPipBoundsState = pipBoundsState; mPipNotification = new PipNotification(context, this); mPipBoundsHandler = pipBoundsHandler; mPipMediaController = pipMediaController; // Ensure that we have the display info in case we get calls to update the bounds // before the listener calls back final DisplayInfo displayInfo = new DisplayInfo(); Loading @@ -261,7 +253,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac mLastOrientation = initialConfig.orientation; loadConfigurationsAndApply(initialConfig); mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class); mWindowManagerShellWrapper = windowManagerShellWrapper; try { mWindowManagerShellWrapper.addPinnedStackListener(mPinnedStackListener); Loading Loading @@ -329,8 +320,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac mState = STATE_NO_PIP; mPipTaskId = TASK_ID_NO_PIP; mPipMediaController = null; mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveMediaSessionListener); if (removePipStack) { try { mActivityTaskManager.removeTask(mPinnedStackId); Loading Loading @@ -371,13 +360,9 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac if (DEBUG) Log.d(TAG, "PINNED_STACK:" + taskInfo); mPinnedStackId = taskInfo.taskId; mPipTaskId = taskInfo.childTaskIds[taskInfo.childTaskIds.length - 1]; mPipComponentName = ComponentName.unflattenFromString( taskInfo.childTaskNames[taskInfo.childTaskNames.length - 1]); // Set state to STATE_PIP so we show it when the pinned stack animation ends. mState = STATE_PIP; mMediaSessionManager.addOnActiveSessionsChangedListener( mActiveMediaSessionListener, null); updateMediaController(mMediaSessionManager.getActiveSessions(null)); mPipMediaController.onActivityPinned(); for (int i = mListeners.size() - 1; i >= 0; i--) { mListeners.get(i).onPipEntered(packageName); } Loading Loading @@ -553,20 +538,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac mListeners.remove(listener); } /** * Adds a {@link MediaListener} to PipController. */ public void addMediaListener(MediaListener listener) { mMediaListeners.add(listener); } /** * Removes a {@link MediaListener} from PipController. */ public void removeMediaListener(MediaListener listener) { mMediaListeners.remove(listener); } /** * Returns {@code true} if PIP is shown. */ Loading Loading @@ -608,69 +579,12 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac } } private void updateMediaController(List<MediaController> controllers) { MediaController mediaController = null; if (controllers != null && getState() != STATE_NO_PIP && mPipComponentName != null) { for (int i = controllers.size() - 1; i >= 0; i--) { MediaController controller = controllers.get(i); // We assumes that an app with PIPable activity // keeps the single instance of media controller especially when PIP is on. if (controller.getPackageName().equals(mPipComponentName.getPackageName())) { mediaController = controller; break; } } } if (mPipMediaController != mediaController) { mPipMediaController = mediaController; for (int i = mMediaListeners.size() - 1; i >= 0; i--) { mMediaListeners.get(i).onMediaControllerChanged(); } if (mPipMediaController == null) { mHandler.postDelayed(mClosePipRunnable, CLOSE_PIP_WHEN_MEDIA_SESSION_GONE_TIMEOUT_MS); } else { mHandler.removeCallbacks(mClosePipRunnable); } } } /** * Gets the {@link android.media.session.MediaController} for the PIPed activity. */ MediaController getMediaController() { return mPipMediaController; } @Override public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) { } /** * Returns the PIPed activity's playback state. * This returns one of {@link #PLAYBACK_STATE_PLAYING}, {@link #PLAYBACK_STATE_PAUSED}, * or {@link #PLAYBACK_STATE_UNAVAILABLE}. */ int getPlaybackState() { if (mPipMediaController == null || mPipMediaController.getPlaybackState() == null) { return PLAYBACK_STATE_UNAVAILABLE; } int state = mPipMediaController.getPlaybackState().getState(); boolean isPlaying = (state == PlaybackState.STATE_BUFFERING || state == PlaybackState.STATE_CONNECTING || state == PlaybackState.STATE_PLAYING || state == PlaybackState.STATE_FAST_FORWARDING || state == PlaybackState.STATE_REWINDING || state == PlaybackState.STATE_SKIPPING_TO_PREVIOUS || state == PlaybackState.STATE_SKIPPING_TO_NEXT); long actions = mPipMediaController.getPlaybackState().getActions(); if (!isPlaying && ((actions & PlaybackState.ACTION_PLAY) != 0)) { return PLAYBACK_STATE_PAUSED; } else if (isPlaying && ((actions & PlaybackState.ACTION_PAUSE) != 0)) { return PLAYBACK_STATE_PLAYING; } return PLAYBACK_STATE_UNAVAILABLE; PipMediaController getPipMediaController() { return mPipMediaController; } @Override Loading Loading @@ -718,14 +632,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac void onPipResizeAboutToStart(); } /** * A listener interface to receive change in PIP's media controller */ public interface MediaListener { /** Invoked when the MediaController on PIPed activity is changed. */ void onMediaControllerChanged(); } private String getStateDescription() { if (mSuspendPipResizingReason == 0) { return stateToName(mState); Loading libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlsView.java +2 −6 Original line number Diff line number Diff line Loading @@ -51,15 +51,11 @@ public class PipControlsView extends LinearLayout { setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); } PipControlButtonView getFullButtonView() { PipControlButtonView getFullscreenButton() { return findViewById(R.id.full_button); } PipControlButtonView getCloseButtonView() { PipControlButtonView getCloseButton() { return findViewById(R.id.close_button); } PipControlButtonView getPlayPauseButtonView() { return findViewById(R.id.play_pause_button); } } libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlsViewController.java +91 −187 Original line number Diff line number Diff line Loading @@ -18,10 +18,10 @@ package com.android.wm.shell.pip.tv; import android.app.PendingIntent; import android.app.RemoteAction; import android.content.Context; import android.graphics.Color; import android.media.session.MediaController; import android.media.session.PlaybackState; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.view.LayoutInflater; import android.view.View; Loading @@ -29,9 +29,8 @@ import android.view.View; import com.android.wm.shell.R; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; /** Loading @@ -42,213 +41,118 @@ public class PipControlsViewController { private static final float DISABLED_ACTION_ALPHA = 0.54f; private final PipControlsView mView; private final LayoutInflater mLayoutInflater; private final Handler mHandler; private final PipController mPipController; private final PipControlButtonView mPlayPauseButtonView; private MediaController mMediaController; private PipControlButtonView mFocusedChild; private Listener mListener; private ArrayList<PipControlButtonView> mCustomButtonViews = new ArrayList<>(); private List<RemoteAction> mCustomActions = new ArrayList<>(); public PipControlsView getView() { return mView; } /** * An interface to listen user action. */ public interface Listener { /** * Called when a user clicks close PIP button. */ void onClosed(); } private View.OnAttachStateChangeListener mOnAttachStateChangeListener = new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { updateMediaController(); mPipController.addMediaListener(mPipMediaListener); } @Override public void onViewDetachedFromWindow(View v) { mPipController.removeMediaListener(mPipMediaListener); } }; private final Context mContext; private final Handler mUiThreadHandler; private final PipControlsView mView; private final List<PipControlButtonView> mAdditionalButtons = new ArrayList<>(); private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() { @Override public void onPlaybackStateChanged(PlaybackState state) { updateUserActions(); } }; private final PipController.MediaListener mPipMediaListener = this::updateMediaController; private final View.OnFocusChangeListener mFocusChangeListener = new View.OnFocusChangeListener() { @Override public void onFocusChange(View view, boolean hasFocus) { if (hasFocus) { mFocusedChild = (PipControlButtonView) view; } else if (mFocusedChild == view) { mFocusedChild = null; } } }; private final List<RemoteAction> mCustomActions = new ArrayList<>(); private final List<RemoteAction> mMediaActions = new ArrayList<>(); public PipControlsViewController(PipControlsView view, PipController pipController, LayoutInflater layoutInflater, Handler handler) { super(); mView = view; public PipControlsViewController(PipControlsView view, PipController pipController) { mContext = view.getContext(); mUiThreadHandler = new Handler(Looper.getMainLooper()); mPipController = pipController; mLayoutInflater = layoutInflater; mHandler = handler; mView = view; mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener); if (mView.isAttachedToWindow()) { mOnAttachStateChangeListener.onViewAttachedToWindow(mView); } mView.getFullscreenButton().setOnClickListener(v -> mPipController.movePipToFullscreen()); mView.getCloseButton().setOnClickListener(v -> mPipController.closePip()); View fullButtonView = mView.getFullButtonView(); fullButtonView.setOnFocusChangeListener(mFocusChangeListener); fullButtonView.setOnClickListener(mView -> mPipController.movePipToFullscreen()); mPipController.getPipMediaController().addActionListener(this::onMediaActionsChanged); } View closeButtonView = mView.getCloseButtonView(); closeButtonView.setOnFocusChangeListener(mFocusChangeListener); closeButtonView.setOnClickListener(v -> { mPipController.closePip(); if (mListener != null) { mListener.onClosed(); PipControlsView getView() { return mView; } }); mPlayPauseButtonView = mView.getPlayPauseButtonView(); mPlayPauseButtonView.setOnFocusChangeListener(mFocusChangeListener); mPlayPauseButtonView.setOnClickListener(v -> { if (mMediaController == null || mMediaController.getPlaybackState() == null) { /** * Updates the set of activity-defined actions. */ void setCustomActions(List<? extends RemoteAction> actions) { if (mCustomActions.isEmpty() && actions.isEmpty()) { // Nothing changed - return early. return; } final int playbackState = mPipController.getPlaybackState(); if (playbackState == PipController.PLAYBACK_STATE_PAUSED) { mMediaController.getTransportControls().play(); } else if (playbackState == PipController.PLAYBACK_STATE_PLAYING) { mMediaController.getTransportControls().pause(); } // View will be updated later in {@link mMediaControllerCallback} }); mCustomActions.clear(); mCustomActions.addAll(actions); updateAdditionalActions(); } private void updateMediaController() { AtomicReference<MediaController> newController = new AtomicReference<>(); newController.set(mPipController.getMediaController()); if (newController.get() == null || mMediaController == newController.get()) { private void onMediaActionsChanged(List<RemoteAction> actions) { if (mMediaActions.isEmpty() && actions.isEmpty()) { // Nothing changed - return early. return; } if (mMediaController != null) { mMediaController.unregisterCallback(mMediaControllerCallback); } mMediaController = newController.get(); if (mMediaController != null) { mMediaController.registerCallback(mMediaControllerCallback); mMediaActions.clear(); mMediaActions.addAll(actions); // Update the view only if there are no custom actions (media actions are only shown when // there no custom actions). if (mCustomActions.isEmpty()) { updateAdditionalActions(); } updateUserActions(); } /** * Updates the actions for the PIP. If there are no custom actions, then the media session * actions are shown. */ private void updateUserActions() { private void updateAdditionalActions() { final List<RemoteAction> actionsToDisplay; if (!mCustomActions.isEmpty()) { // Ensure we have as many buttons as actions while (mCustomButtonViews.size() < mCustomActions.size()) { PipControlButtonView buttonView = (PipControlButtonView) mLayoutInflater.inflate( // If there are custom actions: show them. actionsToDisplay = mCustomActions; } else if (!mMediaActions.isEmpty()) { // If there are no custom actions, but there media actions: show them. actionsToDisplay = mMediaActions; } else { // If there no custom actions and no media actions: clean up all the additional buttons. actionsToDisplay = Collections.emptyList(); } // Make sure we exactly as many additional buttons as we have actions to display. final int actionsNumber = actionsToDisplay.size(); int buttonsNumber = mAdditionalButtons.size(); if (actionsNumber > buttonsNumber) { final LayoutInflater layoutInflater = LayoutInflater.from(mContext); // Add buttons until we have enough to display all of the actions. while (actionsNumber > buttonsNumber) { final PipControlButtonView button = (PipControlButtonView) layoutInflater.inflate( R.layout.tv_pip_custom_control, mView, false); mView.addView(buttonView); mCustomButtonViews.add(buttonView); mView.addView(button); mAdditionalButtons.add(button); buttonsNumber++; } } else if (actionsNumber < buttonsNumber) { // Hide buttons until we as many as the actions. while (actionsNumber < buttonsNumber) { final View button = mAdditionalButtons.get(buttonsNumber - 1); button.setVisibility(View.GONE); button.setOnClickListener(null); // Update the visibility of all views for (int i = 0; i < mCustomButtonViews.size(); i++) { mCustomButtonViews.get(i).setVisibility( i < mCustomActions.size() ? View.VISIBLE : View.GONE); buttonsNumber--; } } // Update the state and visibility of the action buttons, and hide the rest for (int i = 0; i < mCustomActions.size(); i++) { final RemoteAction action = mCustomActions.get(i); PipControlButtonView actionView = mCustomButtonViews.get(i); // TODO: Check if the action drawable has changed before we reload it action.getIcon().loadDrawableAsync(mView.getContext(), d -> { d.setTint(Color.WHITE); actionView.setImageDrawable(d); }, mHandler); actionView.setText(action.getContentDescription()); if (action.isEnabled()) { actionView.setOnClickListener(v -> { // "Assign" actions to the buttons. for (int index = 0; index < actionsNumber; index++) { final RemoteAction action = actionsToDisplay.get(index); final PipControlButtonView button = mAdditionalButtons.get(index); button.setVisibility(View.VISIBLE); // Ensure the button is visible. button.setText(action.getContentDescription()); button.setEnabled(action.isEnabled()); button.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA); button.setOnClickListener(v -> { try { action.getActionIntent().send(); } catch (PendingIntent.CanceledException e) { Log.w(TAG, "Failed to send action", e); } }); } actionView.setEnabled(action.isEnabled()); actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA); } // Hide the media session buttons mPlayPauseButtonView.setVisibility(View.GONE); } else { AtomicInteger state = new AtomicInteger(PipController.STATE_UNKNOWN); state.set(mPipController.getPlaybackState()); if (state.get() == PipController.STATE_UNKNOWN || state.get() == PipController.PLAYBACK_STATE_UNAVAILABLE) { mPlayPauseButtonView.setVisibility(View.GONE); } else { mPlayPauseButtonView.setVisibility(View.VISIBLE); if (state.get() == PipController.PLAYBACK_STATE_PLAYING) { mPlayPauseButtonView.setImageResource(R.drawable.pip_ic_pause_white); mPlayPauseButtonView.setText(R.string.pip_pause); } else { mPlayPauseButtonView.setImageResource(R.drawable.pip_ic_play_arrow_white); mPlayPauseButtonView.setText(R.string.pip_play); } } // Hide all the custom action buttons for (int i = 0; i < mCustomButtonViews.size(); i++) { mCustomButtonViews.get(i).setVisibility(View.GONE); } } } /** * Sets the {@link Listener} to listen user actions. */ public void setListener(Listener listener) { mListener = listener; action.getIcon().loadDrawableAsync(mContext, drawable -> { drawable.setTint(Color.WHITE); button.setImageDrawable(drawable); }, mUiThreadHandler); } /** * Updates the set of activity-defined actions. */ public void setActions(List<? extends RemoteAction> actions) { mCustomActions.clear(); mCustomActions.addAll(actions); updateUserActions(); } } Loading
libs/WindowManager/Shell/res/layout/tv_pip_controls.xml +0 −10 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ --> <!-- Layout for {@link com.android.wm.shell.pip.tv.PipControlsView}. --> <merge xmlns:android="http://schemas.android.com/apk/res/android"> <com.android.wm.shell.pip.tv.PipControlButtonView android:id="@+id/full_button" android:layout_width="@dimen/picture_in_picture_button_width" Loading @@ -31,13 +30,4 @@ android:layout_marginStart="@dimen/picture_in_picture_button_start_margin" android:src="@drawable/pip_ic_close_white" android:text="@string/pip_close" /> <com.android.wm.shell.pip.tv.PipControlButtonView android:id="@+id/play_pause_button" android:layout_width="@dimen/picture_in_picture_button_width" android:layout_height="wrap_content" android:layout_marginStart="@dimen/picture_in_picture_button_start_margin" android:src="@drawable/pip_ic_pause_white" android:text="@string/pip_pause" android:visibility="gone" /> </merge>
libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java +0 −13 Original line number Diff line number Diff line Loading @@ -24,7 +24,6 @@ import android.content.pm.ActivityInfo; import android.graphics.Rect; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.pip.tv.PipController; import java.io.PrintWriter; import java.util.function.Consumer; Loading @@ -33,12 +32,6 @@ import java.util.function.Consumer; * Interface to engage picture in picture feature. */ public interface Pip { /** * Registers a {@link PipController.MediaListener} to PipController. */ default void addMediaListener(PipController.MediaListener listener) { } /** * Closes PIP (PIPed activity and PIP system UI). */ Loading Loading @@ -144,12 +137,6 @@ public interface Pip { default void onTaskStackChanged() { } /** * Removes a {@link PipController.MediaListener} from PipController. */ default void removeMediaListener(PipController.MediaListener listener) { } /** * Resize the Pip to the appropriate size for the input state. * Loading
libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java +10 −104 Original line number Diff line number Diff line Loading @@ -38,9 +38,6 @@ import android.content.pm.ParceledListSlice; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; import android.media.session.MediaController; import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; import android.os.Debug; import android.os.Handler; import android.os.RemoteException; Loading @@ -55,6 +52,7 @@ import com.android.wm.shell.pip.PinnedStackListenerForwarder; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipBoundsHandler; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipTaskOrganizer; import java.util.ArrayList; Loading Loading @@ -110,22 +108,19 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac private final PipBoundsState mPipBoundsState; private final PipBoundsHandler mPipBoundsHandler; private final PipTaskOrganizer mPipTaskOrganizer; private final PipMediaController mPipMediaController; private IActivityTaskManager mActivityTaskManager; private MediaSessionManager mMediaSessionManager; private int mState = STATE_NO_PIP; private int mResumeResizePinnedStackRunnableState = STATE_NO_PIP; private final Handler mHandler = new Handler(); private List<Listener> mListeners = new ArrayList<>(); private List<MediaListener> mMediaListeners = new ArrayList<>(); private Rect mPipBounds; private Rect mDefaultPipBounds = new Rect(); private Rect mMenuModePipBounds; private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED; private int mPipTaskId = TASK_ID_NO_PIP; private int mPinnedStackId = INVALID_STACK_ID; private ComponentName mPipComponentName; private MediaController mPipMediaController; private String[] mLastPackagesResourceGranted; private PipNotification mPipNotification; private ParceledListSlice<RemoteAction> mCustomActions; Loading Loading @@ -168,17 +163,13 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac } } }; private final MediaSessionManager.OnActiveSessionsChangedListener mActiveMediaSessionListener = controllers -> updateMediaController(controllers); private final PinnedStackListenerForwarder.PinnedStackListener mPinnedStackListener = new PipControllerPinnedStackListener(); @Override public void registerSessionListenerForCurrentUser() { // TODO Need confirm if TV have to re-registers when switch user mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveMediaSessionListener); mMediaSessionManager.addOnActiveSessionsChangedListener(mActiveMediaSessionListener, null, UserHandle.USER_CURRENT, null); mPipMediaController.registerSessionListenerForCurrentUser(); } /** Loading Loading @@ -232,12 +223,13 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac PipBoundsState pipBoundsState, PipBoundsHandler pipBoundsHandler, PipTaskOrganizer pipTaskOrganizer, WindowManagerShellWrapper windowManagerShellWrapper ) { PipMediaController pipMediaController, WindowManagerShellWrapper windowManagerShellWrapper) { mContext = context; mPipBoundsState = pipBoundsState; mPipNotification = new PipNotification(context, this); mPipBoundsHandler = pipBoundsHandler; mPipMediaController = pipMediaController; // Ensure that we have the display info in case we get calls to update the bounds // before the listener calls back final DisplayInfo displayInfo = new DisplayInfo(); Loading @@ -261,7 +253,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac mLastOrientation = initialConfig.orientation; loadConfigurationsAndApply(initialConfig); mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class); mWindowManagerShellWrapper = windowManagerShellWrapper; try { mWindowManagerShellWrapper.addPinnedStackListener(mPinnedStackListener); Loading Loading @@ -329,8 +320,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac mState = STATE_NO_PIP; mPipTaskId = TASK_ID_NO_PIP; mPipMediaController = null; mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveMediaSessionListener); if (removePipStack) { try { mActivityTaskManager.removeTask(mPinnedStackId); Loading Loading @@ -371,13 +360,9 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac if (DEBUG) Log.d(TAG, "PINNED_STACK:" + taskInfo); mPinnedStackId = taskInfo.taskId; mPipTaskId = taskInfo.childTaskIds[taskInfo.childTaskIds.length - 1]; mPipComponentName = ComponentName.unflattenFromString( taskInfo.childTaskNames[taskInfo.childTaskNames.length - 1]); // Set state to STATE_PIP so we show it when the pinned stack animation ends. mState = STATE_PIP; mMediaSessionManager.addOnActiveSessionsChangedListener( mActiveMediaSessionListener, null); updateMediaController(mMediaSessionManager.getActiveSessions(null)); mPipMediaController.onActivityPinned(); for (int i = mListeners.size() - 1; i >= 0; i--) { mListeners.get(i).onPipEntered(packageName); } Loading Loading @@ -553,20 +538,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac mListeners.remove(listener); } /** * Adds a {@link MediaListener} to PipController. */ public void addMediaListener(MediaListener listener) { mMediaListeners.add(listener); } /** * Removes a {@link MediaListener} from PipController. */ public void removeMediaListener(MediaListener listener) { mMediaListeners.remove(listener); } /** * Returns {@code true} if PIP is shown. */ Loading Loading @@ -608,69 +579,12 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac } } private void updateMediaController(List<MediaController> controllers) { MediaController mediaController = null; if (controllers != null && getState() != STATE_NO_PIP && mPipComponentName != null) { for (int i = controllers.size() - 1; i >= 0; i--) { MediaController controller = controllers.get(i); // We assumes that an app with PIPable activity // keeps the single instance of media controller especially when PIP is on. if (controller.getPackageName().equals(mPipComponentName.getPackageName())) { mediaController = controller; break; } } } if (mPipMediaController != mediaController) { mPipMediaController = mediaController; for (int i = mMediaListeners.size() - 1; i >= 0; i--) { mMediaListeners.get(i).onMediaControllerChanged(); } if (mPipMediaController == null) { mHandler.postDelayed(mClosePipRunnable, CLOSE_PIP_WHEN_MEDIA_SESSION_GONE_TIMEOUT_MS); } else { mHandler.removeCallbacks(mClosePipRunnable); } } } /** * Gets the {@link android.media.session.MediaController} for the PIPed activity. */ MediaController getMediaController() { return mPipMediaController; } @Override public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) { } /** * Returns the PIPed activity's playback state. * This returns one of {@link #PLAYBACK_STATE_PLAYING}, {@link #PLAYBACK_STATE_PAUSED}, * or {@link #PLAYBACK_STATE_UNAVAILABLE}. */ int getPlaybackState() { if (mPipMediaController == null || mPipMediaController.getPlaybackState() == null) { return PLAYBACK_STATE_UNAVAILABLE; } int state = mPipMediaController.getPlaybackState().getState(); boolean isPlaying = (state == PlaybackState.STATE_BUFFERING || state == PlaybackState.STATE_CONNECTING || state == PlaybackState.STATE_PLAYING || state == PlaybackState.STATE_FAST_FORWARDING || state == PlaybackState.STATE_REWINDING || state == PlaybackState.STATE_SKIPPING_TO_PREVIOUS || state == PlaybackState.STATE_SKIPPING_TO_NEXT); long actions = mPipMediaController.getPlaybackState().getActions(); if (!isPlaying && ((actions & PlaybackState.ACTION_PLAY) != 0)) { return PLAYBACK_STATE_PAUSED; } else if (isPlaying && ((actions & PlaybackState.ACTION_PAUSE) != 0)) { return PLAYBACK_STATE_PLAYING; } return PLAYBACK_STATE_UNAVAILABLE; PipMediaController getPipMediaController() { return mPipMediaController; } @Override Loading Loading @@ -718,14 +632,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac void onPipResizeAboutToStart(); } /** * A listener interface to receive change in PIP's media controller */ public interface MediaListener { /** Invoked when the MediaController on PIPed activity is changed. */ void onMediaControllerChanged(); } private String getStateDescription() { if (mSuspendPipResizingReason == 0) { return stateToName(mState); Loading
libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlsView.java +2 −6 Original line number Diff line number Diff line Loading @@ -51,15 +51,11 @@ public class PipControlsView extends LinearLayout { setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); } PipControlButtonView getFullButtonView() { PipControlButtonView getFullscreenButton() { return findViewById(R.id.full_button); } PipControlButtonView getCloseButtonView() { PipControlButtonView getCloseButton() { return findViewById(R.id.close_button); } PipControlButtonView getPlayPauseButtonView() { return findViewById(R.id.play_pause_button); } }
libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlsViewController.java +91 −187 Original line number Diff line number Diff line Loading @@ -18,10 +18,10 @@ package com.android.wm.shell.pip.tv; import android.app.PendingIntent; import android.app.RemoteAction; import android.content.Context; import android.graphics.Color; import android.media.session.MediaController; import android.media.session.PlaybackState; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.view.LayoutInflater; import android.view.View; Loading @@ -29,9 +29,8 @@ import android.view.View; import com.android.wm.shell.R; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; /** Loading @@ -42,213 +41,118 @@ public class PipControlsViewController { private static final float DISABLED_ACTION_ALPHA = 0.54f; private final PipControlsView mView; private final LayoutInflater mLayoutInflater; private final Handler mHandler; private final PipController mPipController; private final PipControlButtonView mPlayPauseButtonView; private MediaController mMediaController; private PipControlButtonView mFocusedChild; private Listener mListener; private ArrayList<PipControlButtonView> mCustomButtonViews = new ArrayList<>(); private List<RemoteAction> mCustomActions = new ArrayList<>(); public PipControlsView getView() { return mView; } /** * An interface to listen user action. */ public interface Listener { /** * Called when a user clicks close PIP button. */ void onClosed(); } private View.OnAttachStateChangeListener mOnAttachStateChangeListener = new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { updateMediaController(); mPipController.addMediaListener(mPipMediaListener); } @Override public void onViewDetachedFromWindow(View v) { mPipController.removeMediaListener(mPipMediaListener); } }; private final Context mContext; private final Handler mUiThreadHandler; private final PipControlsView mView; private final List<PipControlButtonView> mAdditionalButtons = new ArrayList<>(); private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() { @Override public void onPlaybackStateChanged(PlaybackState state) { updateUserActions(); } }; private final PipController.MediaListener mPipMediaListener = this::updateMediaController; private final View.OnFocusChangeListener mFocusChangeListener = new View.OnFocusChangeListener() { @Override public void onFocusChange(View view, boolean hasFocus) { if (hasFocus) { mFocusedChild = (PipControlButtonView) view; } else if (mFocusedChild == view) { mFocusedChild = null; } } }; private final List<RemoteAction> mCustomActions = new ArrayList<>(); private final List<RemoteAction> mMediaActions = new ArrayList<>(); public PipControlsViewController(PipControlsView view, PipController pipController, LayoutInflater layoutInflater, Handler handler) { super(); mView = view; public PipControlsViewController(PipControlsView view, PipController pipController) { mContext = view.getContext(); mUiThreadHandler = new Handler(Looper.getMainLooper()); mPipController = pipController; mLayoutInflater = layoutInflater; mHandler = handler; mView = view; mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener); if (mView.isAttachedToWindow()) { mOnAttachStateChangeListener.onViewAttachedToWindow(mView); } mView.getFullscreenButton().setOnClickListener(v -> mPipController.movePipToFullscreen()); mView.getCloseButton().setOnClickListener(v -> mPipController.closePip()); View fullButtonView = mView.getFullButtonView(); fullButtonView.setOnFocusChangeListener(mFocusChangeListener); fullButtonView.setOnClickListener(mView -> mPipController.movePipToFullscreen()); mPipController.getPipMediaController().addActionListener(this::onMediaActionsChanged); } View closeButtonView = mView.getCloseButtonView(); closeButtonView.setOnFocusChangeListener(mFocusChangeListener); closeButtonView.setOnClickListener(v -> { mPipController.closePip(); if (mListener != null) { mListener.onClosed(); PipControlsView getView() { return mView; } }); mPlayPauseButtonView = mView.getPlayPauseButtonView(); mPlayPauseButtonView.setOnFocusChangeListener(mFocusChangeListener); mPlayPauseButtonView.setOnClickListener(v -> { if (mMediaController == null || mMediaController.getPlaybackState() == null) { /** * Updates the set of activity-defined actions. */ void setCustomActions(List<? extends RemoteAction> actions) { if (mCustomActions.isEmpty() && actions.isEmpty()) { // Nothing changed - return early. return; } final int playbackState = mPipController.getPlaybackState(); if (playbackState == PipController.PLAYBACK_STATE_PAUSED) { mMediaController.getTransportControls().play(); } else if (playbackState == PipController.PLAYBACK_STATE_PLAYING) { mMediaController.getTransportControls().pause(); } // View will be updated later in {@link mMediaControllerCallback} }); mCustomActions.clear(); mCustomActions.addAll(actions); updateAdditionalActions(); } private void updateMediaController() { AtomicReference<MediaController> newController = new AtomicReference<>(); newController.set(mPipController.getMediaController()); if (newController.get() == null || mMediaController == newController.get()) { private void onMediaActionsChanged(List<RemoteAction> actions) { if (mMediaActions.isEmpty() && actions.isEmpty()) { // Nothing changed - return early. return; } if (mMediaController != null) { mMediaController.unregisterCallback(mMediaControllerCallback); } mMediaController = newController.get(); if (mMediaController != null) { mMediaController.registerCallback(mMediaControllerCallback); mMediaActions.clear(); mMediaActions.addAll(actions); // Update the view only if there are no custom actions (media actions are only shown when // there no custom actions). if (mCustomActions.isEmpty()) { updateAdditionalActions(); } updateUserActions(); } /** * Updates the actions for the PIP. If there are no custom actions, then the media session * actions are shown. */ private void updateUserActions() { private void updateAdditionalActions() { final List<RemoteAction> actionsToDisplay; if (!mCustomActions.isEmpty()) { // Ensure we have as many buttons as actions while (mCustomButtonViews.size() < mCustomActions.size()) { PipControlButtonView buttonView = (PipControlButtonView) mLayoutInflater.inflate( // If there are custom actions: show them. actionsToDisplay = mCustomActions; } else if (!mMediaActions.isEmpty()) { // If there are no custom actions, but there media actions: show them. actionsToDisplay = mMediaActions; } else { // If there no custom actions and no media actions: clean up all the additional buttons. actionsToDisplay = Collections.emptyList(); } // Make sure we exactly as many additional buttons as we have actions to display. final int actionsNumber = actionsToDisplay.size(); int buttonsNumber = mAdditionalButtons.size(); if (actionsNumber > buttonsNumber) { final LayoutInflater layoutInflater = LayoutInflater.from(mContext); // Add buttons until we have enough to display all of the actions. while (actionsNumber > buttonsNumber) { final PipControlButtonView button = (PipControlButtonView) layoutInflater.inflate( R.layout.tv_pip_custom_control, mView, false); mView.addView(buttonView); mCustomButtonViews.add(buttonView); mView.addView(button); mAdditionalButtons.add(button); buttonsNumber++; } } else if (actionsNumber < buttonsNumber) { // Hide buttons until we as many as the actions. while (actionsNumber < buttonsNumber) { final View button = mAdditionalButtons.get(buttonsNumber - 1); button.setVisibility(View.GONE); button.setOnClickListener(null); // Update the visibility of all views for (int i = 0; i < mCustomButtonViews.size(); i++) { mCustomButtonViews.get(i).setVisibility( i < mCustomActions.size() ? View.VISIBLE : View.GONE); buttonsNumber--; } } // Update the state and visibility of the action buttons, and hide the rest for (int i = 0; i < mCustomActions.size(); i++) { final RemoteAction action = mCustomActions.get(i); PipControlButtonView actionView = mCustomButtonViews.get(i); // TODO: Check if the action drawable has changed before we reload it action.getIcon().loadDrawableAsync(mView.getContext(), d -> { d.setTint(Color.WHITE); actionView.setImageDrawable(d); }, mHandler); actionView.setText(action.getContentDescription()); if (action.isEnabled()) { actionView.setOnClickListener(v -> { // "Assign" actions to the buttons. for (int index = 0; index < actionsNumber; index++) { final RemoteAction action = actionsToDisplay.get(index); final PipControlButtonView button = mAdditionalButtons.get(index); button.setVisibility(View.VISIBLE); // Ensure the button is visible. button.setText(action.getContentDescription()); button.setEnabled(action.isEnabled()); button.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA); button.setOnClickListener(v -> { try { action.getActionIntent().send(); } catch (PendingIntent.CanceledException e) { Log.w(TAG, "Failed to send action", e); } }); } actionView.setEnabled(action.isEnabled()); actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA); } // Hide the media session buttons mPlayPauseButtonView.setVisibility(View.GONE); } else { AtomicInteger state = new AtomicInteger(PipController.STATE_UNKNOWN); state.set(mPipController.getPlaybackState()); if (state.get() == PipController.STATE_UNKNOWN || state.get() == PipController.PLAYBACK_STATE_UNAVAILABLE) { mPlayPauseButtonView.setVisibility(View.GONE); } else { mPlayPauseButtonView.setVisibility(View.VISIBLE); if (state.get() == PipController.PLAYBACK_STATE_PLAYING) { mPlayPauseButtonView.setImageResource(R.drawable.pip_ic_pause_white); mPlayPauseButtonView.setText(R.string.pip_pause); } else { mPlayPauseButtonView.setImageResource(R.drawable.pip_ic_play_arrow_white); mPlayPauseButtonView.setText(R.string.pip_play); } } // Hide all the custom action buttons for (int i = 0; i < mCustomButtonViews.size(); i++) { mCustomButtonViews.get(i).setVisibility(View.GONE); } } } /** * Sets the {@link Listener} to listen user actions. */ public void setListener(Listener listener) { mListener = listener; action.getIcon().loadDrawableAsync(mContext, drawable -> { drawable.setTint(Color.WHITE); button.setImageDrawable(drawable); }, mUiThreadHandler); } /** * Updates the set of activity-defined actions. */ public void setActions(List<? extends RemoteAction> actions) { mCustomActions.clear(); mCustomActions.addAll(actions); updateUserActions(); } }