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:
Sergey Nikolaienkov
2020-10-28 08:03:45 +00:00
parent c18512fef5
commit 63a37a3a29
8 changed files with 157 additions and 417 deletions

View File

@@ -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>

View File

@@ -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.
*

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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());
}

View File

@@ -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;
}

View File

@@ -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