Merge "TV PIP: Fix broken TV PIP" into oc-dev
am: 2e0d455177
Change-Id: If3a85b43b3d1dc27aefe58253888b96d2710f673
This commit is contained in:
@@ -31,5 +31,6 @@
|
|||||||
<item>com.google.android.katniss.setting/.SpeechSettingsActivity</item>
|
<item>com.google.android.katniss.setting/.SpeechSettingsActivity</item>
|
||||||
<item>com.google.android.katniss.setting/.SearchSettingsActivity</item>
|
<item>com.google.android.katniss.setting/.SearchSettingsActivity</item>
|
||||||
<item>com.google.android.gsf.notouch/.UsageDiagnosticsSettingActivity</item>
|
<item>com.google.android.gsf.notouch/.UsageDiagnosticsSettingActivity</item>
|
||||||
|
<item>com.google.android.tvlauncher/.notifications.NotificationsSidePanelActivity</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -17,17 +17,9 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
|
<!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
|
||||||
when the PIP menu is shown with settings. -->
|
when the PIP menu is shown with settings. -->
|
||||||
<string translatable="false" name="pip_settings_bounds">"662 54 1142 324"</string>
|
<string translatable="false" name="pip_settings_bounds">"662 756 1142 1026"</string>
|
||||||
|
|
||||||
<!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
|
<!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
|
||||||
when the PIP menu is shown in center. -->
|
when the PIP menu is shown in center. -->
|
||||||
<string translatable="false" name="pip_menu_bounds">"596 280 1324 690"</string>
|
<string translatable="false" name="pip_menu_bounds">"596 280 1324 690"</string>
|
||||||
|
|
||||||
<!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
|
|
||||||
when the PIP is shown in Recents without focus. -->
|
|
||||||
<string translatable="false" name="pip_recents_bounds">"800 54 1120 234"</string>
|
|
||||||
|
|
||||||
<!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
|
|
||||||
when the PIP is shown in Recents with focus. -->
|
|
||||||
<string translatable="false" name="pip_recents_focused_bounds">"775 54 1145 262"</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -17,6 +17,14 @@
|
|||||||
*/
|
*/
|
||||||
-->
|
-->
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
|
||||||
|
<!-- Picture-in-Picture (PIP) notification -->
|
||||||
|
<!-- Title for the notification channel for TV PIP controls. [CHAR LIMIT=NONE] -->
|
||||||
|
<string name="notification_channel_tv_pip">Picture-in-Picture</string>
|
||||||
|
<!-- Title of the picture-in-picture (PIP) notification title
|
||||||
|
when the media doesn't have title [CHAR LIMIT=NONE] -->
|
||||||
|
<string name="pip_notification_unknown_title">(No title program)</string>
|
||||||
|
|
||||||
<!-- Picture-in-Picture (PIP) menu -->
|
<!-- Picture-in-Picture (PIP) menu -->
|
||||||
<eat-comment />
|
<eat-comment />
|
||||||
<!-- Button to close picture-in-picture (PIP) in PIP menu [CHAR LIMIT=30] -->
|
<!-- Button to close picture-in-picture (PIP) in PIP menu [CHAR LIMIT=30] -->
|
||||||
|
|||||||
@@ -61,7 +61,8 @@ import static android.view.Display.DEFAULT_DISPLAY;
|
|||||||
*/
|
*/
|
||||||
public class PipManager implements BasePipManager {
|
public class PipManager implements BasePipManager {
|
||||||
private static final String TAG = "PipManager";
|
private static final String TAG = "PipManager";
|
||||||
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||||
|
|
||||||
private static final String SETTINGS_PACKAGE_AND_CLASS_DELIMITER = "/";
|
private static final String SETTINGS_PACKAGE_AND_CLASS_DELIMITER = "/";
|
||||||
|
|
||||||
private static PipManager sPipManager;
|
private static PipManager sPipManager;
|
||||||
@@ -122,6 +123,7 @@ public class PipManager implements BasePipManager {
|
|||||||
private ComponentName mPipComponentName;
|
private ComponentName mPipComponentName;
|
||||||
private MediaController mPipMediaController;
|
private MediaController mPipMediaController;
|
||||||
private String[] mLastPackagesResourceGranted;
|
private String[] mLastPackagesResourceGranted;
|
||||||
|
private PipNotification mPipNotification;
|
||||||
|
|
||||||
private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
|
private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
|
||||||
|
|
||||||
@@ -246,6 +248,8 @@ public class PipManager implements BasePipManager {
|
|||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
Log.e(TAG, "Failed to register pinned stack listener", e);
|
Log.e(TAG, "Failed to register pinned stack listener", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mPipNotification = new PipNotification(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadConfigurationsAndApply() {
|
private void loadConfigurationsAndApply() {
|
||||||
@@ -267,6 +271,7 @@ public class PipManager implements BasePipManager {
|
|||||||
*/
|
*/
|
||||||
public void onConfigurationChanged() {
|
public void onConfigurationChanged() {
|
||||||
loadConfigurationsAndApply();
|
loadConfigurationsAndApply();
|
||||||
|
mPipNotification.onConfigurationChanged(mContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -345,7 +350,7 @@ public class PipManager implements BasePipManager {
|
|||||||
* @param state In Pip state also used to determine the new size for the Pip.
|
* @param state In Pip state also used to determine the new size for the Pip.
|
||||||
*/
|
*/
|
||||||
void resizePinnedStack(int state) {
|
void resizePinnedStack(int state) {
|
||||||
if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + state);
|
if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + state, new Exception());
|
||||||
boolean wasStateNoPip = (mState == STATE_NO_PIP);
|
boolean wasStateNoPip = (mState == STATE_NO_PIP);
|
||||||
mResumeResizePinnedStackRunnable = state;
|
mResumeResizePinnedStackRunnable = state;
|
||||||
for (int i = mListeners.size() - 1; i >= 0; --i) {
|
for (int i = mListeners.size() - 1; i >= 0; --i) {
|
||||||
@@ -511,8 +516,8 @@ public class PipManager implements BasePipManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the PIPed activity's playback state.
|
* Returns the PIPed activity's playback state.
|
||||||
* This returns one of {@link PLAYBACK_STATE_PLAYING}, {@link PLAYBACK_STATE_PAUSED},
|
* This returns one of {@link #PLAYBACK_STATE_PLAYING}, {@link #PLAYBACK_STATE_PAUSED},
|
||||||
* or {@link PLAYBACK_STATE_UNAVAILABLE}.
|
* or {@link #PLAYBACK_STATE_UNAVAILABLE}.
|
||||||
*/
|
*/
|
||||||
int getPlaybackState() {
|
int getPlaybackState() {
|
||||||
if (mPipMediaController == null || mPipMediaController.getPlaybackState() == null) {
|
if (mPipMediaController == null || mPipMediaController.getPlaybackState() == null) {
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
/*
|
||||||
|
* 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.tv;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.drawable.Icon;
|
||||||
|
import android.media.MediaMetadata;
|
||||||
|
import android.media.session.MediaController;
|
||||||
|
import android.media.session.PlaybackState;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.android.systemui.util.NotificationChannels;
|
||||||
|
import com.android.systemui.R;
|
||||||
|
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
public class PipNotification {
|
||||||
|
private static final String TAG = "PipNotification";
|
||||||
|
private static final boolean DEBUG = PipManager.DEBUG;
|
||||||
|
|
||||||
|
private static final String ACTION_MENU = "PipNotification.menu";
|
||||||
|
private static final String ACTION_CLOSE = "PipNotification.close";
|
||||||
|
|
||||||
|
private final PipManager mPipManager = PipManager.getInstance();
|
||||||
|
|
||||||
|
private final NotificationManager mNotificationManager;
|
||||||
|
private final Notification.Builder mNotificationBuilder;
|
||||||
|
|
||||||
|
private MediaController mMediaController;
|
||||||
|
private String mDefaultTitle;
|
||||||
|
private Icon mDefaultIcon;
|
||||||
|
|
||||||
|
private boolean mNotified;
|
||||||
|
private String mTitle;
|
||||||
|
private Bitmap mArt;
|
||||||
|
|
||||||
|
private PipManager.Listener mPipListener = new PipManager.Listener() {
|
||||||
|
@Override
|
||||||
|
public void onPipEntered() {
|
||||||
|
updateMediaControllerMetadata();
|
||||||
|
notifyPipNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPipActivityClosed() {
|
||||||
|
dismissPipNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onShowPipMenu() {
|
||||||
|
// no-op.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMoveToFullscreen() {
|
||||||
|
dismissPipNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPipResizeAboutToStart() {
|
||||||
|
// no-op.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
|
||||||
|
@Override
|
||||||
|
public void onPlaybackStateChanged(PlaybackState state) {
|
||||||
|
if (updateMediaControllerMetadata() && mNotified) {
|
||||||
|
// update notification
|
||||||
|
notifyPipNotification();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final PipManager.MediaListener mPipMediaListener = new PipManager.MediaListener() {
|
||||||
|
@Override
|
||||||
|
public void onMediaControllerChanged() {
|
||||||
|
MediaController newController = mPipManager.getMediaController();
|
||||||
|
if (mMediaController == newController) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mMediaController != null) {
|
||||||
|
mMediaController.unregisterCallback(mMediaControllerCallback);
|
||||||
|
}
|
||||||
|
mMediaController = newController;
|
||||||
|
if (mMediaController != null) {
|
||||||
|
mMediaController.registerCallback(mMediaControllerCallback);
|
||||||
|
}
|
||||||
|
if (updateMediaControllerMetadata() && mNotified) {
|
||||||
|
// update notification
|
||||||
|
notifyPipNotification();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final BroadcastReceiver mEventReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "Received " + intent.getAction() + " from the notification UI");
|
||||||
|
}
|
||||||
|
switch (intent.getAction()) {
|
||||||
|
case ACTION_MENU:
|
||||||
|
mPipManager.showPictureInPictureMenu();
|
||||||
|
break;
|
||||||
|
case ACTION_CLOSE:
|
||||||
|
mPipManager.closePip();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public PipNotification(Context context) {
|
||||||
|
mNotificationManager = (NotificationManager) context.getSystemService(
|
||||||
|
Context.NOTIFICATION_SERVICE);
|
||||||
|
|
||||||
|
mNotificationBuilder = new Notification.Builder(context, NotificationChannels.TVPIP)
|
||||||
|
.setLocalOnly(true)
|
||||||
|
.setOngoing(false)
|
||||||
|
.setCategory(Notification.CATEGORY_SYSTEM)
|
||||||
|
.extend(new Notification.TvExtender()
|
||||||
|
.setContentIntent(createPendingIntent(context, ACTION_MENU))
|
||||||
|
.setDeleteIntent(createPendingIntent(context, ACTION_CLOSE)));
|
||||||
|
|
||||||
|
mPipManager.addListener(mPipListener);
|
||||||
|
mPipManager.addMediaListener(mPipMediaListener);
|
||||||
|
|
||||||
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
|
intentFilter.addAction(ACTION_MENU);
|
||||||
|
intentFilter.addAction(ACTION_CLOSE);
|
||||||
|
context.registerReceiver(mEventReceiver, intentFilter);
|
||||||
|
|
||||||
|
onConfigurationChanged(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by {@link PipManager} when the configuration is changed.
|
||||||
|
*/
|
||||||
|
void onConfigurationChanged(Context context) {
|
||||||
|
Resources res = context.getResources();
|
||||||
|
mDefaultTitle = res.getString(R.string.pip_notification_unknown_title);
|
||||||
|
mDefaultIcon = Icon.createWithResource(context,
|
||||||
|
res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR
|
||||||
|
? R.drawable.pip_expand_ll : R.drawable.pip_expand_lr);
|
||||||
|
if (mNotified) {
|
||||||
|
// update notification
|
||||||
|
notifyPipNotification();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyPipNotification() {
|
||||||
|
mNotified = true;
|
||||||
|
mNotificationBuilder
|
||||||
|
.setShowWhen(true)
|
||||||
|
.setWhen(System.currentTimeMillis())
|
||||||
|
// TODO: Sending bitmap doesn't work in launcher side. Once launcher supports it,
|
||||||
|
// we can set icon.
|
||||||
|
//.setSmallIcon(mArt != null ? Icon.createWithBitmap(mArt) : mDefaultIcon)
|
||||||
|
.setSmallIcon(mDefaultIcon.getResId())
|
||||||
|
.setContentTitle(!TextUtils.isEmpty(mTitle) ? mTitle : mDefaultTitle);
|
||||||
|
mNotificationManager.notify(SystemMessage.NOTE_TV_PIP, mNotificationBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dismissPipNotification() {
|
||||||
|
mNotified = false;
|
||||||
|
mNotificationManager.cancel(SystemMessage.NOTE_TV_PIP);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean updateMediaControllerMetadata() {
|
||||||
|
String title = null;
|
||||||
|
Bitmap art = null;
|
||||||
|
if (mPipManager.getMediaController() != null) {
|
||||||
|
MediaMetadata metadata = mPipManager.getMediaController().getMetadata();
|
||||||
|
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, mTitle) || art != mArt) {
|
||||||
|
mTitle = title;
|
||||||
|
mArt = art;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PendingIntent createPendingIntent(Context context, String action) {
|
||||||
|
return PendingIntent.getBroadcast(context, 0,
|
||||||
|
new Intent(action), PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ public class NotificationChannels extends SystemUI {
|
|||||||
public static String SCREENSHOTS = "SCN";
|
public static String SCREENSHOTS = "SCN";
|
||||||
public static String GENERAL = "GEN";
|
public static String GENERAL = "GEN";
|
||||||
public static String STORAGE = "DSK";
|
public static String STORAGE = "DSK";
|
||||||
|
public static String TVPIP = "TPP";
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static void createAll(Context context) {
|
static void createAll(Context context) {
|
||||||
@@ -55,6 +56,15 @@ public class NotificationChannels extends SystemUI {
|
|||||||
? NotificationManager.IMPORTANCE_DEFAULT
|
? NotificationManager.IMPORTANCE_DEFAULT
|
||||||
: NotificationManager.IMPORTANCE_LOW)
|
: NotificationManager.IMPORTANCE_LOW)
|
||||||
));
|
));
|
||||||
|
if (isTv(context)) {
|
||||||
|
// TV specific notification channel for TV PIP controls.
|
||||||
|
// Importance should be {@link NotificationManager#IMPORTANCE_MAX} to have the highest
|
||||||
|
// priority, so it can be shown in all times.
|
||||||
|
nm.createNotificationChannel(new NotificationChannel(
|
||||||
|
TVPIP,
|
||||||
|
context.getString(R.string.notification_channel_tv_pip),
|
||||||
|
NotificationManager.IMPORTANCE_MAX));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -212,6 +212,10 @@ message SystemMessage {
|
|||||||
// Package: com.android.systemui
|
// Package: com.android.systemui
|
||||||
NOTE_LOGOUT_USER = 1011;
|
NOTE_LOGOUT_USER = 1011;
|
||||||
|
|
||||||
|
// Notify the user that a TV PIP is running.
|
||||||
|
// Package: com.android.systemui
|
||||||
|
NOTE_TV_PIP = 1100;
|
||||||
|
|
||||||
// Communicate to the user about remote bugreports.
|
// Communicate to the user about remote bugreports.
|
||||||
// Package: android
|
// Package: android
|
||||||
NOTE_REMOTE_BUGREPORT = 678432343;
|
NOTE_REMOTE_BUGREPORT = 678432343;
|
||||||
|
|||||||
Reference in New Issue
Block a user