Home | History | Annotate | Download | only in anim
      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 }