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.preprovisioning;
18 
19 import static android.content.res.Configuration.UI_MODE_NIGHT_MASK;
20 import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
21 import static android.nfc.NfcAdapter.ACTION_NDEF_DISCOVERED;
22 
23 import static com.android.managedprovisioning.model.ProvisioningParams.FLOW_TYPE_LEGACY;
24 import static com.android.managedprovisioning.preprovisioning.PreProvisioningViewModel.STATE_PREPROVISIONING_INITIALIZING;
25 import static com.android.managedprovisioning.preprovisioning.PreProvisioningViewModel.STATE_SHOWING_USER_CONSENT;
26 import static com.android.managedprovisioning.provisioning.Constants.PROVISIONING_SERVICE_INTENT;
27 
28 import static com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_SETUP_FLOW;
29 
30 import android.app.Activity;
31 import android.app.DialogFragment;
32 import android.content.ComponentName;
33 import android.content.Intent;
34 import android.os.Bundle;
35 import android.provider.Settings;
36 import android.view.ContextMenu;
37 import android.view.ContextMenu.ContextMenuInfo;
38 import android.view.View;
39 import android.widget.TextView;
40 
41 import androidx.annotation.VisibleForTesting;
42 
43 import com.android.managedprovisioning.ManagedProvisioningScreens;
44 import com.android.managedprovisioning.R;
45 import com.android.managedprovisioning.analytics.MetricsWriterFactory;
46 import com.android.managedprovisioning.analytics.ProvisioningAnalyticsTracker;
47 import com.android.managedprovisioning.common.AccessibilityContextMenuMaker;
48 import com.android.managedprovisioning.common.GetProvisioningModeUtils;
49 import com.android.managedprovisioning.common.Globals;
50 import com.android.managedprovisioning.common.LogoUtils;
51 import com.android.managedprovisioning.common.ManagedProvisioningSharedPreferences;
52 import com.android.managedprovisioning.common.ProvisionLogger;
53 import com.android.managedprovisioning.common.SettingsFacade;
54 import com.android.managedprovisioning.common.SetupGlifLayoutActivity;
55 import com.android.managedprovisioning.common.SimpleDialog;
56 import com.android.managedprovisioning.common.ThemeHelper;
57 import com.android.managedprovisioning.common.ThemeHelper.DefaultNightModeChecker;
58 import com.android.managedprovisioning.common.ThemeHelper.DefaultSetupWizardBridge;
59 import com.android.managedprovisioning.common.Utils;
60 import com.android.managedprovisioning.model.ProvisioningParams;
61 import com.android.managedprovisioning.preprovisioning.PreProvisioningActivityController.UiParams;
62 import com.android.managedprovisioning.provisioning.AdminIntegratedFlowPrepareActivity;
63 import com.android.managedprovisioning.provisioning.ProvisioningActivity;
64 import com.android.managedprovisioning.provisioning.ProvisioningService;
65 
66 import com.google.android.setupcompat.util.WizardManagerHelper;
67 
68 public class PreProvisioningActivity extends SetupGlifLayoutActivity implements
69         SimpleDialog.SimpleDialogListener, PreProvisioningActivityController.Ui {
70 
71     private static final int ENCRYPT_DEVICE_REQUEST_CODE = 1;
72     @VisibleForTesting
73     protected static final int PROVISIONING_REQUEST_CODE = 2;
74     private static final int WIFI_REQUEST_CODE = 3;
75     private static final int CHANGE_LAUNCHER_REQUEST_CODE = 4;
76     private static final int ORGANIZATION_OWNED_LANDING_PAGE_REQUEST_CODE = 5;
77     private static final int GET_PROVISIONING_MODE_REQUEST_CODE = 6;
78     private static final int FINANCED_DEVICE_PREPARE_REQUEST_CODE = 7;
79     private static final int ADMIN_INTEGRATED_FLOW_PREPARE_REQUEST_CODE = 8;
80 
81     // Note: must match the constant defined in HomeSettings
82     private static final String EXTRA_SUPPORT_MANAGED_PROFILES = "support_managed_profiles";
83 
84     private static final String ERROR_AND_CLOSE_DIALOG = "PreProvErrorAndCloseDialog";
85     private static final String BACK_PRESSED_DIALOG_RESET = "PreProvBackPressedDialogReset";
86     private static final String BACK_PRESSED_DIALOG_CLOSE_ACTIVITY =
87             "PreProvBackPressedDialogCloseActivity";
88     private static final String LAUNCHER_INVALID_DIALOG = "PreProvCurrentLauncherInvalidDialog";
89 
90     private PreProvisioningActivityController mController;
91     private ControllerProvider mControllerProvider;
92     private final AccessibilityContextMenuMaker mContextMenuMaker;
93     private PreProvisioningActivityBridge mBridge;
94     private boolean mShouldForwardTransition;
95 
96     private static final String ERROR_DIALOG_RESET = "ErrorDialogReset";
97 
PreProvisioningActivity()98     public PreProvisioningActivity() {
99         this(activity ->
100                 new PreProvisioningActivityController(activity, activity),
101                 null,
102                 new Utils(),
103                 new SettingsFacade(),
104                 new ThemeHelper(
105                     new DefaultNightModeChecker(),
106                     new DefaultSetupWizardBridge()));
107     }
108 
109     @VisibleForTesting
PreProvisioningActivity( ControllerProvider controllerProvider, AccessibilityContextMenuMaker contextMenuMaker, Utils utils, SettingsFacade settingsFacade, ThemeHelper themeHelper)110     public PreProvisioningActivity(
111             ControllerProvider controllerProvider,
112             AccessibilityContextMenuMaker contextMenuMaker, Utils utils,
113             SettingsFacade settingsFacade, ThemeHelper themeHelper) {
114         super(utils, settingsFacade, themeHelper);
115         mControllerProvider = controllerProvider;
116         mContextMenuMaker =
117                 contextMenuMaker != null ? contextMenuMaker : new AccessibilityContextMenuMaker(
118                         this);
119     }
120 
121     @Override
onCreate(Bundle savedInstanceState)122     protected void onCreate(Bundle savedInstanceState) {
123         if (savedInstanceState == null) {
124             getApplicationContext().startService(PROVISIONING_SERVICE_INTENT);
125         }
126         // TODO(b/192074477): Remove deferred setup-specific logic after the managed account flow
127         //  starts ManagedProvisioning with the isSetupFlow extra
128         // TODO(b/178822333): Remove NFC-specific logic after adding support for the
129         //  admin-integrated flow
130         // This temporary fix only works when called before super.onCreate
131         if (mSettingsFacade.isDeferredSetup(getApplicationContext()) || isNfcSetup()) {
132             getIntent().putExtra(EXTRA_IS_SETUP_FLOW, true);
133         }
134 
135         super.onCreate(savedInstanceState);
136         mController = mControllerProvider.getInstance(this);
137         mBridge = createBridge();
138         mController.getState().observe(this, this::onStateChanged);
139         logMetrics();
140     }
141 
isNfcSetup()142     private boolean isNfcSetup() {
143         return ACTION_NDEF_DISCOVERED.equals(getIntent().getAction());
144     }
145 
146     @Override
onResume()147     protected void onResume() {
148         super.onResume();
149         if (mShouldForwardTransition) {
150             overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out);
151             mShouldForwardTransition = false;
152         }
153     }
154 
createBridge()155     protected PreProvisioningActivityBridge createBridge() {
156         return new PreProvisioningActivityBridgeImpl(
157                 /* activity= */ this,
158                 mUtils,
159                 PreProvisioningActivity.this::initializeLayoutParams,
160                 createBridgeCallbacks(),
161                 getThemeHelper());
162     }
163 
createBridgeCallbacks()164     protected final PreProvisioningActivityBridgeCallbacks createBridgeCallbacks() {
165         return new PreProvisioningActivityBridgeCallbacks() {
166             @Override
167             public void onTermsAccepted() {
168                 mController.continueProvisioningAfterUserConsent();
169             }
170 
171             @Override
172             public void onTermsButtonClicked() {
173                 getTransitionHelper()
174                         .startActivityWithTransition(PreProvisioningActivity.this,
175                                 mController.createViewTermsIntent());
176             }
177         };
178     }
179 
180     private void onStateChanged(Integer state) {
181         switch (state) {
182             case STATE_PREPROVISIONING_INITIALIZING:
183                 mController.initiateProvisioning(getIntent(), getCallingPackage());
184                 break;
185             case STATE_SHOWING_USER_CONSENT:
186                 mController.showUserConsentScreen();
187                 break;
188         }
189     }
190 
191     @Override
192     public void finish() {
193         // The user has backed out of provisioning, so we perform the necessary clean up steps.
194         LogoUtils.cleanUp(this);
195         ProvisioningParams params = mController.getParams();
196         if (params != null) {
197             params.cleanUp();
198         }
199         getEncryptionController().cancelEncryptionReminder();
200         getApplicationContext().stopService(PROVISIONING_SERVICE_INTENT);
201         super.finish();
202     }
203 
204     @Override
205     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
206         switch (requestCode) {
207             case ENCRYPT_DEVICE_REQUEST_CODE:
208                 if (resultCode == RESULT_CANCELED) {
209                     ProvisionLogger.loge("User canceled device encryption.");
210                 }
211                 break;
212             case PROVISIONING_REQUEST_CODE:
213                 mController.onReturnFromProvisioning();
214                 setResult(resultCode);
215                 getTransitionHelper().finishActivity(this);
216                 break;
217             case CHANGE_LAUNCHER_REQUEST_CODE:
218                 mController.continueProvisioningAfterUserConsent();
219                 break;
220             case WIFI_REQUEST_CODE:
221                 if (resultCode == RESULT_CANCELED) {
222                     ProvisionLogger.loge("User canceled wifi picking.");
223                     setResult(resultCode);
224                     getTransitionHelper().finishActivity(this);
225                 } else {
226                     if (resultCode == RESULT_OK) {
227                         ProvisionLogger.logd("Wifi request result is OK");
228                     }
229                     mController.initiateProvisioning(getIntent(), getCallingPackage());
230                 }
231                 break;
232             case ORGANIZATION_OWNED_LANDING_PAGE_REQUEST_CODE:
233             case ADMIN_INTEGRATED_FLOW_PREPARE_REQUEST_CODE:
234                 if (resultCode == RESULT_OK) {
235                     // TODO(b/177849035): Remove NFC-specific logic
236                     if (mController.getParams().isNfc) {
237                         mController.startNfcFlow();
238                     } else {
239                         handleAdminIntegratedFlowPreparerResult();
240                     }
241                 } else {
242                     ProvisionLogger.loge(
243                             "Provisioning was aborted in the preparation stage, "
244                                     + "requestCode = " + requestCode);
245                     if (isDpcInstalled()
246                             && mUtils.isOrganizationOwnedAllowed(mController.getParams())) {
247                         showFactoryResetDialog(R.string.cant_set_up_device,
248                                 R.string.contact_your_admin_for_help);
249                     } else {
250                         showErrorAndClose(
251                                 R.string.cant_set_up_device,
252                                 R.string.contact_your_admin_for_help,
253                                 "Failed provisioning device.");
254                     }
255                 }
256                 break;
257             case GET_PROVISIONING_MODE_REQUEST_CODE:
258                 mShouldForwardTransition = true;
259                 if (resultCode == RESULT_OK) {
260                     if(data != null && mController.updateProvisioningParamsFromIntent(data)) {
261                         mController.showUserConsentScreen();
262                     } else {
263                         ProvisionLogger.loge(
264                                 "Invalid data object returned from GET_PROVISIONING_MODE.");
265                         if (mUtils.isOrganizationOwnedAllowed(mController.getParams())) {
266                             showFactoryResetDialog(R.string.cant_set_up_device,
267                                     R.string.contact_your_admin_for_help);
268                         } else {
269                             showErrorAndClose(
270                                     R.string.cant_set_up_device,
271                                     R.string.contact_your_admin_for_help,
272                                     "Failed provisioning personally-owned device.");
273                         }
274                     }
275                 } else {
276                     ProvisionLogger.loge("Invalid result code from GET_PROVISIONING_MODE. Expected "
277                             + RESULT_OK + " but got " + resultCode + ".");
278                     if (mUtils.isOrganizationOwnedAllowed(mController.getParams())) {
279                         showFactoryResetDialog(R.string.cant_set_up_device,
280                                 R.string.contact_your_admin_for_help);
281                     } else {
282                         showErrorAndClose(
283                                 R.string.cant_set_up_device,
284                                 R.string.contact_your_admin_for_help,
285                                 "Failed to provision personally-owned device.");
286                     }
287                 }
288                 break;
289             case FINANCED_DEVICE_PREPARE_REQUEST_CODE:
290                 if (resultCode == RESULT_OK) {
291                     startFinancedDeviceFlow();
292                 } else {
293                     setResult(resultCode);
294                     getTransitionHelper().finishActivity(this);
295                 }
296                 break;
297             default:
298                 ProvisionLogger.logw("Unknown result code :" + resultCode);
299                 break;
300         }
301     }
302 
303     private boolean isDpcInstalled() {
304         String adminPackageName = mController.getParams().inferDeviceAdminPackageName();
305         return mUtils.isPackageInstalled(adminPackageName, getPackageManager());
306     }
307 
308     private void handleAdminIntegratedFlowPreparerResult() {
309         if (isDpcInstalled()) {
310             startAdminIntegratedFlowPostDpcInstall();
311         } else {
312             String adminPackageName = mController.getParams().inferDeviceAdminPackageName();
313             showErrorAndClose(
314                     R.string.cant_set_up_device,
315                     R.string.contact_your_admin_for_help,
316                     "Package name " + adminPackageName + " is not installed.");
317         }
318     }
319 
320     @Override
321     public void showErrorAndClose(Integer titleId, int messageId, String logText) {
322         ProvisionLogger.loge(logText);
323 
324         SimpleDialog.Builder dialogBuilder = new SimpleDialog.Builder()
325                 .setTitle(titleId)
326                 .setMessage(messageId)
327                 .setCancelable(false)
328                 .setPositiveButtonMessage(R.string.device_owner_error_ok);
329         showDialog(dialogBuilder, ERROR_AND_CLOSE_DIALOG);
330     }
331 
332     @Override
333     public void onNegativeButtonClick(DialogFragment dialog) {
334         switch (dialog.getTag()) {
335             case BACK_PRESSED_DIALOG_CLOSE_ACTIVITY:
336             case BACK_PRESSED_DIALOG_RESET:
337                 // user chose to continue. Do nothing
338                 break;
339             case LAUNCHER_INVALID_DIALOG:
340                 dialog.dismiss();
341                 break;
342             default:
343                 SimpleDialog.throwButtonClickHandlerNotImplemented(dialog);
344         }
345     }
346 
347     @Override
348     public void onPositiveButtonClick(DialogFragment dialog) {
349         switch (dialog.getTag()) {
350             case ERROR_AND_CLOSE_DIALOG:
351             case BACK_PRESSED_DIALOG_CLOSE_ACTIVITY:
352                 onProvisioningAborted();
353                 break;
354             case BACK_PRESSED_DIALOG_RESET:
355                 mUtils.factoryReset(this, "Provisioning cancelled by user on consent screen");
356                 onProvisioningAborted();
357                 break;
358             case LAUNCHER_INVALID_DIALOG:
359                 requestLauncherPick();
360                 break;
361             case ERROR_DIALOG_RESET:
362                 getUtils().factoryReset(this, "Error during preprovisioning");
363                 setResult(Activity.RESULT_CANCELED);
364                 getTransitionHelper().finishActivity(this);
365                 break;
366             default:
367                 SimpleDialog.throwButtonClickHandlerNotImplemented(dialog);
368         }
369     }
370 
371     private void onProvisioningAborted() {
372         setResult(Activity.RESULT_CANCELED);
373         mController.logPreProvisioningCancelled();
374         getTransitionHelper().finishActivity(this);
375     }
376 
377     @Override
378     public void requestEncryption(ProvisioningParams params) {
379         Intent encryptIntent = new Intent(this,
380                 getActivityForScreen(ManagedProvisioningScreens.ENCRYPT));
381         WizardManagerHelper.copyWizardManagerExtras(getIntent(), encryptIntent);
382         encryptIntent.putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, params);
383         getTransitionHelper().startActivityForResultWithTransition(
384                 this, encryptIntent, ENCRYPT_DEVICE_REQUEST_CODE);
385     }
386 
387     @Override
388     public void requestWifiPick() {
389         final Intent intent = mUtils.getWifiPickIntent();
390         WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent);
391         getTransitionHelper()
392                 .startActivityForResultWithTransition(this, intent, WIFI_REQUEST_CODE);
393     }
394 
395     @Override
396     public void showCurrentLauncherInvalid() {
397         SimpleDialog.Builder dialogBuilder = new SimpleDialog.Builder()
398                 .setCancelable(false)
399                 .setTitle(R.string.change_device_launcher)
400                 .setMessage(R.string.launcher_app_cant_be_used_by_work_profile)
401                 .setNegativeButtonMessage(R.string.cancel_provisioning)
402                 .setPositiveButtonMessage(R.string.pick_launcher);
403         showDialog(dialogBuilder, LAUNCHER_INVALID_DIALOG);
404     }
405 
406     @Override
407     public void abortProvisioning() {
408         onProvisioningAborted();
409     }
410 
411     @Override
412     public void prepareAdminIntegratedFlow(ProvisioningParams params) {
413         if (AdminIntegratedFlowPrepareActivity.shouldRunPrepareActivity(mUtils, this, params)) {
414             Intent intent = new Intent(this,
415                     getActivityForScreen(ManagedProvisioningScreens.ADMIN_INTEGRATED_PREPARE));
416             WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent);
417             intent.putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, params);
418             getTransitionHelper().startActivityForResultWithTransition(
419                     this, intent, ADMIN_INTEGRATED_FLOW_PREPARE_REQUEST_CODE);
420         } else {
421             handleAdminIntegratedFlowPreparerResult();
422         }
423     }
424 
425     private void requestLauncherPick() {
426         Intent changeLauncherIntent = new Intent(Settings.ACTION_HOME_SETTINGS);
427         changeLauncherIntent.putExtra(EXTRA_SUPPORT_MANAGED_PROFILES, true);
428         getTransitionHelper().startActivityForResultWithTransition(
429                 this, changeLauncherIntent, CHANGE_LAUNCHER_REQUEST_CODE);
430     }
431 
432     /**
433      * Starts {@link ProvisioningActivity}.
434      */
435     public void startProvisioning(ProvisioningParams params) {
436         Intent intent = new Intent(this,
437                 getActivityForScreen(ManagedProvisioningScreens.PROVISIONING));
438         WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent);
439         intent.putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, params);
440         getTransitionHelper().startActivityForResultWithTransition(
441                 this, intent, PROVISIONING_REQUEST_CODE);
442     }
443 
444     // TODO: The below group of methods do not belong in the activity.
445     // Move them to the controller instead.
446     /**
447      * Starts either the admin-integrated or the legacy flow, depending on the device state and
448      * DPC capabilities.
449      */
450     private void startAdminIntegratedFlowPostDpcInstall() {
451         boolean canPerformAdminIntegratedFlow = mUtils.canPerformAdminIntegratedFlow(
452                 this,
453                 mController.getParams(),
454                 mController.getPolicyComplianceUtils(),
455                 mController.getGetProvisioningModeUtils());
456         if (canPerformAdminIntegratedFlow) {
457             startAdminIntegratedFlowWithoutPredeterminedMode();
458         } else {
459             ProvisionLogger.loge("The admin app does not have handlers for both "
460                     + "ACTION_GET_PROVISIONING_MODE and ACTION_ADMIN_POLICY_COMPLIANCE "
461                     + "intent actions.");
462             if (mUtils.isOrganizationOwnedAllowed(mController.getParams())) {
463                 showFactoryResetDialog(R.string.cant_set_up_device,
464                         R.string.contact_your_admin_for_help);
465             } else {
466                 showErrorAndClose(
467                         R.string.cant_set_up_device,
468                         R.string.contact_your_admin_for_help,
469                         "Failed provisioning personally-owned device.");
470             }
471         }
472         mController.logProvisioningFlowType();
473     }
474 
475     private void startAdminIntegratedFlowWithoutPredeterminedMode() {
476         ProvisionLogger.logi("Starting the admin-integrated flow.");
477         GetProvisioningModeUtils provisioningModeUtils = mController.getGetProvisioningModeUtils();
478         Bundle additionalExtras = mController.getAdditionalExtrasForGetProvisioningModeIntent();
479         provisioningModeUtils.startGetProvisioningModeActivityIfResolved(
480                 this, mController.getParams(), additionalExtras,
481                 GET_PROVISIONING_MODE_REQUEST_CODE, getTransitionHelper());
482     }
483 
484     private void startFinancedDeviceFlow() {
485         ProvisionLogger.logi("Starting the financed device flow.");
486         mController.updateProvisioningFlowState(FLOW_TYPE_LEGACY);
487         mController.continueProvisioningAfterUserConsent();
488     }
489 
490     @Override
491     public void showFactoryResetDialog(Integer titleId, int messageId) {
492         SimpleDialog.Builder dialogBuilder = new SimpleDialog.Builder()
493                 .setTitle(titleId)
494                 .setMessage(messageId)
495                 .setCancelable(false)
496                 .setPositiveButtonMessage(R.string.reset);
497 
498         showDialog(dialogBuilder, ERROR_DIALOG_RESET);
499     }
500 
501     @Override
502     public void initiateUi(UiParams uiParams) {
503         mBridge.initiateUi(uiParams);
504     }
505 
506     @Override
507     public void showOwnershipDisclaimerScreen(ProvisioningParams params) {
508         Intent intent = new Intent(this,
509                 getActivityForScreen(ManagedProvisioningScreens.LANDING));
510         WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent);
511         intent.putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, params);
512         getTransitionHelper().startActivityForResultWithTransition(
513                 this, intent, ORGANIZATION_OWNED_LANDING_PAGE_REQUEST_CODE);
514     }
515 
516     @Override
517     public void prepareFinancedDeviceFlow(ProvisioningParams params) {
518         Intent intent = new Intent(this,
519                 getActivityForScreen(ManagedProvisioningScreens.FINANCED_DEVICE_LANDING));
520         WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent);
521         intent.putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, params);
522         getTransitionHelper().startActivityForResultWithTransition(
523                 this, intent, FINANCED_DEVICE_PREPARE_REQUEST_CODE);
524     }
525 
526     @Override
527     public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
528         super.onCreateContextMenu(menu, v, menuInfo);
529         if (v instanceof TextView) {
530             mContextMenuMaker.populateMenuContent(menu, (TextView) v);
531         }
532     }
533 
534     @Override
535     public void onBackPressed() {
536         if (mUtils.isOrganizationOwnedAllowed(mController.getParams())) {
537             showDialog(mUtils.createCancelProvisioningResetDialogBuilder(),
538                     BACK_PRESSED_DIALOG_RESET);
539         } else {
540             showDialog(mUtils.createCancelProvisioningDialogBuilder(),
541                     BACK_PRESSED_DIALOG_CLOSE_ACTIVITY);
542         }
543     }
544 
545     private void logMetrics() {
546         final ProvisioningAnalyticsTracker analyticsTracker =
547                 new ProvisioningAnalyticsTracker(
548                         MetricsWriterFactory.getMetricsWriter(this, new SettingsFacade()),
549                         new ManagedProvisioningSharedPreferences(this));
550         int nightMode = getResources().getConfiguration().uiMode & UI_MODE_NIGHT_MASK;
551         analyticsTracker.logIsNightMode(nightMode == UI_MODE_NIGHT_YES);
552     }
553 
554     /**
555      * Constructs {@link PreProvisioningActivityController} for a given {@link
556      * PreProvisioningActivity}
557      */
558     interface ControllerProvider {
559         /**
560          * Constructs {@link PreProvisioningActivityController} for a given {@link
561          * PreProvisioningActivity}
562          */
563         PreProvisioningActivityController getInstance(PreProvisioningActivity activity);
564     }
565 }
566