diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index 34354746b97c6..0f98cfba478f6 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -23,4 +23,12 @@
false
+
+
+ "1420 100 1820 325"
+
+
+ "600 331 1320 749"
+
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3f6a0c194d613..b6150e045f174 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2419,6 +2419,10 @@
"0 0 100 100"
+
+ "0 0 300 300"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/layout/tv_pip_overlay.xml b/packages/SystemUI/res/layout/tv_pip_overlay.xml
new file mode 100644
index 0000000000000..e8691b511706b
--- /dev/null
+++ b/packages/SystemUI/res/layout/tv_pip_overlay.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/values/strings_tv.xml b/packages/SystemUI/res/values/strings_tv.xml
new file mode 100644
index 0000000000000..d432a627a84dd
--- /dev/null
+++ b/packages/SystemUI/res/values/strings_tv.xml
@@ -0,0 +1,32 @@
+
+
+
+
+ Close PIP
+
+ Full screen
+
+ Play
+
+ Pause
+
+ Cancel
+
+ Hold HOME to control PIP
+
diff --git a/packages/SystemUI/res/values/styles_tv.xml b/packages/SystemUI/res/values/styles_tv.xml
new file mode 100644
index 0000000000000..3f0caab23ab47
--- /dev/null
+++ b/packages/SystemUI/res/values/styles_tv.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 2d056bfc8a9a0..472648a2f7f6f 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -54,6 +54,7 @@ public class SystemUIApplication extends Application {
com.android.systemui.power.PowerUI.class,
com.android.systemui.media.RingtonePlayer.class,
com.android.systemui.keyboard.KeyboardUI.class,
+ com.android.systemui.tv.pip.PipUI.class
};
/**
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
new file mode 100644
index 0000000000000..2aac69aa79c30
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2016 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.tv.pip;
+
+import android.app.ActivityManager.StackInfo;
+import android.app.ActivityManagerNative;
+import android.app.ActivityOptions;
+import android.app.IActivityManager;
+import android.app.ITaskStackListener;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+
+/**
+ * Manages the picture-in-picture (PIP) UI and states.
+ */
+public class PipManager {
+ private static final String TAG = "PipManager";
+ private static final boolean DEBUG = false;
+
+ private static PipManager sPipManager;
+
+ private static final int STATE_NO_PIP = 0;
+ private static final int STATE_PIP_OVERLAY = 1;
+ private static final int STATE_PIP_MENU = 2;
+
+ private Context mContext;
+ private IActivityManager mActivityManager;
+ private int mState = STATE_NO_PIP;
+ private final Handler mHandler = new Handler();
+ private List mListeners = new ArrayList<>();
+ private Rect mPipBound;
+ private Rect mMenuModePipBound;
+ private boolean mInitialized;
+ private final Runnable mOnActivityPinnedRunnable = new Runnable() {
+ @Override
+ public void run() {
+ StackInfo stackInfo = null;
+ try {
+ stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+ if (stackInfo == null) {
+ Log.w(TAG, "There is no pinned stack");
+ return;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "getStackInfo failed", e);
+ return;
+ }
+ if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
+ mState = STATE_PIP_OVERLAY;
+ launchPipOverlayActivity();
+ }
+ };
+ private final Runnable mOnTaskStackChanged = new Runnable() {
+ @Override
+ public void run() {
+ if (mState != STATE_NO_PIP) {
+ // TODO: check whether PIP task is closed.
+ }
+ }
+ };
+
+ private final BroadcastReceiver mPipButtonReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) Log.d(TAG, "PIP button pressed");
+ if (!hasPipTasks()) {
+ startPip();
+ } else if (mState == STATE_PIP_OVERLAY) {
+ showPipMenu();
+ }
+ }
+ };
+
+ private PipManager() { }
+
+ /**
+ * Initializes {@link PipManager}.
+ */
+ public void initialize(Context context) {
+ if (mInitialized) {
+ return;
+ }
+ mInitialized = true;
+ mContext = context;
+ Resources res = context.getResources();
+ mPipBound = Rect.unflattenFromString(res.getString(
+ com.android.internal.R.string.config_defaultPictureInPictureBounds));
+ mMenuModePipBound = Rect.unflattenFromString(res.getString(
+ com.android.internal.R.string.config_centeredPictureInPictureBounds));
+
+ mActivityManager = ActivityManagerNative.getDefault();
+ TaskStackListener taskStackListener = new TaskStackListener();
+ IActivityManager iam = ActivityManagerNative.getDefault();
+ try {
+ iam.registerTaskStackListener(taskStackListener);
+ } catch (RemoteException e) {
+ Log.e(TAG, "registerTaskStackListener failed", e);
+ }
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PICTURE_IN_PICTURE_BUTTON);
+ mContext.registerReceiver(mPipButtonReceiver, intentFilter);
+ }
+
+ private void startPip() {
+ try {
+ mActivityManager.moveTopActivityToPinnedStack(FULLSCREEN_WORKSPACE_STACK_ID, mPipBound);
+ } catch (RemoteException|IllegalArgumentException e) {
+ Log.e(TAG, "moveTopActivityToPinnedStack failed", e);
+ }
+
+ }
+
+ /**
+ * Closes PIP (PIPped activity and PIP system UI).
+ */
+ public void closePip() {
+ mState = STATE_NO_PIP;
+ StackInfo stackInfo = null;
+ try {
+ stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+ if (stackInfo == null) {
+ return;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "getStackInfo failed", e);
+ return;
+ }
+ for (int taskId : stackInfo.taskIds) {
+ try {
+ mActivityManager.removeTask(taskId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "removeTask failed", e);
+ }
+ }
+ }
+
+ /**
+ * Moves the PIPped activity to the fullscreen and closes PIP system UI.
+ */
+ public void movePipToFullscreen() {
+ mState = STATE_NO_PIP;
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onMoveToFullscreen();
+ }
+ try {
+ mActivityManager.moveTasksToFullscreenStack(PINNED_STACK_ID, true);
+ } catch (RemoteException e) {
+ Log.e(TAG, "moveTasksToFullscreenStack failed", e);
+ }
+ }
+
+ /**
+ * Shows PIP overlay UI by launching {@link PipOverlayActivity}. It also locates the pinned
+ * stack to the default PIP bound {@link com.android.internal.R.string
+ * .config_defaultPictureInPictureBounds}.
+ */
+ public void showPipOverlay() {
+ if (DEBUG) Log.d(TAG, "showPipOverlay()");
+ try {
+ mActivityManager.resizeStack(PINNED_STACK_ID, mPipBound, false);
+ } catch (Exception e) {
+ Log.e(TAG, "resizeStack failed", e);
+ closePip();
+ return;
+ }
+ mState = STATE_PIP_OVERLAY;
+ launchPipOverlayActivity();
+ }
+
+ /**
+ * Shows PIP menu UI by launching {@link PipMenuActivity}. It also locates the pinned
+ * stack to the centered PIP bound {@link com.android.internal.R.string
+ * .config_centeredPictureInPictureBounds}.
+ */
+ public void showPipMenu() {
+ if (DEBUG) Log.d(TAG, "showPipMenu()");
+ try {
+ mActivityManager.resizeStack(PINNED_STACK_ID, mMenuModePipBound, false);
+ } catch (Exception e) {
+ Log.e(TAG, "resizeStack failed", e);
+ closePip();
+ return;
+ }
+ mState = STATE_PIP_MENU;
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onShowPipMenu();
+ }
+ Intent intent = new Intent(mContext, PipMenuActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchStackId(PINNED_STACK_ID);
+ mContext.startActivity(intent, options.toBundle());
+ }
+
+ /**
+ * Adds {@link Listener}.
+ */
+ public void addListener(Listener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Removes {@link Listener}.
+ */
+ public void removeListener(Listener listener) {
+ mListeners.remove(listener);
+ }
+
+ private void launchPipOverlayActivity() {
+ Intent intent = new Intent(mContext, PipOverlayActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchStackId(PINNED_STACK_ID);
+ mContext.startActivity(intent, options.toBundle());
+ }
+
+ private boolean hasPipTasks() {
+ try {
+ StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+ return stackInfo != null;
+ } catch (RemoteException e) {
+ Log.e(TAG, "getStackInfo failed", e);
+ return false;
+ }
+ }
+
+ private class TaskStackListener extends ITaskStackListener.Stub {
+ @Override
+ public void onTaskStackChanged() throws RemoteException {
+ // Post the message back to the UI thread.
+ mHandler.post(mOnTaskStackChanged);
+ }
+
+ @Override
+ public void onActivityPinned() throws RemoteException {
+ // Post the message back to the UI thread.
+ mHandler.post(mOnActivityPinnedRunnable);
+ }
+ }
+
+ /**
+ * A listener interface to receive notification on changes in PIP.
+ */
+ public interface Listener {
+ /**
+ * Invoked when a PIPped activity is closed.
+ */
+ void onPipActivityClosed();
+ /**
+ * Invoked when the PIP menu gets shown.
+ */
+ void onShowPipMenu();
+ /**
+ * Invoked when the PIPped activity is returned back to the fullscreen.
+ */
+ void onMoveToFullscreen();
+ }
+
+ /**
+ * Gets an instance of {@link PipManager}.
+ */
+ public static PipManager getInstance() {
+ if (sPipManager == null) {
+ sPipManager = new PipManager();
+ }
+ return sPipManager;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
new file mode 100644
index 0000000000000..124832143f62a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2016 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.tv.pip;
+
+import android.app.Activity;
+import android.media.session.MediaController;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+
+import com.android.systemui.R;
+
+/**
+ * Activity to show the PIP menu to control PIP.
+ */
+public class PipMenuActivity extends Activity implements PipManager.Listener {
+ private static final String TAG = "PipMenuActivity";
+ private static final boolean DEBUG = false;
+
+ private final PipManager mPipManager = PipManager.getInstance();
+ private MediaController mMediaController;
+
+ @Override
+ protected void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ setContentView(R.layout.tv_pip_menu);
+ mPipManager.addListener(this);
+ findViewById(R.id.full).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mPipManager.movePipToFullscreen();
+ }
+ });
+ findViewById(R.id.exit).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mPipManager.closePip();
+ finish();
+ }
+ });
+ findViewById(R.id.cancel).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mPipManager.showPipOverlay();
+ finish();
+ }
+ });
+ }
+
+ @Override
+ protected void onDestroy() {
+ mPipManager.removeListener(this);
+ super.onDestroy();
+ }
+
+ @Override
+ public void onBackPressed() {
+ mPipManager.showPipOverlay();
+ finish();
+ }
+
+ @Override
+ public void onPipActivityClosed() {
+ finish();
+ }
+
+ @Override
+ public void onShowPipMenu() { }
+
+ @Override
+ public void onMoveToFullscreen() {
+ finish();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
new file mode 100644
index 0000000000000..de997a8c779f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 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.tv.pip;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.View;
+
+import com.android.systemui.R;
+
+/**
+ * Activity to show an overlay on top of PIP activity to show how to pop up PIP menu.
+ */
+public class PipOverlayActivity extends Activity implements PipManager.Listener {
+ private static final String TAG = "PipOverlayActivity";
+ private static final boolean DEBUG = false;
+
+ private static final long SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS = 2000;
+
+ private final PipManager mPipManager = PipManager.getInstance();
+ private final Handler mHandler = new Handler();
+
+ @Override
+ protected void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ setContentView(R.layout.tv_pip_overlay);
+ mPipManager.addListener(this);
+ final View overlayView = findViewById(R.id.guide_overlay);
+ // TODO: apply animation
+ overlayView.setVisibility(View.VISIBLE);
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ overlayView.setVisibility(View.INVISIBLE);
+ }
+ }, SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mHandler.removeCallbacksAndMessages(null);
+ mPipManager.removeListener(this);
+ }
+
+ @Override
+ public void onPipActivityClosed() {
+ finish();
+ }
+
+ @Override
+ public void onShowPipMenu() {
+ finish();
+ }
+
+ @Override
+ public void onMoveToFullscreen() {
+ finish();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipUI.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipUI.java
new file mode 100644
index 0000000000000..182b9b0deb817
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipUI.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 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.tv.pip;
+
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+
+import com.android.systemui.SystemUI;
+
+import static android.content.pm.PackageManager.FEATURE_LEANBACK;
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+
+/**
+ * Controls the picture-in-picture window for TV devices.
+ */
+public class PipUI extends SystemUI {
+ private boolean mSupportPip;
+
+ @Override
+ public void start() {
+ PackageManager pm = mContext.getPackageManager();
+ mSupportPip = pm.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)
+ && pm.hasSystemFeature(FEATURE_LEANBACK);
+ if (!mSupportPip) {
+ return;
+ }
+ PipManager pipManager = PipManager.getInstance();
+ pipManager.initialize(mContext);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (!mSupportPip) {
+ return;
+ }
+ // TODO: handle configuration change.
+ }
+}