1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tv.ui;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorInflater;
21 import android.support.annotation.IntDef;
22 import android.transition.Fade;
23 import android.transition.Scene;
24 import android.transition.Transition;
25 import android.transition.TransitionInflater;
26 import android.transition.TransitionManager;
27 import android.transition.TransitionSet;
28 import android.transition.TransitionValues;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.widget.FrameLayout;
32 import android.widget.FrameLayout.LayoutParams;
33 import com.android.tv.MainActivity;
34 import com.android.tv.R;
35 import com.android.tv.data.api.Channel;
36 import java.lang.annotation.Retention;
37 import java.lang.annotation.RetentionPolicy;
38 
39 public class TvTransitionManager extends TransitionManager {
40     @Retention(RetentionPolicy.SOURCE)
41     @IntDef({
42         SCENE_TYPE_EMPTY,
43         SCENE_TYPE_CHANNEL_BANNER,
44         SCENE_TYPE_INPUT_BANNER,
45         SCENE_TYPE_KEYPAD_CHANNEL_SWITCH,
46         SCENE_TYPE_SELECT_INPUT
47     })
48     public @interface SceneType {}
49 
50     public static final int SCENE_TYPE_EMPTY = 0;
51     public static final int SCENE_TYPE_CHANNEL_BANNER = 1;
52     public static final int SCENE_TYPE_INPUT_BANNER = 2;
53     public static final int SCENE_TYPE_KEYPAD_CHANNEL_SWITCH = 3;
54     public static final int SCENE_TYPE_SELECT_INPUT = 4;
55 
56     private final MainActivity mMainActivity;
57     private final ViewGroup mSceneContainer;
58     private final ChannelBannerView mChannelBannerView;
59     private final InputBannerViewBase mInputBannerView;
60     private final KeypadChannelSwitchView mKeypadChannelSwitchView;
61     private final SelectInputView mSelectInputView;
62     private final FrameLayout mEmptyView;
63     private ViewGroup mCurrentSceneView;
64     private Animator mEnterAnimator;
65     private Animator mExitAnimator;
66 
67     private boolean mInitialized;
68     private Scene mEmptyScene;
69     private Scene mChannelBannerScene;
70     private Scene mInputBannerScene;
71     private Scene mKeypadChannelSwitchScene;
72     private Scene mSelectInputScene;
73     private Scene mCurrentScene;
74 
75     private Listener mListener;
76 
TvTransitionManager( MainActivity mainActivity, ViewGroup sceneContainer, ChannelBannerView channelBannerView, InputBannerViewBase inputBannerView, KeypadChannelSwitchView keypadChannelSwitchView, SelectInputView selectInputView)77     public TvTransitionManager(
78             MainActivity mainActivity,
79             ViewGroup sceneContainer,
80             ChannelBannerView channelBannerView,
81             InputBannerViewBase inputBannerView,
82             KeypadChannelSwitchView keypadChannelSwitchView,
83             SelectInputView selectInputView) {
84         mMainActivity = mainActivity;
85         mSceneContainer = sceneContainer;
86         mChannelBannerView = channelBannerView;
87         mInputBannerView = inputBannerView;
88         mKeypadChannelSwitchView = keypadChannelSwitchView;
89         mSelectInputView = selectInputView;
90         mEmptyView =
91                 (FrameLayout)
92                         mMainActivity
93                                 .getLayoutInflater()
94                                 .inflate(R.layout.empty_info_banner, sceneContainer, false);
95         mCurrentSceneView = mEmptyView;
96     }
97 
goToEmptyScene(boolean withAnimation)98     public void goToEmptyScene(boolean withAnimation) {
99         if (mCurrentScene == mEmptyScene) {
100             return;
101         }
102         initIfNeeded();
103         if (withAnimation) {
104             mEmptyView.setAlpha(1.0f);
105             transitionTo(mEmptyScene);
106         } else {
107             TransitionManager.go(mEmptyScene, null);
108             // When transition is null, transition got stuck without calling endTransitions.
109             TransitionManager.endTransitions(mEmptyScene.getSceneRoot());
110             // Since Fade.OUT transition doesn't run, we need to set alpha manually.
111             mEmptyView.setAlpha(0);
112         }
113     }
114 
goToChannelBannerScene()115     public void goToChannelBannerScene() {
116         initIfNeeded();
117         Channel channel = mMainActivity.getCurrentChannel();
118         if (channel != null && channel.isPassthrough()) {
119             if (mCurrentScene != mInputBannerScene) {
120                 // Show the input banner instead.
121                 LayoutParams lp = (LayoutParams) mInputBannerView.getLayoutParams();
122                 lp.width =
123                         mCurrentScene == mSelectInputScene
124                                 ? mSelectInputView.getWidth()
125                                 : FrameLayout.LayoutParams.WRAP_CONTENT;
126                 mInputBannerView.setLayoutParams(lp);
127                 mInputBannerView.updateLabel();
128                 transitionTo(mInputBannerScene);
129             }
130         } else if (mCurrentScene != mChannelBannerScene) {
131             transitionTo(mChannelBannerScene);
132         }
133     }
134 
goToKeypadChannelSwitchScene()135     public void goToKeypadChannelSwitchScene() {
136         initIfNeeded();
137         if (mCurrentScene != mKeypadChannelSwitchScene) {
138             transitionTo(mKeypadChannelSwitchScene);
139         }
140     }
141 
goToSelectInputScene()142     public void goToSelectInputScene() {
143         initIfNeeded();
144         if (mCurrentScene != mSelectInputScene) {
145             mSelectInputView.setCurrentChannel(mMainActivity.getCurrentChannel());
146             transitionTo(mSelectInputScene);
147         }
148     }
149 
isSceneActive()150     public boolean isSceneActive() {
151         return mCurrentScene != mEmptyScene;
152     }
153 
isKeypadChannelSwitchActive()154     public boolean isKeypadChannelSwitchActive() {
155         return mCurrentScene != null && mCurrentScene == mKeypadChannelSwitchScene;
156     }
157 
isSelectInputActive()158     public boolean isSelectInputActive() {
159         return mCurrentScene != null && mCurrentScene == mSelectInputScene;
160     }
161 
isInputBannerActive()162     public boolean isInputBannerActive() {
163         return mCurrentScene != null && mCurrentScene == mInputBannerScene;
164     }
165 
setListener(Listener listener)166     public void setListener(Listener listener) {
167         mListener = listener;
168     }
169 
initIfNeeded()170     public void initIfNeeded() {
171         if (mInitialized) {
172             return;
173         }
174         mEnterAnimator =
175                 AnimatorInflater.loadAnimator(mMainActivity, R.animator.channel_banner_enter);
176         mExitAnimator =
177                 AnimatorInflater.loadAnimator(mMainActivity, R.animator.channel_banner_exit);
178 
179         mEmptyScene = new Scene(mSceneContainer, (View) mEmptyView);
180         mEmptyScene.setEnterAction(
181                 () -> {
182                     FrameLayout.LayoutParams emptySceneLayoutParams =
183                             (FrameLayout.LayoutParams) mEmptyView.getLayoutParams();
184                     ViewGroup.MarginLayoutParams lp =
185                             (ViewGroup.MarginLayoutParams) mCurrentSceneView.getLayoutParams();
186                     emptySceneLayoutParams.topMargin = mCurrentSceneView.getTop();
187                     emptySceneLayoutParams.setMarginStart(lp.getMarginStart());
188                     emptySceneLayoutParams.height = mCurrentSceneView.getHeight();
189                     emptySceneLayoutParams.width = mCurrentSceneView.getWidth();
190                     mEmptyView.setLayoutParams(emptySceneLayoutParams);
191                     setCurrentScene(mEmptyScene, mEmptyView);
192                 });
193         mEmptyScene.setExitAction(this::removeAllViewsFromOverlay);
194 
195         mChannelBannerScene = buildScene(mSceneContainer, mChannelBannerView);
196         mInputBannerScene = buildScene(mSceneContainer, mInputBannerView);
197         mKeypadChannelSwitchScene = buildScene(mSceneContainer, mKeypadChannelSwitchView);
198         mSelectInputScene = buildScene(mSceneContainer, mSelectInputView);
199         mCurrentScene = mEmptyScene;
200 
201         // Enter transitions
202         TransitionSet enter =
203                 new TransitionSet()
204                         .addTransition(new SceneTransition(SceneTransition.ENTER))
205                         .addTransition(new Fade(Fade.IN));
206         setTransition(mEmptyScene, mChannelBannerScene, enter);
207         setTransition(mEmptyScene, mInputBannerScene, enter);
208         setTransition(mEmptyScene, mKeypadChannelSwitchScene, enter);
209         setTransition(mEmptyScene, mSelectInputScene, enter);
210 
211         // Exit transitions
212         TransitionSet exit =
213                 new TransitionSet()
214                         .addTransition(new SceneTransition(SceneTransition.EXIT))
215                         .addTransition(new Fade(Fade.OUT));
216         setTransition(mChannelBannerScene, mEmptyScene, exit);
217         setTransition(mInputBannerScene, mEmptyScene, exit);
218         setTransition(mKeypadChannelSwitchScene, mEmptyScene, exit);
219         setTransition(mSelectInputScene, mEmptyScene, exit);
220 
221         // All other possible transitions between scenes
222         TransitionInflater ti = TransitionInflater.from(mMainActivity);
223         Transition transition = ti.inflateTransition(R.transition.transition_between_scenes);
224         setTransition(mChannelBannerScene, mKeypadChannelSwitchScene, transition);
225         setTransition(mChannelBannerScene, mSelectInputScene, transition);
226         setTransition(mInputBannerScene, mSelectInputScene, transition);
227         setTransition(mKeypadChannelSwitchScene, mChannelBannerScene, transition);
228         setTransition(mKeypadChannelSwitchScene, mSelectInputScene, transition);
229         setTransition(mSelectInputScene, mChannelBannerScene, transition);
230         setTransition(mSelectInputScene, mInputBannerScene, transition);
231 
232         mInitialized = true;
233     }
234 
235     /** Returns the type of the given scene. */
236     @SceneType
getSceneType(Scene scene)237     public int getSceneType(Scene scene) {
238         if (scene == mChannelBannerScene) {
239             return SCENE_TYPE_CHANNEL_BANNER;
240         } else if (scene == mInputBannerScene) {
241             return SCENE_TYPE_INPUT_BANNER;
242         } else if (scene == mKeypadChannelSwitchScene) {
243             return SCENE_TYPE_KEYPAD_CHANNEL_SWITCH;
244         } else if (scene == mSelectInputScene) {
245             return SCENE_TYPE_SELECT_INPUT;
246         }
247         return SCENE_TYPE_EMPTY;
248     }
249 
setCurrentScene(Scene scene, ViewGroup sceneView)250     private void setCurrentScene(Scene scene, ViewGroup sceneView) {
251         if (mListener != null) {
252             mListener.onSceneChanged(getSceneType(mCurrentScene), getSceneType(scene));
253         }
254         mCurrentScene = scene;
255         mCurrentSceneView = sceneView;
256         // TODO: Is this a still valid call?
257         mMainActivity.updateKeyInputFocus();
258     }
259 
260     public interface TransitionLayout {
261         // TODO: remove the parameter fromEmptyScene once a bug regarding transition alpha
262         // is fixed. The bug is that the transition alpha is not reset after the transition is
263         // canceled.
onEnterAction(boolean fromEmptyScene)264         void onEnterAction(boolean fromEmptyScene);
265 
onExitAction()266         void onExitAction();
267     }
268 
buildScene(ViewGroup sceneRoot, final TransitionLayout layout)269     private Scene buildScene(ViewGroup sceneRoot, final TransitionLayout layout) {
270         final Scene scene = new Scene(sceneRoot, (View) layout);
271         scene.setEnterAction(
272                 () -> {
273                     boolean wasEmptyScene = (mCurrentScene == mEmptyScene);
274                     setCurrentScene(scene, (ViewGroup) layout);
275                     layout.onEnterAction(wasEmptyScene);
276                 });
277         scene.setExitAction(
278                 () -> {
279                     removeAllViewsFromOverlay();
280                     layout.onExitAction();
281                 });
282         return scene;
283     }
284 
removeAllViewsFromOverlay()285     private void removeAllViewsFromOverlay() {
286         // Clean up all the animations which can be still running.
287         mSceneContainer.getOverlay().remove(mChannelBannerView);
288         mSceneContainer.getOverlay().remove(mInputBannerView);
289         mSceneContainer.getOverlay().remove(mKeypadChannelSwitchView);
290         mSceneContainer.getOverlay().remove(mSelectInputView);
291     }
292 
293     private class SceneTransition extends Transition {
294         static final int ENTER = 0;
295         static final int EXIT = 1;
296 
297         private final Animator mAnimator;
298 
SceneTransition(int mode)299         SceneTransition(int mode) {
300             mAnimator = mode == ENTER ? mEnterAnimator : mExitAnimator;
301         }
302 
303         @Override
captureStartValues(TransitionValues transitionValues)304         public void captureStartValues(TransitionValues transitionValues) {}
305 
306         @Override
captureEndValues(TransitionValues transitionValues)307         public void captureEndValues(TransitionValues transitionValues) {}
308 
309         @Override
createAnimator( ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues)310         public Animator createAnimator(
311                 ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
312             Animator animator = mAnimator.clone();
313             animator.setTarget(sceneRoot);
314             animator.addListener(new HardwareLayerAnimatorListenerAdapter(sceneRoot));
315             return animator;
316         }
317     }
318 
319     /** An interface for notification of the scene transition. */
320     public interface Listener {
321         /**
322          * Called when the scene changes. This method is called just before the scene transition.
323          */
onSceneChanged(@ceneType int fromSceneType, @SceneType int toSceneType)324         void onSceneChanged(@SceneType int fromSceneType, @SceneType int toSceneType);
325     }
326 }
327