PIP: Apply the animation spec for the PIP in Recents

Bug: 27540465
Change-Id: Ibdd4a4e3fd194978ccbc29235a1c620ebddff942
This commit is contained in:
Jaewan Kim
2016-03-31 13:37:10 +09:00
parent 6bb61e25a2
commit a0d4d2588d
22 changed files with 792 additions and 337 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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" />

View File

@@ -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" />

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
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);
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);
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
@@ -332,11 +352,22 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener {
EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
}
@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();
}
}
}

View File

@@ -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);
}
}

View File

@@ -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() { }
}

View File

@@ -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;
}
}

View File

@@ -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
@@ -68,11 +54,6 @@ public class PipMenuActivity extends Activity implements PipManager.Listener {
PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH);
}
@Override
public void onBackPressed() {
restorePipAndFinish();
}
@Override
public void onPipEntered() { }
@@ -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;
}
}
}
}
}

View File

@@ -86,7 +86,4 @@ public class PipOnboardingActivity extends Activity implements PipManager.Listen
@Override
public void onPipResizeAboutToStart() { }
@Override
public void onMediaControllerChanged() { }
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}