PIP: Make PIPed activity to be focused from Recents

This makes PIPed activity to look like part of the Recents.

Bug: 26946155
Change-Id: Ic0ac441e57af5594c06701fa9d30400f0f7cc5a5
This commit is contained in:
Jaewan Kim
2016-02-15 17:33:25 -08:00
parent f7b2baa5ba
commit c92a7d12e3
13 changed files with 330 additions and 80 deletions

View File

@@ -31,4 +31,8 @@
is located in center. -->
<string translatable="false" name="config_centeredPictureInPictureBounds">"596 280 1324 690"</string>
<!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
when the PIP is shown with Recents. -->
<string translatable="false" name="config_pictureInPictureBoundsInRecents">"1480 123 1760 303"</string>
</resources>

View File

@@ -2444,6 +2444,10 @@
is located in center. -->
<string translatable="false" name="config_centeredPictureInPictureBounds">"0 0 300 300"</string>
<!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
when the PIP is shown with Recents. -->
<string translatable="false" name="config_pictureInPictureBoundsInRecents">"0 0 100 100"</string>
<!-- Controls the snap mode for the docked stack divider
0 - 3 snap targets: left/top has 16:9 ratio, 1:1, and right/bottom has 16:9 ratio
1 - 3 snap targets: fixed ratio, 1:1, (1 - fixed ratio)

View File

@@ -306,6 +306,7 @@
<java-symbol type="bool" name="config_guestUserEphemeral" />
<java-symbol type="string" name="config_defaultPictureInPictureBounds" />
<java-symbol type="string" name="config_centeredPictureInPictureBounds" />
<java-symbol type="string" name="config_pictureInPictureBoundsInRecents" />
<java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_threshold" />
<java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_factor" />
<java-symbol type="integer" name="config_wifi_framework_5GHz_preference_penalty_threshold" />

View File

@@ -0,0 +1,27 @@
<!--
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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M0 0h24v24H0z" />
<path
android:fillColor="#FFFFFF"
android:pathData="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" />
</vector>

View File

@@ -0,0 +1,27 @@
<!--
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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />
<path
android:pathData="M0 0h24v24H0z" />
</vector>

View File

@@ -0,0 +1,27 @@
<!--
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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M8 5v14l11-7z" />
<path
android:pathData="M0 0h24v24H0z" />
</vector>

View File

@@ -28,10 +28,26 @@
android:clipChildren="false"
android:clipToPadding="false"
android:descendantFocusability="beforeDescendants"
android:layout_gravity="center"
android:gravity="center"
android:paddingStart="@dimen/recents_tv_grid_row_padding"
android:paddingEnd="@dimen/recents_tv_grid_row_padding"
android:focusable="true"/>
android:focusable="true" />
<View
android:id="@+id/pip_shade"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"
android:background="#76000000"/>
<!-- Placeholder view to handle key events for PIP when it's focused.
Size and positions will be adjusted to comply with
config_pictureInPictureBoundsInRecents -->
<View
android:id="@+id/pip"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="invisible"
android:focusable="true" />
</com.android.systemui.recents.tv.views.RecentsTvView>

View File

@@ -17,13 +17,38 @@
*/
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/guide_overlay"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:padding="3dp"
android:textSize="13sp"
android:textColor="#111111"
android:background="#99EEEEEE"
android:text="@string/pip_hold_home" />
android:layout_height="match_parent">
<TextView
android:id="@+id/guide_overlay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:padding="3dp"
android:textSize="13sp"
android:textColor="#111111"
android:background="#99EEEEEE"
android:text="@string/pip_hold_home" />
<LinearLayout
android:id="@+id/guide_buttons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_fullscreen_white_24dp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_pause_white_24dp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_close_white" />
</LinearLayout>
</RelativeLayout>

View File

