Merge "Add more information to the TV PiP notification" into tm-dev am: b6de832709

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17197863

Change-Id: I04f8e24a88d9179a64e8ee7a0df23d788865dd83
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Jacqueline Bronger
2022-05-06 11:09:11 +00:00
committed by Automerger Merge Worker
5 changed files with 318 additions and 74 deletions

View File

@@ -137,6 +137,18 @@ public class ImageUtils {
*/ */
public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth, public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth,
int maxHeight) { int maxHeight) {
return buildScaledBitmap(drawable, maxWidth, maxHeight, false);
}
/**
* Convert a drawable to a bitmap, scaled to fit within maxWidth and maxHeight.
*
* @param allowUpscaling if true, the drawable will not only be scaled down, but also scaled up
* to fit within the maximum size given. This is useful for converting
* vectorized icons which usually have a very small intrinsic size.
*/
public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth,
int maxHeight, boolean allowUpscaling) {
if (drawable == null) { if (drawable == null) {
return null; return null;
} }
@@ -155,7 +167,9 @@ public class ImageUtils {
// a large notification icon if necessary // a large notification icon if necessary
float ratio = Math.min((float) maxWidth / (float) originalWidth, float ratio = Math.min((float) maxWidth / (float) originalWidth,
(float) maxHeight / (float) originalHeight); (float) maxHeight / (float) originalHeight);
ratio = Math.min(1.0f, ratio); if (!allowUpscaling) {
ratio = Math.min(1.0f, ratio);
}
int scaledWidth = (int) (ratio * originalWidth); int scaledWidth = (int) (ratio * originalWidth);
int scaledHeight = (int) (ratio * originalHeight); int scaledHeight = (int) (ratio * originalHeight);
Bitmap result = Bitmap.createBitmap(scaledWidth, scaledHeight, Config.ARGB_8888); Bitmap result = Bitmap.createBitmap(scaledWidth, scaledHeight, Config.ARGB_8888);

View File

@@ -143,8 +143,11 @@ public abstract class TvPipModule {
@Provides @Provides
static TvPipNotificationController provideTvPipNotificationController(Context context, static TvPipNotificationController provideTvPipNotificationController(Context context,
PipMediaController pipMediaController, PipMediaController pipMediaController,
PipParamsChangedForwarder pipParamsChangedForwarder,
TvPipBoundsState tvPipBoundsState,
@ShellMainThread Handler mainHandler) { @ShellMainThread Handler mainHandler) {
return new TvPipNotificationController(context, pipMediaController, mainHandler); return new TvPipNotificationController(context, pipMediaController,
pipParamsChangedForwarder, tvPipBoundsState, mainHandler);
} }
@WMSingleton @WMSingleton

View File

@@ -32,6 +32,7 @@ import android.content.IntentFilter;
import android.graphics.drawable.Icon; import android.graphics.drawable.Icon;
import android.media.MediaMetadata; import android.media.MediaMetadata;
import android.media.session.MediaController; import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.MediaSessionManager; import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState; import android.media.session.PlaybackState;
import android.os.Handler; import android.os.Handler;
@@ -64,7 +65,7 @@ public class PipMediaController {
*/ */
public interface ActionListener { public interface ActionListener {
/** /**
* Called when the media actions changes. * Called when the media actions changed.
*/ */
void onMediaActionsChanged(List<RemoteAction> actions); void onMediaActionsChanged(List<RemoteAction> actions);
} }
@@ -74,11 +75,21 @@ public class PipMediaController {
*/ */
public interface MetadataListener { public interface MetadataListener {
/** /**
* Called when the media metadata changes. * Called when the media metadata changed.
*/ */
void onMediaMetadataChanged(MediaMetadata metadata); void onMediaMetadataChanged(MediaMetadata metadata);
} }
/**
* A listener interface to receive notification on changes to the media session token.
*/
public interface TokenListener {
/**
* Called when the media session token changed.
*/
void onMediaSessionTokenChanged(MediaSession.Token token);
}
private final Context mContext; private final Context mContext;
private final Handler mMainHandler; private final Handler mMainHandler;
private final HandlerExecutor mHandlerExecutor; private final HandlerExecutor mHandlerExecutor;
@@ -133,6 +144,7 @@ public class PipMediaController {
private final ArrayList<ActionListener> mActionListeners = new ArrayList<>(); private final ArrayList<ActionListener> mActionListeners = new ArrayList<>();
private final ArrayList<MetadataListener> mMetadataListeners = new ArrayList<>(); private final ArrayList<MetadataListener> mMetadataListeners = new ArrayList<>();
private final ArrayList<TokenListener> mTokenListeners = new ArrayList<>();
public PipMediaController(Context context, Handler mainHandler) { public PipMediaController(Context context, Handler mainHandler) {
mContext = context; mContext = context;
@@ -204,6 +216,31 @@ public class PipMediaController {
mMetadataListeners.remove(listener); mMetadataListeners.remove(listener);
} }
/**
* Adds a new token listener.
*/
public void addTokenListener(TokenListener listener) {
if (!mTokenListeners.contains(listener)) {
mTokenListeners.add(listener);
listener.onMediaSessionTokenChanged(getToken());
}
}
/**
* Removes a token listener.
*/
public void removeTokenListener(TokenListener listener) {
listener.onMediaSessionTokenChanged(null);
mTokenListeners.remove(listener);
}
private MediaSession.Token getToken() {
if (mMediaController == null) {
return null;
}
return mMediaController.getSessionToken();
}
private MediaMetadata getMediaMetadata() { private MediaMetadata getMediaMetadata() {
return mMediaController != null ? mMediaController.getMetadata() : null; return mMediaController != null ? mMediaController.getMetadata() : null;
} }
@@ -294,6 +331,7 @@ public class PipMediaController {
} }
notifyActionsChanged(); notifyActionsChanged();
notifyMetadataChanged(getMediaMetadata()); notifyMetadataChanged(getMediaMetadata());
notifyTokenChanged(getToken());
// TODO(winsonc): Consider if we want to close the PIP after a timeout (like on TV) // TODO(winsonc): Consider if we want to close the PIP after a timeout (like on TV)
} }
@@ -317,4 +355,10 @@ public class PipMediaController {
mMetadataListeners.forEach(l -> l.onMediaMetadataChanged(metadata)); mMetadataListeners.forEach(l -> l.onMediaMetadataChanged(metadata));
} }
} }
private void notifyTokenChanged(MediaSession.Token token) {
if (!mTokenListeners.isEmpty()) {
mTokenListeners.forEach(l -> l.onMediaSessionTokenChanged(token));
}
}
} }

