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.app.Activity; 25 import android.graphics.drawable.Animatable2; 26 import android.graphics.drawable.AnimatedVectorDrawable; 27 import android.graphics.drawable.Drawable; 28 import android.support.annotation.NonNull; 29 import android.view.View; 30 import android.view.ViewGroup.LayoutParams; 31 import android.widget.ImageView; 32 import android.widget.TextView; 33 34 import com.android.managedprovisioning.R; 35 36 import java.util.List; 37 38 /** 39 * <p>Drives the animation showing benefits of having a Managed Profile. 40 * <p>Tightly coupled with the {@link R.layout#intro_animation} layout. 41 */ 42 public class BenefitsAnimation { 43 /** Array of Id pairs: {{@link ObjectAnimator}, {@link TextView}} */ 44 private static final int[][] ID_ANIMATION_TARGET = { 45 {R.anim.text_scene_0_animation, R.id.text_0}, 46 {R.anim.text_scene_1_animation, R.id.text_1}, 47 {R.anim.text_scene_2_animation, R.id.text_2}, 48 {R.anim.text_scene_3_animation, R.id.text_3}, 49 {R.anim.text_scene_master_animation, R.id.text_master}}; 50 51 private static final int[] SLIDE_CAPTION_TEXT_VIEWS = { 52 R.id.text_0, R.id.text_1, R.id.text_2, R.id.text_3}; 53 54 /** Id of an {@link ImageView} containing the animated graphic */ 55 private static final int ID_ANIMATED_GRAPHIC = R.id.animated_info; 56 57 /** Id of an {@link ImageView} containing the animated pager dots */ 58 private static final int ID_ANIMATED_DOTS = R.id.animated_dots; 59 60 private static final int SLIDE_COUNT = 3; 61 private static final int ANIMATION_ORIGINAL_WIDTH_PX = 1080; 62 63 private final AnimatedVectorDrawable mTopAnimation; 64 private final AnimatedVectorDrawable mDotsAnimation; 65 private final Animator mTextAnimation; 66 private final Activity mActivity; 67 68 private boolean mStopped; 69 70 /** 71 * @param captions slide captions for the animation 72 * @param contentDescription for accessibility 73 */ 74 public BenefitsAnimation(@NonNull Activity activity, @NonNull List<Integer> captions, 75 int contentDescription) { 76 if (captions.size() != SLIDE_COUNT) { 77 throw new IllegalArgumentException( 78 "Wrong number of slide captions. Expected: " + SLIDE_COUNT); 79 } 80 mActivity = checkNotNull(activity); 81 mTextAnimation = checkNotNull(assembleTextAnimation()); 82 applySlideCaptions(captions); 83 applyContentDescription(contentDescription); 84 mDotsAnimation = checkNotNull(extractAnimationFromImageView(ID_ANIMATED_DOTS)); 85 mTopAnimation = checkNotNull(extractAnimationFromImageView(ID_ANIMATED_GRAPHIC)); 86 87 // chain all animations together 88 chainAnimations(); 89 90 // once the screen is ready, adjust size 91 mActivity.findViewById(android.R.id.content).post(this::adjustToScreenSize); 92 } 93 94 /** Starts playing the animation in a loop. */ 95 public void start() { 96 mStopped = false; 97 mTopAnimation.start(); 98 } 99 100 /** Stops the animation. */ 101 public void stop() { 102 mStopped = true; 103 mTopAnimation.stop(); 104 } 105 106 /** 107 * Adjust animation and text to match actual screen size 108 */ 109 private void adjustToScreenSize() { 110 if (mActivity.isDestroyed()) { 111 return; 112 } 113 114 ImageView animatedInfo = mActivity.findViewById(R.id.animated_info); 115 int widthPx = animatedInfo.getWidth(); 116 float scaleRatio = (float) widthPx / ANIMATION_ORIGINAL_WIDTH_PX; 117 118 // adjust animation height; width happens automatically 119 LayoutParams layoutParams = animatedInfo.getLayoutParams(); 120 int originalHeight = animatedInfo.getHeight(); 121 int adjustedHeight = (int) (originalHeight * scaleRatio); 122 layoutParams.height = adjustedHeight; 123 animatedInfo.setLayoutParams(layoutParams); 124 125 // adjust captions size only if downscaling 126 if (scaleRatio < 1) { 127 for (int textViewId : SLIDE_CAPTION_TEXT_VIEWS) { 128 View view = mActivity.findViewById(textViewId); 129 view.setScaleX(scaleRatio); 130 view.setScaleY(scaleRatio); 131 } 132 } 133 134 // if the content is bigger than the screen, try to shrink just the animation 135 int offset = adjustedHeight - originalHeight; 136 int contentHeight = mActivity.findViewById(R.id.intro_po_content).getHeight() + offset; 137 int viewportHeight = mActivity.findViewById(R.id.suw_layout_content).getHeight(); 138 if (contentHeight > viewportHeight) { 139 int targetHeight = layoutParams.height - (contentHeight - viewportHeight); 140 int minHeight = mActivity.getResources().getDimensionPixelSize( 141 R.dimen.intro_animation_min_height); 142 143 // if the animation becomes too small, leave it as is and the scrollbar will show 144 if (targetHeight >= minHeight) { 145 layoutParams.height = targetHeight; 146 animatedInfo.setLayoutParams(layoutParams); 147 } 148 } 149 } 150 151 /** 152 * <p>Chains all three sub-animations, and configures them to play in sync in a loop. 153 * <p>Looping {@link AnimatedVectorDrawable} and {@link AnimatorSet} currently not possible in 154 * XML. 155 */ 156 private void chainAnimations() { 157 mTopAnimation.registerAnimationCallback(new Animatable2.AnimationCallback() { 158 @Override 159 public void onAnimationStart(Drawable drawable) { 160 super.onAnimationStart(drawable); 161 162 // starting the other animations at the same time 163 mDotsAnimation.start(); 164 mTextAnimation.start(); 165 } 166 167 @Override 168 public void onAnimationEnd(Drawable drawable) { 169 super.onAnimationEnd(drawable); 170 171 // without explicitly stopping them, sometimes they won't restart 172 mDotsAnimation.stop(); 173 mTextAnimation.cancel(); 174 175 // repeating the animation in loop 176 if (!mStopped) { 177 mTopAnimation.start(); 178 } 179 } 180 }); 181 } 182 183 /** 184 * <p>Inflates animators required to animate text headers' part of the whole animation. 185 * <p>This has to be done through code, as setting a target on {@link 186 * android.animation.ObjectAnimator} is not currently possible in XML. 187 * 188 * @return {@link AnimatorSet} responsible for the animated text 189 */ 190 private AnimatorSet assembleTextAnimation() { 191 Animator[] animators = new Animator[ID_ANIMATION_TARGET.length]; 192 for (int i = 0; i < ID_ANIMATION_TARGET.length; i++) { 193 int[] instance = ID_ANIMATION_TARGET[i]; 194 animators[i] = AnimatorInflater.loadAnimator(mActivity, instance[0]); 195 animators[i].setTarget(mActivity.findViewById(instance[1])); 196 } 197 198 AnimatorSet animatorSet = new AnimatorSet(); 199 animatorSet.playTogether(animators); 200 return animatorSet; 201 } 202 203 /** 204 * @param captions slide titles 205 */ 206 private void applySlideCaptions(List<Integer> captions) { 207 int slideIx = 0; 208 for (int viewId : SLIDE_CAPTION_TEXT_VIEWS) { 209 ((TextView) mActivity.findViewById(viewId)).setText( 210 captions.get(slideIx++ % captions.size())); 211 } 212 } 213 214 private void applyContentDescription(int contentDescription) { 215 mActivity.findViewById(R.id.animation_top_level_frame).setContentDescription( 216 mActivity.getString(contentDescription)); 217 } 218 219 /** Extracts an {@link AnimatedVectorDrawable} from a containing {@link ImageView}. */ 220 private AnimatedVectorDrawable extractAnimationFromImageView(int id) { 221 ImageView imageView = mActivity.findViewById(id); 222 return (AnimatedVectorDrawable) imageView.getDrawable(); 223 } 224 }