Merge "TV PIP: Fix broken TV PIP" into oc-dev

This commit is contained in:
TreeHugger Robot
2017-05-08 22:26:13 +00:00
committed by Android (Google) Code Review
7 changed files with 258 additions and 13 deletions

View File

@@ -31,5 +31,6 @@
<item>com.google.android.katniss.setting/.SpeechSettingsActivity</item>
<item>com.google.android.katniss.setting/.SearchSettingsActivity</item>
<item>com.google.android.gsf.notouch/.UsageDiagnosticsSettingActivity</item>
<item>com.google.android.tvlauncher/.notifications.NotificationsSidePanelActivity</item>
</string-array>
</resources>

View File

@@ -17,17 +17,9 @@
<resources>
<!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
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,
when the PIP menu is shown in center. -->
<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>

View File

@@ -17,6 +17,14 @@
*/
-->
<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 -->
<eat-comment />
<!-- Button to close picture-in-picture (PIP) in PIP menu [CHAR LIMIT=30] -->

View File

@@ -61,7 +61,8 @@ import static android.view.Display.DEFAULT_DISPLAY;
*/
public class PipManager implements BasePipManager {
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 PipManager sPipManager;
@@ -122,6 +123,7 @@ public class PipManager implements BasePipManager {
private ComponentName mPipComponentName;
private MediaController mPipMediaController;
private String[] mLastPackagesResourceGranted;
private PipNotification mPipNotification;
private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
@@ -246,6 +248,8 @@ public class PipManager implements BasePipManager {
} catch (RemoteException e) {
Log.e(TAG, "Failed to register pinned stack listener", e);
}
mPipNotification = new PipNotification(context);
}
private void loadConfigurationsAndApply() {
@@ -267,6 +271,7 @@ public class PipManager implements BasePipManager {
*/
public void onConfigurationChanged() {
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.
*/
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);
mResumeResizePinnedStackRunnable = state;
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.
* This returns one of {@link PLAYBACK_STATE_PLAYING}, {@link PLAYBACK_STATE_PAUSED},
* or {@link PLAYBACK_STATE_UNAVAILABLE}.
* This returns one of {@link #PLAYBACK_STATE_PLAYING}, {@link #PLAYBACK_STATE_PAUSED},
* or {@link #PLAYBACK_STATE_UNAVAILABLE}.
*/
int getPlaybackState() {
if (mPipMediaController == null || mPipMediaController.getPlaybackState() == null) {

View File

@@ -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);
}
}

View File

@@ -30,6 +30,7 @@ public class NotificationChannels extends SystemUI {
public static String SCREENSHOTS = "SCN";
public static String GENERAL = "GEN";
public static String STORAGE = "DSK";
public static String TVPIP = "TPP";
@VisibleForTesting
static void createAll(Context context) {
@@ -55,6 +56,15 @@ public class NotificationChannels extends SystemUI {
? NotificationManager.IMPORTANCE_DEFAULT
: 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

View File

@@ -212,6 +212,10 @@ message SystemMessage {
// Package: com.android.systemui
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.
// Package: android
NOTE_REMOTE_BUGREPORT = 678432343;