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
17package com.android.tv.ui;
18
19import android.animation.Animator;
20import android.animation.AnimatorInflater;
21import android.support.annotation.IntDef;
22import android.transition.Fade;
23import android.transition.Scene;
24import android.transition.Transition;
25import android.transition.TransitionInflater;
26import android.transition.TransitionManager;
27import android.transition.TransitionSet;
28import android.transition.TransitionValues;
29import android.view.View;
30import android.view.ViewGroup;
31import android.widget.FrameLayout;
32import android.widget.FrameLayout.LayoutParams;
33
34import com.android.tv.MainActivity;
35import com.android.tv.R;
36import com.android.tv.data.Channel;
37
38import java.lang.annotation.Retention;
39import java.lang.annotation.RetentionPolicy;
40
41public 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
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
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
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
122    public void goToKeypadChannelSwitchScene() {
123        initIfNeeded();
124        if (mCurrentScene != mKeypadChannelSwitchScene) {
125            transitionTo(mKeypadChannelSwitchScene);
126        }
127    }
128
129    public void goToSelectInputScene() {
130        initIfNeeded();
131        if (mCurrentScene != mSelectInputScene) {
132            mSelectInputView.setCurrentChannel(mMainActivity.getCurrentChannel());
133            transitionTo(mSelectInputScene);
134        }
135    }
136
137    public boolean isSceneActive() {
138        return mCurrentScene != mEmptyScene;
139    }
140
141    public boolean isKeypadChannelSwitchActive() {
142        return mCurrentScene != null && mCurrentScene == mKeypadChannelSwitchScene;
143    }
144
145    public boolean isSelectInputActive() {
146        return mCurrentScene != null && mCurrentScene == mSelectInputScene;
147    }
148
149    public void setListener(Listener listener) {
150        mListener = listener;
151    }
152
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     */
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
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.
253        void onEnterAction(boolean fromEmptyScene);
254
255        void onExitAction();
256    }
257
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
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
292        SceneTransition(int mode) {
293            mAnimator = mode == ENTER ? mEnterAnimator : mExitAnimator;
294        }
295
296        @Override
297        public void captureStartValues(TransitionValues transitionValues) {
298        }
299
300        @Override
301        public void captureEndValues(TransitionValues transitionValues) {
302        }
303
304        @Override
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         */
321        void onSceneChanged(@SceneType int fromSceneType, @SceneType int toSceneType);
322    }
323}
324