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 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 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 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 135 public void goToKeypadChannelSwitchScene() { 136 initIfNeeded(); 137 if (mCurrentScene != mKeypadChannelSwitchScene) { 138 transitionTo(mKeypadChannelSwitchScene); 139 } 140 } 141 142 public void goToSelectInputScene() { 143 initIfNeeded(); 144 if (mCurrentScene != mSelectInputScene) { 145 mSelectInputView.setCurrentChannel(mMainActivity.getCurrentChannel()); 146 transitionTo(mSelectInputScene); 147 } 148 } 149 150 public boolean isSceneActive() { 151 return mCurrentScene != mEmptyScene; 152 } 153 154 public boolean isKeypadChannelSwitchActive() { 155 return mCurrentScene != null && mCurrentScene == mKeypadChannelSwitchScene; 156 } 157 158 public boolean isSelectInputActive() { 159 return mCurrentScene != null && mCurrentScene == mSelectInputScene; 160 } 161 162 public void setListener(Listener listener) { 163 mListener = listener; 164 } 165 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 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 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. 269 void onEnterAction(boolean fromEmptyScene); 270 271 void onExitAction(); 272 } 273 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 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 310 SceneTransition(int mode) { 311 mAnimator = mode == ENTER ? mEnterAnimator : mExitAnimator; 312 } 313 314 @Override 315 public void captureStartValues(TransitionValues transitionValues) {} 316 317 @Override 318 public void captureEndValues(TransitionValues transitionValues) {} 319 320 @Override 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 */ 335 void onSceneChanged(@SceneType int fromSceneType, @SceneType int toSceneType); 336 } 337 } 338