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 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