Merge "TV PIP: Fix broken TV PIP" into oc-dev
diff --git a/packages/SystemUI/res/values/arrays_tv.xml b/packages/SystemUI/res/values/arrays_tv.xml
index e52c5db..7541b0e 100644
--- a/packages/SystemUI/res/values/arrays_tv.xml
+++ b/packages/SystemUI/res/values/arrays_tv.xml
@@ -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>
diff --git a/packages/SystemUI/res/values/config_tv.xml b/packages/SystemUI/res/values/config_tv.xml
index 40e3b12..ffd58dc 100644
--- a/packages/SystemUI/res/values/config_tv.xml
+++ b/packages/SystemUI/res/values/config_tv.xml
@@ -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>
diff --git a/packages/SystemUI/res/values/strings_tv.xml b/packages/SystemUI/res/values/strings_tv.xml
index e578068..a9bdb71 100644
--- a/packages/SystemUI/res/values/strings_tv.xml
+++ b/packages/SystemUI/res/values/strings_tv.xml
@@ -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] -->
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 9735bfc..6667b71 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 @@
  */
 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 @@
     private ComponentName mPipComponentName;
     private MediaController mPipMediaController;
     private String[] mLastPackagesResourceGranted;
+    private PipNotification mPipNotification;
 
     private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
 
@@ -246,6 +248,8 @@
         } 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 void onConfigurationChanged() {
         loadConfigurationsAndApply();
+        mPipNotification.onConfigurationChanged(mContext);
     }
 
     /**
@@ -345,7 +350,7 @@
      * @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 @@
 
     /**
      * 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 0000000..727eb5a
--- /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.
+ * <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);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
index cd85a76..ae8afe4 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 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 @@
                                 ? 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 53b3fe9..2f6b7e6 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -212,6 +212,10 @@
     // 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;