Use PipMediaController in .tv.PipController
Make .tv.PipController rely on PipMediaController for media session monitoring and managering. Refactor .tv.PipControlsViewController to use PipMediaController to retrieve the media buttons. Make .tv.PipNotification subscribe to PipMediaController to receive callbacks about media metadata updates. Bug: 169575409 Bug: 165795012 Test: make WindowManager-Shell; make SystemUI Change-Id: I397f93eb4433116ba4f476bda7374b07d97e082d
This commit is contained in:
@@ -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"
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -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).
|
||||
*/
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
|
||||
/**
|
||||
@@ -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() {
|
||||
private final Context mContext;
|
||||
private final Handler mUiThreadHandler;
|
||||
private final PipControlsView mView;
|
||||
private final List<PipControlButtonView> mAdditionalButtons = new ArrayList<>();
|
||||
|
||||
private final List<RemoteAction> mCustomActions = new ArrayList<>();
|
||||
private final List<RemoteAction> mMediaActions = new ArrayList<>();
|
||||
|
||||
public PipControlsViewController(PipControlsView view, PipController pipController) {
|
||||
mContext = view.getContext();
|
||||
mUiThreadHandler = new Handler(Looper.getMainLooper());
|
||||
mPipController = pipController;
|
||||
mView = view;
|
||||
|
||||
mView.getFullscreenButton().setOnClickListener(v -> mPipController.movePipToFullscreen());
|
||||
mView.getCloseButton().setOnClickListener(v -> mPipController.closePip());
|
||||
|
||||
mPipController.getPipMediaController().addActionListener(this::onMediaActionsChanged);
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public PipControlsViewController(PipControlsView view, PipController pipController,
|
||||
LayoutInflater layoutInflater, Handler handler) {
|
||||
super();
|
||||
mView = view;
|
||||
mPipController = pipController;
|
||||
mLayoutInflater = layoutInflater;
|
||||
mHandler = handler;
|
||||
|
||||
mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
|
||||
if (mView.isAttachedToWindow()) {
|
||||
mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
|
||||
}
|
||||
|
||||
View fullButtonView = mView.getFullButtonView();
|
||||
fullButtonView.setOnFocusChangeListener(mFocusChangeListener);
|
||||
fullButtonView.setOnClickListener(mView -> mPipController.movePipToFullscreen());
|
||||
|
||||
View closeButtonView = mView.getCloseButtonView();
|
||||
closeButtonView.setOnFocusChangeListener(mFocusChangeListener);
|
||||
closeButtonView.setOnClickListener(v -> {
|
||||
mPipController.closePip();
|
||||
if (mListener != null) {
|
||||
mListener.onClosed();
|
||||
}
|
||||
});
|
||||
|
||||
mPlayPauseButtonView = mView.getPlayPauseButtonView();
|
||||
mPlayPauseButtonView.setOnFocusChangeListener(mFocusChangeListener);
|
||||
mPlayPauseButtonView.setOnClickListener(v -> {
|
||||
if (mMediaController == null || mMediaController.getPlaybackState() == null) {
|
||||
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}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateMediaController() {
|
||||
AtomicReference<MediaController> newController = new AtomicReference<>();
|
||||
newController.set(mPipController.getMediaController());
|
||||
|
||||
if (newController.get() == null || mMediaController == newController.get()) {
|
||||
return;
|
||||
}
|
||||
if (mMediaController != null) {
|
||||
mMediaController.unregisterCallback(mMediaControllerCallback);
|
||||
}
|
||||
mMediaController = newController.get();
|
||||
if (mMediaController != null) {
|
||||
mMediaController.registerCallback(mMediaControllerCallback);
|
||||
}
|
||||
updateUserActions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the actions for the PIP. If there are no custom actions, then the media session
|
||||
* actions are shown.
|
||||
*/
|
||||
private void updateUserActions() {
|
||||
if (!mCustomActions.isEmpty()) {
|
||||
// Ensure we have as many buttons as actions
|
||||
while (mCustomButtonViews.size() < mCustomActions.size()) {
|
||||
PipControlButtonView buttonView = (PipControlButtonView) mLayoutInflater.inflate(
|
||||
R.layout.tv_pip_custom_control, mView, false);
|
||||
mView.addView(buttonView);
|
||||
mCustomButtonViews.add(buttonView);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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 -> {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the set of activity-defined actions.
|
||||
*/
|
||||
public void setActions(List<? extends RemoteAction> actions) {
|
||||
void setCustomActions(List<? extends RemoteAction> actions) {
|
||||
if (mCustomActions.isEmpty() && actions.isEmpty()) {
|
||||
// Nothing changed - return early.
|
||||
return;
|
||||
}
|
||||
mCustomActions.clear();
|
||||
mCustomActions.addAll(actions);
|
||||
updateUserActions();
|
||||
updateAdditionalActions();
|
||||
}
|
||||
|
||||
private void onMediaActionsChanged(List<RemoteAction> actions) {
|
||||
if (mMediaActions.isEmpty() && actions.isEmpty()) {
|
||||
// Nothing changed - return early.
|
||||
return;
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAdditionalActions() {
|
||||
final List<RemoteAction> actionsToDisplay;
|
||||
if (!mCustomActions.isEmpty()) {
|
||||
// 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(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);
|
||||
|
||||
buttonsNumber--;
|
||||
}
|
||||
}
|
||||
|
||||
// "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);
|
||||
}
|
||||
});
|
||||
|
||||
action.getIcon().loadDrawableAsync(mContext, drawable -> {
|
||||
drawable.setTint(Color.WHITE);
|
||||
button.setImageDrawable(drawable);
|
||||
}, mUiThreadHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,8 +56,7 @@ public class PipMenuActivity extends Activity implements PipController.Listener
|
||||
}
|
||||
setContentView(R.layout.tv_pip_menu);
|
||||
mPipControlsViewController = new PipControlsViewController(
|
||||
findViewById(R.id.pip_controls), sPipController,
|
||||
getLayoutInflater(), getApplicationContext().getMainThreadHandler());
|
||||
findViewById(R.id.pip_controls), sPipController);
|
||||
sPipController.addListener(this);
|
||||
mRestorePipSizeWhenClose = true;
|
||||
mFadeInAnimation = AnimatorInflater.loadAnimator(
|
||||
@@ -141,7 +140,7 @@ public class PipMenuActivity extends Activity implements PipController.Listener
|
||||
if (DEBUG) Log.d(TAG, "onPipMenuActionsChanged()");
|
||||
|
||||
boolean hasCustomActions = actions != null && !actions.getList().isEmpty();
|
||||
mPipControlsViewController.setActions(
|
||||
mPipControlsViewController.setCustomActions(
|
||||
hasCustomActions ? actions.getList() : Collections.emptyList());
|
||||
}
|
||||
|
||||
|
||||
@@ -28,36 +28,32 @@ import android.content.pm.ParceledListSlice;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.MediaMetadata;
|
||||
import android.media.session.MediaController;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
|
||||
import com.android.wm.shell.R;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A notification that informs users that PIP is running and also provides PIP controls.
|
||||
* <p>Once it's created, it will manage the PIP notification UI by itself except for handling
|
||||
* configuration changes.
|
||||
*/
|
||||
public class PipNotification {
|
||||
private static final String TAG = "PipNotification";
|
||||
private static final String NOTIFICATION_TAG = PipNotification.class.getSimpleName();
|
||||
private static final boolean DEBUG = PipController.DEBUG;
|
||||
private static final String TAG = "PipNotification";
|
||||
|
||||
private static final String NOTIFICATION_TAG = PipNotification.class.getSimpleName();
|
||||
public static final String NOTIFICATION_CHANNEL_TVPIP = "TPP";
|
||||
|
||||
static final String ACTION_MENU = "PipNotification.menu";
|
||||
static final String ACTION_CLOSE = "PipNotification.close";
|
||||
|
||||
public static final String NOTIFICATION_CHANNEL_TVPIP = "TPP";
|
||||
|
||||
private final PackageManager mPackageManager;
|
||||
|
||||
private final PipController mPipController;
|
||||
|
||||
private final NotificationManager mNotificationManager;
|
||||
private final Notification.Builder mNotificationBuilder;
|
||||
|
||||
private MediaController mMediaController;
|
||||
private String mDefaultTitle;
|
||||
private int mDefaultIconResId;
|
||||
|
||||
@@ -71,7 +67,6 @@ public class PipNotification {
|
||||
@Override
|
||||
public void onPipEntered(String packageName) {
|
||||
mPackageName = packageName;
|
||||
updateMediaControllerMetadata();
|
||||
notifyPipNotification();
|
||||
}
|
||||
|
||||
@@ -103,51 +98,9 @@ public class PipNotification {
|
||||
}
|
||||
};
|
||||
|
||||
private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
|
||||
@Override
|
||||
public void onPlaybackStateChanged(PlaybackState state) {
|
||||
if (updateMediaControllerMetadata() && mNotified) {
|
||||
// update notification
|
||||
notifyPipNotification();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMetadataChanged(MediaMetadata metadata) {
|
||||
if (updateMediaControllerMetadata() && mNotified) {
|
||||
// update notification
|
||||
notifyPipNotification();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final PipController.MediaListener mPipMediaListener =
|
||||
new PipController.MediaListener() {
|
||||
@Override
|
||||
public void onMediaControllerChanged() {
|
||||
MediaController newController = mPipController.getMediaController();
|
||||
if (newController == null || mMediaController == newController) {
|
||||
return;
|
||||
}
|
||||
if (mMediaController != null) {
|
||||
mMediaController.unregisterCallback(mMediaControllerCallback);
|
||||
}
|
||||
mMediaController = newController;
|
||||
if (mMediaController != null) {
|
||||
mMediaController.registerCallback(mMediaControllerCallback);
|
||||
}
|
||||
if (updateMediaControllerMetadata() && mNotified) {
|
||||
// update notification
|
||||
notifyPipNotification();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public PipNotification(Context context, PipController pipController) {
|
||||
mPackageManager = context.getPackageManager();
|
||||
|
||||
mNotificationManager = (NotificationManager) context.getSystemService(
|
||||
Context.NOTIFICATION_SERVICE);
|
||||
mNotificationManager = context.getSystemService(NotificationManager.class);
|
||||
|
||||
mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL_TVPIP)
|
||||
.setLocalOnly(true)
|
||||
@@ -157,13 +110,19 @@ public class PipNotification {
|
||||
.setContentIntent(createPendingIntent(context, ACTION_MENU))
|
||||
.setDeleteIntent(createPendingIntent(context, ACTION_CLOSE)));
|
||||
|
||||
mPipController = pipController;
|
||||
pipController.addListener(mPipListener);
|
||||
pipController.addMediaListener(mPipMediaListener);
|
||||
pipController.getPipMediaController().addMetadataListener(this::onMediaMetadataChanged);
|
||||
|
||||
onConfigurationChanged(context);
|
||||
}
|
||||
|
||||
private void onMediaMetadataChanged(MediaMetadata metadata) {
|
||||
if (updateMediaControllerMetadata(metadata) && mNotified) {
|
||||
// update notification
|
||||
notifyPipNotification();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@link PipController} when the configuration is changed.
|
||||
*/
|
||||
@@ -199,28 +158,28 @@ public class PipNotification {
|
||||
mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
|
||||
}
|
||||
|
||||
private boolean updateMediaControllerMetadata() {
|
||||
private boolean updateMediaControllerMetadata(MediaMetadata metadata) {
|
||||
String title = null;
|
||||
Bitmap art = null;
|
||||
if (mPipController.getMediaController() != null) {
|
||||
MediaMetadata metadata = mPipController.getMediaController().getMetadata();
|
||||
if (metadata != null) {
|
||||
title = metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE);
|
||||
if (TextUtils.isEmpty(title)) {
|
||||
title = metadata.getString(MediaMetadata.METADATA_KEY_TITLE);
|
||||
}
|
||||
art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
|
||||
if (art == null) {
|
||||
art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
|
||||
}
|
||||
if (metadata != null) {
|
||||
title = metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE);
|
||||
if (TextUtils.isEmpty(title)) {
|
||||
title = metadata.getString(MediaMetadata.METADATA_KEY_TITLE);
|
||||
}
|
||||
art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
|
||||
if (art == null) {
|
||||
art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
|
||||
}
|
||||
}
|
||||
if (!TextUtils.equals(title, mMediaTitle) || art != mArt) {
|
||||
mMediaTitle = title;
|
||||
mArt = art;
|
||||
return true;
|
||||
|
||||
if (TextUtils.equals(title, mMediaTitle) && Objects.equals(art, mArt)) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
mMediaTitle = title;
|
||||
mArt = art;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
package com.android.systemui.wmshell;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import com.android.systemui.dagger.WMSingleton;
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
@@ -27,6 +25,7 @@ import com.android.wm.shell.common.DisplayController;
|
||||
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.PipSurfaceTransactionHelper;
|
||||
import com.android.wm.shell.pip.PipTaskOrganizer;
|
||||
import com.android.wm.shell.pip.PipUiEventLogger;
|
||||
@@ -53,6 +52,7 @@ public abstract class TvPipModule {
|
||||
PipBoundsState pipBoundsState,
|
||||
PipBoundsHandler pipBoundsHandler,
|
||||
PipTaskOrganizer pipTaskOrganizer,
|
||||
PipMediaController pipMediaController,
|
||||
WindowManagerShellWrapper windowManagerShellWrapper) {
|
||||
return Optional.of(
|
||||
new PipController(
|
||||
@@ -60,16 +60,15 @@ public abstract class TvPipModule {
|
||||
pipBoundsState,
|
||||
pipBoundsHandler,
|
||||
pipTaskOrganizer,
|
||||
pipMediaController,
|
||||
windowManagerShellWrapper));
|
||||
}
|
||||
|
||||
@WMSingleton
|
||||
@Provides
|
||||
static PipControlsViewController providePipControlsViewController(
|
||||
PipControlsView pipControlsView, PipController pipController,
|
||||
LayoutInflater layoutInflater, Handler handler) {
|
||||
return new PipControlsViewController(pipControlsView, pipController, layoutInflater,
|
||||
handler);
|
||||
PipControlsView pipControlsView, PipController pipController) {
|
||||
return new PipControlsViewController(pipControlsView, pipController);
|
||||
}
|
||||
|
||||
@WMSingleton
|
||||
|
||||
Reference in New Issue
Block a user