View File

@@ -293,6 +293,8 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
} }
mTvPipBoundsState.setTvPipManuallyCollapsed(!expanding); mTvPipBoundsState.setTvPipManuallyCollapsed(!expanding);
mTvPipBoundsState.setTvPipExpanded(expanding); mTvPipBoundsState.setTvPipExpanded(expanding);
mPipNotificationController.updateExpansionState();
updatePinnedStackBounds(); updatePinnedStackBounds();
} }

View File

@@ -16,36 +16,47 @@
package com.android.wm.shell.pip.tv; package com.android.wm.shell.pip.tv;
import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE;
import static android.app.Notification.Action.SEMANTIC_ACTION_NONE;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.RemoteAction;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.media.MediaMetadata; import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.media.session.MediaSession;
import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.text.TextUtils; import android.text.TextUtils;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.protolog.common.ProtoLog; import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ImageUtils;
import com.android.wm.shell.R; import com.android.wm.shell.R;
import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.Objects; import java.util.ArrayList;
import java.util.List;
/** /**
* A notification that informs users that PIP is running and also provides PIP controls. * 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 * <p>Once it's created, it will manage the PiP notification UI by itself except for handling
* configuration changes. * configuration changes and user initiated expanded PiP toggling.
*/ */
public class TvPipNotificationController { public class TvPipNotificationController {
private static final String TAG = "TvPipNotification"; private static final String TAG = "TvPipNotification";
private static final boolean DEBUG = TvPipController.DEBUG;
// Referenced in com.android.systemui.util.NotificationChannels. // Referenced in com.android.systemui.util.NotificationChannels.
public static final String NOTIFICATION_CHANNEL = "TVPIP"; public static final String NOTIFICATION_CHANNEL = "TVPIP";
@@ -60,6 +71,8 @@ public class TvPipNotificationController {
"com.android.wm.shell.pip.tv.notification.action.MOVE_PIP"; "com.android.wm.shell.pip.tv.notification.action.MOVE_PIP";
private static final String ACTION_TOGGLE_EXPANDED_PIP = private static final String ACTION_TOGGLE_EXPANDED_PIP =
"com.android.wm.shell.pip.tv.notification.action.TOGGLE_EXPANDED_PIP"; "com.android.wm.shell.pip.tv.notification.action.TOGGLE_EXPANDED_PIP";
private static final String ACTION_FULLSCREEN =
"com.android.wm.shell.pip.tv.notification.action.FULLSCREEN";
private final Context mContext; private final Context mContext;
private final PackageManager mPackageManager; private final PackageManager mPackageManager;
@@ -68,44 +81,88 @@ public class TvPipNotificationController {
private final ActionBroadcastReceiver mActionBroadcastReceiver; private final ActionBroadcastReceiver mActionBroadcastReceiver;
private final Handler mMainHandler; private final Handler mMainHandler;
private Delegate mDelegate; private Delegate mDelegate;
private final TvPipBoundsState mTvPipBoundsState;
private String mDefaultTitle; private String mDefaultTitle;
private final List<RemoteAction> mCustomActions = new ArrayList<>();
private final List<RemoteAction> mMediaActions = new ArrayList<>();
private RemoteAction mCustomCloseAction;
private MediaSession.Token mMediaSessionToken;
/** Package name for the application that owns PiP window. */ /** Package name for the application that owns PiP window. */
private String mPackageName; private String mPackageName;
private boolean mNotified;
private String mMediaTitle; private boolean mIsNotificationShown;
private Bitmap mArt; private String mPipTitle;
private String mPipSubtitle;
private Bitmap mActivityIcon;
public TvPipNotificationController(Context context, PipMediaController pipMediaController, public TvPipNotificationController(Context context, PipMediaController pipMediaController,
PipParamsChangedForwarder pipParamsChangedForwarder, TvPipBoundsState tvPipBoundsState,
Handler mainHandler) { Handler mainHandler) {
mContext = context; mContext = context;
mPackageManager = context.getPackageManager(); mPackageManager = context.getPackageManager();
mNotificationManager = context.getSystemService(NotificationManager.class); mNotificationManager = context.getSystemService(NotificationManager.class);
mMainHandler = mainHandler; mMainHandler = mainHandler;
mTvPipBoundsState = tvPipBoundsState;
mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL) mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL)
.setLocalOnly(true) .setLocalOnly(true)
.setOngoing(false) .setOngoing(true)
.setCategory(Notification.CATEGORY_SYSTEM) .setCategory(Notification.CATEGORY_SYSTEM)
.setShowWhen(true) .setShowWhen(true)
.setSmallIcon(R.drawable.pip_icon) .setSmallIcon(R.drawable.pip_icon)
.setAllowSystemGeneratedContextualActions(false)
.setContentIntent(createPendingIntent(context, ACTION_FULLSCREEN))
.setDeleteIntent(getCloseAction().actionIntent)
.extend(new Notification.TvExtender() .extend(new Notification.TvExtender()
.setContentIntent(createPendingIntent(context, ACTION_SHOW_PIP_MENU)) .setContentIntent(createPendingIntent(context, ACTION_SHOW_PIP_MENU))
.setDeleteIntent(createPendingIntent(context, ACTION_CLOSE_PIP))); .setDeleteIntent(createPendingIntent(context, ACTION_CLOSE_PIP)));
mActionBroadcastReceiver = new ActionBroadcastReceiver(); mActionBroadcastReceiver = new ActionBroadcastReceiver();
pipMediaController.addMetadataListener(this::onMediaMetadataChanged); pipMediaController.addActionListener(this::onMediaActionsChanged);
pipMediaController.addTokenListener(this::onMediaSessionTokenChanged);
pipParamsChangedForwarder.addListener(
new PipParamsChangedForwarder.PipParamsChangedCallback() {
@Override
public void onExpandedAspectRatioChanged(float ratio) {
updateExpansionState();
}
@Override
public void onActionsChanged(List<RemoteAction> actions,
RemoteAction closeAction) {
mCustomActions.clear();
mCustomActions.addAll(actions);
mCustomCloseAction = closeAction;
updateNotificationContent();
}
@Override
public void onTitleChanged(String title) {
mPipTitle = title;
updateNotificationContent();
}
@Override
public void onSubtitleChanged(String subtitle) {
mPipSubtitle = subtitle;
updateNotificationContent();
}
});
onConfigurationChanged(context); onConfigurationChanged(context);
} }
void setDelegate(Delegate delegate) { void setDelegate(Delegate delegate) {
if (DEBUG) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: setDelegate(), delegate=%s",
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, TAG, delegate);
"%s: setDelegate(), delegate=%s", TAG, delegate);
}
if (mDelegate != null) { if (mDelegate != null) {
throw new IllegalStateException( throw new IllegalStateException(
"The delegate has already been set and should not change."); "The delegate has already been set and should not change.");
@@ -118,90 +175,181 @@ public class TvPipNotificationController {
} }
void show(String packageName) { void show(String packageName) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: show %s", TAG, packageName);
if (mDelegate == null) { if (mDelegate == null) {
throw new IllegalStateException("Delegate is not set."); throw new IllegalStateException("Delegate is not set.");
} }
mIsNotificationShown = true;
mPackageName = packageName; mPackageName = packageName;
update(); mActivityIcon = getActivityIcon();
mActionBroadcastReceiver.register(); mActionBroadcastReceiver.register();
updateNotificationContent();
} }
void dismiss() { void dismiss() {
mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP); ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: dismiss()", TAG);
mNotified = false;
mIsNotificationShown = false;
mPackageName = null; mPackageName = null;
mActionBroadcastReceiver.unregister(); mActionBroadcastReceiver.unregister();
mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
} }
private void onMediaMetadataChanged(MediaMetadata metadata) { private Notification.Action getToggleAction(boolean expanded) {
if (updateMediaControllerMetadata(metadata) && mNotified) { if (expanded) {
// update notification return createSystemAction(R.drawable.pip_ic_collapse,
update(); R.string.pip_collapse, ACTION_TOGGLE_EXPANDED_PIP);
} else {
return createSystemAction(R.drawable.pip_ic_expand, R.string.pip_expand,
ACTION_TOGGLE_EXPANDED_PIP);
} }
} }
private Notification.Action createSystemAction(int iconRes, int titleRes, String action) {
Notification.Action.Builder builder = new Notification.Action.Builder(
Icon.createWithResource(mContext, iconRes),
mContext.getString(titleRes),
createPendingIntent(mContext, action));
builder.setContextual(true);
return builder.build();
}
private void onMediaActionsChanged(List<RemoteAction> actions) {
mMediaActions.clear();
mMediaActions.addAll(actions);
if (mCustomActions.isEmpty()) {
updateNotificationContent();
}
}
private void onMediaSessionTokenChanged(MediaSession.Token token) {
mMediaSessionToken = token;
updateNotificationContent();
}
private Notification.Action remoteToNotificationAction(RemoteAction action) {
return remoteToNotificationAction(action, SEMANTIC_ACTION_NONE);
}
private Notification.Action remoteToNotificationAction(RemoteAction action,
int semanticAction) {
Notification.Action.Builder builder = new Notification.Action.Builder(action.getIcon(),
action.getTitle(),
action.getActionIntent());
if (action.getContentDescription() != null) {
Bundle extras = new Bundle();
extras.putCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION,
action.getContentDescription());
builder.addExtras(extras);
}
builder.setSemanticAction(semanticAction);
builder.setContextual(true);
return builder.build();
}
private Notification.Action[] getNotificationActions() {
final List<Notification.Action> actions = new ArrayList<>();
// 1. Fullscreen
actions.add(getFullscreenAction());
// 2. Close
actions.add(getCloseAction());
// 3. App actions
final List<RemoteAction> appActions =
mCustomActions.isEmpty() ? mMediaActions : mCustomActions;
for (RemoteAction appAction : appActions) {
if (PipUtils.remoteActionsMatch(mCustomCloseAction, appAction)
|| !appAction.isEnabled()) {
continue;
}
actions.add(remoteToNotificationAction(appAction));
}
// 4. Move
actions.add(getMoveAction());
// 5. Toggle expansion (if expanded PiP enabled)
if (mTvPipBoundsState.getDesiredTvExpandedAspectRatio() > 0
&& mTvPipBoundsState.isTvExpandedPipSupported()) {
actions.add(getToggleAction(mTvPipBoundsState.isTvPipExpanded()));
}
return actions.toArray(new Notification.Action[0]);
}
private Notification.Action getCloseAction() {
if (mCustomCloseAction == null) {
return createSystemAction(R.drawable.pip_ic_close_white, R.string.pip_close,
ACTION_CLOSE_PIP);
} else {
return remoteToNotificationAction(mCustomCloseAction, SEMANTIC_ACTION_DELETE);
}
}
private Notification.Action getFullscreenAction() {
return createSystemAction(R.drawable.pip_ic_fullscreen_white,
R.string.pip_fullscreen, ACTION_FULLSCREEN);
}
private Notification.Action getMoveAction() {
return createSystemAction(R.drawable.pip_ic_move_white, R.string.pip_move,
ACTION_MOVE_PIP);
}
/** /**
* Called by {@link PipController} when the configuration is changed. * Called by {@link TvPipController} when the configuration is changed.
*/ */
void onConfigurationChanged(Context context) { void onConfigurationChanged(Context context) {
mDefaultTitle = context.getResources().getString(R.string.pip_notification_unknown_title); mDefaultTitle = context.getResources().getString(R.string.pip_notification_unknown_title);
if (mNotified) { updateNotificationContent();
// Update the notification.
update();
}
} }
private void update() { void updateExpansionState() {
mNotified = true; updateNotificationContent();
}
private void updateNotificationContent() {
if (mPackageManager == null || !mIsNotificationShown) {
return;
}
Notification.Action[] actions = getNotificationActions();
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: update(), title: %s, subtitle: %s, mediaSessionToken: %s, #actions: %s", TAG,
getNotificationTitle(), mPipSubtitle, mMediaSessionToken, actions.length);
for (Notification.Action action : actions) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: action: %s", TAG,
action.toString());
}
mNotificationBuilder mNotificationBuilder
.setWhen(System.currentTimeMillis()) .setWhen(System.currentTimeMillis())
.setContentTitle(getNotificationTitle()); .setContentTitle(getNotificationTitle())
if (mArt != null) { .setContentText(mPipSubtitle)
mNotificationBuilder.setStyle(new Notification.BigPictureStyle() .setSubText(getApplicationLabel(mPackageName))
.bigPicture(mArt)); .setActions(actions);
} else { setPipIcon();
mNotificationBuilder.setStyle(null);
} Bundle extras = new Bundle();
extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, mMediaSessionToken);
mNotificationBuilder.setExtras(extras);
// TvExtender not recognized if not set last.
mNotificationBuilder.extend(new Notification.TvExtender()
.setContentIntent(createPendingIntent(mContext, ACTION_SHOW_PIP_MENU))
.setDeleteIntent(createPendingIntent(mContext, ACTION_CLOSE_PIP)));
mNotificationManager.notify(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP, mNotificationManager.notify(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP,
mNotificationBuilder.build()); mNotificationBuilder.build());
} }
private boolean updateMediaControllerMetadata(MediaMetadata metadata) {
String title = null;
Bitmap art = null;
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) && Objects.equals(art, mArt)) {
return false;
}
mMediaTitle = title;
mArt = art;
return true;
}
private String getNotificationTitle() { private String getNotificationTitle() {
if (!TextUtils.isEmpty(mMediaTitle)) { if (!TextUtils.isEmpty(mPipTitle)) {
return mMediaTitle; return mPipTitle;
} }
final String applicationTitle = getApplicationLabel(mPackageName); final String applicationTitle = getApplicationLabel(mPackageName);
if (!TextUtils.isEmpty(applicationTitle)) { if (!TextUtils.isEmpty(applicationTitle)) {
return applicationTitle; return applicationTitle;
} }
return mDefaultTitle; return mDefaultTitle;
} }
@@ -214,10 +362,37 @@ public class TvPipNotificationController {
} }
} }
private void setPipIcon() {
if (mActivityIcon != null) {
mNotificationBuilder.setLargeIcon(mActivityIcon);
return;
}
// Fallback: Picture-in-Picture icon
mNotificationBuilder.setLargeIcon(Icon.createWithResource(mContext, R.drawable.pip_icon));
}
private Bitmap getActivityIcon() {
if (mContext == null) return null;
ComponentName componentName = PipUtils.getTopPipActivity(mContext).first;
if (componentName == null) return null;
Drawable drawable;
try {
drawable = mPackageManager.getActivityIcon(componentName);
} catch (PackageManager.NameNotFoundException e) {
return null;
}
int width = mContext.getResources().getDimensionPixelSize(
android.R.dimen.notification_large_icon_width);
int height = mContext.getResources().getDimensionPixelSize(
android.R.dimen.notification_large_icon_height);
return ImageUtils.buildScaledBitmap(drawable, width, height, /* allowUpscaling */ true);
}
private static PendingIntent createPendingIntent(Context context, String action) { private static PendingIntent createPendingIntent(Context context, String action) {
return PendingIntent.getBroadcast(context, 0, return PendingIntent.getBroadcast(context, 0,
new Intent(action).setPackage(context.getPackageName()), new Intent(action).setPackage(context.getPackageName()),
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} }
private class ActionBroadcastReceiver extends BroadcastReceiver { private class ActionBroadcastReceiver extends BroadcastReceiver {
@@ -228,6 +403,7 @@ public class TvPipNotificationController {
mIntentFilter.addAction(ACTION_SHOW_PIP_MENU); mIntentFilter.addAction(ACTION_SHOW_PIP_MENU);
mIntentFilter.addAction(ACTION_MOVE_PIP); mIntentFilter.addAction(ACTION_MOVE_PIP);
mIntentFilter.addAction(ACTION_TOGGLE_EXPANDED_PIP); mIntentFilter.addAction(ACTION_TOGGLE_EXPANDED_PIP);
mIntentFilter.addAction(ACTION_FULLSCREEN);
} }
boolean mRegistered = false; boolean mRegistered = false;
@@ -249,10 +425,8 @@ public class TvPipNotificationController {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
final String action = intent.getAction(); final String action = intent.getAction();
if (DEBUG) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: on(Broadcast)Receive(), action=%s", TAG, action);
"%s: on(Broadcast)Receive(), action=%s", TAG, action);
}
if (ACTION_SHOW_PIP_MENU.equals(action)) { if (ACTION_SHOW_PIP_MENU.equals(action)) {
mDelegate.showPictureInPictureMenu(); mDelegate.showPictureInPictureMenu();
@@ -262,14 +436,21 @@ public class TvPipNotificationController {
mDelegate.enterPipMovementMenu(); mDelegate.enterPipMovementMenu();
} else if (ACTION_TOGGLE_EXPANDED_PIP.equals(action)) { } else if (ACTION_TOGGLE_EXPANDED_PIP.equals(action)) {
mDelegate.togglePipExpansion(); mDelegate.togglePipExpansion();
} else if (ACTION_FULLSCREEN.equals(action)) {
mDelegate.movePipToFullscreen();
} }
} }
} }
interface Delegate { interface Delegate {
void showPictureInPictureMenu(); void showPictureInPictureMenu();
void closePip(); void closePip();
void enterPipMovementMenu(); void enterPipMovementMenu();
void togglePipExpansion(); void togglePipExpansion();
void movePipToFullscreen();
} }
} }