blob: 298822981c0495f6eeac1326c21c7c8c8ff457cd [file] [log] [blame]
/*
* 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);
}
}
}