diff options
22 files changed, 792 insertions, 337 deletions
diff --git a/packages/SystemUI/res/anim/tv_pip_controls_fade_in.xml b/packages/SystemUI/res/anim/tv_pip_controls_buttons_in_recents_focus_gain_animation.xml index 89e4aac8dae5..ebc6a4a74a85 100644 --- a/packages/SystemUI/res/anim/tv_pip_controls_fade_in.xml +++ b/packages/SystemUI/res/anim/tv_pip_controls_buttons_in_recents_focus_gain_animation.xml @@ -17,13 +17,13 @@ <set xmlns:android="http://schemas.android.com/apk/res/android"> <objectAnimator - android:propertyName="translationY" - android:valueTo="10dp" - android:interpolator="@android:interpolator/linear_out_slow_in" + android:propertyName="scaleX" + android:valueTo="1.0" + android:interpolator="@android:interpolator/fast_out_slow_in" android:duration="@integer/recents_tv_pip_focus_anim_duration" /> <objectAnimator - android:propertyName="alpha" + android:propertyName="scaleY" android:valueTo="1.0" - android:interpolator="@android:interpolator/linear_out_slow_in" + android:interpolator="@android:interpolator/fast_out_slow_in" android:duration="@integer/recents_tv_pip_focus_anim_duration" /> </set> diff --git a/packages/SystemUI/res/anim/tv_pip_controls_fade_out.xml b/packages/SystemUI/res/anim/tv_pip_controls_buttons_in_recents_focus_lose_animation.xml index c73fed6d4892..95499bd38977 100644 --- a/packages/SystemUI/res/anim/tv_pip_controls_fade_out.xml +++ b/packages/SystemUI/res/anim/tv_pip_controls_buttons_in_recents_focus_lose_animation.xml @@ -17,13 +17,13 @@ <set xmlns:android="http://schemas.android.com/apk/res/android"> <objectAnimator - android:propertyName="translationY" - android:valueTo="0dp" - android:interpolator="@android:interpolator/fast_out_linear_in" + android:propertyName="scaleX" + android:valueTo="0.7" + android:interpolator="@android:interpolator/fast_out_slow_in" android:duration="@integer/recents_tv_pip_focus_anim_duration" /> <objectAnimator - android:propertyName="alpha" - android:valueTo="0.0" - android:interpolator="@android:interpolator/fast_out_linear_in" + android:propertyName="scaleY" + android:valueTo="0.7" + android:interpolator="@android:interpolator/fast_out_slow_in" android:duration="@integer/recents_tv_pip_focus_anim_duration" /> </set> diff --git a/packages/SystemUI/res/anim/tv_pip_controls_in_recents_focus_gain_animation.xml b/packages/SystemUI/res/anim/tv_pip_controls_in_recents_focus_gain_animation.xml new file mode 100644 index 000000000000..7555bdda8b64 --- /dev/null +++ b/packages/SystemUI/res/anim/tv_pip_controls_in_recents_focus_gain_animation.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:propertyName="translationY" + android:valueTo="0dp" + android:interpolator="@android:interpolator/fast_out_slow_in" + android:duration="@integer/recents_tv_pip_focus_anim_duration" /> diff --git a/packages/SystemUI/res/anim/tv_pip_controls_in_recents_focus_lose_animation.xml b/packages/SystemUI/res/anim/tv_pip_controls_in_recents_focus_lose_animation.xml new file mode 100644 index 000000000000..b40ccd467bec --- /dev/null +++ b/packages/SystemUI/res/anim/tv_pip_controls_in_recents_focus_lose_animation.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:propertyName="translationY" + android:valueTo="-57dp" + android:interpolator="@android:interpolator/fast_out_slow_in" + android:duration="@integer/recents_tv_pip_focus_anim_duration" /> diff --git a/packages/SystemUI/res/anim/tv_pip_controls_text_in_recents_focus_gain_animation.xml b/packages/SystemUI/res/anim/tv_pip_controls_text_in_recents_focus_gain_animation.xml new file mode 100644 index 000000000000..681ff917b646 --- /dev/null +++ b/packages/SystemUI/res/anim/tv_pip_controls_text_in_recents_focus_gain_animation.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:propertyName="alpha" + android:valueTo="1" + android:interpolator="@android:interpolator/fast_out_slow_in" + android:duration="@integer/recents_tv_pip_focus_anim_duration" /> diff --git a/packages/SystemUI/res/anim/tv_pip_controls_text_in_recents_focus_lose_animation.xml b/packages/SystemUI/res/anim/tv_pip_controls_text_in_recents_focus_lose_animation.xml new file mode 100644 index 000000000000..e6deb0f0bfdc --- /dev/null +++ b/packages/SystemUI/res/anim/tv_pip_controls_text_in_recents_focus_lose_animation.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:propertyName="alpha" + android:valueTo="0" + android:interpolator="@android:interpolator/fast_out_slow_in" + android:duration="@integer/recents_tv_pip_focus_anim_duration" /> diff --git a/packages/SystemUI/res/drawable/tv_pip_button_focused.xml b/packages/SystemUI/res/drawable/tv_pip_button_focused.xml index 5cabb77a6971..405ea0cb9fbc 100644 --- a/packages/SystemUI/res/drawable/tv_pip_button_focused.xml +++ b/packages/SystemUI/res/drawable/tv_pip_button_focused.xml @@ -17,8 +17,8 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> <size - android:width="36dp" - android:height="36dp" /> + android:width="34dp" + android:height="34dp" /> <solid android:color="#4DFFFFFF" /> </shape> diff --git a/packages/SystemUI/res/layout/recents_on_tv.xml b/packages/SystemUI/res/layout/recents_on_tv.xml index 0f8c77c3042b..28ea66d8345d 100644 --- a/packages/SystemUI/res/layout/recents_on_tv.xml +++ b/packages/SystemUI/res/layout/recents_on_tv.xml @@ -31,18 +31,12 @@ android:focusable="true" android:layoutDirection="rtl" /> + <!-- Placeholder view to give focus to the PIP menus. --> <View - android:id="@+id/pip_shade" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:visibility="gone" - android:background="#76000000" /> - - <include - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="top|center_horizontal" - android:layout_marginTop="132dp" - layout="@layout/tv_pip_controls" /> + android:id="@+id/pip" + android:layout_width="0dp" + android:layout_height="0dp" + android:focusable="true" + android:visibility="gone" /> </com.android.systemui.recents.tv.views.RecentsTvView> diff --git a/packages/SystemUI/res/layout/tv_pip_controls.xml b/packages/SystemUI/res/layout/tv_pip_controls.xml index 2e0c9e76c504..563441ff3e35 100644 --- a/packages/SystemUI/res/layout/tv_pip_controls.xml +++ b/packages/SystemUI/res/layout/tv_pip_controls.xml @@ -17,13 +17,8 @@ */ --> -<com.android.systemui.tv.pip.PipControlsView - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/pip_controls" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="center" - android:orientation="horizontal"> +<!-- Layout for {@link com.android.systemui.tv.pip.PipControlsView}. --> +<merge xmlns:android="http://schemas.android.com/apk/res/android"> <LinearLayout android:layout_width="100dp" @@ -98,4 +93,4 @@ android:textSize="12sp" android:textColor="#EEEEEE" /> </LinearLayout> -</com.android.systemui.tv.pip.PipControlsView> +</merge> diff --git a/packages/SystemUI/res/layout/tv_pip_menu.xml b/packages/SystemUI/res/layout/tv_pip_menu.xml index c2c83ffb9d62..2647a99632c1 100644 --- a/packages/SystemUI/res/layout/tv_pip_menu.xml +++ b/packages/SystemUI/res/layout/tv_pip_menu.xml @@ -26,7 +26,8 @@ android:gravity="top|center_horizontal" android:clipChildren="false"> - <include - layout="@layout/tv_pip_controls" - android:clipChildren="false" /> + <com.android.systemui.tv.pip.PipControlsView + android:id="@+id/pip_controls" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> </LinearLayout> diff --git a/packages/SystemUI/res/layout/tv_pip_overlay.xml b/packages/SystemUI/res/layout/tv_pip_overlay.xml index c5c7e843208b..64bf3b5bbdbf 100644 --- a/packages/SystemUI/res/layout/tv_pip_overlay.xml +++ b/packages/SystemUI/res/layout/tv_pip_overlay.xml @@ -38,25 +38,4 @@ android:gravity="center" android:maxLines="2" 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="19dp" - android:layout_height="19dp" - android:src="@drawable/ic_fullscreen_white_24dp" /> - <ImageView - android:layout_width="19dp" - android:layout_height="19dp" - android:src="@drawable/ic_close_white" /> - <ImageView - android:id="@+id/guide_button_play_pause" - android:layout_width="19dp" - android:layout_height="19dp" - android:src="@drawable/ic_pause_white_24dp" /> - </LinearLayout> </RelativeLayout> diff --git a/packages/SystemUI/res/layout/tv_pip_recents_overlay.xml b/packages/SystemUI/res/layout/tv_pip_recents_overlay.xml new file mode 100644 index 000000000000..1e464d83ebdd --- /dev/null +++ b/packages/SystemUI/res/layout/tv_pip_recents_overlay.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:gravity="top|center_horizontal"> + + <com.android.systemui.tv.pip.PipRecentsControlsView + android:id="@+id/pip_controls" + android:layout_width="wrap_content" + android:layout_height="match_parent" /> + + <View + android:id="@+id/recents" + android:layout_width="1dp" + android:layout_height="1dp" + android:focusable="true" /> +</LinearLayout> diff --git a/packages/SystemUI/res/values/dimens_tv.xml b/packages/SystemUI/res/values/dimens_tv.xml index 953dd650b2aa..337513d4daea 100644 --- a/packages/SystemUI/res/values/dimens_tv.xml +++ b/packages/SystemUI/res/values/dimens_tv.xml @@ -38,9 +38,6 @@ <dimen name="recents_tv_unselected_item_z">6dp</dimen> <dimen name="recents_tv_selected_item_z_delta">10dp</dimen> - <!-- Extra space around the PIP and its outline in PIP onboarding activity --> - <dimen name="tv_pip_bounds_space">3dp</dimen> - <!-- Values for text on recents cards on tv --> <dimen name="recents_tv_title_text_size">12sp</dimen> @@ -52,4 +49,10 @@ <dimen name="recents_tv_dismiss_icon_bottom_margin">1dip</dimen> <dimen name="recents_tv_dismiss_text_size">12sp</dimen> + <!-- Values for PIP in recents --> + <dimen name="recents_tv_pip_controls_margin_top">10dp</dimen> + + <!-- Extra space around the PIP and its outline in PIP onboarding activity --> + <dimen name="tv_pip_bounds_space">3dp</dimen> + </resources> diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java index 134b90c99fff..483f9e523c55 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java @@ -15,8 +15,6 @@ */ package com.android.systemui.recents.tv; -import android.animation.AnimatorInflater; -import android.animation.AnimatorSet; import android.app.Activity; import android.app.ActivityOptions; import android.content.Intent; @@ -55,11 +53,12 @@ import com.android.systemui.recents.model.RecentsTaskLoadPlan; import com.android.systemui.recents.model.RecentsTaskLoader; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.recents.tv.animations.FocusAnimationHolder; import com.android.systemui.recents.tv.views.RecentsTvView; import com.android.systemui.recents.tv.views.TaskStackHorizontalViewAdapter; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.tv.pip.PipManager; -import com.android.systemui.tv.pip.PipControlsView; +import com.android.systemui.tv.pip.PipRecentsOverlayManager; import java.util.ArrayList; import java.util.Collections; @@ -80,15 +79,13 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { private boolean mIgnoreAltTabRelease; private RecentsTvView mRecentsView; - private PipControlsView mPipControlsView; - private View mPipShadeView; - private AnimatorSet mPipControlsViewFadeInAnimator; - private AnimatorSet mPipControlsViewFadeOutAnimator; + private FocusAnimationHolder mRecentsFocusAnimationHolder; + private View mPipView; private TaskStackHorizontalViewAdapter mTaskStackViewAdapter; private FinishRecentsRunnable mFinishLaunchHomeRunnable; - private PipManager mPipManager; - private PipManager.Listener mPipListener = new PipManager.Listener() { + private final PipManager mPipManager = PipManager.getInstance(); + private final PipManager.Listener mPipListener = new PipManager.Listener() { @Override public void onPipEntered() { updatePipUI(); @@ -113,10 +110,38 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { @Override public void onPipResizeAboutToStart() { } - - @Override - public void onMediaControllerChanged() { } }; + private PipRecentsOverlayManager mPipRecentsOverlayManager; + private final PipRecentsOverlayManager.Callback mPipRecentsOverlayManagerCallback = + new PipRecentsOverlayManager.Callback() { + @Override + public void onClosed() { + dismissRecentsToLaunchTargetTaskOrHome(); + } + + @Override + public void onBackPressed() { + RecentsTvActivity.this.onBackPressed(); + } + + @Override + public void onRecentsFocused() { + mRecentsView.requestFocus(); + } + }; + private final View.OnFocusChangeListener mPipViewFocusChangeListener = + new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus) { + mRecentsFocusAnimationHolder.startFocusLoseAnimation(); + mPipRecentsOverlayManager.requestFocus( + mTaskStackViewAdapter.getItemCount() > 0); + } else { + mRecentsFocusAnimationHolder.startFocusGainAnimation(); + } + } + }; /** * A common Runnable to finish Recents by launching Home with an animation depending on the @@ -248,7 +273,7 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { finish(); return; } - mPipManager = PipManager.getInstance(); + mPipRecentsOverlayManager = PipManager.getInstance().getPipRecentsOverlayManager(); // Register this activity with the event bus EventBus.getDefault().register(this, EVENT_BUS_PRIORITY); @@ -263,21 +288,19 @@ 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); - mPipControlsView = (PipControlsView) findViewById(R.id.pip_controls); - mPipControlsView.setListener(new PipControlsView.Listener() { - @Override - public void onClosed() { - dismissRecentsToLaunchTargetTaskOrHome(); - } - }); - mPipShadeView = findViewById(R.id.pip_shade); + mRecentsFocusAnimationHolder = new FocusAnimationHolder(mRecentsView); + + mPipView = findViewById(R.id.pip); + // 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); - mPipControlsViewFadeInAnimator = (AnimatorSet) AnimatorInflater.loadAnimator(this, - R.anim.tv_pip_controls_fade_in); - mPipControlsViewFadeInAnimator.setTarget(mPipControlsView); - mPipControlsViewFadeOutAnimator = (AnimatorSet) AnimatorInflater.loadAnimator(this, - R.anim.tv_pip_controls_fade_out); - mPipControlsViewFadeOutAnimator.setTarget(mPipControlsView); + mPipRecentsOverlayManager.setCallback(mPipRecentsOverlayManagerCallback); getWindow().getAttributes().privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; @@ -289,7 +312,6 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent); - updatePipUI(); mPipManager.addListener(mPipListener); } @@ -321,9 +343,7 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { SystemServicesProxy ssp = Recents.getSystemServices(); EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, true)); - mPipManager.onRecentsStarted(); - // Give focus to the recents row whenever its visible to an user. - mRecentsView.requestFocus(); + updatePipUI(); } @Override @@ -333,10 +353,21 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { } @Override + public void onResume() { + super.onResume(); + mPipRecentsOverlayManager.onRecentsResumed(); + } + + @Override + public void onPause() { + super.onPause(); + mPipRecentsOverlayManager.onRecentsPaused(); + } + + @Override protected void onStop() { super.onStop(); - mPipManager.onRecentsStopped(); mIgnoreAltTabRelease = false; // Notify that recents is now hidden EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false)); @@ -480,25 +511,13 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { private void updatePipUI() { if (mPipManager.isPipShown()) { - mPipControlsView.setAlpha(0); - mPipControlsView.setVisibility(View.VISIBLE); - mPipShadeView.setVisibility(View.INVISIBLE); - mPipControlsView.setOnChildFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - mPipManager.onPipViewFocusChangedInRecents(hasFocus); - if (hasFocus) { - mPipControlsViewFadeInAnimator.start(); - } else { - mPipControlsViewFadeOutAnimator.start(); - } - mPipShadeView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE); - } - }); - mPipShadeView.setVisibility(View.GONE); + mPipView.setVisibility(View.VISIBLE); + mPipView.setOnFocusChangeListener(mPipViewFocusChangeListener); + mPipView.requestFocus(); } else { - mPipControlsView.setVisibility(View.GONE); - mPipShadeView.setVisibility(View.GONE); + mPipView.setVisibility(View.GONE); + mPipRecentsOverlayManager.removePipRecentsOverlayView(); + mRecentsFocusAnimationHolder.reset(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/animations/FocusAnimationHolder.java b/packages/SystemUI/src/com/android/systemui/recents/tv/animations/FocusAnimationHolder.java new file mode 100644 index 000000000000..864540c616e9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/tv/animations/FocusAnimationHolder.java @@ -0,0 +1,63 @@ +/* + * 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.recents.tv.animations; + +import android.content.res.Resources; +import android.view.View; + +import com.android.systemui.Interpolators; +import com.android.systemui.R; +import com.android.systemui.recents.tv.views.TaskCardView; + +/** + * Collections of Recents row's animation depending on the PIP's focus. + */ +public class FocusAnimationHolder { + private final float DIM_ALPHA = 0.5f; + + private View mRecentsRowView; + private int mCardYDelta; + private long mDuration; + + public FocusAnimationHolder(View recentsRowView) { + mRecentsRowView = recentsRowView; + + Resources res = recentsRowView.getResources(); + mCardYDelta = res.getDimensionPixelOffset(R.dimen.recents_tv_dismiss_shift_down); + mDuration = res.getInteger(R.integer.recents_tv_pip_focus_anim_duration); + } + + public void startFocusGainAnimation() { + mRecentsRowView.animate() + .setDuration(mDuration) + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .alpha(1f) + .translationY(0); + } + + public void startFocusLoseAnimation() { + mRecentsRowView.animate() + .setDuration(mDuration) + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .alpha(DIM_ALPHA) + .translationY(mCardYDelta); + } + + public void reset() { + mRecentsRowView.setTransitionAlpha(1f); + mRecentsRowView.setTranslationY(0); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlsView.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlsView.java index 15ad1f1b6099..3f87611fe2d4 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlsView.java +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlsView.java @@ -16,11 +16,12 @@ package com.android.systemui.tv.pip; -import android.app.Activity; import android.content.Context; import android.media.session.MediaController; import android.media.session.PlaybackState; import android.view.View; +import android.view.Gravity; +import android.view.LayoutInflater; import android.view.View.OnFocusChangeListener; import android.widget.ImageView; import android.widget.TextView; @@ -40,28 +41,29 @@ import static com.android.systemui.tv.pip.PipManager.PLAYBACK_STATE_UNAVAILABLE; /** * A view containing PIP controls including fullscreen, close, and media controls. */ -public class PipControlsView extends LinearLayout implements PipManager.Listener { +public class PipControlsView extends LinearLayout { /** * An interface to listen user action. */ - public interface Listener { + public abstract static interface Listener { /** * Called when an user clicks close PIP button. */ - void onClosed(); - } + public abstract void onClosed(); + }; - private final PipManager mPipManager = PipManager.getInstance(); private MediaController mMediaController; - private Listener mListener; - private View mFullButtonView; - private View mFullDescriptionView; - private View mPlayPauseView; - private ImageView mPlayPauseButtonImageView; - private TextView mPlayPauseDescriptionTextView; - private View mCloseButtonView; - private View mCloseDescriptionView; + final PipManager mPipManager = PipManager.getInstance(); + Listener mListener; + + View mFullButtonView; + View mFullDescriptionView; + View mPlayPauseView; + ImageView mPlayPauseButtonImageView; + TextView mPlayPauseDescriptionTextView; + View mCloseButtonView; + View mCloseDescriptionView; private boolean mHasFocus; private OnFocusChangeListener mOnChildFocusChangeListener; @@ -73,6 +75,13 @@ public class PipControlsView extends LinearLayout implements PipManager.Listener } }; + private PipManager.MediaListener mPipMediaListener = new PipManager.MediaListener() { + @Override + public void onMediaControllerChanged() { + updateMediaController(); + } + }; + public PipControlsView(Context context) { this(context, null, 0, 0); } @@ -87,6 +96,12 @@ public class PipControlsView extends LinearLayout implements PipManager.Listener public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + LayoutInflater inflater = (LayoutInflater) getContext() + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(R.layout.tv_pip_controls, this); + + setOrientation(LinearLayout.HORIZONTAL); + setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); } @Override @@ -161,13 +176,13 @@ public class PipControlsView extends LinearLayout implements PipManager.Listener public void onAttachedToWindow() { super.onAttachedToWindow(); updateMediaController(); - mPipManager.addListener(this); + mPipManager.addMediaListener(mPipMediaListener); } @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); - mPipManager.removeListener(this); + mPipManager.removeMediaListener(mPipMediaListener); if (mMediaController != null) { mMediaController.unregisterCallback(mMediaControllerCallback); } @@ -230,24 +245,4 @@ public class PipControlsView extends LinearLayout implements PipManager.Listener public void setListener(Listener listener) { mListener = listener; } - - @Override - public void onPipEntered() { } - - @Override - public void onPipActivityClosed() { } - - @Override - public void onShowPipMenu() { } - - @Override - public void onMoveToFullscreen() { } - - @Override - public void onMediaControllerChanged() { - updateMediaController(); - } - - @Override - public void onPipResizeAboutToStart() { } } diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java index 68e0883fbc88..b5c1f5739e65 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java @@ -62,9 +62,27 @@ public class PipManager { private static final int MAX_RUNNING_TASKS_COUNT = 10; + /** + * State when there's no PIP. + */ public static final int STATE_NO_PIP = 0; + /** + * State when PIP is shown with an overlay message on top of it. + * This is used as default PIP state. + */ public static final int STATE_PIP_OVERLAY = 1; + /** + * State when PIP menu dialog is shown. + */ public static final int STATE_PIP_MENU = 2; + /** + * State when PIP is shown in Recents. + */ + public static final int STATE_PIP_RECENTS = 3; + /** + * State when PIP is shown in Recents and it's focused to allow an user to control. + */ + public static final int STATE_PIP_RECENTS_FOCUSED = 4; private static final int TASK_ID_NO_PIP = -1; private static final int INVALID_RESOURCE_TYPE = -1; @@ -90,11 +108,13 @@ public class PipManager { private int mSuspendPipResizingReason; private Context mContext; + private PipRecentsOverlayManager mPipRecentsOverlayManager; private IActivityManager mActivityManager; private MediaSessionManager mMediaSessionManager; private int mState = STATE_NO_PIP; private final Handler mHandler = new Handler(); private List<Listener> mListeners = new ArrayList<>(); + private List<MediaListener> mMediaListeners = new ArrayList<>(); private Rect mCurrentPipBounds; private Rect mPipBounds; private Rect mMenuModePipBounds; @@ -107,9 +127,6 @@ public class PipManager { private MediaController mPipMediaController; private boolean mOnboardingShown; - private boolean mIsRecentsShown; - private boolean mIsPipFocusedInRecent; - private final Runnable mResizePinnedStackRunnable = new Runnable() { @Override public void run() { @@ -178,6 +195,7 @@ public class PipManager { mOnboardingShown = Prefs.getBoolean( mContext, TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN, false); + mPipRecentsOverlayManager = new PipRecentsOverlayManager(context); mMediaSessionManager = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); } @@ -231,7 +249,7 @@ public class PipManager { /** * Moves the PIPed activity to the fullscreen and closes PIP system UI. */ - public void movePipToFullscreen() { + void movePipToFullscreen() { mState = STATE_NO_PIP; mPipTaskId = TASK_ID_NO_PIP; for (int i = mListeners.size() - 1; i >= 0; --i) { @@ -247,8 +265,11 @@ public class PipManager { */ private void showPipOverlay() { if (DEBUG) Log.d(TAG, "showPipOverlay()"); - mState = STATE_PIP_OVERLAY; - PipOverlayActivity.showPipOverlay(mContext); + 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()); } /** @@ -279,8 +300,10 @@ public class PipManager { * Resize the Pip to the appropriate size for the input state. * @param state In Pip state also used to determine the new size for the Pip. */ - public void resizePinnedStack(int state) { + void resizePinnedStack(int state) { if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + state); + boolean wasRecentsShown = + (mState == STATE_PIP_RECENTS || mState == STATE_PIP_RECENTS_FOCUSED); mState = state; for (int i = mListeners.size() - 1; i >= 0; --i) { mListeners.get(i).onPipResizeAboutToStart(); @@ -291,7 +314,6 @@ public class PipManager { mSuspendPipResizingReason); return; } - int animationDurationMs = -1; switch (mState) { case STATE_NO_PIP: mCurrentPipBounds = null; @@ -300,25 +322,24 @@ public class PipManager { mCurrentPipBounds = mMenuModePipBounds; break; case STATE_PIP_OVERLAY: - if (mIsRecentsShown) { - if (mCurrentPipBounds == mRecentsFocusedPipBounds - || mCurrentPipBounds == mRecentsFocusedPipBounds) { - animationDurationMs = mRecentsFocusChangedAnimationDurationMs; - } - if (mIsPipFocusedInRecent) { - mCurrentPipBounds = mRecentsFocusedPipBounds; - } else { - mCurrentPipBounds = mRecentsPipBounds; - } - } else { - mCurrentPipBounds = mPipBounds; - } + mCurrentPipBounds = mPipBounds; + break; + case STATE_PIP_RECENTS: + mCurrentPipBounds = mRecentsPipBounds; + break; + case STATE_PIP_RECENTS_FOCUSED: + mCurrentPipBounds = mRecentsFocusedPipBounds; break; default: mCurrentPipBounds = mPipBounds; break; } try { + int animationDurationMs = -1; + if (wasRecentsShown + && (mState == STATE_PIP_RECENTS || mState == STATE_PIP_RECENTS_FOCUSED)) { + animationDurationMs = mRecentsFocusChangedAnimationDurationMs; + } mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds, true, true, true, animationDurationMs); } catch (RemoteException e) { @@ -327,67 +348,18 @@ public class PipManager { } /** - * Returns the current PIP bound for activities to sync their UI with PIP. + * Returns the default PIP bound. */ public Rect getPipBounds() { - return mCurrentPipBounds; + return mPipBounds; } /** - * Called when Recents is started. - * PIPed activity will be resized accordingly and overlay will show available buttons. + * Returns the focused PIP bound while Recents is shown. + * This is used to place PIP controls in Recents. */ - 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); - } - - /** - * Returns {@code true} if the PIP view in - * {@link com.android.systemui.recents.tv.RecentsTvActivity} is focused in Recents. - * This API is valid only when {@link isRecentsShown()} returns {@code true}. - */ - boolean isPipViewFocusdInRecents() { - return mIsPipFocusedInRecent; + public Rect getRecentsFocusedPipBounds() { + return mRecentsFocusedPipBounds; } /** @@ -396,6 +368,10 @@ public class PipManager { */ private void showPipMenu() { if (DEBUG) Log.d(TAG, "showPipMenu()"); + if (mPipRecentsOverlayManager.isRecentsShown()) { + if (DEBUG) Log.d(TAG, "Ignore showing PIP menu"); + return; + } mState = STATE_PIP_MENU; for (int i = mListeners.size() - 1; i >= 0; --i) { mListeners.get(i).onShowPipMenu(); @@ -405,14 +381,34 @@ public class PipManager { mContext.startActivity(intent); } + /** + * Adds a {@link Listener} to PipManager. + */ public void addListener(Listener listener) { mListeners.add(listener); } + /** + * Removes a {@link Listener} from PipManager. + */ public void removeListener(Listener listener) { mListeners.remove(listener); } + /** + * Adds a {@link MediaListener} to PipManager. + */ + public void addMediaListener(MediaListener listener) { + mMediaListeners.add(listener); + } + + /** + * Removes a {@link MediaListener} from PipManager. + */ + public void removeMediaListener(MediaListener listener) { + mMediaListeners.remove(listener); + } + private void launchPipOnboardingActivityIfNeeded() { if (DEBUG_FORCE_ONBOARDING || !mOnboardingShown) { mOnboardingShown = true; @@ -485,8 +481,8 @@ public class PipManager { } if (mPipMediaController != mediaController) { mPipMediaController = mediaController; - for (int i = mListeners.size() - 1; i >= 0; i--) { - mListeners.get(i).onMediaControllerChanged(); + for (int i = mMediaListeners.size() - 1; i >= 0; i--) { + mMediaListeners.get(i).onMediaControllerChanged(); } if (mPipMediaController == null) { mHandler.postDelayed(mClosePipRunnable, @@ -530,7 +526,7 @@ public class PipManager { return PLAYBACK_STATE_UNAVAILABLE; } - TaskStackListener mTaskStackListener = new TaskStackListener() { + private TaskStackListener mTaskStackListener = new TaskStackListener() { @Override public void onTaskStackChanged() { if (mState != STATE_NO_PIP) { @@ -582,10 +578,10 @@ public class PipManager { mMediaSessionManager.addOnActiveSessionsChangedListener( mActiveMediaSessionListener, null); updateMediaController(mMediaSessionManager.getActiveSessions(null)); - if (mIsRecentsShown) { + if (mPipRecentsOverlayManager.isRecentsShown()) { // If an activity becomes PIPed again after the fullscreen, the Recents is shown // behind so we need to resize the pinned stack and show the correct overlay. - resizePinnedStack(STATE_PIP_OVERLAY); + resizePinnedStack(STATE_PIP_RECENTS); } for (int i = mListeners.size() - 1; i >= 0; i--) { mListeners.get(i).onPipEntered(); @@ -604,7 +600,18 @@ public class PipManager { if (DEBUG) Log.d(TAG, "onPinnedStackAnimationEnded()"); switch (mState) { case STATE_PIP_OVERLAY: - showPipOverlay(); + if (!mPipRecentsOverlayManager.isRecentsShown()) { + showPipOverlay(); + break; + } else { + // This happens only if an activity is PIPed after the Recents is shown. + // See {@link PipRecentsOverlayManager.requestFocus} for more details. + resizePinnedStack(mState); + break; + } + case STATE_PIP_RECENTS: + case STATE_PIP_RECENTS_FOCUSED: + mPipRecentsOverlayManager.addPipRecentsOverlayView(); break; case STATE_PIP_MENU: showPipMenu(); @@ -621,7 +628,7 @@ public class PipManager { * Invoked when an activity is pinned and PIP manager is set corresponding information. * Classes must use this instead of {@link android.app.ITaskStackListener.onActivityPinned} * because there's no guarantee for the PIP manager be return relavent information - * correctly. (e.g. {@link isPipShown}, {@link getPipBounds}) + * correctly. (e.g. {@link isPipShown}). */ void onPipEntered(); /** Invoked when a PIPed activity is closed. */ @@ -632,6 +639,12 @@ public class PipManager { void onMoveToFullscreen(); /** Invoked when we are above to start resizing the Pip. */ void onPipResizeAboutToStart(); + } + + /** + * A listener interface to receive change in PIP's media controller + */ + public interface MediaListener { /** Invoked when the MediaController on PIPed activity is changed. */ void onMediaControllerChanged(); } @@ -645,4 +658,11 @@ public class PipManager { } return sPipManager; } + + /** + * Gets an instance of {@link PipRecentsOverlayManager}. + */ + public PipRecentsOverlayManager getPipRecentsOverlayManager() { + return mPipRecentsOverlayManager; + } } diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java index ea9275f8a918..c54e73a7cda6 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java @@ -20,12 +20,6 @@ import android.app.Activity; import android.os.Bundle; import com.android.systemui.R; -import com.android.systemui.SystemUI; -import com.android.systemui.SystemUIApplication; -import com.android.systemui.recents.Recents; - -import static android.content.pm.PackageManager.FEATURE_LEANBACK; -import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; /** * Activity to show the PIP menu to control PIP. @@ -36,7 +30,6 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { private final PipManager mPipManager = PipManager.getInstance(); private PipControlsView mPipControlsView; - private boolean mPipMovedToFullscreen; @Override protected void onCreate(Bundle bundle) { @@ -47,17 +40,10 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { mPipControlsView = (PipControlsView) findViewById(R.id.pip_controls); } - private void restorePipAndFinish() { - if (!mPipMovedToFullscreen) { - mPipManager.resizePinnedStack(PipManager.STATE_PIP_OVERLAY); - } - finish(); - } - @Override public void onPause() { super.onPause(); - restorePipAndFinish(); + finish(); } @Override @@ -69,11 +55,6 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { } @Override - public void onBackPressed() { - restorePipAndFinish(); - } - - @Override public void onPipEntered() { } @Override @@ -86,31 +67,13 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { @Override public void onMoveToFullscreen() { - mPipMovedToFullscreen = true; finish(); } @Override - public void onMediaControllerChanged() { } - - @Override public void onPipResizeAboutToStart() { finish(); mPipManager.suspendPipResizing( PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH); } - - @Override - public void finish() { - super.finish(); - if (mPipManager.isRecentsShown() && !mPipMovedToFullscreen) { - SystemUI[] services = ((SystemUIApplication) getApplication()).getServices(); - for (int i = services.length - 1; i >= 0; i--) { - if (services[i] instanceof Recents) { - ((Recents) services[i]).showRecents(false, null); - break; - } - } - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java index 79daf3d51056..86ceff48408f 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java @@ -86,7 +86,4 @@ public class PipOnboardingActivity extends Activity implements PipManager.Listen @Override public void onPipResizeAboutToStart() { } - - @Override - public void onMediaControllerChanged() { } } diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java index 12cb4cd77824..5472ad6718dd 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java @@ -35,14 +35,6 @@ import static android.app.ActivityManager.StackId.PINNED_STACK_ID; public class PipOverlayActivity extends Activity implements PipManager.Listener { private static final long SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS = 4000; - /** - * The single instance of PipOverlayActivity to prevent it from restarting. - * Note that {@link PipManager} moves the PIPed activity to fullscreen if the activity is - * restarted. It's because the activity may be started by the Launcher or an intent again, - * but we don't want do so for the PipOverlayActivity. - */ - private static PipOverlayActivity sPipOverlayActivity; - private final PipManager mPipManager = PipManager.getInstance(); private final Handler mHandler = new Handler(); private View mGuideOverlayView; @@ -54,47 +46,17 @@ public class PipOverlayActivity extends Activity implements PipManager.Listener } }; - /** - * Launches the PIP overlay. This should be only called on the main thread. - */ - public static void showPipOverlay(Context context) { - if (sPipOverlayActivity == null) { - Intent intent = new Intent(context, PipOverlayActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - final ActivityOptions options = ActivityOptions.makeBasic(); - options.setLaunchStackId(PINNED_STACK_ID); - context.startActivity(intent, options.toBundle()); - } - } - @Override protected void onCreate(Bundle bundle) { super.onCreate(bundle); setContentView(R.layout.tv_pip_overlay); mGuideOverlayView = findViewById(R.id.guide_overlay); - mGuideButtonsView = findViewById(R.id.guide_buttons); - mGuideButtonPlayPauseImageView = (ImageView) findViewById(R.id.guide_button_play_pause); mPipManager.addListener(this); - - sPipOverlayActivity = this; } @Override protected void onResume() { super.onResume(); - // TODO: Implement animation for this - if (mPipManager.isRecentsShown()) { - mGuideOverlayView.setVisibility(View.GONE); - if (mPipManager.isPipViewFocusdInRecents()) { - mGuideButtonsView.setVisibility(View.GONE); - } else { - mGuideButtonsView.setVisibility(View.VISIBLE); - updateGuideButtonsView(); - } - } else { - mGuideOverlayView.setVisibility(View.VISIBLE); - mGuideButtonsView.setVisibility(View.GONE); - } mHandler.removeCallbacks(mHideGuideOverlayRunnable); mHandler.postDelayed(mHideGuideOverlayRunnable, SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS); } @@ -109,7 +71,6 @@ public class PipOverlayActivity extends Activity implements PipManager.Listener @Override protected void onDestroy() { super.onDestroy(); - sPipOverlayActivity = null; mHandler.removeCallbacksAndMessages(null); mPipManager.removeListener(this); mPipManager.resumePipResizing( @@ -140,32 +101,4 @@ public class PipOverlayActivity extends Activity implements PipManager.Listener mPipManager.suspendPipResizing( PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_OVERLAY_ACTIVITY_FINISH); } - - @Override - public void onMediaControllerChanged() { - updateGuideButtonsView(); - } - - @Override - public void finish() { - sPipOverlayActivity = null; - super.finish(); - } - - private void updateGuideButtonsView() { - switch (mPipManager.getPlaybackState()) { - case PipManager.PLAYBACK_STATE_PLAYING: - mGuideButtonPlayPauseImageView.setVisibility(View.VISIBLE); - mGuideButtonPlayPauseImageView.setImageResource(R.drawable.ic_pause_white_24dp); - break; - case PipManager.PLAYBACK_STATE_PAUSED: - mGuideButtonPlayPauseImageView.setVisibility(View.VISIBLE); - mGuideButtonPlayPauseImageView.setImageResource( - R.drawable.ic_play_arrow_white_24dp); - break; - case PipManager.PLAYBACK_STATE_UNAVAILABLE: - mGuideButtonPlayPauseImageView.setVisibility(View.GONE); - break; - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsControlsView.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsControlsView.java new file mode 100644 index 000000000000..8b8c1058bb99 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsControlsView.java @@ -0,0 +1,169 @@ +/* + * 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.animation.Animator; +import android.animation.AnimatorInflater; +import android.animation.AnimatorSet; +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.View.OnFocusChangeListener; + +import com.android.systemui.R; + +import static com.android.systemui.tv.pip.PipManager.PLAYBACK_STATE_PLAYING; +import static com.android.systemui.tv.pip.PipManager.PLAYBACK_STATE_PAUSED; +import static com.android.systemui.tv.pip.PipManager.PLAYBACK_STATE_UNAVAILABLE; + +/** + * An extended version of {@link PipControlsView} that supports animation in Recents. + */ +public class PipRecentsControlsView extends PipControlsView { + /** + * An interface to listen user action. + */ + public interface Listener extends PipControlsView.Listener { + /** + * Called when an user presses BACK key and up. + */ + abstract void onBackPressed(); + } + + private AnimatorSet mFocusGainAnimatorSet; + private AnimatorSet mFocusLoseAnimatorSet; + + public PipRecentsControlsView(Context context) { + this(context, null, 0, 0); + } + + public PipRecentsControlsView(Context context, AttributeSet attrs) { + this(context, attrs, 0, 0); + } + + public PipRecentsControlsView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public PipRecentsControlsView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public void onFinishInflate() { + super.onFinishInflate(); + + int buttonsFocusGainAnim = R.anim.tv_pip_controls_buttons_in_recents_focus_gain_animation; + int textFocusGainAnim = R.anim.tv_pip_controls_text_in_recents_focus_gain_animation; + mFocusGainAnimatorSet = new AnimatorSet(); + mFocusGainAnimatorSet.playTogether( + loadAnimator(this, R.anim.tv_pip_controls_in_recents_focus_gain_animation), + loadAnimator(mFullButtonView,buttonsFocusGainAnim), + loadAnimator(mPlayPauseButtonImageView, buttonsFocusGainAnim), + loadAnimator(mCloseButtonView, buttonsFocusGainAnim), + loadAnimator(mFullDescriptionView, textFocusGainAnim), + loadAnimator(mPlayPauseDescriptionTextView, textFocusGainAnim), + loadAnimator(mCloseDescriptionView, textFocusGainAnim)); + + int buttonsFocusLoseAnim = R.anim.tv_pip_controls_buttons_in_recents_focus_lose_animation; + int textFocusLoseAnim = R.anim.tv_pip_controls_text_in_recents_focus_lose_animation; + mFocusLoseAnimatorSet = new AnimatorSet(); + mFocusLoseAnimatorSet.playTogether( + loadAnimator(this, R.anim.tv_pip_controls_in_recents_focus_lose_animation), + loadAnimator(mFullButtonView, buttonsFocusLoseAnim), + loadAnimator(mPlayPauseButtonImageView, buttonsFocusLoseAnim), + loadAnimator(mCloseButtonView, buttonsFocusLoseAnim), + loadAnimator(mFullDescriptionView, textFocusLoseAnim), + loadAnimator(mPlayPauseDescriptionTextView, textFocusLoseAnim), + loadAnimator(mCloseDescriptionView, textFocusLoseAnim)); + + Rect pipBounds = mPipManager.getRecentsFocusedPipBounds(); + int pipControlsMarginTop = getContext().getResources().getDimensionPixelSize( + R.dimen.recents_tv_pip_controls_margin_top); + setPadding(0, pipBounds.bottom + pipControlsMarginTop, 0, 0); + } + + private Animator loadAnimator(View view, int animatorResId) { + Animator animator = AnimatorInflater.loadAnimator(getContext(), animatorResId); + animator.setTarget(view); + return animator; + } + + /** + * Starts focus gaining animation. + */ + public void startFocusGainAnimation() { + if (mFocusLoseAnimatorSet.isStarted()) { + mFocusLoseAnimatorSet.cancel(); + } + mFocusGainAnimatorSet.start(); + } + + /** + * Starts focus losing animation. + */ + public void startFocusLoseAnimation() { + if (mFocusGainAnimatorSet.isStarted()) { + mFocusGainAnimatorSet.cancel(); + } + mFocusLoseAnimatorSet.start(); + } + + /** + * Resets the view to the initial state. (i.e. end of the focus gain) + */ + public void reset() { + if (mFocusGainAnimatorSet.isStarted()) { + mFocusGainAnimatorSet.cancel(); + } + if (mFocusLoseAnimatorSet.isStarted()) { + mFocusLoseAnimatorSet.cancel(); + } + + // Reset to initial state (i.e. end of focused) + requestFocus(); + setTranslationY(0); + setScaleXY(mFullButtonView, 1); + setScaleXY(mPlayPauseButtonImageView, 1); + setScaleXY(mCloseButtonView, 1); + mFullDescriptionView.setAlpha(1); + mPlayPauseDescriptionTextView.setAlpha(1); + mCloseDescriptionView.setAlpha(1); + } + + private void setScaleXY(View view, float scale) { + view.setScaleX(scale); + view.setScaleY(scale); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (!event.isCanceled() + && event.getKeyCode() == KeyEvent.KEYCODE_BACK + && event.getAction() == KeyEvent.ACTION_UP) { + if (mListener != null) { + ((PipRecentsControlsView.Listener) mListener).onBackPressed(); + } + return true; + } + return super.dispatchKeyEvent(event); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsOverlayManager.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsOverlayManager.java new file mode 100644 index 000000000000..b90b7279a5b5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsOverlayManager.java @@ -0,0 +1,207 @@ +/* + * 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.animation.AnimatorInflater; +import android.animation.AnimatorSet; +import android.content.Context; +import android.graphics.PixelFormat; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager.LayoutParams; +import android.view.WindowManager; + +import com.android.systemui.R; + +import static com.android.systemui.tv.pip.PipManager.STATE_PIP_OVERLAY; +import static com.android.systemui.tv.pip.PipManager.STATE_PIP_RECENTS; +import static com.android.systemui.tv.pip.PipManager.STATE_PIP_RECENTS_FOCUSED; + +public class PipRecentsOverlayManager { + private static final String TAG = "PipRecentsOverlayManager"; + + public interface Callback { + void onClosed(); + void onBackPressed(); + void onRecentsFocused(); + } + + private final PipManager mPipManager = PipManager.getInstance(); + private final WindowManager mWindowManager; + private final View mOverlayView; + private final PipRecentsControlsView mPipControlsView; + private final View mRecentsView; + + private final LayoutParams mPipRecentsControlsViewLayoutParams; + private final LayoutParams mPipRecentsControlsViewFocusedLayoutParams; + + private boolean mIsPipRecentsOverlayShown; + private boolean mIsRecentsShown; + private boolean mIsPipFocusedInRecent; + private Callback mCallback; + private PipRecentsControlsView.Listener mPipControlsViewListener = + new PipRecentsControlsView.Listener() { + @Override + public void onClosed() { + if (mCallback != null) { + mCallback.onClosed(); + } + } + + @Override + public void onBackPressed() { + if (mCallback != null) { + mCallback.onBackPressed(); + } + } + }; + + PipRecentsOverlayManager(Context context) { + mWindowManager = (WindowManager) context.getSystemService(WindowManager.class); + + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mOverlayView = inflater.inflate(R.layout.tv_pip_recents_overlay, null); + mPipControlsView = (PipRecentsControlsView) mOverlayView.findViewById(R.id.pip_controls); + mRecentsView = mOverlayView.findViewById(R.id.recents); + mRecentsView.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus) { + clearFocus(); + } + } + }); + + mPipRecentsControlsViewLayoutParams = new WindowManager.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, + LayoutParams.TYPE_SYSTEM_DIALOG, + LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCHABLE, + PixelFormat.TRANSLUCENT); + mPipRecentsControlsViewFocusedLayoutParams = new WindowManager.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, + LayoutParams.TYPE_SYSTEM_DIALOG, + 0, + PixelFormat.TRANSLUCENT); + } + + /** + * Add Recents overlay view. + * This is expected to be called after the PIP animation is over. + */ + void addPipRecentsOverlayView() { + if (mIsPipRecentsOverlayShown) { + return; + } + mIsPipRecentsOverlayShown = true; + mIsPipFocusedInRecent = true; + mPipControlsView.reset(); + mWindowManager.addView(mOverlayView, mPipRecentsControlsViewFocusedLayoutParams); + } + + /** + * Remove Recents overlay view. + * This should be called when Recents or PIP is closed. + */ + public void removePipRecentsOverlayView() { + if (!mIsPipRecentsOverlayShown) { + return; + } + mWindowManager.removeView(mOverlayView); + mIsPipRecentsOverlayShown = false; + } + + /** + * Request focus to the PIP Recents overlay. + * Called when the PIP view in {@link com.android.systemui.recents.tv.RecentsTvActivity} + * is focused. + * This should be called only by {@link com.android.systemui.recents.tv.RecentsTvActivity}. + * @param hasRecentsFocusable {@code true} if Recents can have focus. (i.e. Has a recent task) + */ + public void requestFocus(boolean hasRecentsFocusable) { + if (!mIsRecentsShown || mIsPipFocusedInRecent) { + return; + } + mIsPipFocusedInRecent = true; + mPipManager.resizePinnedStack(STATE_PIP_RECENTS_FOCUSED); + + mWindowManager.updateViewLayout(mOverlayView, mPipRecentsControlsViewFocusedLayoutParams); + mPipControlsView.requestFocus(); + mPipControlsView.startFocusGainAnimation(); + mRecentsView.setVisibility(hasRecentsFocusable ? View.VISIBLE : View.GONE); + } + + /** + * Request focus to the PIP Recents overlay. + * Called when the PIP view in {@link com.android.systemui.recents.tv.RecentsTvActivity} + * is focused. + * This should be called only by {@link com.android.systemui.recents.tv.RecentsTvActivity}. + */ + private void clearFocus() { + if (!mIsRecentsShown || !mIsPipFocusedInRecent) { + return; + } + mIsPipFocusedInRecent = false; + mPipManager.resizePinnedStack(STATE_PIP_RECENTS); + mWindowManager.updateViewLayout(mOverlayView, mPipRecentsControlsViewLayoutParams); + mPipControlsView.startFocusLoseAnimation(); + if (mCallback != null) { + mCallback.onRecentsFocused(); + } + } + + public void setCallback(Callback listener) { + mCallback = listener; + mPipControlsView.setListener(mCallback != null ? mPipControlsViewListener : null); + } + + /** + * Called when Recents is resumed. + * PIPed activity will be resized accordingly and overlay will show available buttons. + */ + public void onRecentsResumed() { + if (!mPipManager.isPipShown()) { + return; + } + mIsRecentsShown = true; + mIsPipFocusedInRecent = true; + mPipManager.resizePinnedStack(STATE_PIP_RECENTS_FOCUSED); + // Overlay view will be added after the resize animation ends, if any. + } + + /** + * Called when Recents is paused. + * PIPed activity will be resized accordingly and overlay will hide available buttons. + */ + public void onRecentsPaused() { + mIsRecentsShown = false; + mIsPipFocusedInRecent = false; + removePipRecentsOverlayView(); + + if (mPipManager.isPipShown()) { + mPipManager.resizePinnedStack(STATE_PIP_OVERLAY); + } + } + + /** + * Returns {@code true} if recents is shown. + */ + boolean isRecentsShown() { + return mIsRecentsShown; + } +} |