Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 36b01e64 authored by Jaewan Kim's avatar Jaewan Kim Committed by android-build-merger
Browse files

Merge "TV PIP: Fix broken TV PIP" into oc-dev am: 2e0d4551

am: 84744bda

Change-Id: Ic3d5d6f34a5ee9ddd231efc973576b57394b281f
parents d4dcb0e0 84744bda
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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>
+1 −9
Original line number Diff line number Diff line
@@ -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>
+8 −0
Original line number Diff line number Diff line
@@ -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] -->
+9 −4
Original line number Diff line number Diff line
@@ -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) {
+225 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
Loading