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