diff --git a/packages/SystemUI/res/values/arrays_tv.xml b/packages/SystemUI/res/values/arrays_tv.xml index e52c5db9d227b..7541b0e8c0841 100644 --- a/packages/SystemUI/res/values/arrays_tv.xml +++ b/packages/SystemUI/res/values/arrays_tv.xml @@ -31,5 +31,6 @@ com.google.android.katniss.setting/.SpeechSettingsActivity com.google.android.katniss.setting/.SearchSettingsActivity com.google.android.gsf.notouch/.UsageDiagnosticsSettingActivity + com.google.android.tvlauncher/.notifications.NotificationsSidePanelActivity diff --git a/packages/SystemUI/res/values/config_tv.xml b/packages/SystemUI/res/values/config_tv.xml index 40e3b128a55e5..ffd58dcfd50db 100644 --- a/packages/SystemUI/res/values/config_tv.xml +++ b/packages/SystemUI/res/values/config_tv.xml @@ -17,17 +17,9 @@ - "662 54 1142 324" + "662 756 1142 1026" "596 280 1324 690" - - - "800 54 1120 234" - - - "775 54 1145 262" diff --git a/packages/SystemUI/res/values/strings_tv.xml b/packages/SystemUI/res/values/strings_tv.xml index e578068163d75..a9bdb71de039e 100644 --- a/packages/SystemUI/res/values/strings_tv.xml +++ b/packages/SystemUI/res/values/strings_tv.xml @@ -17,6 +17,14 @@ */ --> + + + + Picture-in-Picture + + (No title program) + diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java index 9735bfc666d73..6667b71efc0e9 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java @@ -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) { diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java new file mode 100644 index 0000000000000..727eb5a3dba44 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java @@ -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. + *

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); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java index cd85a760159bb..ae8afe4dcad7b 100644 --- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java +++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java @@ -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 diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index 53b3fe9c1a0d6..2f6b7e619e682 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -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;