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