Loading packages/SystemUI/res/values/arrays_tv.xml +1 −0 Original line number Diff line number Diff line Loading @@ -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> packages/SystemUI/res/values/config_tv.xml +1 −9 Original line number Diff line number Diff line Loading @@ -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> packages/SystemUI/res/values/strings_tv.xml +8 −0 Original line number Diff line number Diff line Loading @@ -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] --> Loading packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java +9 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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() { Loading @@ -267,6 +271,7 @@ public class PipManager implements BasePipManager { */ public void onConfigurationChanged() { loadConfigurationsAndApply(); mPipNotification.onConfigurationChanged(mContext); } /** Loading Loading @@ -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) { Loading Loading @@ -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) { Loading packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java 0 → 100644 +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
packages/SystemUI/res/values/arrays_tv.xml +1 −0 Original line number Diff line number Diff line Loading @@ -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>
packages/SystemUI/res/values/config_tv.xml +1 −9 Original line number Diff line number Diff line Loading @@ -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>
packages/SystemUI/res/values/strings_tv.xml +8 −0 Original line number Diff line number Diff line Loading @@ -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] --> Loading
packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java +9 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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() { Loading @@ -267,6 +271,7 @@ public class PipManager implements BasePipManager { */ public void onConfigurationChanged() { loadConfigurationsAndApply(); mPipNotification.onConfigurationChanged(mContext); } /** Loading Loading @@ -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) { Loading Loading @@ -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) { Loading
packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java 0 → 100644 +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); } }