/*
|
* Copyright (C) 2017 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
package com.android.systemui.pip.phone;
|
|
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
|
import android.app.IActivityManager;
|
import android.app.PendingIntent;
|
import android.app.RemoteAction;
|
import android.content.BroadcastReceiver;
|
import android.content.ComponentName;
|
import android.content.Context;
|
import android.content.Intent;
|
import android.content.IntentFilter;
|
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Icon;
|
import android.media.session.MediaController;
|
import android.media.session.MediaSession;
|
import android.media.session.MediaSessionManager;
|
import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener;
|
import android.media.session.PlaybackState;
|
import android.os.UserHandle;
|
|
import com.android.systemui.Dependency;
|
import com.android.systemui.R;
|
import com.android.systemui.statusbar.policy.UserInfoController;
|
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.List;
|
|
/**
|
* Interfaces with the {@link MediaSessionManager} to compose the right set of actions to show (only
|
* if there are no actions from the PiP activity itself). The active media controller is only set
|
* when there is a media session from the top PiP activity.
|
*/
|
public class PipMediaController {
|
|
private static final String ACTION_PLAY = "com.android.systemui.pip.phone.PLAY";
|
private static final String ACTION_PAUSE = "com.android.systemui.pip.phone.PAUSE";
|
private static final String ACTION_NEXT = "com.android.systemui.pip.phone.NEXT";
|
private static final String ACTION_PREV = "com.android.systemui.pip.phone.PREV";
|
|
/**
|
* A listener interface to receive notification on changes to the media actions.
|
*/
|
public interface ActionListener {
|
/**
|
* Called when the media actions changes.
|
*/
|
void onMediaActionsChanged(List<RemoteAction> actions);
|
}
|
|
private final Context mContext;
|
private final IActivityManager mActivityManager;
|
|
private final MediaSessionManager mMediaSessionManager;
|
private MediaController mMediaController;
|
|
private RemoteAction mPauseAction;
|
private RemoteAction mPlayAction;
|
private RemoteAction mNextAction;
|
private RemoteAction mPrevAction;
|
|
private BroadcastReceiver mPlayPauseActionReceiver = new BroadcastReceiver() {
|
@Override
|
public void onReceive(Context context, Intent intent) {
|
final String action = intent.getAction();
|
if (action.equals(ACTION_PLAY)) {
|
mMediaController.getTransportControls().play();
|
} else if (action.equals(ACTION_PAUSE)) {
|
mMediaController.getTransportControls().pause();
|
} else if (action.equals(ACTION_NEXT)) {
|
mMediaController.getTransportControls().skipToNext();
|
} else if (action.equals(ACTION_PREV)) {
|
mMediaController.getTransportControls().skipToPrevious();
|
}
|
}
|
};
|
|
private final MediaController.Callback mPlaybackChangedListener = new MediaController.Callback() {
|
@Override
|
public void onPlaybackStateChanged(PlaybackState state) {
|
notifyActionsChanged();
|
}
|
};
|
|
private final MediaSessionManager.OnActiveSessionsChangedListener mSessionsChangedListener =
|
new OnActiveSessionsChangedListener() {
|
@Override
|
public void onActiveSessionsChanged(List<MediaController> controllers) {
|
resolveActiveMediaController(controllers);
|
}
|
};
|
|
private ArrayList<ActionListener> mListeners = new ArrayList<>();
|
|
public PipMediaController(Context context, IActivityManager activityManager) {
|
mContext = context;
|
mActivityManager = activityManager;
|
IntentFilter mediaControlFilter = new IntentFilter();
|
mediaControlFilter.addAction(ACTION_PLAY);
|
mediaControlFilter.addAction(ACTION_PAUSE);
|
mediaControlFilter.addAction(ACTION_NEXT);
|
mediaControlFilter.addAction(ACTION_PREV);
|
mContext.registerReceiver(mPlayPauseActionReceiver, mediaControlFilter);
|
|
createMediaActions();
|
mMediaSessionManager =
|
(MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
|
|
// The media session listener needs to be re-registered when switching users
|
UserInfoController userInfoController = Dependency.get(UserInfoController.class);
|
userInfoController.addCallback((String name, Drawable picture, String userAccount) ->
|
registerSessionListenerForCurrentUser());
|
}
|
|
/**
|
* Handles when an activity is pinned.
|
*/
|
public void onActivityPinned() {
|
// Once we enter PiP, try to find the active media controller for the top most activity
|
resolveActiveMediaController(mMediaSessionManager.getActiveSessionsForUser(null,
|
UserHandle.USER_CURRENT));
|
}
|
|
/**
|
* Adds a new media action listener.
|
*/
|
public void addListener(ActionListener listener) {
|
if (!mListeners.contains(listener)) {
|
mListeners.add(listener);
|
listener.onMediaActionsChanged(getMediaActions());
|
}
|
}
|
|
/**
|
* Removes a media action listener.
|
*/
|
public void removeListener(ActionListener listener) {
|
listener.onMediaActionsChanged(Collections.EMPTY_LIST);
|
mListeners.remove(listener);
|
}
|
|
/**
|
* Gets the set of media actions currently available.
|
*/
|
private List<RemoteAction> getMediaActions() {
|
if (mMediaController == null || mMediaController.getPlaybackState() == null) {
|
return Collections.EMPTY_LIST;
|
}
|
|
ArrayList<RemoteAction> mediaActions = new ArrayList<>();
|
int state = mMediaController.getPlaybackState().getState();
|
boolean isPlaying = MediaSession.isActiveState(state);
|
long actions = mMediaController.getPlaybackState().getActions();
|
|
// Prev action
|
mPrevAction.setEnabled((actions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0);
|
mediaActions.add(mPrevAction);
|
|
// Play/pause action
|
if (!isPlaying && ((actions & PlaybackState.ACTION_PLAY) != 0)) {
|
mediaActions.add(mPlayAction);
|
} else if (isPlaying && ((actions & PlaybackState.ACTION_PAUSE) != 0)) {
|
mediaActions.add(mPauseAction);
|
}
|
|
// Next action
|
mNextAction.setEnabled((actions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0);
|
mediaActions.add(mNextAction);
|
return mediaActions;
|
}
|
|
/**
|
* Creates the standard media buttons that we may show.
|
*/
|
private void createMediaActions() {
|
String pauseDescription = mContext.getString(R.string.pip_pause);
|
mPauseAction = new RemoteAction(Icon.createWithResource(mContext,
|
R.drawable.ic_pause_white), pauseDescription, pauseDescription,
|
PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PAUSE),
|
FLAG_UPDATE_CURRENT));
|
|
String playDescription = mContext.getString(R.string.pip_play);
|
mPlayAction = new RemoteAction(Icon.createWithResource(mContext,
|
R.drawable.ic_play_arrow_white), playDescription, playDescription,
|
PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PLAY),
|
FLAG_UPDATE_CURRENT));
|
|
String nextDescription = mContext.getString(R.string.pip_skip_to_next);
|
mNextAction = new RemoteAction(Icon.createWithResource(mContext,
|
R.drawable.ic_skip_next_white), nextDescription, nextDescription,
|
PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_NEXT),
|
FLAG_UPDATE_CURRENT));
|
|
String prevDescription = mContext.getString(R.string.pip_skip_to_prev);
|
mPrevAction = new RemoteAction(Icon.createWithResource(mContext,
|
R.drawable.ic_skip_previous_white), prevDescription, prevDescription,
|
PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PREV),
|
FLAG_UPDATE_CURRENT));
|
}
|
|
/**
|
* Re-registers the session listener for the current user.
|
*/
|
private void registerSessionListenerForCurrentUser() {
|
mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionsChangedListener);
|
mMediaSessionManager.addOnActiveSessionsChangedListener(mSessionsChangedListener, null,
|
UserHandle.USER_CURRENT, null);
|
}
|
|
/**
|
* Tries to find and set the active media controller for the top PiP activity.
|
*/
|
private void resolveActiveMediaController(List<MediaController> controllers) {
|
if (controllers != null) {
|
final ComponentName topActivity = PipUtils.getTopPinnedActivity(mContext,
|
mActivityManager).first;
|
if (topActivity != null) {
|
for (int i = 0; i < controllers.size(); i++) {
|
final MediaController controller = controllers.get(i);
|
if (controller.getPackageName().equals(topActivity.getPackageName())) {
|
setActiveMediaController(controller);
|
return;
|
}
|
}
|
}
|
}
|
setActiveMediaController(null);
|
}
|
|
/**
|
* Sets the active media controller for the top PiP activity.
|
*/
|
private void setActiveMediaController(MediaController controller) {
|
if (controller != mMediaController) {
|
if (mMediaController != null) {
|
mMediaController.unregisterCallback(mPlaybackChangedListener);
|
}
|
mMediaController = controller;
|
if (controller != null) {
|
controller.registerCallback(mPlaybackChangedListener);
|
}
|
notifyActionsChanged();
|
|
// TODO(winsonc): Consider if we want to close the PIP after a timeout (like on TV)
|
}
|
}
|
|
/**
|
* Notifies all listeners that the actions have changed.
|
*/
|
private void notifyActionsChanged() {
|
if (!mListeners.isEmpty()) {
|
List<RemoteAction> actions = getMediaActions();
|
mListeners.forEach(l -> l.onMediaActionsChanged(actions));
|
}
|
}
|
}
|