diff --git a/packages/SystemUI/res/drawable/tv_pip_play_button.xml b/packages/SystemUI/res/drawable/tv_pip_play_button.xml new file mode 100644 index 0000000000000..fecdc095dac47 --- /dev/null +++ b/packages/SystemUI/res/drawable/tv_pip_play_button.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + diff --git a/packages/SystemUI/res/layout/tv_pip_menu.xml b/packages/SystemUI/res/layout/tv_pip_menu.xml index 0b98d0ea71f51..1fec49e65b512 100644 --- a/packages/SystemUI/res/layout/tv_pip_menu.xml +++ b/packages/SystemUI/res/layout/tv_pip_menu.xml @@ -34,7 +34,7 @@ android:gravity="center" android:clipChildren="false"> - - - - mListeners = new ArrayList<>(); @@ -79,6 +83,8 @@ public class PipManager { private Rect mRecentsFocusedPipBounds; private boolean mInitialized; private int mPipTaskId = TASK_ID_NO_PIP; + private ComponentName mPipComponentName; + private MediaController mPipMediaController; private boolean mOnboardingShown; private boolean mIsRecentsShown; @@ -100,10 +106,15 @@ public class PipManager { } if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo); mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1]; + mPipComponentName = ComponentName.unflattenFromString( + stackInfo.taskNames[stackInfo.taskNames.length - 1]); // Set state to overlay so we show it when the pinned stack animation ends. mState = STATE_PIP_OVERLAY; mCurrentPipBounds = mPipBounds; launchPipOnboardingActivityIfNeeded(); + mMediaSessionManager.addOnActiveSessionsChangedListener( + mActiveMediaSessionListener, null); + updateMediaController(mMediaSessionManager.getActiveSessions(null)); } }; private final Runnable mOnTaskStackChanged = new Runnable() { @@ -176,6 +187,13 @@ public class PipManager { } }; + private final MediaSessionManager.OnActiveSessionsChangedListener mActiveMediaSessionListener = + new MediaSessionManager.OnActiveSessionsChangedListener() { + @Override + public void onActiveSessionsChanged(List controllers) { + updateMediaController(controllers); + } + }; private PipManager() { } @@ -215,6 +233,9 @@ public class PipManager { mContext.registerReceiver(mBroadcastReceiver, intentFilter); mOnboardingShown = Prefs.getBoolean( mContext, TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN, false); + + mMediaSessionManager = + (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); } /** @@ -248,6 +269,8 @@ public class PipManager { private void closePipInternal(boolean removePipStack) { mState = STATE_NO_PIP; mPipTaskId = TASK_ID_NO_PIP; + mPipMediaController = null; + mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveMediaSessionListener); if (removePipStack) { try { mActivityManager.removeStack(PINNED_STACK_ID); @@ -502,6 +525,34 @@ public class PipManager { } } + private void updateMediaController(List controllers) { + MediaController mediaController = null; + if (controllers != null && mState != 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 = mListeners.size() - 1; i >= 0; i--) { + mListeners.get(i).onMediaControllerChanged(); + } + } + } + + /** + * Gets the {@link android.media.session.MediaController} for the PIPed activity. + */ + MediaController getMediaController() { + return mPipMediaController; + } + private class TaskStackListener extends ITaskStackListener.Stub { @Override public void onTaskStackChanged() throws RemoteException { @@ -542,6 +593,8 @@ public class PipManager { void onMoveToFullscreen(); /** Invoked when we are above to start resizing the Pip. */ void onPipResizeAboutToStart(); + /** Invoked when the MediaController on PIPed activity is changed. */ + void onMediaControllerChanged(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java index a392becbcf981..fb7fa4de088f4 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java @@ -18,34 +18,49 @@ package com.android.systemui.tv.pip; import android.app.Activity; import android.media.session.MediaController; +import android.media.session.PlaybackState; import android.os.Bundle; import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; import com.android.systemui.R; +import static android.content.pm.PackageManager.FEATURE_LEANBACK; +import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; +import static android.media.session.PlaybackState.ACTION_PAUSE; +import static android.media.session.PlaybackState.ACTION_PLAY; + /** * Activity to show the PIP menu to control PIP. */ public class PipMenuActivity extends Activity implements PipManager.Listener { private static final String TAG = "PipMenuActivity"; - private static final boolean DEBUG = false; private final PipManager mPipManager = PipManager.getInstance(); private MediaController mMediaController; private View mFullButtonView; private View mFullDescriptionView; - private View mPlayPauseButtonView; - private View mPlayPauseDescriptionView; + private View mPlayPauseView; + private ImageView mPlayPauseButtonImageView; + private TextView mPlayPauseDescriptionTextView; private View mCloseButtonView; private View mCloseDescriptionView; + private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() { + @Override + public void onPlaybackStateChanged(PlaybackState state) { + updatePlayPauseView(state); + } + }; + @Override protected void onCreate(Bundle bundle) { super.onCreate(bundle); setContentView(R.layout.tv_pip_menu); mPipManager.addListener(this); - mFullButtonView = findViewById(R.id.full); + mFullButtonView = findViewById(R.id.full_button); mFullDescriptionView = findViewById(R.id.full_desc); mFullButtonView.setOnClickListener(new View.OnClickListener() { @Override @@ -61,22 +76,33 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { } }); - mPlayPauseButtonView = findViewById(R.id.play_pause); - mPlayPauseDescriptionView = findViewById(R.id.play_pause_desc); - mPlayPauseButtonView.setOnClickListener(new View.OnClickListener() { + mPlayPauseView = findViewById(R.id.play_pause); + mPlayPauseButtonImageView = (ImageView) findViewById(R.id.play_pause_button); + mPlayPauseDescriptionTextView = (TextView) findViewById(R.id.play_pause_desc); + mPlayPauseButtonImageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - // TODO: Implement play/pause. + if (mMediaController == null || mMediaController.getPlaybackState() == null) { + return; + } + long actions = mMediaController.getPlaybackState().getActions(); + int state = mMediaController.getPlaybackState().getState(); + if (((actions & ACTION_PLAY) != 0) && !isPlaying(state)) { + mMediaController.getTransportControls().play(); + } else if ((actions & ACTION_PAUSE) != 0 && isPlaying(state)) { + mMediaController.getTransportControls().pause(); + } + // View will be updated later in {@link mMediaControllerCallback} } }); - mPlayPauseButtonView.setOnFocusChangeListener(new View.OnFocusChangeListener() { + mPlayPauseButtonImageView.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { - mPlayPauseDescriptionView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE); + mPlayPauseDescriptionTextView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE); } }); - mCloseButtonView = findViewById(R.id.close); + mCloseButtonView = findViewById(R.id.close_button); mCloseDescriptionView = findViewById(R.id.close_desc); mCloseButtonView.setOnClickListener(new View.OnClickListener() { @Override @@ -91,6 +117,50 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { mCloseDescriptionView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE); } }); + updateMediaController(); + } + + private void updateMediaController() { + MediaController newController = mPipManager.getMediaController(); + if (mMediaController == newController) { + return; + } + if (mMediaController != null) { + mMediaController.unregisterCallback(mMediaControllerCallback); + } + mMediaController = newController; + if (mMediaController != null) { + mMediaController.registerCallback(mMediaControllerCallback); + updatePlayPauseView(mMediaController.getPlaybackState()); + } else { + updatePlayPauseView(null); + } + } + + private void updatePlayPauseView(PlaybackState playbackState) { + if (playbackState != null + && (playbackState.getActions() & (ACTION_PLAY | ACTION_PAUSE)) != 0) { + mPlayPauseView.setVisibility(View.VISIBLE); + if (isPlaying(playbackState.getState())) { + mPlayPauseButtonImageView.setImageResource(R.drawable.tv_pip_pause_button); + mPlayPauseDescriptionTextView.setText(R.string.pip_pause); + } else { + mPlayPauseButtonImageView.setImageResource(R.drawable.tv_pip_play_button); + mPlayPauseDescriptionTextView.setText(R.string.pip_play); + } + } else { + mPlayPauseView.setVisibility(View.GONE); + } + } + + private boolean isPlaying(int state) { + return 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; } private void restorePipAndFinish() { @@ -107,6 +177,9 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { @Override protected void onDestroy() { super.onDestroy(); + if (mMediaController != null) { + mMediaController.unregisterCallback(mMediaControllerCallback); + } mPipManager.removeListener(this); mPipManager.resumePipResizing( PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH); @@ -130,6 +203,11 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { finish(); } + @Override + public void onMediaControllerChanged() { + updateMediaController(); + } + @Override public void onPipResizeAboutToStart() { finish(); diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java index e5c07d2367c5d..ad45625b0f9cf 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java @@ -82,6 +82,8 @@ public class PipOnboardingActivity extends Activity implements PipManager.Listen } @Override - public void onPipResizeAboutToStart() { - } + public void onPipResizeAboutToStart() { } + + @Override + public void onMediaControllerChanged() { } } diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java index cfeab6d24a95e..95d655c2f3200 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java @@ -103,4 +103,8 @@ public class PipOverlayActivity extends Activity implements PipManager.Listener mPipManager.suspendPipResizing( PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_OVERLAY_ACTIVITY_FINISH); } + + @Override + public void onMediaControllerChanged() { + } }