diff options
| author | 2016-03-22 22:16:59 +0900 | |
|---|---|---|
| committer | 2016-03-25 13:30:16 +0900 | |
| commit | 8f584b8dee2a2a15a1777fabf6ba033b78419b9a (patch) | |
| tree | 1299e8ce12368b15e9f90c651bf4582ba8b73659 | |
| parent | fe952f3a0b0ad5c481fa3e52385866f777a4d6e2 (diff) | |
PIP: Apply the new UX spec for PIP in Recents
Detailed animations will be applied in another CL.
Bug: 27540465
Change-Id: I3664dc10efadeb8f183a4871a684c706e1472999
16 files changed, 604 insertions, 298 deletions
diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml index 58d3d9153013..c0716e93b548 100644 --- a/core/res/res/values-television/config.xml +++ b/core/res/res/values-television/config.xml @@ -26,12 +26,4 @@ <!-- Default bounds [left top right bottom] on screen for picture-in-picture windows. --> <string translatable="false" name="config_defaultPictureInPictureBounds">"1328 54 1808 324"</string> - - <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows, when the PIP - 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">"1484 96 1804 276"</string> </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 01b2c4715a49..6ecaa1faac55 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2462,14 +2462,6 @@ <!-- Default bounds [left top right bottom] on screen for picture-in-picture windows. --> <string translatable="false" name="config_defaultPictureInPictureBounds">"0 0 100 100"</string> - <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows, when the PIP - 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) diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index ce7d80bfe223..4d26e076d82e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -308,8 +308,6 @@ <java-symbol type="bool" name="config_guestUserEphemeral" /> <java-symbol type="bool" name="config_localDisplaysMirrorContent" /> <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" /> diff --git a/packages/SystemUI/res/anim/tv_pip_controls_fade_in.xml b/packages/SystemUI/res/anim/tv_pip_controls_fade_in.xml new file mode 100644 index 000000000000..89e4aac8dae5 --- /dev/null +++ b/packages/SystemUI/res/anim/tv_pip_controls_fade_in.xml @@ -0,0 +1,29 @@ +<?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. +--> + +<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:duration="@integer/recents_tv_pip_focus_anim_duration" /> + <objectAnimator + android:propertyName="alpha" + android:valueTo="1.0" + android:interpolator="@android:interpolator/linear_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_fade_out.xml new file mode 100644 index 000000000000..c73fed6d4892 --- /dev/null +++ b/packages/SystemUI/res/anim/tv_pip_controls_fade_out.xml @@ -0,0 +1,29 @@ +<?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. +--> + +<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: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:duration="@integer/recents_tv_pip_focus_anim_duration" /> +</set> diff --git a/packages/SystemUI/res/layout/recents_on_tv.xml b/packages/SystemUI/res/layout/recents_on_tv.xml index f0bfebe7ef7c..0f8c77c3042b 100644 --- a/packages/SystemUI/res/layout/recents_on_tv.xml +++ b/packages/SystemUI/res/layout/recents_on_tv.xml @@ -19,8 +19,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false" - android:clipToPadding="false" - android:layoutDirection="rtl"> + android:clipToPadding="false"> <com.android.systemui.recents.tv.views.TaskStackHorizontalGridView android:id="@+id/task_list" android:layout_width="wrap_content" @@ -29,20 +28,21 @@ android:clipToPadding="false" android:descendantFocusability="beforeDescendants" android:layout_marginTop="@dimen/recents_tv_gird_row_top_margin" - android:focusable="true" /> + android:focusable="true" + android:layoutDirection="rtl" /> + <View android:id="@+id/pip_shade" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone" - android:background="#76000000"/> + 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" /> - <!-- Placeholder view to handle key events for PIP when it's focused. - Size and positions will be adjusted to comply with the PIP bounds --> - <View - android:id="@+id/pip" - android:layout_width="0dp" - android:layout_height="0dp" - android:visibility="gone" - android:focusable="true" /> </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 new file mode 100644 index 000000000000..2e0c9e76c504 --- /dev/null +++ b/packages/SystemUI/res/layout/tv_pip_controls.xml @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 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. +*/ +--> + +<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"> + + <LinearLayout + android:layout_width="100dp" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="center"> + + <ImageView android:id="@+id/full_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:focusable="true" + android:src="@drawable/tv_pip_full_button" /> + + <TextView android:id="@+id/full_desc" + android:layout_width="100dp" + android:layout_height="wrap_content" + android:layout_marginTop="3dp" + android:gravity="center" + android:visibility="invisible" + android:text="@string/pip_fullscreen" + android:fontFamily="sans-serif" + android:textSize="12sp" + android:textColor="#EEEEEE" /> + </LinearLayout> + + <LinearLayout + android:layout_width="100dp" + android:layout_height="wrap_content" + android:layout_marginStart="-50dp" + android:orientation="vertical" + android:gravity="center"> + + <ImageView android:id="@+id/close_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:focusable="true" + android:src="@drawable/tv_pip_close_button" /> + + <TextView android:id="@+id/close_desc" + android:layout_width="100dp" + android:layout_height="wrap_content" + android:layout_marginTop="3dp" + android:gravity="center" + android:visibility="invisible" + android:text="@string/pip_close" + android:fontFamily="sans-serif" + android:textSize="12sp" + android:textColor="#EEEEEE" /> + </LinearLayout> + + <LinearLayout android:id="@+id/play_pause" + android:layout_width="100dp" + android:layout_height="wrap_content" + android:layout_marginStart="-50dp" + android:orientation="vertical" + android:gravity="center" > + + <ImageView android:id="@+id/play_pause_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:focusable="true" + android:src="@drawable/tv_pip_pause_button" /> + + <TextView android:id="@+id/play_pause_desc" + android:layout_width="100dp" + android:layout_height="wrap_content" + android:layout_marginTop="3dp" + android:gravity="center" + android:visibility="invisible" + android:text="@string/pip_pause" + android:fontFamily="sans-serif" + android:textSize="12sp" + android:textColor="#EEEEEE" /> + </LinearLayout> +</com.android.systemui.tv.pip.PipControlsView> diff --git a/packages/SystemUI/res/layout/tv_pip_menu.xml b/packages/SystemUI/res/layout/tv_pip_menu.xml index 1fec49e65b51..c2c83ffb9d62 100644 --- a/packages/SystemUI/res/layout/tv_pip_menu.xml +++ b/packages/SystemUI/res/layout/tv_pip_menu.xml @@ -26,85 +26,7 @@ android:gravity="top|center_horizontal" android:clipChildren="false"> - <LinearLayout - android:layout_width="34dp" - android:layout_height="wrap_content" - android:layout_marginEnd="3dp" - android:orientation="vertical" - android:gravity="center" - android:clipChildren="false"> - - <ImageView android:id="@+id/full_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:focusable="true" - android:src="@drawable/tv_pip_full_button" /> - - <TextView android:id="@+id/full_desc" - android:layout_width="100dp" - android:layout_height="wrap_content" - android:layout_marginTop="3dp" - android:gravity="center" - android:visibility="invisible" - android:text="@string/pip_fullscreen" - android:fontFamily="sans-serif" - android:textSize="12sp" - android:textColor="#EEEEEE" - android:clipChildren="false" /> - </LinearLayout> - - <LinearLayout android:id="@+id/play_pause" - android:layout_width="34dp" - android:layout_height="wrap_content" - android:layout_marginStart="3dp" - android:layout_marginEnd="3dp" - android:orientation="vertical" - android:gravity="center" - android:clipChildren="false"> - - <ImageView android:id="@+id/play_pause_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:focusable="true" - android:src="@drawable/tv_pip_pause_button" /> - - <TextView android:id="@+id/play_pause_desc" - android:layout_width="100dp" - android:layout_height="wrap_content" - android:layout_marginTop="3dp" - android:gravity="center" - android:visibility="invisible" - android:text="@string/pip_pause" - android:fontFamily="sans-serif" - android:textSize="12sp" - android:textColor="#EEEEEE" - android:clipChildren="false" /> - </LinearLayout> - - <LinearLayout - android:layout_width="34dp" - android:layout_height="wrap_content" - android:layout_marginStart="3dp" - android:orientation="vertical" - android:gravity="center" - android:clipChildren="false"> - - <ImageView android:id="@+id/close_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:focusable="true" - android:src="@drawable/tv_pip_close_button" /> - - <TextView android:id="@+id/close_desc" - android:layout_width="100dp" - android:layout_height="wrap_content" - android:layout_marginTop="3dp" - android:gravity="center" - android:visibility="invisible" - android:text="@string/pip_close" - android:fontFamily="sans-serif" - android:textSize="12sp" - android:textColor="#EEEEEE" - android:clipChildren="false" /> - </LinearLayout> + <include + layout="@layout/tv_pip_controls" + android:clipChildren="false" /> </LinearLayout> diff --git a/packages/SystemUI/res/layout/tv_pip_overlay.xml b/packages/SystemUI/res/layout/tv_pip_overlay.xml index 1ba423bb22cf..c5c7e843208b 100644 --- a/packages/SystemUI/res/layout/tv_pip_overlay.xml +++ b/packages/SystemUI/res/layout/tv_pip_overlay.xml @@ -46,16 +46,17 @@ android:layout_centerHorizontal="true" android:orientation="horizontal"> <ImageView - android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_width="19dp" + android:layout_height="19dp" 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: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/values/config_tv.xml b/packages/SystemUI/res/values/config_tv.xml new file mode 100644 index 000000000000..22b7578db068 --- /dev/null +++ b/packages/SystemUI/res/values/config_tv.xml @@ -0,0 +1,29 @@ +<?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. +--> + +<resources> + <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows, + when the PIP menu is shown in center. --> + <string translatable="false" name="pip_menu_bounds">"596 280 1324 690"</string> + + <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows, + when the PIP is shown in Recents without focus. --> + <string translatable="false" name="pip_recents_bounds">"800 54 1120 234"</string> + + <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows, + when the PIP is shown in Recents with focus. --> + <string translatable="false" name="pip_recents_focused_bounds">"775 54 1145 262"</string> +</resources> diff --git a/packages/SystemUI/res/values/integers_tv.xml b/packages/SystemUI/res/values/integers_tv.xml index c60c24556ca6..20cd330b73e3 100644 --- a/packages/SystemUI/res/values/integers_tv.xml +++ b/packages/SystemUI/res/values/integers_tv.xml @@ -17,4 +17,5 @@ <integer name="item_scale_anim_duration">150</integer> <integer name="dismiss_short_duration">200</integer> <integer name="dismiss_long_duration">400</integer> -</resources>
\ No newline at end of file + <integer name="recents_tv_pip_focus_anim_duration">200</integer> +</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 d8e82d9e86ec..2c345232fbbf 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java @@ -15,6 +15,8 @@ */ 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; @@ -57,6 +59,7 @@ 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 java.util.ArrayList; import java.util.Collections; @@ -77,8 +80,10 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { private boolean mIgnoreAltTabRelease; private RecentsTvView mRecentsView; - private View mPipView; + private PipControlsView mPipControlsView; private View mPipShadeView; + private AnimatorSet mPipControlsViewFadeInAnimator; + private AnimatorSet mPipControlsViewFadeOutAnimator; private TaskStackHorizontalViewAdapter mTaskStackViewAdapter; private FinishRecentsRunnable mFinishLaunchHomeRunnable; @@ -95,7 +100,9 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { } @Override - public void onShowPipMenu() { } + public void onShowPipMenu() { + updatePipUI(); + } @Override public void onMoveToFullscreen() { } @@ -106,7 +113,6 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { @Override public void onMediaControllerChanged() { } }; - private boolean mHasPip; /** * A common Runnable to finish Recents by launching Home with an animation depending on the @@ -253,8 +259,22 @@ 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); + mPipControlsView = (PipControlsView) findViewById(R.id.pip_controls); + mPipControlsView.setListener(new PipControlsView.Listener() { + @Override + public void onClosed() { + dismissRecentsToLaunchTargetTaskOrHome(); + } + }); mPipShadeView = findViewById(R.id.pip_shade); + + 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); + getWindow().getAttributes().privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; @@ -265,7 +285,6 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent); - mHasPip = false; updatePipUI(); mPipManager.addListener(mPipListener); } @@ -456,37 +475,25 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { } private void updatePipUI() { - if (mHasPip == mPipManager.isPipShown()) { - return; - } - mHasPip = mPipManager.isPipShown(); - if (mHasPip) { - // 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() { + 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); } else { - mPipView.setVisibility(View.GONE); + mPipControlsView.setVisibility(View.GONE); mPipShadeView.setVisibility(View.GONE); } } diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlsView.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlsView.java new file mode 100644 index 000000000000..0e64dcd187b0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlsView.java @@ -0,0 +1,251 @@ +/* + * 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.content.Context; +import android.media.session.MediaController; +import android.media.session.PlaybackState; +import android.view.View; +import android.view.View.OnFocusChangeListener; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.LinearLayout; +import android.util.AttributeSet; + +import com.android.systemui.R; + +import static android.media.session.PlaybackState.ACTION_PAUSE; +import static android.media.session.PlaybackState.ACTION_PLAY; + +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; + + +/** + * A view containing PIP controls including fullscreen, close, and media controls. + */ +public class PipControlsView extends LinearLayout implements PipManager.Listener { + /** + * An interface to listen user action. + */ + public interface Listener { + /** + * Called when an user clicks close PIP button. + */ + 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; + + private boolean mHasFocus; + private OnFocusChangeListener mOnChildFocusChangeListener; + + private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() { + @Override + public void onPlaybackStateChanged(PlaybackState state) { + updatePlayPauseView(); + } + }; + + public PipControlsView(Context context) { + this(context, null, 0, 0); + } + + public PipControlsView(Context context, AttributeSet attrs) { + this(context, attrs, 0, 0); + } + + public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public void onFinishInflate() { + super.onFinishInflate(); + + mFullButtonView = findViewById(R.id.full_button); + mFullDescriptionView = findViewById(R.id.full_desc); + mFullButtonView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mPipManager.movePipToFullscreen(); + } + }); + mFullButtonView.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + mFullDescriptionView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE); + onChildViewFocusChanged(); + } + }); + + mPlayPauseView = findViewById(R.id.play_pause); + mPlayPauseButtonImageView = (ImageView) findViewById(R.id.play_pause_button); + mPlayPauseDescriptionTextView = (TextView) findViewById(R.id.play_pause_desc); + mPlayPauseButtonImageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mMediaController == null || mMediaController.getPlaybackState() == null) { + return; + } + long actions = mMediaController.getPlaybackState().getActions(); + int state = mMediaController.getPlaybackState().getState(); + if (mPipManager.getPlaybackState() == PLAYBACK_STATE_PAUSED) { + mMediaController.getTransportControls().play(); + } else if (mPipManager.getPlaybackState() == PLAYBACK_STATE_PLAYING) { + mMediaController.getTransportControls().pause(); + } + // View will be updated later in {@link mMediaControllerCallback} + } + }); + mPlayPauseButtonImageView.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + mPlayPauseDescriptionTextView.setVisibility( + hasFocus ? View.VISIBLE : View.INVISIBLE); + onChildViewFocusChanged(); + } + }); + + mCloseButtonView = findViewById(R.id.close_button); + mCloseDescriptionView = findViewById(R.id.close_desc); + mCloseButtonView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mPipManager.closePip(); + mListener.onClosed(); + } + }); + mCloseButtonView.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + mCloseDescriptionView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE); + onChildViewFocusChanged(); + } + }); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + updateMediaController(); + mPipManager.addListener(this); + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mPipManager.removeListener(this); + if (mMediaController != null) { + mMediaController.unregisterCallback(mMediaControllerCallback); + } + } + + private void updateMediaController() { + MediaController newController = mPipManager.getMediaController(); + if (mMediaController == newController) { + return; + } + if (mMediaController != null) { + mMediaController.unregisterCallback(mMediaControllerCallback); + } + mMediaController = newController; + if (mMediaController != null) { + mMediaController.registerCallback(mMediaControllerCallback); + } + updatePlayPauseView(); + } + + private void updatePlayPauseView() { + int state = mPipManager.getPlaybackState(); + if (state == PLAYBACK_STATE_UNAVAILABLE) { + mPlayPauseView.setVisibility(View.GONE); + } else { + mPlayPauseView.setVisibility(View.VISIBLE); + if (state == PLAYBACK_STATE_PLAYING) { + mPlayPauseButtonImageView.setImageResource(R.drawable.tv_pip_pause_button); + mPlayPauseDescriptionTextView.setText(R.string.pip_pause); + } else { + mPlayPauseButtonImageView.setImageResource(R.drawable.tv_pip_play_button); + mPlayPauseDescriptionTextView.setText(R.string.pip_play); + } + } + } + + /** + * Sets a listener to be invoked when {@link android.view.View.hasFocus()} is changed. + */ + public void setOnChildFocusChangeListener(OnFocusChangeListener listener) { + mOnChildFocusChangeListener = listener; + } + + private void onChildViewFocusChanged() { + // At this moment, hasFocus() returns true although there's no focused child. + boolean hasFocus = (mFullButtonView != null && mFullButtonView.isFocused()) + || (mPlayPauseButtonImageView != null && mPlayPauseButtonImageView.isFocused()) + || (mCloseButtonView != null && mCloseButtonView.isFocused()); + if (mHasFocus != hasFocus) { + mHasFocus = hasFocus; + if (mOnChildFocusChangeListener != null) { + mOnChildFocusChangeListener.onFocusChange(getFocusedChild(), mHasFocus); + } + } + } + + /** + * Sets the {@link Listener} to listen user actions. + */ + 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 acb1a7f21e78..68e0883fbc88 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java @@ -30,6 +30,7 @@ import android.content.res.Resources; import android.graphics.Rect; import android.media.session.MediaController; import android.media.session.MediaSessionManager; +import android.media.session.PlaybackState; import android.os.Debug; import android.os.Handler; import android.os.RemoteException; @@ -37,6 +38,7 @@ import android.os.SystemProperties; import android.util.Log; import com.android.systemui.Prefs; +import com.android.systemui.R; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener; @@ -70,12 +72,23 @@ public class PipManager { public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH = 0x1; public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_OVERLAY_ACTIVITY_FINISH = 0x2; + /** + * PIPed activity is playing a media and it can be paused. + */ + static final int PLAYBACK_STATE_PLAYING = 0; + /** + * PIPed activity has a paused media and it can be played. + */ + static final int PLAYBACK_STATE_PAUSED = 1; + /** + * Users are unable to control PIPed activity's media playback. + */ + static final int PLAYBACK_STATE_UNAVAILABLE = 2; + private static final int CLOSE_PIP_WHEN_MEDIA_SESSION_GONE_TIMEOUT_MS = 3000; private int mSuspendPipResizingReason; - private static final float SCALE_FACTOR = 1.1f; - private Context mContext; private IActivityManager mActivityManager; private MediaSessionManager mMediaSessionManager; @@ -87,6 +100,7 @@ public class PipManager { private Rect mMenuModePipBounds; private Rect mRecentsPipBounds; private Rect mRecentsFocusedPipBounds; + private int mRecentsFocusChangedAnimationDurationMs; private boolean mInitialized; private int mPipTaskId = TASK_ID_NO_PIP; private ComponentName mPipComponentName; @@ -148,15 +162,13 @@ public class PipManager { mPipBounds = Rect.unflattenFromString(res.getString( com.android.internal.R.string.config_defaultPictureInPictureBounds)); mMenuModePipBounds = Rect.unflattenFromString(res.getString( - com.android.internal.R.string.config_centeredPictureInPictureBounds)); + R.string.pip_menu_bounds)); 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())); + R.string.pip_recents_bounds)); + mRecentsFocusedPipBounds = Rect.unflattenFromString(res.getString( + R.string.pip_recents_focused_bounds)); + mRecentsFocusChangedAnimationDurationMs = res.getInteger( + R.integer.recents_tv_pip_focus_anim_duration); mActivityManager = ActivityManagerNative.getDefault(); SystemServicesProxy.getInstance(context).registerTaskStackListener(mTaskStackListener); @@ -279,6 +291,7 @@ public class PipManager { mSuspendPipResizingReason); return; } + int animationDurationMs = -1; switch (mState) { case STATE_NO_PIP: mCurrentPipBounds = null; @@ -288,6 +301,10 @@ public class PipManager { break; case STATE_PIP_OVERLAY: if (mIsRecentsShown) { + if (mCurrentPipBounds == mRecentsFocusedPipBounds + || mCurrentPipBounds == mRecentsFocusedPipBounds) { + animationDurationMs = mRecentsFocusChangedAnimationDurationMs; + } if (mIsPipFocusedInRecent) { mCurrentPipBounds = mRecentsFocusedPipBounds; } else { @@ -302,7 +319,8 @@ public class PipManager { break; } try { - mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds, true, true, true, -1); + mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds, + true, true, true, animationDurationMs); } catch (RemoteException e) { Log.e(TAG, "resizeStack failed", e); } @@ -364,9 +382,17 @@ public class PipManager { } /** + * 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; + } + + /** * 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}. + * stack to the centered PIP bound {@link R.config_centeredPictureInPictureBounds}. */ private void showPipMenu() { if (DEBUG) Log.d(TAG, "showPipMenu()"); @@ -478,6 +504,32 @@ public class PipManager { return mPipMediaController; } + /** + * Returns the PIPed activity's playback state. + * This returns one of {@link PLAYBACK_STATE_PLAYING}, {@link PLAYBACK_STATE_PAUSED}, + * or {@link PLAYBACK_STATE_UNAVAILABLE}. + */ + int getPlaybackState() { + if (mPipMediaController == null || mPipMediaController.getPlaybackState() == null) { + return PLAYBACK_STATE_UNAVAILABLE; + } + int state = mPipMediaController.getPlaybackState().getState(); + boolean isPlaying = (state == PlaybackState.STATE_BUFFERING + || state == PlaybackState.STATE_CONNECTING + || state == PlaybackState.STATE_PLAYING + || state == PlaybackState.STATE_FAST_FORWARDING + || state == PlaybackState.STATE_REWINDING + || state == PlaybackState.STATE_SKIPPING_TO_PREVIOUS + || state == PlaybackState.STATE_SKIPPING_TO_NEXT); + long actions = mPipMediaController.getPlaybackState().getActions(); + if (!isPlaying && ((actions & PlaybackState.ACTION_PLAY) != 0)) { + return PLAYBACK_STATE_PAUSED; + } else if (isPlaying && ((actions & PlaybackState.ACTION_PAUSE) != 0)) { + return PLAYBACK_STATE_PLAYING; + } + return PLAYBACK_STATE_UNAVAILABLE; + } + TaskStackListener mTaskStackListener = new TaskStackListener() { @Override public void onTaskStackChanged() { 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 b8b837a0cb84..ea9275f8a918 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java @@ -17,12 +17,7 @@ package com.android.systemui.tv.pip; import android.app.Activity; -import android.media.session.MediaController; -import android.media.session.PlaybackState; import android.os.Bundle; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; import com.android.systemui.R; import com.android.systemui.SystemUI; @@ -31,8 +26,6 @@ import com.android.systemui.recents.Recents; import static android.content.pm.PackageManager.FEATURE_LEANBACK; import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; -import static android.media.session.PlaybackState.ACTION_PAUSE; -import static android.media.session.PlaybackState.ACTION_PLAY; /** * Activity to show the PIP menu to control PIP. @@ -41,131 +34,17 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { private static final String TAG = "PipMenuActivity"; private final PipManager mPipManager = PipManager.getInstance(); - private MediaController mMediaController; - - private View mFullButtonView; - private View mFullDescriptionView; - private View mPlayPauseView; - private ImageView mPlayPauseButtonImageView; - private TextView mPlayPauseDescriptionTextView; - private View mCloseButtonView; - private View mCloseDescriptionView; - private boolean mPipMovedToFullscreen; - private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() { - @Override - public void onPlaybackStateChanged(PlaybackState state) { - updatePlayPauseView(state); - } - }; + private PipControlsView mPipControlsView; + private boolean mPipMovedToFullscreen; @Override protected void onCreate(Bundle bundle) { super.onCreate(bundle); setContentView(R.layout.tv_pip_menu); mPipManager.addListener(this); - mFullButtonView = findViewById(R.id.full_button); - mFullDescriptionView = findViewById(R.id.full_desc); - mFullButtonView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mPipManager.movePipToFullscreen(); - mPipMovedToFullscreen = true; - finish(); - } - }); - mFullButtonView.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - mFullDescriptionView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE); - } - }); - - mPlayPauseView = findViewById(R.id.play_pause); - mPlayPauseButtonImageView = (ImageView) findViewById(R.id.play_pause_button); - mPlayPauseDescriptionTextView = (TextView) findViewById(R.id.play_pause_desc); - mPlayPauseButtonImageView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mMediaController == null || mMediaController.getPlaybackState() == null) { - return; - } - long actions = mMediaController.getPlaybackState().getActions(); - int state = mMediaController.getPlaybackState().getState(); - if (((actions & ACTION_PLAY) != 0) && !isPlaying(state)) { - mMediaController.getTransportControls().play(); - } else if ((actions & ACTION_PAUSE) != 0 && isPlaying(state)) { - mMediaController.getTransportControls().pause(); - } - // View will be updated later in {@link mMediaControllerCallback} - } - }); - mPlayPauseButtonImageView.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - mPlayPauseDescriptionTextView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE); - } - }); - - mCloseButtonView = findViewById(R.id.close_button); - mCloseDescriptionView = findViewById(R.id.close_desc); - mCloseButtonView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mPipManager.closePip(); - finish(); - } - }); - mCloseButtonView.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - mCloseDescriptionView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE); - } - }); - updateMediaController(); - } - private void updateMediaController() { - MediaController newController = mPipManager.getMediaController(); - if (mMediaController == newController) { - return; - } - if (mMediaController != null) { - mMediaController.unregisterCallback(mMediaControllerCallback); - } - mMediaController = newController; - if (mMediaController != null) { - mMediaController.registerCallback(mMediaControllerCallback); - updatePlayPauseView(mMediaController.getPlaybackState()); - } else { - updatePlayPauseView(null); - } - } - - private void updatePlayPauseView(PlaybackState playbackState) { - if (playbackState != null - && (playbackState.getActions() & (ACTION_PLAY | ACTION_PAUSE)) != 0) { - mPlayPauseView.setVisibility(View.VISIBLE); - if (isPlaying(playbackState.getState())) { - mPlayPauseButtonImageView.setImageResource(R.drawable.tv_pip_pause_button); - mPlayPauseDescriptionTextView.setText(R.string.pip_pause); - } else { - mPlayPauseButtonImageView.setImageResource(R.drawable.tv_pip_play_button); - mPlayPauseDescriptionTextView.setText(R.string.pip_play); - } - } else { - mPlayPauseView.setVisibility(View.GONE); - } - } - - private boolean isPlaying(int state) { - return state == PlaybackState.STATE_BUFFERING - || state == PlaybackState.STATE_CONNECTING - || state == PlaybackState.STATE_PLAYING - || state == PlaybackState.STATE_FAST_FORWARDING - || state == PlaybackState.STATE_REWINDING - || state == PlaybackState.STATE_SKIPPING_TO_PREVIOUS - || state == PlaybackState.STATE_SKIPPING_TO_NEXT; + mPipControlsView = (PipControlsView) findViewById(R.id.pip_controls); } private void restorePipAndFinish() { @@ -184,9 +63,6 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { @Override protected void onDestroy() { super.onDestroy(); - if (mMediaController != null) { - mMediaController.unregisterCallback(mMediaControllerCallback); - } mPipManager.removeListener(this); mPipManager.resumePipResizing( PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH); @@ -210,13 +86,12 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { @Override public void onMoveToFullscreen() { + mPipMovedToFullscreen = true; finish(); } @Override - public void onMediaControllerChanged() { - updateMediaController(); - } + public void onMediaControllerChanged() { } @Override public void onPipResizeAboutToStart() { 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 1de321dd1004..12cb4cd77824 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java @@ -23,6 +23,7 @@ import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.view.View; +import android.widget.ImageView; import com.android.systemui.R; @@ -46,9 +47,10 @@ public class PipOverlayActivity extends Activity implements PipManager.Listener private final Handler mHandler = new Handler(); private View mGuideOverlayView; private View mGuideButtonsView; + private ImageView mGuideButtonPlayPauseImageView; private final Runnable mHideGuideOverlayRunnable = new Runnable() { public void run() { - mGuideOverlayView.setVisibility(View.INVISIBLE); + mGuideOverlayView.setVisibility(View.GONE); } }; @@ -71,6 +73,7 @@ public class PipOverlayActivity extends Activity implements PipManager.Listener 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; @@ -80,12 +83,17 @@ public class PipOverlayActivity extends Activity implements PipManager.Listener protected void onResume() { super.onResume(); // TODO: Implement animation for this - if (!mPipManager.isRecentsShown()) { - mGuideOverlayView.setVisibility(View.VISIBLE); - mGuideButtonsView.setVisibility(View.INVISIBLE); + if (mPipManager.isRecentsShown()) { + mGuideOverlayView.setVisibility(View.GONE); + if (mPipManager.isPipViewFocusdInRecents()) { + mGuideButtonsView.setVisibility(View.GONE); + } else { + mGuideButtonsView.setVisibility(View.VISIBLE); + updateGuideButtonsView(); + } } else { - mGuideOverlayView.setVisibility(View.INVISIBLE); - mGuideButtonsView.setVisibility(View.VISIBLE); + mGuideOverlayView.setVisibility(View.VISIBLE); + mGuideButtonsView.setVisibility(View.GONE); } mHandler.removeCallbacks(mHideGuideOverlayRunnable); mHandler.postDelayed(mHideGuideOverlayRunnable, SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS); @@ -134,11 +142,30 @@ public class PipOverlayActivity extends Activity implements PipManager.Listener } @Override - public void onMediaControllerChanged() { } + 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; + } + } } |