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 
17 package com.android.managedprovisioning.provisioning;
18 
19 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE;
20 
21 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_PROVISIONING_ACTIVITY_TIME_MS;
22 import static com.android.internal.util.Preconditions.checkNotNull;
23 
24 import static java.util.Objects.requireNonNull;
25 
26 import android.annotation.IntDef;
27 import android.app.Activity;
28 import android.app.admin.DevicePolicyManager;
29 import android.content.Intent;
30 import android.os.Bundle;
31 import android.os.UserHandle;
32 import android.view.ViewGroup;
33 
34 import androidx.annotation.VisibleForTesting;
35 
36 import com.android.managedprovisioning.ManagedProvisioningScreens;
37 import com.android.managedprovisioning.R;
38 import com.android.managedprovisioning.common.ManagedProvisioningSharedPreferences;
39 import com.android.managedprovisioning.common.PolicyComplianceUtils;
40 import com.android.managedprovisioning.common.ProvisionLogger;
41 import com.android.managedprovisioning.common.SettingsFacade;
42 import com.android.managedprovisioning.common.ThemeHelper;
43 import com.android.managedprovisioning.common.ThemeHelper.DefaultNightModeChecker;
44 import com.android.managedprovisioning.common.ThemeHelper.DefaultSetupWizardBridge;
45 import com.android.managedprovisioning.common.Utils;
46 import com.android.managedprovisioning.finalization.PreFinalizationController;
47 import com.android.managedprovisioning.finalization.UserProvisioningStateHelper;
48 import com.android.managedprovisioning.model.ProvisioningParams;
49 import com.android.managedprovisioning.provisioning.TransitionAnimationHelper.TransitionAnimationCallback;
50 import com.android.managedprovisioning.provisioning.TransitionAnimationHelper.TransitionAnimationStateManager;
51 
52 import com.airbnb.lottie.LottieAnimationView;
53 import com.google.android.setupcompat.logging.ScreenKey;
54 import com.google.android.setupcompat.logging.SetupMetric;
55 import com.google.android.setupcompat.logging.SetupMetricsLogger;
56 import com.google.android.setupcompat.util.WizardManagerHelper;
57 import com.google.android.setupdesign.util.Partner;
58 
59 
60 import java.lang.annotation.Retention;
61 import java.lang.annotation.RetentionPolicy;
62 import java.util.Collections;
63 import java.util.HashMap;
64 import java.util.Map;
65 
66 /**
67  * Progress activity shown whilst provisioning is ongoing.
68  *
69  * <p>This activity registers for updates of the provisioning process from the
70  * {@link ProvisioningManager}. It shows progress updates as provisioning progresses and handles
71  * showing of cancel and error dialogs.</p>
72  */
73 public class ProvisioningActivity extends AbstractProvisioningActivity
74         implements TransitionAnimationCallback, TransitionAnimationStateManager {
75     private static final int RESULT_CODE_COMPLETE_DEVICE_FINANCE = 121;
76     /*
77      * Returned after the work profile has been completed. Note this is before launching the DPC.
78      */
79     @VisibleForTesting
80     static final int RESULT_CODE_WORK_PROFILE_CREATED = 122;
81     /*
82      * Returned after the device owner has been set. Note this is before launching the DPC.
83      */
84     @VisibleForTesting
85     static final int RESULT_CODE_DEVICE_OWNER_SET = 123;
86 
87     static final int PROVISIONING_MODE_WORK_PROFILE = 1;
88     static final int PROVISIONING_MODE_FULLY_MANAGED_DEVICE = 2;
89     static final int PROVISIONING_MODE_WORK_PROFILE_ON_FULLY_MANAGED_DEVICE = 3;
90     static final int PROVISIONING_MODE_FINANCED_DEVICE = 4;
91     static final int PROVISIONING_MODE_WORK_PROFILE_ON_ORG_OWNED_DEVICE = 5;
92     private static final String SETUP_METRIC_PROVISIONING_SCREEN_NAME = "ShowProvisioningScreens";
93     private static String SETUP_METRIC_PROVISIONING_ENDED_NAME = "ProvisioningEnded";
94     private ViewGroup mButtonFooterContainer;
95 
96     @IntDef(prefix = { "PROVISIONING_MODE_" }, value = {
97         PROVISIONING_MODE_WORK_PROFILE,
98         PROVISIONING_MODE_FULLY_MANAGED_DEVICE,
99         PROVISIONING_MODE_WORK_PROFILE_ON_FULLY_MANAGED_DEVICE,
100         PROVISIONING_MODE_FINANCED_DEVICE,
101         PROVISIONING_MODE_WORK_PROFILE_ON_ORG_OWNED_DEVICE
102     })
103     @Retention(RetentionPolicy.SOURCE)
104     @interface ProvisioningMode {}
105 
106     private static final Map<Integer, Integer> PROVISIONING_MODE_TO_PROGRESS_LABEL = Map.of(
107                 PROVISIONING_MODE_WORK_PROFILE, R.string.work_profile_provisioning_progress_label,
108                 PROVISIONING_MODE_FULLY_MANAGED_DEVICE,
109                     R.string.fully_managed_device_provisioning_progress_label,
110                 PROVISIONING_MODE_WORK_PROFILE_ON_FULLY_MANAGED_DEVICE,
111                     R.string.fully_managed_device_provisioning_progress_label,
112                 PROVISIONING_MODE_FINANCED_DEVICE, R.string.just_a_sec,
113                 PROVISIONING_MODE_WORK_PROFILE_ON_ORG_OWNED_DEVICE,
114                     R.string.work_profile_provisioning_progress_label);
115 
116     private UserProvisioningStateHelper mUserProvisioningStateHelper;
117     private PolicyComplianceUtils mPolicyComplianceUtils;
118     private ProvisioningManager mProvisioningManager;
119     private ProvisioningActivityBridge mBridge;
120 
ProvisioningActivity()121     public ProvisioningActivity() {
122         this(
123                 /* provisioningManager */ null, // defined in getProvisioningManager()
124                 new Utils(),
125                 /* userProvisioningStateHelper */ null, // defined in onCreate()
126                 new PolicyComplianceUtils(),
127                 new SettingsFacade(),
128                 new ThemeHelper(new DefaultNightModeChecker(), new DefaultSetupWizardBridge()));
129     }
130 
131     @VisibleForTesting
ProvisioningActivity(ProvisioningManager provisioningManager, Utils utils, UserProvisioningStateHelper userProvisioningStateHelper, PolicyComplianceUtils policyComplianceUtils, SettingsFacade settingsFacade, ThemeHelper themeHelper)132     public ProvisioningActivity(ProvisioningManager provisioningManager,
133             Utils utils,
134             UserProvisioningStateHelper userProvisioningStateHelper,
135             PolicyComplianceUtils policyComplianceUtils,
136             SettingsFacade settingsFacade,
137             ThemeHelper themeHelper) {
138         super(utils, settingsFacade, themeHelper);
139         mProvisioningManager = provisioningManager;
140         mUserProvisioningStateHelper = userProvisioningStateHelper;
141         mPolicyComplianceUtils = checkNotNull(policyComplianceUtils);
142     }
143 
144     @Override
onCreate(Bundle savedInstanceState)145     protected void onCreate(Bundle savedInstanceState) {
146         super.onCreate(savedInstanceState);
147 
148         setupMetricScreenName = SETUP_METRIC_PROVISIONING_SCREEN_NAME;
149         mScreenKey = ScreenKey.of(setupMetricScreenName, this);
150 
151         SetupMetricsLogger.logMetrics(this, mScreenKey,
152                 SetupMetric.ofImpression(setupMetricScreenName));
153 
154         mBridge = createBridge();
155         mBridge.initiateUi(/* activity= */ this);
156 
157         // assign this Activity as the view store owner to access saved state and receive updates
158         getProvisioningManager().setViewModelStoreOwner(this);
159 
160         if (mUserProvisioningStateHelper == null) {
161             mUserProvisioningStateHelper = new UserProvisioningStateHelper(this);
162         }
163 
164         if (mState == STATE_PROVISIONING_FINALIZED) {
165             updateProvisioningFinalizedScreen();
166         }
167 
168         writeSharedPreferences();
169     }
170 
writeSharedPreferences()171     private void writeSharedPreferences() {
172         ManagedProvisioningSharedPreferences sharedPreferences =
173                 new ManagedProvisioningSharedPreferences(this);
174         sharedPreferences.writeNavigationBarColor(getWindow().getNavigationBarColor());
175         sharedPreferences.writeNavigationBarDividerColor(
176                 getWindow().getNavigationBarDividerColor());
177         sharedPreferences.writeTextPrimaryColor(mUtils.getTextPrimaryColor(this));
178         sharedPreferences.writeTextSecondaryColor(mUtils.getTextSecondaryColor(this));
179         sharedPreferences.writeBackgroundColor(mUtils.getBackgroundColor(this));
180         sharedPreferences.writeAccentColor(mUtils.getAccentColor(this));
181         sharedPreferences.writeNotificationBackgroundColor(
182                 Partner.getColor(this, R.color.setup_notification_bg_color));
183     }
184 
createBridge()185     protected ProvisioningActivityBridge createBridge() {
186         return ProvisioningActivityBridgeImpl.builder()
187                 .setParams(mParams)
188                 .setUtils(mUtils)
189                 .setProvisioningMode(getProvisioningMode())
190                 .setProvisioningManager(getProvisioningManager())
191                 .setTransitionAnimationCallback(this)
192                 .setInitializeLayoutParamsConsumer(
193                         ProvisioningActivity.this::initializeLayoutParams)
194                 .setShouldSkipEducationScreens(shouldSkipEducationScreens())
195                 .setProgressLabelResId(getProgressLabelResId())
196                 .setBridgeCallbacks(createCallbacks())
197                 .setStateManager(this)
198                 .build();
199     }
200 
getProgressLabelResId()201     protected Integer getProgressLabelResId() {
202         return PROVISIONING_MODE_TO_PROGRESS_LABEL.get(getProvisioningMode());
203     }
204 
createCallbacks()205     protected final ProvisioningActivityBridgeCallbacks createCallbacks() {
206         return new ProvisioningActivityBridgeCallbacks() {
207             @Override
208             public void onNextButtonClicked() {
209                 ProvisioningActivity.this.onNextButtonClicked();
210             }
211 
212             @Override
213             public void onAbortButtonClicked() {
214                 ProvisioningActivity.this.onAbortButtonClicked();
215             }
216 
217             @Override
218             public boolean isProvisioningFinalized() {
219                 return mState == STATE_PROVISIONING_FINALIZED;
220             }
221         };
222     }
223 
224     @Override
225     protected ProvisioningManager getProvisioningManager() {
226         if (mProvisioningManager == null) {
227             mProvisioningManager = ProvisioningManager.getInstance(this);
228         }
229         return mProvisioningManager;
230     }
231 
232     @VisibleForTesting
233     protected void setProvisioningManager(ProvisioningManager provisioningManager) {
234         mProvisioningManager = requireNonNull(provisioningManager);
235     }
236 
237     @Override
238     public void preFinalizationCompleted() {
239         if (mState == STATE_PROVISIONING_COMPLETED || mState == STATE_PROVISIONING_FINALIZED) {
240             return;
241         }
242 
243         if (!validatePolicyComplianceExists()) {
244             ProvisionLogger.loge("POLICY_COMPLIANCE handler not implemented by the admin app.");
245             error(R.string.cant_set_up_device,
246                     R.string.contact_your_admin_for_help,
247                     /* resetRequired */ mParams.isOrganizationOwnedProvisioning);
248             return;
249         }
250 
251         ProvisionLogger.logi("ProvisioningActivity pre-finalization completed");
252 
253         // TODO(183094412): Decouple state from AbstractProvisioningActivity
254         mState = STATE_PROVISIONING_COMPLETED;
255 
256         if (shouldSkipEducationScreens()
257                 || mBridge.shouldShowButtonsWhenPreProvisioningCompletes()) {
258             updateProvisioningFinalizedScreen();
259         }
260     }
261 
262     // Enforces DPCs to implement the POLICY_COMPLIANCE handler for NFC and financed device
263     // provisioning, since we no longer set up the DPC on setup wizard's exit procedure.
264     // No need to verify it for the other flows, as that was already done earlier.
265     // TODO(b/177849035): Remove financed device-specific logic
266     private boolean validatePolicyComplianceExists() {
267         if (!mUtils.isFinancedDeviceAction(mParams.provisioningAction)) {
268             return true;
269         }
270         return mPolicyComplianceUtils.isPolicyComplianceActivityResolvableForManagedUser(
271                 this, mParams, mUtils);
272     }
273 
274     protected final void updateProvisioningFinalizedScreen() {
275         mBridge.onProvisioningFinalized(/* activity= */ this);
276 
277         SetupMetricsLogger.logMetrics(this, mScreenKey,
278                 SetupMetric.ofWaitingEnd(SETUP_METRIC_PROVISIONING_ENDED_NAME));
279 
280         // TODO(183094412): Decouple state from AbstractProvisioningActivity
281         mState = STATE_PROVISIONING_FINALIZED;
282     }
283 
284     @VisibleForTesting
285     protected void onNextButtonClicked() {
286         markDeviceManagementEstablishedAndFinish();
287     }
288 
289     @VisibleForTesting
290     protected void onAbortButtonClicked() {
291         final Intent intent = new Intent(this,
292                 getActivityForScreen(ManagedProvisioningScreens.RESET_AND_RETURN_DEVICE));
293         WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent);
294         getTransitionHelper().startActivityWithTransition(this, intent);
295     }
296 
297     private void finishActivity() {
298         if (mParams.provisioningAction.equals(ACTION_PROVISION_FINANCED_DEVICE)) {
299             setResult(RESULT_CODE_COMPLETE_DEVICE_FINANCE);
300         } else {
301             setResult(Activity.RESULT_OK);
302         }
303         getTransitionHelper().finishActivity(this);
304     }
305 
306     private void markDeviceManagementEstablishedAndFinish() {
307         new PreFinalizationController(this, mUserProvisioningStateHelper)
308                 .deviceManagementEstablished(mParams);
309         if (mParams.flowType == ProvisioningParams.FLOW_TYPE_ADMIN_INTEGRATED) {
310             if (mUtils.isProfileOwnerAction(mParams.provisioningAction)) {
311                 setResult(RESULT_CODE_WORK_PROFILE_CREATED);
312             } else if (mUtils.isDeviceOwnerAction(mParams.provisioningAction)) {
313                 setResult(RESULT_CODE_DEVICE_OWNER_SET);
314             } else if (mUtils.isFinancedDeviceAction(mParams.provisioningAction)) {
315                 setResult(RESULT_CODE_COMPLETE_DEVICE_FINANCE);
316             }
317             getTransitionHelper().finishActivity(this);
318         } else {
319             finishActivity();
320         }
321     }
322 
323     @Override
324     protected int getMetricsCategory() {
325         return PROVISIONING_PROVISIONING_ACTIVITY_TIME_MS;
326     }
327 
328     @Override
329     protected void decideCancelProvisioningDialog() {
330         // TODO(b/213306538): Improve behaviour when cancelling BYOD mid-provisioning
331         if (!mParams.isOrganizationOwnedProvisioning) {
332             return;
333         }
334 
335         if (getUtils().isDeviceOwnerAction(mParams.provisioningAction)
336                 || mParams.isOrganizationOwnedProvisioning) {
337             showCancelProvisioningDialog(/* resetRequired = */true);
338         } else {
339             showCancelProvisioningDialog(/* resetRequired = */false);
340         }
341     }
342 
343     @Override
344     protected void onStart() {
345         super.onStart();
346         mBridge.onStart(this);
347     }
348 
349     @Override
350     protected void onStop() {
351         super.onStop();
352         mBridge.onStop();
353         // remove this Activity as the view store owner to avoid memory leaks
354         if (isFinishing()) {
355             getProvisioningManager().clearViewModelStoreOwner();
356         }
357     }
358 
359     @Override
360     public void onAllTransitionsShown() {
361         if (mState == STATE_PROVISIONING_COMPLETED) {
362             updateProvisioningFinalizedScreen();
363         }
364     }
365 
366     @Override
367     public void onAnimationSetup(LottieAnimationView animationView) {
368         getThemeHelper().setupAnimationDynamicColors(this, animationView, getIntent());
369     }
370 
371     @Override
372     public void saveState(TransitionAnimationHelper.TransitionAnimationState state) {
373         getProvisioningManager().saveTransitionAnimationState(state);
374     }
375 
376     @Override
377     public TransitionAnimationHelper.TransitionAnimationState restoreState() {
378         return getProvisioningManager().restoreTransitionAnimationState();
379     }
380 
381     @Override
382     protected boolean isWaitingScreen() {
383         return shouldSkipEducationScreens();
384     }
385 
386     protected @ProvisioningMode int getProvisioningMode() {
387         int provisioningMode = 0;
388         final boolean isProfileOwnerAction =
389                 mUtils.isProfileOwnerAction(mParams.provisioningAction);
390         if (isProfileOwnerAction) {
391             if (getSystemService(DevicePolicyManager.class).isDeviceManaged()) {
392                 provisioningMode = PROVISIONING_MODE_WORK_PROFILE_ON_FULLY_MANAGED_DEVICE;
393             } else if (mParams.isOrganizationOwnedProvisioning) {
394                 provisioningMode = PROVISIONING_MODE_WORK_PROFILE_ON_ORG_OWNED_DEVICE;
395             } else {
396                 provisioningMode = PROVISIONING_MODE_WORK_PROFILE;
397             }
398         } else if (mUtils.isDeviceOwnerAction(mParams.provisioningAction)) {
399             provisioningMode = PROVISIONING_MODE_FULLY_MANAGED_DEVICE;
400         } else if (mUtils.isFinancedDeviceAction(mParams.provisioningAction)) {
401             provisioningMode = PROVISIONING_MODE_FINANCED_DEVICE;
402         }
403         return provisioningMode;
404     }
405 
406     protected boolean shouldSkipEducationScreens() {
407         return mParams.skipEducationScreens
408                 || getProvisioningMode() == PROVISIONING_MODE_WORK_PROFILE_ON_FULLY_MANAGED_DEVICE
409                 || getProvisioningMode() == PROVISIONING_MODE_FINANCED_DEVICE;
410     }
411 }
412