blob: 615b1730579c9ffadde5a530c715dd056056a6ed [file] [log] [blame]
/*
* Copyright (C) 2020 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.wm.shell.flicker.testapp;
import static android.media.MediaMetadata.METADATA_KEY_TITLE;
import static android.media.session.PlaybackState.ACTION_PAUSE;
import static android.media.session.PlaybackState.ACTION_PLAY;
import static android.media.session.PlaybackState.ACTION_STOP;
import static android.media.session.PlaybackState.STATE_PAUSED;
import static android.media.session.PlaybackState.STATE_PLAYING;
import static android.media.session.PlaybackState.STATE_STOPPED;
import static com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP;
import static com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION;
import static com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP;
import static com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_PIP_ORIENTATION;
import android.app.Activity;
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.drawable.Icon;
import android.media.MediaMetadata;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.util.Log;
import android.util.Rational;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.RadioButton;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class PipActivity extends Activity {
private static final String TAG = PipActivity.class.getSimpleName();
/**
* A media session title for when the session is in {@link STATE_PLAYING}.
* TvPipNotificationTests check whether the actual notification title matches this string.
*/
private static final String TITLE_STATE_PLAYING = "TestApp media is playing";
/**
* A media session title for when the session is in {@link STATE_PAUSED}.
* TvPipNotificationTests check whether the actual notification title matches this string.
*/
private static final String TITLE_STATE_PAUSED = "TestApp media is paused";
private static final Rational RATIO_DEFAULT = null;
private static final Rational RATIO_SQUARE = new Rational(1, 1);
private static final Rational RATIO_WIDE = new Rational(2, 1);
private static final Rational RATIO_TALL = new Rational(1, 2);
private static final String PIP_ACTION_NO_OP = "No-Op";
private static final String PIP_ACTION_OFF = "Off";
private static final String PIP_ACTION_ON = "On";
private static final String PIP_ACTION_CLEAR = "Clear";
private static final String ACTION_NO_OP = "com.android.wm.shell.flicker.testapp.NO_OP";
private static final String ACTION_SWITCH_OFF =
"com.android.wm.shell.flicker.testapp.SWITCH_OFF";
private static final String ACTION_SWITCH_ON = "com.android.wm.shell.flicker.testapp.SWITCH_ON";
private static final String ACTION_CLEAR = "com.android.wm.shell.flicker.testapp.CLEAR";
private final PictureInPictureParams.Builder mPipParamsBuilder =
new PictureInPictureParams.Builder()
.setAspectRatio(RATIO_DEFAULT);
private MediaSession mMediaSession;
private final PlaybackState.Builder mPlaybackStateBuilder = new PlaybackState.Builder()
.setActions(ACTION_PLAY | ACTION_PAUSE | ACTION_STOP)
.setState(STATE_STOPPED, 0, 1f);
private PlaybackState mPlaybackState = mPlaybackStateBuilder.build();
private final MediaMetadata.Builder mMediaMetadataBuilder = new MediaMetadata.Builder();
private final List<RemoteAction> mSwitchOffActions = new ArrayList<>();
private final List<RemoteAction> mSwitchOnActions = new ArrayList<>();
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (isInPictureInPictureMode()) {
switch (intent.getAction()) {
case ACTION_SWITCH_ON:
mPipParamsBuilder.setActions(mSwitchOnActions);
break;
case ACTION_SWITCH_OFF:
mPipParamsBuilder.setActions(mSwitchOffActions);
break;
case ACTION_CLEAR:
mPipParamsBuilder.setActions(Collections.emptyList());
break;
case ACTION_NO_OP:
return;
default:
Log.w(TAG, "Unhandled action=" + intent.getAction());
return;
}
setPictureInPictureParams(mPipParamsBuilder.build());
} else {
switch (intent.getAction()) {
case ACTION_ENTER_PIP:
enterPip(null);
break;
case ACTION_SET_REQUESTED_ORIENTATION:
setRequestedOrientation(Integer.parseInt(intent.getStringExtra(
EXTRA_PIP_ORIENTATION)));
break;
default:
Log.w(TAG, "Unhandled action=" + intent.getAction());
return;
}
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Window window = getWindow();
final WindowManager.LayoutParams layoutParams = window.getAttributes();
layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams
.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
window.setAttributes(layoutParams);
setContentView(R.layout.activity_pip);
findViewById(R.id.media_session_start)
.setOnClickListener(v -> updateMediaSessionState(STATE_PLAYING));
findViewById(R.id.media_session_stop)
.setOnClickListener(v -> updateMediaSessionState(STATE_STOPPED));
mMediaSession = new MediaSession(this, "WMShell_TestApp");
mMediaSession.setPlaybackState(mPlaybackStateBuilder.build());
mMediaSession.setCallback(new MediaSession.Callback() {
@Override
public void onPlay() {
updateMediaSessionState(STATE_PLAYING);
}
@Override
public void onPause() {
updateMediaSessionState(STATE_PAUSED);
}
@Override
public void onStop() {
updateMediaSessionState(STATE_STOPPED);
}
});
// Build two sets of the custom actions. We'll replace one with the other when 'On'/'Off'
// action is invoked.
// The first set consists of 3 actions: 1) Off; 2) No-Op; 3) Clear.
// The second set consists of 2 actions: 1) On; 2) Clear.
// Upon invocation 'Clear' action clear-off all the custom actions, including itself.
final Icon icon = Icon.createWithResource(this, android.R.drawable.ic_menu_help);
final RemoteAction noOpAction = buildRemoteAction(icon, PIP_ACTION_NO_OP, ACTION_NO_OP);
final RemoteAction switchOnAction =
buildRemoteAction(icon, PIP_ACTION_ON, ACTION_SWITCH_ON);
final RemoteAction switchOffAction =
buildRemoteAction(icon, PIP_ACTION_OFF, ACTION_SWITCH_OFF);
final RemoteAction clearAllAction = buildRemoteAction(icon, PIP_ACTION_CLEAR, ACTION_CLEAR);
mSwitchOffActions.addAll(Arrays.asList(switchOnAction, clearAllAction));
mSwitchOnActions.addAll(Arrays.asList(noOpAction, switchOffAction, clearAllAction));
final IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_NO_OP);
filter.addAction(ACTION_SWITCH_ON);
filter.addAction(ACTION_SWITCH_OFF);
filter.addAction(ACTION_CLEAR);
filter.addAction(ACTION_SET_REQUESTED_ORIENTATION);
filter.addAction(ACTION_ENTER_PIP);
registerReceiver(mBroadcastReceiver, filter);
handleIntentExtra(getIntent());
}
@Override
protected void onDestroy() {
unregisterReceiver(mBroadcastReceiver);
super.onDestroy();
}
@Override
protected void onUserLeaveHint() {
// Only used when auto PiP is disabled. This is to simulate the behavior that an app
// supports regular PiP but not auto PiP.
final boolean manuallyEnterPip =
((RadioButton) findViewById(R.id.enter_pip_on_leave_manual)).isChecked();
if (manuallyEnterPip) {
enterPictureInPictureMode();
}
}
private RemoteAction buildRemoteAction(Icon icon, String label, String action) {
final Intent intent = new Intent(action);
final PendingIntent pendingIntent =
PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
return new RemoteAction(icon, label, label, pendingIntent);
}
public void enterPip(View v) {
final boolean withCustomActions =
((CheckBox) findViewById(R.id.with_custom_actions)).isChecked();
mPipParamsBuilder.setActions(
withCustomActions ? mSwitchOnActions : Collections.emptyList());
enterPictureInPictureMode(mPipParamsBuilder.build());
}
public void onAutoPipSelected(View v) {
switch (v.getId()) {
case R.id.enter_pip_on_leave_manual:
// disable auto enter PiP
case R.id.enter_pip_on_leave_disabled:
mPipParamsBuilder.setAutoEnterEnabled(false);
setPictureInPictureParams(mPipParamsBuilder.build());
break;
case R.id.enter_pip_on_leave_autoenter:
mPipParamsBuilder.setAutoEnterEnabled(true);
setPictureInPictureParams(mPipParamsBuilder.build());
break;
}
}
public void onRatioSelected(View v) {
switch (v.getId()) {
case R.id.ratio_default:
mPipParamsBuilder.setAspectRatio(RATIO_DEFAULT);
break;
case R.id.ratio_square:
mPipParamsBuilder.setAspectRatio(RATIO_SQUARE);
break;
case R.id.ratio_wide:
mPipParamsBuilder.setAspectRatio(RATIO_WIDE);
break;
case R.id.ratio_tall:
mPipParamsBuilder.setAspectRatio(RATIO_TALL);
break;
}
}
private void updateMediaSessionState(int newState) {
if (mPlaybackState.getState() == newState) {
return;
}
final String title;
switch (newState) {
case STATE_PLAYING:
title = TITLE_STATE_PLAYING;
break;
case STATE_PAUSED:
title = TITLE_STATE_PAUSED;
break;
case STATE_STOPPED:
title = "";
break;
default:
throw new IllegalArgumentException("Unknown state " + newState);
}
mPlaybackStateBuilder.setState(newState, 0, 1f);
mPlaybackState = mPlaybackStateBuilder.build();
mMediaMetadataBuilder.putText(METADATA_KEY_TITLE, title);
mMediaSession.setPlaybackState(mPlaybackState);
mMediaSession.setMetadata(mMediaMetadataBuilder.build());
mMediaSession.setActive(newState != STATE_STOPPED);
}
private void handleIntentExtra(Intent intent) {
// Set the fixed orientation if requested
if (intent.hasExtra(EXTRA_PIP_ORIENTATION)) {
final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_PIP_ORIENTATION));
setRequestedOrientation(ori);
}
// Enter picture in picture with the given aspect ratio if provided
if (intent.hasExtra(EXTRA_ENTER_PIP)) {
mPipParamsBuilder.setActions(mSwitchOnActions);
enterPip(null);
}
}
}