@@ -998,20 +998,4 @@ public class SystemServicesProxy {
e.printStackTrace();
}
}
public void focusPinnedStack() {
try {
mIam.setFocusedStack(PINNED_STACK_ID);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void focusHomeStack() {
try {
mIam.setFocusedStack(HOME_STACK_ID);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}

View File

@@ -18,6 +18,7 @@ package com.android.systemui.recents.tv;
import android.app.Activity;
import android.app.ActivityOptions;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.Log;
@@ -25,6 +26,7 @@ import android.view.KeyEvent;
import android.view.View;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.WindowManager;
import android.widget.FrameLayout.LayoutParams;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
@@ -58,6 +60,7 @@ import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.tv.pip.PipManager;
import java.util.ArrayList;
/**
* The main TV recents activity started by the RecentsImpl.
*/
@@ -73,9 +76,28 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener {
private boolean mIgnoreAltTabRelease;
private RecentsTvView mRecentsView;
private View mPipView;
private View mPipShadeView;
private TaskStackHorizontalViewAdapter mTaskStackViewAdapter;
private FinishRecentsRunnable mFinishLaunchHomeRunnable;
private PipManager mPipManager;
private PipManager.Listener mPipListener = new PipManager.Listener() {
@Override
public void onPipActivityClosed() {
mPipView.setVisibility(View.GONE);
mPipShadeView.setVisibility(View.GONE);
}
@Override
public void onShowPipMenu() { }
@Override
public void onMoveToFullscreen() { }
@Override
public void onPipResizeAboutToStart() { }
};
/**
* A common Runnable to finish Recents by launching Home with an animation depending on the
@@ -212,6 +234,7 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener {
finish();
return;
}
mPipManager = PipManager.getInstance();
// Register this activity with the event bus
EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
@@ -226,7 +249,8 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener {
mRecentsView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
mPipView = findViewById(R.id.pip);
mPipShadeView = findViewById(R.id.pip_shade);
getWindow().getAttributes().privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
@@ -265,6 +289,38 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener {
// Notify that recents is now visible
SystemServicesProxy ssp = Recents.getSystemServices();
EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, ssp, true));
if (mPipManager.isPipShown()) {
// Place mPipView at the PIP bounds for fine tuned focus handling.
Rect pipBounds = mPipManager.getPipBounds();
LayoutParams lp = (LayoutParams) mPipView.getLayoutParams();
lp.width = pipBounds.width();
lp.height = pipBounds.height();
lp.leftMargin = pipBounds.left;
lp.topMargin = pipBounds.top;
mPipView.setLayoutParams(lp);
mPipView.setVisibility(View.VISIBLE);
mPipView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPipManager.resizePinnedStack(PipManager.STATE_PIP_MENU);
}
});
mPipView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
mPipManager.onPipViewFocusChangedInRecents(hasFocus);
mPipShadeView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE);
}
});
mPipManager.addListener(mPipListener);
} else {
mPipView.setVisibility(View.GONE);
}
mPipManager.onRecentsStarted();
// Give focus to the recents row whenever its visible to an user.
mRecentsView.requestFocus();
}
@Override
@@ -277,6 +333,8 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener {
protected void onStop() {
super.onStop();
mPipManager.onRecentsStopped();
mPipManager.removeListener(mPipListener);
mIgnoreAltTabRelease = false;
// Notify that recents is now hidden
SystemServicesProxy ssp = Recents.getSystemServices();
@@ -316,18 +374,6 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener {
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP: {
SystemServicesProxy ssp = Recents.getSystemServices();
PipManager.getInstance().resizePinnedStack(PipManager.STATE_PIP_MENU);
ssp.focusPinnedStack();
return true;
}
case KeyEvent.KEYCODE_DPAD_DOWN: {
SystemServicesProxy ssp = Recents.getSystemServices();
PipManager.getInstance().resizePinnedStack(PipManager.STATE_PIP_OVERLAY);
ssp.focusHomeStack();
return true;
}
case KeyEvent.KEYCODE_DEL:
case KeyEvent.KEYCODE_FORWARD_DEL: {
EventBus.getDefault().send(new DismissFocusedTaskViewEvent());

View File

@@ -200,19 +200,6 @@ public class RecentsTvView extends FrameLayout {
EventBus.getDefault().unregister(this);
}
/**
* This is called with the full size of the window since we are handling our own insets.
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (mTaskStackHorizontalView != null && mTaskStackHorizontalView.getVisibility() != GONE) {
mTaskStackHorizontalView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight());
}
// Layout the empty view
mEmptyView.layout(left, top, right, bottom);
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
mSystemInsets.set(insets.getSystemWindowInsets());

View File

@@ -65,17 +65,25 @@ public class PipManager {
public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_OVERLAY_ACTIVITY_FINISH = 0x2;
private int mSuspendPipResizingReason;
private static final float SCALE_FACTOR = 1.1f;
private Context mContext;
private IActivityManager mActivityManager;
private int mState = STATE_NO_PIP;
private final Handler mHandler = new Handler();
private List<Listener> mListeners = new ArrayList<>();
private Rect mPipBound;
private Rect mMenuModePipBound;
private Rect mCurrentPipBounds;
private Rect mPipBounds;
private Rect mMenuModePipBounds;
private Rect mRecentsPipBounds;
private Rect mRecentsFocusedPipBounds;
private boolean mInitialized;
private int mPipTaskId = TASK_ID_NO_PIP;
private boolean mOnboardingShown;
private boolean mIsRecentsShown;
private boolean mIsPipFocusedInRecent;
private final Runnable mOnActivityPinnedRunnable = new Runnable() {
@Override
public void run() {
@@ -83,7 +91,7 @@ public class PipManager {
try {
stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
if (stackInfo == null) {
Log.w(TAG, "There is no pinned stack");
Log.w(TAG, "Cannot find pinned stack");
return;
}
} catch (RemoteException e) {
@@ -94,6 +102,7 @@ public class PipManager {
mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
// Set state to overlay so we show it when the pinned stack animation ends.
mState = STATE_PIP_OVERLAY;
mCurrentPipBounds = mPipBounds;
launchPipOnboardingActivityIfNeeded();
}
};
@@ -133,10 +142,13 @@ public class PipManager {
private final Runnable mOnPinnedStackAnimationEnded = new Runnable() {
@Override
public void run() {
if (mState == STATE_PIP_OVERLAY) {
showPipOverlay();
} else if (mState == STATE_PIP_MENU) {
showPipMenu();
switch (mState) {
case STATE_PIP_OVERLAY:
showPipOverlay();
break;
case STATE_PIP_MENU:
showPipMenu();
break;
}
}
};
@@ -177,10 +189,18 @@ public class PipManager {
mInitialized = true;
mContext = context;
Resources res = context.getResources();
mPipBound = Rect.unflattenFromString(res.getString(
mPipBounds = Rect.unflattenFromString(res.getString(
com.android.internal.R.string.config_defaultPictureInPictureBounds));
mMenuModePipBound = Rect.unflattenFromString(res.getString(
mMenuModePipBounds = Rect.unflattenFromString(res.getString(
com.android.internal.R.string.config_centeredPictureInPictureBounds));
mRecentsPipBounds = Rect.unflattenFromString(res.getString(
com.android.internal.R.string.config_pictureInPictureBoundsInRecents));
float scaleBy = (SCALE_FACTOR - 1.0f) / 2;
mRecentsFocusedPipBounds = new Rect(
(int) (mRecentsPipBounds.left - scaleBy * mRecentsPipBounds.width()),
(int) (mRecentsPipBounds.top - scaleBy * mRecentsPipBounds.height()),
(int) (mRecentsPipBounds.right + scaleBy * mRecentsPipBounds.width()),
(int) (mRecentsPipBounds.bottom + scaleBy * mRecentsPipBounds.height()));
mActivityManager = ActivityManagerNative.getDefault();
TaskStackListener taskStackListener = new TaskStackListener();
@@ -203,7 +223,7 @@ public class PipManager {
*/
public void requestTvPictureInPicture() {
if (DEBUG) Log.d(TAG, "requestTvPictureInPicture()");
if (!hasPipTasks()) {
if (!isPipShown()) {
startPip();
} else if (mState == STATE_PIP_OVERLAY) {
resizePinnedStack(STATE_PIP_MENU);
@@ -212,7 +232,7 @@ public class PipManager {
private void startPip() {
try {
mActivityManager.moveTopActivityToPinnedStack(FULLSCREEN_WORKSPACE_STACK_ID, mPipBound);
mActivityManager.moveTopActivityToPinnedStack(FULLSCREEN_WORKSPACE_STACK_ID, mPipBounds);
} catch (RemoteException|IllegalArgumentException e) {
Log.e(TAG, "moveTopActivityToPinnedStack failed", e);
}
@@ -235,6 +255,9 @@ public class PipManager {
Log.e(TAG, "removeStack failed", e);
}
}
for (int i = mListeners.size() - 1; i >= 0; --i) {
mListeners.get(i).onPipActivityClosed();
}
}
/**
@@ -295,36 +318,99 @@ public class PipManager {
public void resizePinnedStack(int state) {
if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + state);
mState = state;
Rect bounds;
for (int i = mListeners.size() - 1; i >= 0; --i) {
mListeners.get(i).onPipResizeAboutToStart();
}
switch (mState) {
case STATE_PIP_MENU:
bounds = mMenuModePipBound;
break;
case STATE_NO_PIP:
bounds = null;
break;
default:
bounds = mPipBound;
break;
}
if (mSuspendPipResizingReason != 0) {
if (DEBUG) Log.d(TAG,
"resizePinnedStack() deferring mSuspendPipResizingReason=" +
mSuspendPipResizingReason);
return;
}
switch (mState) {
case STATE_NO_PIP:
mCurrentPipBounds = null;
break;
case STATE_PIP_MENU:
mCurrentPipBounds = mMenuModePipBounds;
break;
case STATE_PIP_OVERLAY:
if (mIsRecentsShown) {
if (mIsPipFocusedInRecent) {
mCurrentPipBounds = mRecentsFocusedPipBounds;
} else {
mCurrentPipBounds = mRecentsPipBounds;
}
} else {
mCurrentPipBounds = mPipBounds;
}
break;
default:
mCurrentPipBounds = mPipBounds;
break;
}
try {
mActivityManager.resizeStack(PINNED_STACK_ID, bounds, true, true, true);
mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds, true, true, true);
} catch (RemoteException e) {
Log.e(TAG, "showPipMenu failed", e);
}
}
/**
* Returns the current PIP bound for activities to sync their UI with PIP.
*/
public Rect getPipBounds() {
return mCurrentPipBounds;
}
/**
* Called when Recents is started.
* PIPed activity will be resized accordingly and overlay will show available buttons.
*/
public void onRecentsStarted() {
mIsRecentsShown = true;
mIsPipFocusedInRecent = false;
if (mState == STATE_NO_PIP) {
return;
}
resizePinnedStack(STATE_PIP_OVERLAY);
}
/**
* Called when Recents is stopped.
* PIPed activity will be resized accordingly and overlay will hide available buttons.
*/
public void onRecentsStopped() {
mIsRecentsShown = false;
mIsPipFocusedInRecent = false;
if (mState == STATE_NO_PIP) {
return;
}
resizePinnedStack(STATE_PIP_OVERLAY);
}
/**
* Returns {@code true} if recents is shown.
*/
boolean isRecentsShown() {
return mIsRecentsShown;
}
/**
* Called when the PIP view in {@link com.android.systemui.recents.tv.RecentsTvActivity}
* is focused.
* This only resizes pinned stack so it looks like it's in Recents.
* This should be called only by {@link com.android.systemui.recents.tv.RecentsTvActivity}.
*/
public void onPipViewFocusChangedInRecents(boolean hasFocus) {
mIsPipFocusedInRecent = hasFocus;
if (mState != STATE_PIP_OVERLAY) {
Log.w(TAG, "There is no pinned stack to handle focus change.");
return;
}
resizePinnedStack(STATE_PIP_OVERLAY);
}
/**
* 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
@@ -362,6 +448,13 @@ public class PipManager {
}
}
/**
* Returns {@code true} if PIP is shown.
*/
public boolean isPipShown() {
return hasPipTasks();
}
private boolean hasPipTasks() {
try {
StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);

View File

@@ -35,6 +35,7 @@ public class PipOverlayActivity extends Activity implements PipManager.Listener
private final PipManager mPipManager = PipManager.getInstance();
private final Handler mHandler = new Handler();
private View mGuideOverlayView;
private View mGuideButtonsView;
private final Runnable mHideGuideOverlayRunnable = new Runnable() {
public void run() {
mGuideOverlayView.setVisibility(View.INVISIBLE);
@@ -46,13 +47,21 @@ public class PipOverlayActivity extends Activity implements PipManager.Listener
super.onCreate(bundle);
setContentView(R.layout.tv_pip_overlay);
mGuideOverlayView = findViewById(R.id.guide_overlay);
mGuideButtonsView = findViewById(R.id.guide_buttons);
mPipManager.addListener(this);
}
@Override
protected void onResume() {
super.onResume();
mGuideOverlayView.setVisibility(View.VISIBLE);
// TODO: Implement animation for this
if (!mPipManager.isRecentsShown()) {
mGuideOverlayView.setVisibility(View.VISIBLE);
mGuideButtonsView.setVisibility(View.INVISIBLE);
} else {
mGuideOverlayView.setVisibility(View.INVISIBLE);
mGuideButtonsView.setVisibility(View.VISIBLE);
}
mHandler.removeCallbacks(mHideGuideOverlayRunnable);
mHandler.postDelayed(mHideGuideOverlayRunnable, SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS);
}