• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 InputBannerView 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, InputBannerView inputBannerView, KeypadChannelSwitchView keypadChannelSwitchView, SelectInputView selectInputView)77     public TvTransitionManager(
78             MainActivity mainActivity,
79             ViewGroup sceneContainer,
80             ChannelBannerView channelBannerView,
81             InputBannerView 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 
setListener(Listener listener)162     public void setListener(Listener listener) {
163         mListener = listener;
164     }
165 
initIfNeeded()166     public void initIfNeeded() {
167         if (mInitialized) {
168             return;
169         }
170         mEnterAnimator =
171                 AnimatorInflater.loadAnimator(mMainActivity, R.animator.channel_banner_enter);
172         mExitAnimator =
173                 AnimatorInflater.loadAnimator(mMainActivity, R.animator.channel_banner_exit);
174 
175         mEmptyScene = new Scene(mSceneContainer, (View) mEmptyView);
176         mEmptyScene.setEnterAction(
177                 new Runnable() {
178                     @Override
179                     public void run() {
180                         FrameLayout.LayoutParams emptySceneLayoutParams =
181                                 (FrameLayout.LayoutParams) mEmptyView.getLayoutParams();
182                         ViewGroup.MarginLayoutParams lp =
183                                 (ViewGroup.MarginLayoutParams) mCurrentSceneView.getLayoutParams();
184                         emptySceneLayoutParams.topMargin = mCurrentSceneView.getTop();
185                         emptySceneLayoutParams.setMarginStart(lp.getMarginStart());
186                         emptySceneLayoutParams.height = mCurrentSceneView.getHeight();
187                         emptySceneLayoutParams.width = mCurrentSceneView.getWidth();
188                         mEmptyView.setLayoutParams(emptySceneLayoutParams);
189                         setCurrentScene(mEmptyScene, mEmptyView);
190                     }
191                 });
192         mEmptyScene.setExitAction(
193                 new Runnable() {
194                     @Override
195                     public void run() {
196                         removeAllViewsFromOverlay();
197                     }
198                 });
199 
200         mChannelBannerScene = buildScene(mSceneContainer, mChannelBannerView);
201         mInputBannerScene = buildScene(mSceneContainer, mInputBannerView);
202         mKeypadChannelSwitchScene = buildScene(mSceneContainer, mKeypadChannelSwitchView);
203         mSelectInputScene = buildScene(mSceneContainer, mSelectInputView);
204         mCurrentScene = mEmptyScene;
205 
206         // Enter transitions
207         TransitionSet enter =
208                 new TransitionSet()
209                         .addTransition(new SceneTransition(SceneTransition.ENTER))
210                         .addTransition(new Fade(Fade.IN));
211         setTransition(mEmptyScene, mChannelBannerScene, enter);
212         setTransition(mEmptyScene, mInputBannerScene, enter);
213         setTransition(mEmptyScene, mKeypadChannelSwitchScene, enter);
214         setTransition(mEmptyScene, mSelectInputScene, enter);
215 
216         // Exit transitions
217         TransitionSet exit =
218                 new TransitionSet()
219                         .addTransition(new SceneTransition(SceneTransition.EXIT))
220                         .addTransition(new Fade(Fade.OUT));
221         setTransition(mChannelBannerScene, mEmptyScene, exit);
222         setTransition(mInputBannerScene, mEmptyScene, exit);
223         setTransition(mKeypadChannelSwitchScene, mEmptyScene, exit);
224         setTransition(mSelectInputScene, mEmptyScene, exit);
225 
226         // All other possible transitions between scenes
227         TransitionInflater ti = TransitionInflater.from(mMainActivity);
228         Transition transition = ti.inflateTransition(R.transition.transition_between_scenes);
229         setTransition(mChannelBannerScene, mKeypadChannelSwitchScene, transition);
230         setTransition(mChannelBannerScene, mSelectInputScene, transition);
231         setTransition(mInputBannerScene, mSelectInputScene, transition);
232         setTransition(mKeypadChannelSwitchScene, mChannelBannerScene, transition);
233         setTransition(mKeypadChannelSwitchScene, mSelectInputScene, transition);
234         setTransition(mSelectInputScene, mChannelBannerScene, transition);
235         setTransition(mSelectInputScene, mInputBannerScene, transition);
236 
237         mInitialized = true;
238     }
239 
240     /** Returns the type of the given scene. */
241     @SceneType
getSceneType(Scene scene)242     public int getSceneType(Scene scene) {
243         if (scene == mChannelBannerScene) {
244             return SCENE_TYPE_CHANNEL_BANNER;
245         } else if (scene == mInputBannerScene) {
246             return SCENE_TYPE_INPUT_BANNER;
247         } else if (scene == mKeypadChannelSwitchScene) {
248             return SCENE_TYPE_KEYPAD_CHANNEL_SWITCH;
249         } else if (scene == mSelectInputScene) {
250             return SCENE_TYPE_SELECT_INPUT;
251         }
252         return SCENE_TYPE_EMPTY;
253     }
254 
setCurrentScene(Scene scene, ViewGroup sceneView)255     private void setCurrentScene(Scene scene, ViewGroup sceneView) {
256         if (mListener != null) {
257             mListener.onSceneChanged(getSceneType(mCurrentScene), getSceneType(scene));
258         }
259         mCurrentScene = scene;
260         mCurrentSceneView = sceneView;
261         // TODO: Is this a still valid call?
262         mMainActivity.updateKeyInputFocus();
263     }
264 
265     public interface TransitionLayout {
266         // TODO: remove the parameter fromEmptyScene once a bug regarding transition alpha
267         // is fixed. The bug is that the transition alpha is not reset after the transition is
268         // canceled.
onEnterAction(boolean fromEmptyScene)269         void onEnterAction(boolean fromEmptyScene);
270 
onExitAction()271         void onExitAction();
272     }
273 
buildScene(ViewGroup sceneRoot, final TransitionLayout layout)274     private Scene buildScene(ViewGroup sceneRoot, final TransitionLayout layout) {
275         final Scene scene = new Scene(sceneRoot, (View) layout);
276         scene.setEnterAction(
277                 new Runnable() {
278                     @Override
279                     public void run() {
280                         boolean wasEmptyScene = (mCurrentScene == mEmptyScene);
281                         setCurrentScene(scene, (ViewGroup) layout);
282                         layout.onEnterAction(wasEmptyScene);
283                     }
284                 });
285         scene.setExitAction(
286                 new Runnable() {
287                     @Override
288                     public void run() {
289                         removeAllViewsFromOverlay();
290                         layout.onExitAction();
291                     }
292                 });
293         return scene;
294     }
295 
removeAllViewsFromOverlay()296     private void removeAllViewsFromOverlay() {
297         // Clean up all the animations which can be still running.
298         mSceneContainer.getOverlay().remove(mChannelBannerView);
299         mSceneContainer.getOverlay().remove(mInputBannerView);
300         mSceneContainer.getOverlay().remove(mKeypadChannelSwitchView);
301         mSceneContainer.getOverlay().remove(mSelectInputView);
302     }
303 
304     private class SceneTransition extends Transition {
305         static final int ENTER = 0;
306         static final int EXIT = 1;
307 
308         private final Animator mAnimator;
309 
SceneTransition(int mode)310         SceneTransition(int mode) {
311             mAnimator = mode == ENTER ? mEnterAnimator : mExitAnimator;
312         }
313 
314         @Override
captureStartValues(TransitionValues transitionValues)315         public void captureStartValues(TransitionValues transitionValues) {}
316 
317         @Override
captureEndValues(TransitionValues transitionValues)318         public void captureEndValues(TransitionValues transitionValues) {}
319 
320         @Override
createAnimator( ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues)321         public Animator createAnimator(
322                 ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
323             Animator animator = mAnimator.clone();
324             animator.setTarget(sceneRoot);
325             animator.addListener(new HardwareLayerAnimatorListenerAdapter(sceneRoot));
326             return animator;
327         }
328     }
329 
330     /** An interface for notification of the scene transition. */
331     public interface Listener {
332         /**
333          * Called when the scene changes. This method is called just before the scene transition.
334          */
onSceneChanged(@ceneType int fromSceneType, @SceneType int toSceneType)335         void onSceneChanged(@SceneType int fromSceneType, @SceneType int toSceneType);
336     }
337 }
338