• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_ORG_OWNED_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                 /* shouldLoop */ true)
72     }, R.string.fully_managed_device_provisioning_summary);
73 
74     @VisibleForTesting
75     static final ProvisioningModeWrapper WORK_PROFILE_ON_ORG_OWNED_DEVICE_WRAPPER
76             = new ProvisioningModeWrapper(new TransitionScreenWrapper[] {
77         new TransitionScreenWrapper(R.string.cope_provisioning_step_1_header,
78                 R.drawable.separate_work_and_personal_animation),
79         new TransitionScreenWrapper(R.string.cope_provisioning_step_2_header,
80                 R.drawable.personal_apps_separate_hidden_from_work_animation,
81                 /* subHeader */ 0,
82                 /* showContactAdmin */ false,
83                 /* shouldLoop */ false),
84         new TransitionScreenWrapper(R.string.cope_provisioning_step_3_header,
85                 R.drawable.it_admin_control_device_block_apps_animation)
86     }, R.string.cope_provisioning_summary);
87 
88     private static final int TRANSITION_TIME_MILLIS = 5000;
89     private static final int CROSSFADE_ANIMATION_DURATION_MILLIS = 500;
90 
91     private final CrossFadeHelper mCrossFadeHelper;
92     private final AnimationComponents mAnimationComponents;
93     private final Runnable mStartNextTransitionRunnable = this::startNextAnimation;
94     private final boolean mShowAnimations;
95     private TransitionAnimationCallback mCallback;
96     private final ProvisioningModeWrapper mProvisioningModeWrapper;
97 
98     private Handler mUiThreadHandler = new Handler(Looper.getMainLooper());
99     private int mCurrentTransitionIndex;
100     private RepeatingVectorAnimation mRepeatingVectorAnimation;
101 
TransitionAnimationHelper(@rovisioningMode int provisioningMode, AnimationComponents animationComponents, TransitionAnimationCallback callback)102     TransitionAnimationHelper(@ProvisioningMode int provisioningMode,
103             AnimationComponents animationComponents, TransitionAnimationCallback callback) {
104         mAnimationComponents = checkNotNull(animationComponents);
105         mCallback = checkNotNull(callback);
106         mProvisioningModeWrapper = getProvisioningModeWrapper(provisioningMode);
107         mCrossFadeHelper = getCrossFadeHelper();
108         mShowAnimations = shouldShowAnimations();
109 
110         applyContentDescription();
111         updateUiValues(mCurrentTransitionIndex);
112     }
113 
areAllTransitionsShown()114     boolean areAllTransitionsShown() {
115         return mCurrentTransitionIndex == mProvisioningModeWrapper.transitions.length - 1;
116     }
117 
start()118     void start() {
119         mUiThreadHandler.postDelayed(mStartNextTransitionRunnable, TRANSITION_TIME_MILLIS);
120         updateUiValues(mCurrentTransitionIndex);
121         startCurrentAnimatedDrawable();
122     }
123 
clean()124     void clean() {
125         stopCurrentAnimatedDrawable();
126         mCrossFadeHelper.cleanup();
127         mUiThreadHandler.removeCallbacksAndMessages(null);
128         mUiThreadHandler = null;
129         mCallback = null;
130     }
131 
132     @VisibleForTesting
getCrossFadeHelper()133     CrossFadeHelper getCrossFadeHelper() {
134         return new CrossFadeHelper(
135             mAnimationComponents.asList(),
136             CROSSFADE_ANIMATION_DURATION_MILLIS,
137             new Callback() {
138                 @Override
139                 public void fadeOutCompleted() {
140                     stopCurrentAnimatedDrawable();
141                     mCurrentTransitionIndex++;
142                     updateUiValues(mCurrentTransitionIndex);
143                     startCurrentAnimatedDrawable();
144                 }
145 
146                 @Override
147                 public void fadeInCompleted() {
148                     mUiThreadHandler.postDelayed(
149                         mStartNextTransitionRunnable, TRANSITION_TIME_MILLIS);
150                 }
151             });
152     }
153 
154     @VisibleForTesting
155     void startNextAnimation() {
156         if (mCurrentTransitionIndex >= mProvisioningModeWrapper.transitions.length-1) {
157             if (mCallback != null) {
158                 mCallback.onAllTransitionsShown();
159             }
160             return;
161         }
162         mCrossFadeHelper.start();
163     }
164 
165     @VisibleForTesting
166     void startCurrentAnimatedDrawable() {
167         if (!mShowAnimations) {
168             return;
169         }
170         if (!(mAnimationComponents.image.getDrawable() instanceof AnimatedVectorDrawable)) {
171             return;
172         }
173         final AnimatedVectorDrawable vectorDrawable =
174             (AnimatedVectorDrawable) mAnimationComponents.image.getDrawable();
175         boolean shouldLoop = getTransitionForIndex(mCurrentTransitionIndex).shouldLoop;
176         mRepeatingVectorAnimation = new RepeatingVectorAnimation(vectorDrawable, shouldLoop);
177         mRepeatingVectorAnimation.start();
178     }
179 
180     @VisibleForTesting
181     void stopCurrentAnimatedDrawable() {
182         if (!mShowAnimations) {
183             return;
184         }
185         if (!(mAnimationComponents.image.getDrawable() instanceof AnimatedVectorDrawable)) {
186             return;
187         }
188         mRepeatingVectorAnimation.stop();
189     }
190 
191     @VisibleForTesting
192     void updateUiValues(int currentTransitionIndex) {
193         final TransitionScreenWrapper transition =
194                 getTransitionForIndex(currentTransitionIndex);
195 
196         mAnimationComponents.header.setText(transition.header);
197 
198         final ImageView image = mAnimationComponents.image;
199         if (mShowAnimations) {
200             image.setImageResource(transition.drawable);
201         } else {
202             image.setVisibility(View.GONE);
203         }
204 
205         final TextView subHeader = mAnimationComponents.subHeader;
206         if (transition.subHeader != 0) {
207             subHeader.setVisibility(View.VISIBLE);
208             subHeader.setText(transition.subHeader);
209         } else {
210             subHeader.setVisibility(View.INVISIBLE);
211         }
212 
213         final TextView providerInfo = mAnimationComponents.providerInfo;
214         if (transition.showContactAdmin) {
215             providerInfo.setVisibility(View.VISIBLE);
216         } else {
217             providerInfo.setVisibility(View.INVISIBLE);
218         }
219     }
220 
221     private TransitionScreenWrapper getTransitionForIndex(int currentTransitionIndex) {
222         TransitionScreenWrapper[] transitions = mProvisioningModeWrapper.transitions;
223         return transitions[currentTransitionIndex % transitions.length];
224     }
225 
226     @VisibleForTesting
227     ProvisioningModeWrapper getProvisioningModeWrapper(
228             @ProvisioningMode int provisioningMode) {
229         switch (provisioningMode) {
230             case PROVISIONING_MODE_WORK_PROFILE:
231                 return WORK_PROFILE_WRAPPER;
232             case PROVISIONING_MODE_FULLY_MANAGED_DEVICE:
233                 return FULLY_MANAGED_DEVICE_WRAPPER;
234             case PROVISIONING_MODE_WORK_PROFILE_ON_ORG_OWNED_DEVICE:
235                 return WORK_PROFILE_ON_ORG_OWNED_DEVICE_WRAPPER;
236         }
237         throw new IllegalStateException("Unexpected provisioning mode " + provisioningMode);
238     }
239 
240     private boolean shouldShowAnimations() {
241         final Context context = mAnimationComponents.header.getContext();
242         return context.getResources().getBoolean(R.bool.show_edu_animations);
243     }
244 
245     private void applyContentDescription() {
246         final TextView header = mAnimationComponents.header;
247         final Context context = header.getContext();
248         header.setContentDescription(context.getString(mProvisioningModeWrapper.summary));
249     }
250 
251     private static final class TransitionScreenWrapper {
252         final @StringRes int header;
253         final @DrawableRes int drawable;
254         final @StringRes int subHeader;
255         final boolean showContactAdmin;
256         final boolean shouldLoop;
257 
258         TransitionScreenWrapper(@StringRes int header, @DrawableRes int drawable) {
259             this(header, drawable, /* subHeader */ 0, /* showContactAdmin */ false,
260                     /* shouldLoop */ true);
261         }
262 
263         TransitionScreenWrapper(@StringRes int header, @DrawableRes int drawable,
264                 @StringRes int subHeader, boolean showContactAdmin, boolean shouldLoop) {
265             this.header = checkNotNull(header,
266                     "Header resource id must be a positive number.");
267             this.drawable = checkNotNull(drawable,
268                     "Drawable resource id must be a positive number.");
269             this.subHeader = subHeader;
270             this.showContactAdmin = showContactAdmin;
271             this.shouldLoop = shouldLoop;
272         }
273     }
274 
275     private static final class ProvisioningModeWrapper {
276         final TransitionScreenWrapper[] transitions;
277         final @StringRes int summary;
278 
279         ProvisioningModeWrapper(TransitionScreenWrapper[] transitions, @StringRes int summary) {
280             this.transitions = checkNotNull(transitions);
281             this.summary = summary;
282         }
283     }
284 
285     static final class AnimationComponents {
286         private final TextView header;
287         private final TextView subHeader;
288         private final ImageView image;
289         private final TextView providerInfo;
290 
291         AnimationComponents(
292                 TextView header, TextView subHeader, ImageView image, TextView providerInfo) {
293             this.header = checkNotNull(header);
294             this.subHeader = checkNotNull(subHeader);
295             this.image = checkNotNull(image);
296             this.providerInfo = checkNotNull(providerInfo);
297         }
298 
299         List<View> asList() {
300             return Arrays.asList(header, subHeader, image, providerInfo);
301         }
302     }
303 }
304