Merge "Add more information to the TV PiP notification" into tm-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
b6de832709
@@ -137,6 +137,18 @@ public class ImageUtils {
|
||||
*/
|
||||
public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth,
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
@@ -155,7 +167,9 @@ public class ImageUtils {
|
||||
// a large notification icon if necessary
|
||||
float ratio = Math.min((float) maxWidth / (float) originalWidth,
|
||||
(float) maxHeight / (float) originalHeight);
|
||||
ratio = Math.min(1.0f, ratio);
|
||||
if (!allowUpscaling) {
|
||||
ratio = Math.min(1.0f, ratio);
|
||||
}
|
||||
int scaledWidth = (int) (ratio * originalWidth);
|
||||
int scaledHeight = (int) (ratio * originalHeight);
|
||||
Bitmap result = Bitmap.createBitmap(scaledWidth, scaledHeight, Config.ARGB_8888);
|
||||
|
||||
@@ -143,8 +143,11 @@ public abstract class TvPipModule {
|
||||
@Provides
|
||||
static TvPipNotificationController provideTvPipNotificationController(Context context,
|
||||
PipMediaController pipMediaController,
|
||||
PipParamsChangedForwarder pipParamsChangedForwarder,
|
||||
TvPipBoundsState tvPipBoundsState,
|
||||
@ShellMainThread Handler mainHandler) {
|
||||
return new TvPipNotificationController(context, pipMediaController, mainHandler);
|
||||
return new TvPipNotificationController(context, pipMediaController,
|
||||
pipParamsChangedForwarder, tvPipBoundsState, mainHandler);
|
||||
}
|
||||
|
||||
@WMSingleton
|
||||
|
||||
@@ -32,6 +32,7 @@ import android.content.IntentFilter;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.media.MediaMetadata;
|
||||
import android.media.session.MediaController;
|
||||
import android.media.session.MediaSession;
|
||||
import android.media.session.MediaSessionManager;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.os.Handler;
|
||||
@@ -64,7 +65,7 @@ public class PipMediaController {
|
||||
*/
|
||||
public interface ActionListener {
|
||||
/**
|
||||
* Called when the media actions changes.
|
||||
* Called when the media actions changed.
|
||||
*/
|
||||
void onMediaActionsChanged(List<RemoteAction> actions);
|
||||
}
|
||||
@@ -74,11 +75,21 @@ public class PipMediaController {
|
||||
*/
|
||||
public interface MetadataListener {
|
||||
/**
|
||||
* Called when the media metadata changes.
|
||||
* Called when the media metadata changed.
|
||||
*/
|
||||
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 Handler mMainHandler;
|
||||
private final HandlerExecutor mHandlerExecutor;
|
||||
@@ -133,6 +144,7 @@ public class PipMediaController {
|
||||
|
||||
private final ArrayList<ActionListener> mActionListeners = new ArrayList<>();
|
||||
private final ArrayList<MetadataListener> mMetadataListeners = new ArrayList<>();
|
||||
private final ArrayList<TokenListener> mTokenListeners = new ArrayList<>();
|
||||
|
||||
public PipMediaController(Context context, Handler mainHandler) {
|
||||
mContext = context;
|
||||
@@ -204,6 +216,31 @@ public class PipMediaController {
|
||||
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() {
|
||||
return mMediaController != null ? mMediaController.getMetadata() : null;
|
||||
}
|
||||
@@ -294,6 +331,7 @@ public class PipMediaController {
|
||||
}
|
||||
notifyActionsChanged();
|
||||
notifyMetadataChanged(getMediaMetadata());
|
||||
notifyTokenChanged(getToken());
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyTokenChanged(MediaSession.Token token) {
|
||||
if (!mTokenListeners.isEmpty()) {
|
||||
mTokenListeners.forEach(l -> l.onMediaSessionTokenChanged(token));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,6 +293,8 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
|
||||
}
|
||||
mTvPipBoundsState.setTvPipManuallyCollapsed(!expanding);
|
||||
mTvPipBoundsState.setTvPipExpanded(expanding);
|
||||
mPipNotificationController.updateExpansionState();
|
||||
|
||||
updatePinnedStackBounds();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,36 +16,47 @@
|
||||
|
||||
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.NotificationManager;
|
||||
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.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
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.text.TextUtils;
|
||||
|
||||
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
|
||||
import com.android.internal.protolog.common.ProtoLog;
|
||||
import com.android.internal.util.ImageUtils;
|
||||
import com.android.wm.shell.R;
|
||||
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 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.
|
||||
* <p>Once it's created, it will manage the PIP notification UI by itself except for handling
|
||||
* configuration changes.
|
||||
* 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 and user initiated expanded PiP toggling.
|
||||
*/
|
||||
public class TvPipNotificationController {
|
||||
private static final String TAG = "TvPipNotification";
|
||||
private static final boolean DEBUG = TvPipController.DEBUG;
|
||||
|
||||
// Referenced in com.android.systemui.util.NotificationChannels.
|
||||
public static final String NOTIFICATION_CHANNEL = "TVPIP";
|
||||
@@ -60,6 +71,8 @@ public class TvPipNotificationController {
|
||||
"com.android.wm.shell.pip.tv.notification.action.MOVE_PIP";
|
||||
private static final String 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 PackageManager mPackageManager;
|
||||
@@ -68,44 +81,88 @@ public class TvPipNotificationController {
|
||||
private final ActionBroadcastReceiver mActionBroadcastReceiver;
|
||||
private final Handler mMainHandler;
|
||||
private Delegate mDelegate;
|
||||
private final TvPipBoundsState mTvPipBoundsState;
|
||||
|
||||
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. */
|
||||
private String mPackageName;
|
||||
private boolean mNotified;
|
||||
private String mMediaTitle;
|
||||
private Bitmap mArt;
|
||||
|
||||
private boolean mIsNotificationShown;
|
||||
private String mPipTitle;
|
||||
private String mPipSubtitle;
|
||||
|
||||
private Bitmap mActivityIcon;
|
||||
|
||||
public TvPipNotificationController(Context context, PipMediaController pipMediaController,
|
||||
PipParamsChangedForwarder pipParamsChangedForwarder, TvPipBoundsState tvPipBoundsState,
|
||||
Handler mainHandler) {
|
||||
mContext = context;
|
||||
mPackageManager = context.getPackageManager();
|
||||
mNotificationManager = context.getSystemService(NotificationManager.class);
|
||||
mMainHandler = mainHandler;
|
||||
mTvPipBoundsState = tvPipBoundsState;
|
||||
|
||||
mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL)
|
||||
.setLocalOnly(true)
|
||||
.setOngoing(false)
|
||||
.setOngoing(true)
|
||||
.setCategory(Notification.CATEGORY_SYSTEM)
|
||||
.setShowWhen(true)
|
||||
.setSmallIcon(R.drawable.pip_icon)
|
||||
.setAllowSystemGeneratedContextualActions(false)
|
||||
.setContentIntent(createPendingIntent(context, ACTION_FULLSCREEN))
|
||||
.setDeleteIntent(getCloseAction().actionIntent)
|
||||
.extend(new Notification.TvExtender()
|
||||
.setContentIntent(createPendingIntent(context, ACTION_SHOW_PIP_MENU))
|
||||
.setDeleteIntent(createPendingIntent(context, ACTION_CLOSE_PIP)));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void setDelegate(Delegate delegate) {
|
||||
if (DEBUG) {
|
||||
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
|
||||
"%s: setDelegate(), delegate=%s", TAG, delegate);
|
||||
}
|
||||
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: setDelegate(), delegate=%s",
|
||||
TAG, delegate);
|
||||
|
||||
if (mDelegate != null) {
|
||||
throw new IllegalStateException(
|
||||
"The delegate has already been set and should not change.");
|
||||
@@ -118,90 +175,181 @@ public class TvPipNotificationController {
|
||||
}
|
||||
|
||||
void show(String packageName) {
|
||||
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: show %s", TAG, packageName);
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Delegate is not set.");
|
||||
}
|
||||
|
||||
mIsNotificationShown = true;
|
||||
mPackageName = packageName;
|
||||
update();
|
||||
mActivityIcon = getActivityIcon();
|
||||
mActionBroadcastReceiver.register();
|
||||
|
||||
updateNotificationContent();
|
||||
}
|
||||
|
||||
void dismiss() {
|
||||
mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
|
||||
mNotified = false;
|
||||
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: dismiss()", TAG);
|
||||
|
||||
mIsNotificationShown = false;
|
||||
mPackageName = null;
|
||||
mActionBroadcastReceiver.unregister();
|
||||
|
||||
mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
|
||||
}
|
||||
|
||||
private void onMediaMetadataChanged(MediaMetadata metadata) {
|
||||
if (updateMediaControllerMetadata(metadata) && mNotified) {
|
||||
// update notification
|
||||
update();
|
||||
private Notification.Action getToggleAction(boolean expanded) {
|
||||
if (expanded) {
|
||||
return createSystemAction(R.drawable.pip_ic_collapse,
|
||||
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) {
|
||||
mDefaultTitle = context.getResources().getString(R.string.pip_notification_unknown_title);
|
||||
if (mNotified) {
|
||||
// Update the notification.
|
||||
update();
|
||||
}
|
||||
updateNotificationContent();
|
||||
}
|
||||
|
||||
private void update() {
|
||||
mNotified = true;
|
||||
void updateExpansionState() {
|
||||
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
|
||||
.setWhen(System.currentTimeMillis())
|
||||
.setContentTitle(getNotificationTitle());
|
||||
if (mArt != null) {
|
||||
mNotificationBuilder.setStyle(new Notification.BigPictureStyle()
|
||||
.bigPicture(mArt));
|
||||
} else {
|
||||
mNotificationBuilder.setStyle(null);
|
||||
}
|
||||
.setContentTitle(getNotificationTitle())
|
||||
.setContentText(mPipSubtitle)
|
||||
.setSubText(getApplicationLabel(mPackageName))
|
||||
.setActions(actions);
|
||||
setPipIcon();
|
||||
|
||||
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,
|
||||
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() {
|
||||
if (!TextUtils.isEmpty(mMediaTitle)) {
|
||||
return mMediaTitle;
|
||||
if (!TextUtils.isEmpty(mPipTitle)) {
|
||||
return mPipTitle;
|
||||
}
|
||||
|
||||
final String applicationTitle = getApplicationLabel(mPackageName);
|
||||
if (!TextUtils.isEmpty(applicationTitle)) {
|
||||
return applicationTitle;
|
||||
}
|
||||
|
||||
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) {
|
||||
return PendingIntent.getBroadcast(context, 0,
|
||||
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 {
|
||||
@@ -228,6 +403,7 @@ public class TvPipNotificationController {
|
||||
mIntentFilter.addAction(ACTION_SHOW_PIP_MENU);
|
||||
mIntentFilter.addAction(ACTION_MOVE_PIP);
|
||||
mIntentFilter.addAction(ACTION_TOGGLE_EXPANDED_PIP);
|
||||
mIntentFilter.addAction(ACTION_FULLSCREEN);
|
||||
}
|
||||
boolean mRegistered = false;
|
||||
|
||||
@@ -249,10 +425,8 @@ public class TvPipNotificationController {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final String action = intent.getAction();
|
||||
if (DEBUG) {
|
||||
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
|
||||
"%s: on(Broadcast)Receive(), action=%s", TAG, action);
|
||||
}
|
||||
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
|
||||
"%s: on(Broadcast)Receive(), action=%s", TAG, action);
|
||||
|
||||
if (ACTION_SHOW_PIP_MENU.equals(action)) {
|
||||
mDelegate.showPictureInPictureMenu();
|
||||
@@ -262,14 +436,21 @@ public class TvPipNotificationController {
|
||||
mDelegate.enterPipMovementMenu();
|
||||
} else if (ACTION_TOGGLE_EXPANDED_PIP.equals(action)) {
|
||||
mDelegate.togglePipExpansion();
|
||||
} else if (ACTION_FULLSCREEN.equals(action)) {
|
||||
mDelegate.movePipToFullscreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Delegate {
|
||||
void showPictureInPictureMenu();
|
||||
|
||||
void closePip();
|
||||
|
||||
void enterPipMovementMenu();
|
||||
|
||||
void togglePipExpansion();
|
||||
|
||||
void movePipToFullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user