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