| /* |
| * Copyright 2019, The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.managedprovisioning.provisioning; |
| |
| import static com.android.internal.util.Preconditions.checkNotNull; |
| import static com.android.managedprovisioning.provisioning.ProvisioningActivity.PROVISIONING_MODE_FULLY_MANAGED_DEVICE; |
| import static com.android.managedprovisioning.provisioning.ProvisioningActivity.PROVISIONING_MODE_WORK_PROFILE; |
| import static com.android.managedprovisioning.provisioning.ProvisioningActivity.PROVISIONING_MODE_WORK_PROFILE_ON_ORG_OWNED_DEVICE; |
| |
| import android.annotation.DrawableRes; |
| import android.annotation.StringRes; |
| import android.content.Context; |
| import android.graphics.drawable.AnimatedVectorDrawable; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.view.View; |
| import android.widget.ImageView; |
| import android.widget.TextView; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.managedprovisioning.R; |
| import com.android.managedprovisioning.common.CrossFadeHelper; |
| import com.android.managedprovisioning.common.CrossFadeHelper.Callback; |
| import com.android.managedprovisioning.common.RepeatingVectorAnimation; |
| import com.android.managedprovisioning.provisioning.ProvisioningActivity.ProvisioningMode; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| /** |
| * Handles the animated transitions in the education screens. Transitions consist of cross fade |
| * animations between different headers and banner images. |
| */ |
| class TransitionAnimationHelper { |
| |
| interface TransitionAnimationCallback { |
| void onAllTransitionsShown(); |
| } |
| |
| @VisibleForTesting |
| static final ProvisioningModeWrapper WORK_PROFILE_WRAPPER |
| = new ProvisioningModeWrapper(new TransitionScreenWrapper[] { |
| new TransitionScreenWrapper(R.string.work_profile_provisioning_step_1_header, |
| R.drawable.separate_work_and_personal_animation), |
| new TransitionScreenWrapper(R.string.work_profile_provisioning_step_2_header, |
| R.drawable.pause_work_apps_animation), |
| new TransitionScreenWrapper(R.string.work_profile_provisioning_step_3_header, |
| R.drawable.not_private_animation) |
| }, R.string.work_profile_provisioning_summary); |
| |
| @VisibleForTesting |
| static final ProvisioningModeWrapper FULLY_MANAGED_DEVICE_WRAPPER |
| = new ProvisioningModeWrapper(new TransitionScreenWrapper[] { |
| new TransitionScreenWrapper(R.string.fully_managed_device_provisioning_step_1_header, |
| R.drawable.connect_on_the_go_animation), |
| new TransitionScreenWrapper(R.string.fully_managed_device_provisioning_step_2_header, |
| R.drawable.not_private_animation, |
| R.string.fully_managed_device_provisioning_step_2_subheader, |
| /* showContactAdmin */ true, |
| /* shouldLoop */ true) |
| }, R.string.fully_managed_device_provisioning_summary); |
| |
| @VisibleForTesting |
| static final ProvisioningModeWrapper WORK_PROFILE_ON_ORG_OWNED_DEVICE_WRAPPER |
| = new ProvisioningModeWrapper(new TransitionScreenWrapper[] { |
| new TransitionScreenWrapper(R.string.cope_provisioning_step_1_header, |
| R.drawable.separate_work_and_personal_animation), |
| new TransitionScreenWrapper(R.string.cope_provisioning_step_2_header, |
| R.drawable.personal_apps_separate_hidden_from_work_animation, |
| /* subHeader */ 0, |
| /* showContactAdmin */ false, |
| /* shouldLoop */ false), |
| new TransitionScreenWrapper(R.string.cope_provisioning_step_3_header, |
| R.drawable.it_admin_control_device_block_apps_animation) |
| }, R.string.cope_provisioning_summary); |
| |
| private static final int TRANSITION_TIME_MILLIS = 5000; |
| private static final int CROSSFADE_ANIMATION_DURATION_MILLIS = 500; |
| |
| private final CrossFadeHelper mCrossFadeHelper; |
| private final AnimationComponents mAnimationComponents; |
| private final Runnable mStartNextTransitionRunnable = this::startNextAnimation; |
| private final boolean mShowAnimations; |
| private TransitionAnimationCallback mCallback; |
| private final ProvisioningModeWrapper mProvisioningModeWrapper; |
| |
| private Handler mUiThreadHandler = new Handler(Looper.getMainLooper()); |
| private int mCurrentTransitionIndex; |
| private RepeatingVectorAnimation mRepeatingVectorAnimation; |
| |
| TransitionAnimationHelper(@ProvisioningMode int provisioningMode, |
| AnimationComponents animationComponents, TransitionAnimationCallback callback) { |
| mAnimationComponents = checkNotNull(animationComponents); |
| mCallback = checkNotNull(callback); |
| mProvisioningModeWrapper = getProvisioningModeWrapper(provisioningMode); |
| mCrossFadeHelper = getCrossFadeHelper(); |
| mShowAnimations = shouldShowAnimations(); |
| |
| applyContentDescription(); |
| updateUiValues(mCurrentTransitionIndex); |
| } |
| |
| boolean areAllTransitionsShown() { |
| return mCurrentTransitionIndex == mProvisioningModeWrapper.transitions.length - 1; |
| } |
| |
| void start() { |
| mUiThreadHandler.postDelayed(mStartNextTransitionRunnable, TRANSITION_TIME_MILLIS); |
| updateUiValues(mCurrentTransitionIndex); |
| startCurrentAnimatedDrawable(); |
| } |
| |
| void clean() { |
| stopCurrentAnimatedDrawable(); |
| mCrossFadeHelper.cleanup(); |
| mUiThreadHandler.removeCallbacksAndMessages(null); |
| mUiThreadHandler = null; |
| mCallback = null; |
| } |
| |
| @VisibleForTesting |
| CrossFadeHelper getCrossFadeHelper() { |
| return new CrossFadeHelper( |
| mAnimationComponents.asList(), |
| CROSSFADE_ANIMATION_DURATION_MILLIS, |
| new Callback() { |
| @Override |
| public void fadeOutCompleted() { |
| stopCurrentAnimatedDrawable(); |
| mCurrentTransitionIndex++; |
| updateUiValues(mCurrentTransitionIndex); |
| startCurrentAnimatedDrawable(); |
| } |
| |
| @Override |
| public void fadeInCompleted() { |
| mUiThreadHandler.postDelayed( |
| mStartNextTransitionRunnable, TRANSITION_TIME_MILLIS); |
| } |
| }); |
| } |
| |
| @VisibleForTesting |
| void startNextAnimation() { |
| if (mCurrentTransitionIndex >= mProvisioningModeWrapper.transitions.length-1) { |
| if (mCallback != null) { |
| mCallback.onAllTransitionsShown(); |
| } |
| return; |
| } |
| mCrossFadeHelper.start(); |
| } |
| |
| @VisibleForTesting |
| void startCurrentAnimatedDrawable() { |
| if (!mShowAnimations) { |
| return; |
| } |
| if (!(mAnimationComponents.image.getDrawable() instanceof AnimatedVectorDrawable)) { |
| return; |
| } |
| final AnimatedVectorDrawable vectorDrawable = |
| (AnimatedVectorDrawable) mAnimationComponents.image.getDrawable(); |
| boolean shouldLoop = getTransitionForIndex(mCurrentTransitionIndex).shouldLoop; |
| mRepeatingVectorAnimation = new RepeatingVectorAnimation(vectorDrawable, shouldLoop); |
| mRepeatingVectorAnimation.start(); |
| } |
| |
| @VisibleForTesting |
| void stopCurrentAnimatedDrawable() { |
| if (!mShowAnimations) { |
| return; |
| } |
| if (!(mAnimationComponents.image.getDrawable() instanceof AnimatedVectorDrawable)) { |
| return; |
| } |
| mRepeatingVectorAnimation.stop(); |
| } |
| |
| @VisibleForTesting |
| void updateUiValues(int currentTransitionIndex) { |
| final TransitionScreenWrapper transition = |
| getTransitionForIndex(currentTransitionIndex); |
| |
| mAnimationComponents.header.setText(transition.header); |
| |
| final ImageView image = mAnimationComponents.image; |
| if (mShowAnimations) { |
| image.setImageResource(transition.drawable); |
| } else { |
| image.setVisibility(View.GONE); |
| } |
| |
| final TextView subHeader = mAnimationComponents.subHeader; |
| if (transition.subHeader != 0) { |
| subHeader.setVisibility(View.VISIBLE); |
| subHeader.setText(transition.subHeader); |
| } else { |
| subHeader.setVisibility(View.INVISIBLE); |
| } |
| |
| final TextView providerInfo = mAnimationComponents.providerInfo; |
| if (transition.showContactAdmin) { |
| providerInfo.setVisibility(View.VISIBLE); |
| } else { |
| providerInfo.setVisibility(View.INVISIBLE); |
| } |
| } |
| |
| private TransitionScreenWrapper getTransitionForIndex(int currentTransitionIndex) { |
| TransitionScreenWrapper[] transitions = mProvisioningModeWrapper.transitions; |
| return transitions[currentTransitionIndex % transitions.length]; |
| } |
| |
| @VisibleForTesting |
| ProvisioningModeWrapper getProvisioningModeWrapper( |
| @ProvisioningMode int provisioningMode) { |
| switch (provisioningMode) { |
| case PROVISIONING_MODE_WORK_PROFILE: |
| return WORK_PROFILE_WRAPPER; |
| case PROVISIONING_MODE_FULLY_MANAGED_DEVICE: |
| return FULLY_MANAGED_DEVICE_WRAPPER; |
| case PROVISIONING_MODE_WORK_PROFILE_ON_ORG_OWNED_DEVICE: |
| return WORK_PROFILE_ON_ORG_OWNED_DEVICE_WRAPPER; |
| } |
| throw new IllegalStateException("Unexpected provisioning mode " + provisioningMode); |
| } |
| |
| private boolean shouldShowAnimations() { |
| final Context context = mAnimationComponents.header.getContext(); |
| return context.getResources().getBoolean(R.bool.show_edu_animations); |
| } |
| |
| private void applyContentDescription() { |
| final TextView header = mAnimationComponents.header; |
| final Context context = header.getContext(); |
| header.setContentDescription(context.getString(mProvisioningModeWrapper.summary)); |
| } |
| |
| private static final class TransitionScreenWrapper { |
| final @StringRes int header; |
| final @DrawableRes int drawable; |
| final @StringRes int subHeader; |
| final boolean showContactAdmin; |
| final boolean shouldLoop; |
| |
| TransitionScreenWrapper(@StringRes int header, @DrawableRes int drawable) { |
| this(header, drawable, /* subHeader */ 0, /* showContactAdmin */ false, |
| /* shouldLoop */ true); |
| } |
| |
| TransitionScreenWrapper(@StringRes int header, @DrawableRes int drawable, |
| @StringRes int subHeader, boolean showContactAdmin, boolean shouldLoop) { |
| this.header = checkNotNull(header, |
| "Header resource id must be a positive number."); |
| this.drawable = checkNotNull(drawable, |
| "Drawable resource id must be a positive number."); |
| this.subHeader = subHeader; |
| this.showContactAdmin = showContactAdmin; |
| this.shouldLoop = shouldLoop; |
| } |
| } |
| |
| private static final class ProvisioningModeWrapper { |
| final TransitionScreenWrapper[] transitions; |
| final @StringRes int summary; |
| |
| ProvisioningModeWrapper(TransitionScreenWrapper[] transitions, @StringRes int summary) { |
| this.transitions = checkNotNull(transitions); |
| this.summary = summary; |
| } |
| } |
| |
| static final class AnimationComponents { |
| private final TextView header; |
| private final TextView subHeader; |
| private final ImageView image; |
| private final TextView providerInfo; |
| |
| AnimationComponents( |
| TextView header, TextView subHeader, ImageView image, TextView providerInfo) { |
| this.header = checkNotNull(header); |
| this.subHeader = checkNotNull(subHeader); |
| this.image = checkNotNull(image); |
| this.providerInfo = checkNotNull(providerInfo); |
| } |
| |
| List<View> asList() { |
| return Arrays.asList(header, subHeader, image, providerInfo); |
| } |
| } |
| } |