1 /* 2 * Copyright 2016, 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 package com.android.managedprovisioning.preprovisioning.anim; 17 18 import static com.android.internal.util.Preconditions.checkNotNull; 19 20 import android.animation.Animator; 21 import android.animation.AnimatorInflater; 22 import android.animation.AnimatorSet; 23 import android.animation.ObjectAnimator; 24 import android.annotation.AnimRes; 25 import android.annotation.InterpolatorRes; 26 import android.app.Activity; 27 import android.graphics.drawable.Animatable2; 28 import android.graphics.drawable.AnimatedVectorDrawable; 29 import android.graphics.drawable.Drawable; 30 import androidx.annotation.NonNull; 31 import android.view.ContextThemeWrapper; 32 import android.view.View; 33 import android.view.ViewGroup.LayoutParams; 34 import android.view.animation.AnimationUtils; 35 import android.widget.FrameLayout; 36 import android.widget.ImageView; 37 import android.widget.TextView; 38 39 import com.android.managedprovisioning.R; 40 import com.android.managedprovisioning.model.CustomizationParams; 41 42 import java.util.ArrayList; 43 import java.util.List; 44 45 /** 46 * <p>Drives the animation showing benefits of having a Managed Profile. 47 * <p>Tightly coupled with the {@link R.layout#intro_animation} layout. 48 * The animation consists of 4 horizontally scrolling text labels, placed horizontally. 49 * Each label's horizontal translation is * <code>i*screenWidth/2</code>, where i is the 50 * number of the label (0, 1, 2 or 3) and screenWidth is the actual screen width. We need to 51 * calculate the views' widths dynamically because we want them to fit the display width. 52 */ 53 public class BenefitsAnimation { 54 /** Array of Id pairs: {{@link ObjectAnimator}, {@link TextView}} */ 55 private static final int[][] ID_ANIMATION_TARGET = { 56 {R.anim.text_scene_0_animation, R.id.text_0}, 57 {R.anim.text_scene_1_animation, R.id.text_1}, 58 {R.anim.text_scene_2_animation, R.id.text_2}, 59 {R.anim.text_scene_3_animation, R.id.text_3}}; 60 61 private static final int[] SLIDE_CAPTION_TEXT_VIEWS = { 62 R.id.text_0, R.id.text_1, R.id.text_2, R.id.text_3}; 63 64 /** Id of an {@link ImageView} containing the animated graphic */ 65 private static final int ID_ANIMATED_GRAPHIC = R.id.animated_info; 66 67 private static final int SLIDE_COUNT = 3; 68 private static final int ANIMATION_ORIGINAL_WIDTH_PX = 1080; 69 private static final int TRANSLATION_DURATION_MS = 1001; 70 private static final float MASTER_CONTAINER_DISPLAY_WIDTH_MULTIPLIER = 2.5f; 71 72 private final AnimatedVectorDrawable mTopAnimation; 73 private Animator mTextAnimation; 74 private final Activity mActivity; 75 76 private boolean mStopped; 77 78 /** 79 * @param captions slide captions for the animation 80 * @param contentDescription for accessibility 81 */ BenefitsAnimation(@onNull Activity activity, @NonNull List<Integer> captions, int contentDescription, CustomizationParams customizationParams)82 public BenefitsAnimation(@NonNull Activity activity, @NonNull List<Integer> captions, 83 int contentDescription, CustomizationParams customizationParams) { 84 if (captions.size() != SLIDE_COUNT) { 85 throw new IllegalArgumentException( 86 "Wrong number of slide captions. Expected: " + SLIDE_COUNT); 87 } 88 mActivity = checkNotNull(activity); 89 applySlideCaptions(captions); 90 applyContentDescription(contentDescription); 91 92 setTopInfoDrawable(customizationParams); 93 94 mTopAnimation = checkNotNull(extractAnimationFromImageView(ID_ANIMATED_GRAPHIC)); 95 96 // once the screen is ready, adjust size 97 mActivity.findViewById(android.R.id.content).post(this::adjustToScreenSize); 98 mActivity.findViewById(android.R.id.content).post(this::prepareAnimations); 99 } 100 prepareAnimations()101 private void prepareAnimations() { 102 mTextAnimation = checkNotNull(assembleTextAnimation()); 103 // chain all animations together 104 chainAnimations(); 105 } 106 setTopInfoDrawable(CustomizationParams customizationParams)107 private void setTopInfoDrawable(CustomizationParams customizationParams) { 108 int swiperTheme = new SwiperThemeMatcher(mActivity, new ColorMatcher()) 109 .findTheme(customizationParams.mainColor); 110 111 ContextThemeWrapper wrapper = new ContextThemeWrapper(mActivity, swiperTheme); 112 Drawable drawable = 113 mActivity.getResources().getDrawable( 114 R.drawable.topinfo_animation, 115 wrapper.getTheme()); 116 ImageView imageView = mActivity.findViewById(ID_ANIMATED_GRAPHIC); 117 imageView.setImageDrawable(drawable); 118 } 119 120 /** Starts playing the animation in a loop. */ start()121 public void start() { 122 mStopped = false; 123 mTopAnimation.start(); 124 } 125 126 /** Stops the animation. */ stop()127 public void stop() { 128 mStopped = true; 129 mTopAnimation.stop(); 130 } 131 132 /** 133 * Adjust animation and text to match actual screen size 134 */ adjustToScreenSize()135 private void adjustToScreenSize() { 136 if (mActivity.isDestroyed()) { 137 return; 138 } 139 140 setupLabelDimensions(); 141 142 ImageView animatedInfo = mActivity.findViewById(R.id.animated_info); 143 int widthPx = animatedInfo.getWidth(); 144 float scaleRatio = (float) widthPx / ANIMATION_ORIGINAL_WIDTH_PX; 145 146 // adjust animation height; width happens automatically 147 LayoutParams layoutParams = animatedInfo.getLayoutParams(); 148 int originalHeight = animatedInfo.getHeight(); 149 int adjustedHeight = (int) (originalHeight * scaleRatio); 150 layoutParams.height = adjustedHeight; 151 animatedInfo.setLayoutParams(layoutParams); 152 153 // if the content is bigger than the screen, try to shrink just the animation 154 int offset = adjustedHeight - originalHeight; 155 int contentHeight = mActivity.findViewById(R.id.intro_po_content).getHeight() + offset; 156 int viewportHeight = mActivity.findViewById(R.id.suc_layout_content).getHeight(); 157 if (contentHeight > viewportHeight) { 158 int targetHeight = layoutParams.height - (contentHeight - viewportHeight); 159 int minHeight = mActivity.getResources().getDimensionPixelSize( 160 R.dimen.intro_animation_min_height); 161 162 // if the animation becomes too small, leave it as is and the scrollbar will show 163 if (targetHeight >= minHeight) { 164 layoutParams.height = targetHeight; 165 animatedInfo.setLayoutParams(layoutParams); 166 } 167 } 168 } 169 setupLabelDimensions()170 private void setupLabelDimensions() { 171 final ImageView animatedInfo = mActivity.findViewById(R.id.animated_info); 172 final int width = animatedInfo.getWidth(); 173 final FrameLayout textAnimationViewport = 174 mActivity.findViewById(R.id.text_animation_viewport); 175 final LayoutParams viewportParams = textAnimationViewport.getLayoutParams(); 176 viewportParams.width = width; 177 textAnimationViewport.setLayoutParams(viewportParams); 178 179 final FrameLayout textMasterContainer = mActivity.findViewById(R.id.text_master); 180 final LayoutParams masterContainerParams = textMasterContainer.getLayoutParams(); 181 masterContainerParams.width = (int) (width * MASTER_CONTAINER_DISPLAY_WIDTH_MULTIPLIER); 182 textMasterContainer.setLayoutParams(masterContainerParams); 183 184 int translation = 0; 185 final int step = width / 2; 186 for (int textViewId : SLIDE_CAPTION_TEXT_VIEWS) { 187 final View textView = mActivity.findViewById(textViewId); 188 final LayoutParams layoutParams = textView.getLayoutParams(); 189 layoutParams.width = width; 190 textView.setLayoutParams(layoutParams); 191 textView.setTranslationX(translation); 192 translation += step; 193 } 194 } 195 196 /** 197 * <p>Chains all three sub-animations, and configures them to play in sync in a loop. 198 * <p>Looping {@link AnimatedVectorDrawable} and {@link AnimatorSet} currently not possible in 199 * XML. 200 */ chainAnimations()201 private void chainAnimations() { 202 mTopAnimation.registerAnimationCallback(new Animatable2.AnimationCallback() { 203 @Override 204 public void onAnimationStart(Drawable drawable) { 205 super.onAnimationStart(drawable); 206 207 // starting the other animations at the same time 208 mTextAnimation.start(); 209 } 210 211 @Override 212 public void onAnimationEnd(Drawable drawable) { 213 super.onAnimationEnd(drawable); 214 215 // without explicitly stopping them, sometimes they won't restart 216 mTextAnimation.cancel(); 217 218 // repeating the animation in loop 219 if (!mStopped) { 220 mTopAnimation.start(); 221 } 222 } 223 }); 224 } 225 226 /** 227 * <p>Inflates animators required to animate text headers' part of the whole animation. 228 * <p>This has to be done through code, as setting a target on {@link 229 * android.animation.ObjectAnimator} is not currently possible in XML. 230 * 231 * @return {@link AnimatorSet} responsible for the animated text 232 */ assembleTextAnimation()233 private AnimatorSet assembleTextAnimation() { 234 Animator[] animators = new Animator[ID_ANIMATION_TARGET.length + 1]; 235 for (int i = 0; i < ID_ANIMATION_TARGET.length; i++) { 236 int[] instance = ID_ANIMATION_TARGET[i]; 237 animators[i] = AnimatorInflater.loadAnimator(mActivity, instance[0]); 238 animators[i].setTarget(mActivity.findViewById(instance[1])); 239 } 240 animators[ID_ANIMATION_TARGET.length] = getTranslationAnimatorSet(); 241 242 AnimatorSet animatorSet = new AnimatorSet(); 243 animatorSet.playTogether(animators); 244 return animatorSet; 245 } 246 247 /** 248 * Creates and returns a sequential horizontal translation animation of the benefits labels. 249 */ getTranslationAnimatorSet()250 private AnimatorSet getTranslationAnimatorSet() { 251 ImageView animatedInfo = mActivity.findViewById(R.id.animated_info); 252 final int width = animatedInfo.getWidth(); 253 AnimatorSet translationSet = new AnimatorSet(); 254 View textContainer = mActivity.findViewById(R.id.text_master); 255 List<Animator> animators = new ArrayList<>(); 256 animators.add(getTranslationAnimator(textContainer, 0, 0, 257 android.R.interpolator.linear)); 258 animators.add(getTranslationAnimator(textContainer, 0, -width/2, 259 android.R.interpolator.fast_out_slow_in)); 260 animators.add(getTranslationAnimator(textContainer, -width/2, -width/2, 261 android.R.interpolator.linear)); 262 animators.add(getTranslationAnimator(textContainer, -width/2, -width, 263 android.R.interpolator.fast_out_slow_in)); 264 animators.add(getTranslationAnimator(textContainer, -width, -width, 265 android.R.interpolator.linear)); 266 animators.add(getTranslationAnimator(textContainer, -width, (int) (-width * 1.5), 267 android.R.interpolator.fast_out_slow_in)); 268 translationSet.playSequentially(animators); 269 return translationSet; 270 } 271 getTranslationAnimator(View container, float from, float to, @AnimRes @InterpolatorRes int interpolator)272 private Animator getTranslationAnimator(View container, float from, float to, 273 @AnimRes @InterpolatorRes int interpolator) { 274 ObjectAnimator animator = ObjectAnimator.ofFloat(container, "translationX", from, to); 275 animator.setDuration(TRANSLATION_DURATION_MS); 276 animator.setInterpolator(AnimationUtils.loadInterpolator(mActivity, interpolator)); 277 return animator; 278 } 279 280 /** 281 * @param captions slide titles 282 */ applySlideCaptions(List<Integer> captions)283 private void applySlideCaptions(List<Integer> captions) { 284 int slideIx = 0; 285 for (int viewId : SLIDE_CAPTION_TEXT_VIEWS) { 286 ((TextView) mActivity.findViewById(viewId)).setText( 287 captions.get(slideIx++ % captions.size())); 288 } 289 } 290 applyContentDescription(int contentDescription)291 private void applyContentDescription(int contentDescription) { 292 mActivity.findViewById(R.id.animation_top_level_frame).setContentDescription( 293 mActivity.getString(contentDescription)); 294 } 295 296 /** Extracts an {@link AnimatedVectorDrawable} from a containing {@link ImageView}. */ extractAnimationFromImageView(int id)297 private AnimatedVectorDrawable extractAnimationFromImageView(int id) { 298 ImageView imageView = mActivity.findViewById(id); 299 return (AnimatedVectorDrawable) imageView.getDrawable(); 300 } 301 }