1 /* 2 * Copyright 2019, 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.provisioning; 17 18 import static com.android.internal.util.Preconditions.checkNotNull; 19 import static com.android.managedprovisioning.provisioning.ProvisioningActivity.PROVISIONING_MODE_FULLY_MANAGED_DEVICE; 20 import static com.android.managedprovisioning.provisioning.ProvisioningActivity.PROVISIONING_MODE_WORK_PROFILE; 21 import static com.android.managedprovisioning.provisioning.ProvisioningActivity.PROVISIONING_MODE_WORK_PROFILE_ON_FULLY_MANAGED_DEVICE; 22 23 import android.annotation.DrawableRes; 24 import android.annotation.StringRes; 25 import android.content.Context; 26 import android.graphics.drawable.AnimatedVectorDrawable; 27 import android.os.Handler; 28 import android.os.Looper; 29 import android.view.View; 30 import android.widget.ImageView; 31 import android.widget.TextView; 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.managedprovisioning.R; 34 import com.android.managedprovisioning.common.CrossFadeHelper; 35 import com.android.managedprovisioning.common.CrossFadeHelper.Callback; 36 import com.android.managedprovisioning.common.RepeatingVectorAnimation; 37 import com.android.managedprovisioning.provisioning.ProvisioningActivity.ProvisioningMode; 38 import java.util.Arrays; 39 import java.util.List; 40 41 /** 42 * Handles the animated transitions in the education screens. Transitions consist of cross fade 43 * animations between different headers and banner images. 44 */ 45 class TransitionAnimationHelper { 46 47 interface TransitionAnimationCallback { onAllTransitionsShown()48 void onAllTransitionsShown(); 49 } 50 51 @VisibleForTesting 52 static final ProvisioningModeWrapper WORK_PROFILE_WRAPPER 53 = new ProvisioningModeWrapper(new TransitionScreenWrapper[] { 54 new TransitionScreenWrapper(R.string.work_profile_provisioning_step_1_header, 55 R.drawable.separate_work_and_personal_animation), 56 new TransitionScreenWrapper(R.string.work_profile_provisioning_step_2_header, 57 R.drawable.pause_work_apps_animation), 58 new TransitionScreenWrapper(R.string.work_profile_provisioning_step_3_header, 59 R.drawable.not_private_animation) 60 }, R.string.work_profile_provisioning_summary); 61 62 @VisibleForTesting 63 static final ProvisioningModeWrapper FULLY_MANAGED_DEVICE_WRAPPER 64 = new ProvisioningModeWrapper(new TransitionScreenWrapper[] { 65 new TransitionScreenWrapper(R.string.fully_managed_device_provisioning_step_1_header, 66 R.drawable.connect_on_the_go_animation), 67 new TransitionScreenWrapper(R.string.fully_managed_device_provisioning_step_2_header, 68 R.drawable.not_private_animation, 69 R.string.fully_managed_device_provisioning_step_2_subheader, 70 /* showContactAdmin */ true) 71 }, R.string.fully_managed_device_provisioning_summary); 72 73 private static final int TRANSITION_TIME_MILLIS = 5000; 74 private static final int CROSSFADE_ANIMATION_DURATION_MILLIS = 500; 75 76 private final CrossFadeHelper mCrossFadeHelper; 77 private final AnimationComponents mAnimationComponents; 78 private final Runnable mStartNextTransitionRunnable = this::startNextAnimation; 79 private final boolean mShowAnimations; 80 private TransitionAnimationCallback mCallback; 81 private final ProvisioningModeWrapper mProvisioningModeWrapper; 82 83 private Handler mUiThreadHandler = new Handler(Looper.getMainLooper()); 84 private int mCurrentTransitionIndex; 85 private RepeatingVectorAnimation mRepeatingVectorAnimation; 86 TransitionAnimationHelper(@rovisioningMode int provisioningMode, AnimationComponents animationComponents, TransitionAnimationCallback callback)87 TransitionAnimationHelper(@ProvisioningMode int provisioningMode, 88 AnimationComponents animationComponents, TransitionAnimationCallback callback) { 89 mAnimationComponents = checkNotNull(animationComponents); 90 mCallback = checkNotNull(callback); 91 mProvisioningModeWrapper = getProvisioningModeWrapper(provisioningMode); 92 mCrossFadeHelper = getCrossFadeHelper(); 93 mShowAnimations = shouldShowAnimations(); 94 95 applyContentDescription(); 96 updateUiValues(mCurrentTransitionIndex); 97 } 98 areAllTransitionsShown()99 boolean areAllTransitionsShown() { 100 return mCurrentTransitionIndex == mProvisioningModeWrapper.transitions.length - 1; 101 } 102 start()103 void start() { 104 mUiThreadHandler.postDelayed(mStartNextTransitionRunnable, TRANSITION_TIME_MILLIS); 105 updateUiValues(mCurrentTransitionIndex); 106 startCurrentAnimatedDrawable(); 107 } 108 clean()109 void clean() { 110 stopCurrentAnimatedDrawable(); 111 mCrossFadeHelper.cleanup(); 112 mUiThreadHandler.removeCallbacksAndMessages(null); 113 mUiThreadHandler = null; 114 mCallback = null; 115 } 116 117 @VisibleForTesting getCrossFadeHelper()118 CrossFadeHelper getCrossFadeHelper() { 119 return new CrossFadeHelper( 120 mAnimationComponents.asList(), 121 CROSSFADE_ANIMATION_DURATION_MILLIS, 122 new Callback() { 123 @Override 124 public void fadeOutCompleted() { 125 stopCurrentAnimatedDrawable(); 126 mCurrentTransitionIndex++; 127 updateUiValues(mCurrentTransitionIndex); 128 startCurrentAnimatedDrawable(); 129 } 130 131 @Override 132 public void fadeInCompleted() { 133 mUiThreadHandler.postDelayed( 134 mStartNextTransitionRunnable, TRANSITION_TIME_MILLIS); 135 } 136 }); 137 } 138 139 @VisibleForTesting 140 void startNextAnimation() { 141 if (mCurrentTransitionIndex >= mProvisioningModeWrapper.transitions.length-1) { 142 if (mCallback != null) { 143 mCallback.onAllTransitionsShown(); 144 } 145 return; 146 } 147 mCrossFadeHelper.start(); 148 } 149 150 @VisibleForTesting 151 void startCurrentAnimatedDrawable() { 152 if (!mShowAnimations) { 153 return; 154 } 155 if (!(mAnimationComponents.image.getDrawable() instanceof AnimatedVectorDrawable)) { 156 return; 157 } 158 final AnimatedVectorDrawable vectorDrawable = 159 (AnimatedVectorDrawable) mAnimationComponents.image.getDrawable(); 160 mRepeatingVectorAnimation = new RepeatingVectorAnimation(vectorDrawable); 161 mRepeatingVectorAnimation.start(); 162 } 163 164 @VisibleForTesting 165 void stopCurrentAnimatedDrawable() { 166 if (!mShowAnimations) { 167 return; 168 } 169 if (!(mAnimationComponents.image.getDrawable() instanceof AnimatedVectorDrawable)) { 170 return; 171 } 172 mRepeatingVectorAnimation.stop(); 173 } 174 175 @VisibleForTesting 176 void updateUiValues(int currentTransitionIndex) { 177 final TransitionScreenWrapper[] transitions = mProvisioningModeWrapper.transitions; 178 final TransitionScreenWrapper transition = 179 transitions[currentTransitionIndex % transitions.length]; 180 181 mAnimationComponents.header.setText(transition.header); 182 183 final ImageView image = mAnimationComponents.image; 184 if (mShowAnimations) { 185 image.setImageResource(transition.drawable); 186 } else { 187 image.setVisibility(View.GONE); 188 } 189 190 final TextView subHeader = mAnimationComponents.subHeader; 191 if (transition.subHeader != 0) { 192 subHeader.setVisibility(View.VISIBLE); 193 subHeader.setText(transition.subHeader); 194 } else { 195 subHeader.setVisibility(View.INVISIBLE); 196 } 197 198 final TextView providerInfo = mAnimationComponents.providerInfo; 199 if (transition.showContactAdmin) { 200 providerInfo.setVisibility(View.VISIBLE); 201 } else { 202 providerInfo.setVisibility(View.INVISIBLE); 203 } 204 } 205 206 @VisibleForTesting 207 ProvisioningModeWrapper getProvisioningModeWrapper( 208 @ProvisioningMode int provisioningMode) { 209 switch (provisioningMode) { 210 case PROVISIONING_MODE_WORK_PROFILE: 211 return WORK_PROFILE_WRAPPER; 212 case PROVISIONING_MODE_FULLY_MANAGED_DEVICE: 213 return FULLY_MANAGED_DEVICE_WRAPPER; 214 } 215 throw new IllegalStateException("Unexpected provisioning mode " + provisioningMode); 216 } 217 218 private boolean shouldShowAnimations() { 219 final Context context = mAnimationComponents.header.getContext(); 220 return context.getResources().getBoolean(R.bool.show_edu_animations); 221 } 222 223 private void applyContentDescription() { 224 final TextView header = mAnimationComponents.header; 225 final Context context = header.getContext(); 226 header.setContentDescription(context.getString(mProvisioningModeWrapper.summary)); 227 } 228 229 private static final class TransitionScreenWrapper { 230 final @StringRes int header; 231 final @DrawableRes int drawable; 232 final @StringRes int subHeader; 233 final boolean showContactAdmin; 234 235 TransitionScreenWrapper(@StringRes int header, @DrawableRes int drawable) { 236 this(header, drawable, /* subHeader */ 0, /* showContactAdmin */ false); 237 } 238 239 TransitionScreenWrapper(@StringRes int header, @DrawableRes int drawable, 240 @StringRes int subHeader, boolean showContactAdmin) { 241 this.header = checkNotNull(header, 242 "Header resource id must be a positive number."); 243 this.drawable = checkNotNull(drawable, 244 "Drawable resource id must be a positive number."); 245 this.subHeader = subHeader; 246 this.showContactAdmin = showContactAdmin; 247 } 248 } 249 250 private static final class ProvisioningModeWrapper { 251 final TransitionScreenWrapper[] transitions; 252 final @StringRes int summary; 253 254 ProvisioningModeWrapper(TransitionScreenWrapper[] transitions, @StringRes int summary) { 255 this.transitions = checkNotNull(transitions); 256 this.summary = summary; 257 } 258 } 259 260 static final class AnimationComponents { 261 private final TextView header; 262 private final TextView subHeader; 263 private final ImageView image; 264 private final TextView providerInfo; 265 266 AnimationComponents( 267 TextView header, TextView subHeader, ImageView image, TextView providerInfo) { 268 this.header = checkNotNull(header); 269 this.subHeader = checkNotNull(subHeader); 270 this.image = checkNotNull(image); 271 this.providerInfo = checkNotNull(providerInfo); 272 } 273 274 List<View> asList() { 275 return Arrays.asList(header, subHeader, image, providerInfo); 276 } 277 } 278 } 279