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