1 /*
2  * Copyright (C) 2020 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.car.provision;
18 
19 import static android.app.Activity.RESULT_CANCELED;
20 import static android.app.Activity.RESULT_FIRST_USER;
21 import static android.app.Activity.RESULT_OK;
22 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE;
23 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
24 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION;
25 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM;
26 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_TRIGGER;
27 import static android.app.admin.DevicePolicyManager.PROVISIONING_TRIGGER_QR_CODE;
28 import static android.car.settings.CarSettings.Secure.KEY_ENABLE_INITIAL_NOTICE_SCREEN_TO_USER;
29 import static android.car.settings.CarSettings.Secure.KEY_SETUP_WIZARD_IN_PROGRESS;
30 
31 import android.Manifest.permission;
32 import android.app.Activity;
33 import android.app.AlertDialog;
34 import android.app.Notification;
35 import android.app.NotificationChannel;
36 import android.app.NotificationManager;
37 import android.app.admin.DevicePolicyManager;
38 import android.content.BroadcastReceiver;
39 import android.content.ComponentName;
40 import android.content.Context;
41 import android.content.Intent;
42 import android.content.IntentFilter;
43 import android.content.pm.PackageInfo;
44 import android.content.pm.PackageManager;
45 import android.os.Bundle;
46 import android.os.SystemProperties;
47 import android.os.UserHandle;
48 import android.os.UserManager;
49 import android.provider.Settings;
50 import android.provider.Settings.SettingNotFoundException;
51 import android.util.Log;
52 import android.view.View;
53 import android.view.WindowInsets;
54 import android.view.WindowInsetsController;
55 import android.widget.ArrayAdapter;
56 import android.widget.Button;
57 import android.widget.Spinner;
58 import android.widget.TextView;
59 
60 import com.android.car.setupwizardlib.util.CarDrivingStateMonitor;
61 
62 import java.io.FileDescriptor;
63 import java.io.PrintWriter;
64 import java.util.ArrayList;
65 import java.util.List;
66 
67 /**
68  * Reference implementeation for a Car SetupWizard.
69  *
70  * <p>Features:
71  *
72  * <ul>
73  *   <li>Shows UI where user can confirm setup.
74  *   <li>Listen to UX restriction events, so it exits setup when the car moves.
75  *   <li>Add option to setup managed-provisioning mode.
76  *   <li>Sets car-specific properties.
77  * </ul>
78  *
79  * <p>By default, it doesn't show the UI, unless the {@code persist.dev.car_provision.show_ui}
80  * property is set to {@code true}. For example, you can change it by running something like:
81  <pre><code>
82      adb root
83      adb shell setprop persist.dev.car_provision.show_ui true && \
84      adb shell pm enable --user cur com.android.car.provision/.DefaultActivity &&\
85      adb shell settings put secure --user cur user_setup_complete 0 && \
86      adb shell settings put secure --user 0 user_setup_complete 0 &&\
87      adb shell settings put global device_provisioned 0 &&\
88      adb shell rm -f /data/system/device_policies_version &&\
89      adb shell rm -f /data/system/device_policies.xml &&\
90      adb shell rm -f /data/system/device_owner_2.xml ;\
91      adb shell rm -f /data/system/users/`adb shell am get-current-user`/profile_owner.xml
92      adb shell stop && adb shell start
93   <code></pre>
94  */
95 public final class DefaultActivity extends Activity {
96 
97     static final String TAG = "CarProvision";
98 
99     // TODO(b/170333009): copied from ManagedProvisioning app, as they're hidden;
100     private static final String PROVISION_FINALIZATION_INSIDE_SUW =
101             "android.app.action.PROVISION_FINALIZATION_INSIDE_SUW";
102     private static final int RESULT_CODE_PROFILE_OWNER_SET = 122;
103     private static final int RESULT_CODE_DEVICE_OWNER_SET = 123;
104 
105 
106     private static final int REQUEST_CODE_STEP1 = 42;
107     private static final int REQUEST_CODE_STEP2_PO = 43;
108     private static final int REQUEST_CODE_STEP2_DO = 44;
109 
110     private static final int NOTIFICATION_ID = 108;
111     private static final String IMPORTANCE_DEFAULT_ID = "importance_default";
112 
113     private static final List<DpcInfo> sSupportedDpcApps = new ArrayList<>(2);
114 
115     private static final String TEST_DPC_NAME = "TestDPC (downloadable)";
116     private static final String TEST_DPC_PACKAGE = "com.afwsamples.testdpc";
117     private static final String TEST_DPC_LEGACY_ACTIVITY = TEST_DPC_PACKAGE
118             + ".SetupManagementLaunchActivity";
119     private static final String TEST_DPC_RECEIVER = TEST_DPC_PACKAGE
120             + ".DeviceAdminReceiver";
121     private static final String LOCAL_TEST_DPC_NAME = "TestDPC (local only)";
122 
123     private static final String SHOW_UI_SYSTEM_PROPERTY = "persist.dev.car_provision.show_ui";
124 
125     static {
126         DpcInfo testDpc = new DpcInfo(TEST_DPC_NAME,
127                 TEST_DPC_PACKAGE,
128                 TEST_DPC_LEGACY_ACTIVITY,
129                 TEST_DPC_RECEIVER,
130                 "gJD2YwtOiWJHkSMkkIfLRlj-quNqG1fb6v100QmzM9w=",
131                 "https://testdpc-latest-apk.appspot.com/preview");
132         // Locally-built version of the TestDPC
133         DpcInfo localTestDpc = new DpcInfo(LOCAL_TEST_DPC_NAME,
134                 TEST_DPC_PACKAGE,
135                 TEST_DPC_LEGACY_ACTIVITY,
136                 TEST_DPC_RECEIVER,
137                 /* checkSum= */ null,
138                 /* downloadUrl = */ null);
139         sSupportedDpcApps.add(testDpc);
140         sSupportedDpcApps.add(localTestDpc);
141     }
142 
143     private CarDrivingStateMonitor mCarDrivingStateMonitor;
144 
145     private TextView mErrorsTextView;
146     private Button mFinishSetupButton;
147     private Button mFactoryResetButton;
148     private Spinner mDpcAppsSpinner;
149     private Button mLegacyProvisioningWorkflowButton;
150     private Button mProvisioningWorkflowButton;
151 
152     private final BroadcastReceiver mDrivingStateExitReceiver = new BroadcastReceiver() {
153         @Override
154         public void onReceive(Context context, Intent intent) {
155             Log.d(TAG, "onReceive(): " + intent);
156             exitSetup();
157         }
158     };
159 
160     @Override
onCreate(Bundle icicle)161     protected void onCreate(Bundle icicle) {
162         super.onCreate(icicle);
163 
164         int userId = getUserId();
165         Log.i(TAG, "onCreate() for user " + userId + " Intent: " + getIntent());
166 
167         if (userId == UserHandle.USER_SYSTEM && UserManager.isHeadlessSystemUserMode()) {
168             // System user will be provisioned together with the first non-system user
169             Log.i(TAG, "onCreate(): skipping setup on headless system user");
170             disableSelfAndFinish();
171             return;
172         }
173 
174         if (!showUi()) {
175             Log.w(TAG, "onCreate(): skipping UI because " + SHOW_UI_SYSTEM_PROPERTY
176                     + " was not set to true");
177             finishSetup();
178             return;
179         }
180 
181         DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
182         if (dpm.isDeviceManaged()) {
183             Log.i(TAG, "onCreate(): skipping UI on managed device");
184             finishSetup();
185             return;
186         }
187 
188         setCarSetupInProgress(true);
189         setContentView(R.layout.default_activity);
190 
191         mErrorsTextView = findViewById(R.id.error_message);
192         mFinishSetupButton = findViewById(R.id.finish_setup);
193         mFactoryResetButton = findViewById(R.id.factory_reset);
194         mDpcAppsSpinner = findViewById(R.id.dpc_apps);
195         mLegacyProvisioningWorkflowButton = findViewById(R.id.legacy_provision_workflow);
196         mProvisioningWorkflowButton = findViewById(R.id.provision_workflow);
197 
198         mLegacyProvisioningWorkflowButton
199                 .setOnClickListener((v) -> launchLegacyProvisioningWorkflow());
200         mProvisioningWorkflowButton.setOnClickListener((v) -> launchProvisioningWorkflow());
201         mFinishSetupButton.setOnClickListener((v) -> finishSetup());
202         mFactoryResetButton.setOnClickListener((v) -> factoryReset());
203 
204         hideSystemUi();
205         updateUi();
206         setManagedProvisioning(dpm);
207         startMonitor();
208     }
209 
showUi()210     private boolean showUi() {
211         boolean result = false;
212         try {
213             result = SystemProperties.getBoolean(SHOW_UI_SYSTEM_PROPERTY, false);
214         } catch (Exception e) {
215             Log.w(TAG, "error getting property " + SHOW_UI_SYSTEM_PROPERTY);
216         }
217         return result;
218     }
219 
startMonitor()220     private void startMonitor() {
221         Log.d(TAG, "startMonitor()");
222         registerReceiver(mDrivingStateExitReceiver,
223                 new IntentFilter(CarDrivingStateMonitor.EXIT_BROADCAST_ACTION),
224                 permission.DISPATCH_PROVISIONING_MESSAGE, /* scheduler= */ null,
225                 Context.RECEIVER_EXPORTED);
226 
227         mCarDrivingStateMonitor = CarDrivingStateMonitor.get(this);
228         mCarDrivingStateMonitor.startMonitor();
229     }
230 
231     @Override
finish()232     public void finish() {
233         Log.i(TAG, "finish() for user " + getUserId());
234 
235         stopMonitor();
236 
237         super.finish();
238     };
239 
240     @Override
dump(String prefix, FileDescriptor fd, PrintWriter pw, String[] args)241     public void dump(String prefix, FileDescriptor fd, PrintWriter pw, String[] args) {
242         if (args == null || args.length == 0) {
243             showDpcs(pw);
244             showHelp(pw);
245             return;
246         }
247 
248         if (args[0].equals("--help")) {
249             showHelp(pw);
250             return;
251         }
252 
253         addDpc(pw, args);
254     };
255 
showDpcs(PrintWriter pw)256     private void showDpcs(PrintWriter pw) {
257         pw.printf("%d DPCs\n", sSupportedDpcApps.size());
258         sSupportedDpcApps.forEach((dpc) -> pw.printf("\t%s\n", dpc));
259     }
260 
showHelp(PrintWriter pw)261     private void showHelp(PrintWriter pw) {
262         pw.println("\nTo add a new DPC, use: --name name --package-name package-name"
263                 + "--receiver-name receiver-name [--legacy-activity-name legacy-activity-name] "
264                 + "[--checksum checksum] [--download-url download-url]");
265     }
266 
addDpc(PrintWriter pw, String[] args)267     private void addDpc(PrintWriter pw, String[] args) {
268         String name = null;
269         String packageName = null;
270         String legacyActivityName = null;
271         String receiverName = null;
272         String checkSum = null;
273         String downloadUrl = null;
274 
275         for (int i = 0; i < args.length; i++) {
276             try {
277                 switch (args[i]) {
278                     case "--name":
279                         name = args[++i];
280                         break;
281                     case "--package-name":
282                         packageName = args[++i];
283                         break;
284                     case "--legacy-activity-name":
285                         legacyActivityName = args[++i];
286                         break;
287                     case "--receiver-name":
288                         receiverName = args[++i];
289                         break;
290                     case "--checksum":
291                         checkSum = args[++i];
292                         break;
293                     case "--download-url":
294                         downloadUrl = args[++i];
295                         break;
296                     default:
297                         pw.printf("Invalid option at index %d: %s\n", i, args[i]);
298                         return;
299                 }
300             } catch (Exception e) {
301                 // most likely a missing arg...
302                 pw.printf("Error handing arg %d: %s\n", i, e);
303                 return;
304             }
305         }
306 
307         DpcInfo dpc = new DpcInfo(name, packageName, legacyActivityName, receiverName, checkSum,
308                 downloadUrl);
309         Log.i(TAG, "Adding new DPC from dump(): " + dpc);
310         sSupportedDpcApps.add(dpc);
311         pw.printf("Added new DPC: %s\n", dpc);
312 
313         updateUi();
314     }
315 
stopMonitor()316     private void stopMonitor() {
317         Log.d(TAG, "stopMonitor()");
318 
319         if (mCarDrivingStateMonitor == null) {
320             // Happens when device is managed (and startMonitor() is skipped)
321             Log.d(TAG, "Already stopped (or never stopped)");
322             return;
323         }
324 
325         if (mDrivingStateExitReceiver != null) {
326             unregisterReceiver(mDrivingStateExitReceiver);
327         }
328 
329         mCarDrivingStateMonitor.stopMonitor();
330         mCarDrivingStateMonitor = null;
331     }
332 
hideSystemUi()333     private void hideSystemUi() {
334         WindowInsetsController insetsController = getWindow().getDecorView()
335                 .getWindowInsetsController();
336         if (insetsController == null) {
337             Log.w(TAG, "No insets controller");
338             return;
339         }
340         Log.d(TAG, "Hiding the system UI bars");
341         insetsController.hide(WindowInsets.Type.navigationBars());
342     }
343 
updateUi()344     private void updateUi() {
345         String[] appNames = new String[sSupportedDpcApps.size()];
346         for (int i = 0; i < sSupportedDpcApps.size(); i++) {
347             appNames[i] = sSupportedDpcApps.get(i).name;
348         }
349         mDpcAppsSpinner.setAdapter(new ArrayAdapter<String>(this,
350                 android.R.layout.simple_spinner_item, appNames));
351         mDpcAppsSpinner.setSelection(appNames.length - 1);
352     }
353 
setManagedProvisioning(DevicePolicyManager dpm)354     private void setManagedProvisioning(DevicePolicyManager dpm) {
355         if (!getPackageManager()
356                 .hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)) {
357             Log.i(TAG, "Disabling provisioning buttons because device does not have the "
358                     + PackageManager.FEATURE_DEVICE_ADMIN + " feature");
359             return;
360         }
361         if (!dpm.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE)) {
362             Log.w(TAG, "Disabling provisioning buttons because device cannot be provisioned - "
363                     + "it can only be set on first boot");
364             return;
365         }
366 
367         mProvisioningWorkflowButton.setEnabled(true);
368         mLegacyProvisioningWorkflowButton.setEnabled(true);
369     }
370 
checkDpcAppExists(String dpcApp)371     private boolean checkDpcAppExists(String dpcApp) {
372         if (!checkAppExists(dpcApp, UserHandle.USER_SYSTEM)) return false;
373         if (!checkAppExists(dpcApp, getUserId())) return false;
374         return true;
375     }
376 
checkAppExists(String app, int userId)377     private boolean checkAppExists(String app, int userId) {
378         Log.d(TAG, "Checking if " + app + " exits for user " + userId);
379         try {
380             PackageInfo info = getPackageManager().getPackageInfoAsUser(app, /* flags= */ 0,
381                     userId);
382             if (info == null) {
383                 Log.i(TAG, "No app " + app + " for user " + userId);
384                 return false;
385             }
386             Log.d(TAG, "Found it: " + info);
387             return true;
388         } catch (PackageManager.NameNotFoundException e) {
389             return false;
390         } catch (Exception e) {
391             Log.e(TAG, "Error checking if " + app + " exists for user " + userId, e);
392             return false;
393         }
394     }
395 
finishSetup()396     private void finishSetup() {
397         Log.i(TAG, "finishing setup for user " + getUserId());
398         provisionUserAndDevice();
399         disableSelfAndFinish();
400     }
401 
factoryReset()402     private void factoryReset() {
403         new AlertDialog.Builder(this).setMessage(R.string.factory_reset_warning)
404             .setPositiveButton(android.R.string.ok, (d, w)->sendFactoryResetIntent())
405             .show();
406     }
407 
sendFactoryResetIntent()408     private void sendFactoryResetIntent() {
409         provisionUserAndDevice();
410 
411         Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
412         intent.setPackage("android");
413         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
414         intent.putExtra(Intent.EXTRA_REASON, "Requested by user on SUW");
415 
416         Log.i(TAG, "factory resetting device with intent " + intent);
417         sendBroadcast(intent);
418 
419         disableSelfAndFinish();
420     }
421 
provisionUserAndDevice()422     private void provisionUserAndDevice() {
423         Log.d(TAG, "setting Settings properties");
424         // Add a persistent setting to allow other apps to know the device has been provisioned.
425         if (!isDeviceProvisioned()) {
426             Settings.Global.putInt(getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 1);
427         }
428 
429         maybeMarkSystemUserSetupComplete();
430         Log.v(TAG, "Marking USER_SETUP_COMPLETE for user " + getUserId());
431         markUserSetupComplete(this);
432 
433         // Set car-specific properties
434         setCarSetupInProgress(false);
435         Settings.Secure.putInt(getContentResolver(), KEY_ENABLE_INITIAL_NOTICE_SCREEN_TO_USER, 0);
436     }
437 
isDeviceProvisioned()438     private boolean isDeviceProvisioned() {
439         try {
440             return Settings.Global.getInt(getContentResolver(),
441                     Settings.Global.DEVICE_PROVISIONED) == 1;
442         } catch (SettingNotFoundException e) {
443             Log.wtf(TAG, "DEVICE_PROVISIONED is not found.");
444             return false;
445         }
446     }
447 
isUserSetupComplete(Context context)448     private boolean isUserSetupComplete(Context context) {
449         return Settings.Secure.getInt(context.getContentResolver(),
450                 Settings.Secure.USER_SETUP_COMPLETE, /* default= */ 0) == 1;
451     }
452 
maybeMarkSystemUserSetupComplete()453     private void maybeMarkSystemUserSetupComplete() {
454         Context systemUserContext = getApplicationContext().createContextAsUser(
455                 UserHandle.SYSTEM, /* flags= */ 0);
456         if (!isUserSetupComplete(systemUserContext) && getUserId() != UserHandle.USER_SYSTEM
457                 && UserManager.isHeadlessSystemUserMode()) {
458             Log.v(TAG, "Marking USER_SETUP_COMPLETE for system user");
459             markUserSetupComplete(systemUserContext);
460         }
461     }
462 
setCarSetupInProgress(boolean inProgress)463     private void setCarSetupInProgress(boolean inProgress) {
464         Settings.Secure.putInt(getContentResolver(), KEY_SETUP_WIZARD_IN_PROGRESS,
465                 inProgress ? 1 : 0);
466     }
467 
markUserSetupComplete(Context context)468     private void markUserSetupComplete(Context context) {
469         Settings.Secure.putInt(context.getContentResolver(),
470                 Settings.Secure.USER_SETUP_COMPLETE, 1);
471     }
472 
exitSetup()473     private void exitSetup() {
474         Log.d(TAG, "exiting setup early for user " + getUserId());
475         provisionUserAndDevice();
476         notifySetupExited();
477         disableSelfAndFinish();
478     }
479 
notifySetupExited()480     private void notifySetupExited() {
481         Log.d(TAG, "Sending exited setup notification");
482 
483         NotificationManager notificationMgr = getSystemService(NotificationManager.class);
484         notificationMgr.createNotificationChannel(new NotificationChannel(
485                 IMPORTANCE_DEFAULT_ID, "Importance Default",
486                 NotificationManager.IMPORTANCE_DEFAULT));
487         Notification notification = new Notification
488                 .Builder(this, IMPORTANCE_DEFAULT_ID)
489                 .setContentTitle(getString(R.string.exited_setup_title))
490                 .setContentText(getString(R.string.exited_setup_content))
491                 .setCategory(Notification.CATEGORY_CAR_INFORMATION)
492                 .setSmallIcon(R.drawable.car_ic_mode)
493                 .build();
494         notificationMgr.notify(NOTIFICATION_ID, notification);
495     }
496 
getSelectedDpcInfo()497     private DpcInfo getSelectedDpcInfo() {
498         return sSupportedDpcApps.get(mDpcAppsSpinner.getSelectedItemPosition());
499     }
500 
launchLegacyProvisioningWorkflow()501     private void launchLegacyProvisioningWorkflow() {
502         DpcInfo dpcInfo = getSelectedDpcInfo();
503         if (!checkDpcAppExists(dpcInfo.packageName)) {
504             showErrorMessage("Cannot provision device because " + dpcInfo.packageName
505                     + " is not available.\n Make sure it's installed for both user 0 and user "
506                     + getUserId());
507             return;
508         }
509 
510         Intent intent = new Intent();
511         intent.setComponent(dpcInfo.getLegacyActivityComponentName());
512         Log.i(TAG, "Provisioning device using LEGACY workflow while running as user "
513                 + getUserId() + ". DPC: " + dpcInfo + ". Intent: " + intent);
514         startActivityForResult(intent, REQUEST_CODE_STEP1);
515     }
516 
launchProvisioningWorkflow()517     private void launchProvisioningWorkflow() {
518         DpcInfo dpcInfo = getSelectedDpcInfo();
519 
520         Intent intent = new Intent(ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE);
521         // TODO(b/170333009): add a UI with options for EXTRA_PROVISIONING_TRIGGER.
522         intent.putExtra(EXTRA_PROVISIONING_TRIGGER, PROVISIONING_TRIGGER_QR_CODE);
523         intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
524                 dpcInfo.getAdminReceiverComponentName());
525         if (dpcInfo.checkSum != null) {
526             intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM, dpcInfo.checkSum);
527         }
528         if (dpcInfo.downloadUrl != null) {
529             intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION,
530                     dpcInfo.downloadUrl);
531         }
532 
533         Log.i(TAG, "Provisioning device using NEW workflow while running as user "
534                 + getUserId() + ". DPC: " + dpcInfo + ". Intent: " + intent);
535 
536         startActivityForResult(intent, REQUEST_CODE_STEP1);
537     }
538 
disableSelfAndFinish()539     private void disableSelfAndFinish() {
540         Log.d(TAG, "disableSelfAndFinish()");
541 
542         // Remove this activity from the package manager.
543         PackageManager pm = getPackageManager();
544         ComponentName name = new ComponentName(this, DefaultActivity.class);
545         Log.i(TAG, "Disabling itself (" + name + ") for user " + getUserId());
546         pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
547                 PackageManager.DONT_KILL_APP);
548 
549         finish();
550     }
551 
552     @Override
onActivityResult(int requestCode, int resultCode, Intent data)553     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
554         Log.d(TAG, "onActivityResult(): request=" + requestCode + ", result="
555                 + resultCodeToString(resultCode) + ", data=" + data);
556 
557         switch (requestCode) {
558             case REQUEST_CODE_STEP1:
559                 onProvisioningStep1Result(resultCode);
560                 break;
561             case REQUEST_CODE_STEP2_PO:
562             case REQUEST_CODE_STEP2_DO:
563                 onProvisioningStep2Result(requestCode, resultCode);
564                 break;
565             default:
566                 showErrorMessage("onActivityResult(): invalid request code " + requestCode);
567 
568         }
569     }
570 
onProvisioningStep1Result(int resultCode)571     private void onProvisioningStep1Result(int resultCode) {
572         int requestCodeStep2;
573         switch (resultCode) {
574             case RESULT_CODE_PROFILE_OWNER_SET:
575                 requestCodeStep2 = REQUEST_CODE_STEP2_PO;
576                 break;
577             case RESULT_CODE_DEVICE_OWNER_SET:
578                 requestCodeStep2 = REQUEST_CODE_STEP2_DO;
579                 break;
580             default:
581                 showErrorMessage("onProvisioningStep1Result(): invalid result code "
582                         + resultCodeToString(resultCode)
583                         + getManagedProvisioningFailureWarning());
584                 return;
585         }
586         Intent intent = new Intent(PROVISION_FINALIZATION_INSIDE_SUW)
587                 .addCategory(Intent.CATEGORY_DEFAULT);
588         Log.i(TAG, "Finalizing DPC with " + intent);
589         startActivityForResult(intent, requestCodeStep2);
590     }
591 
getManagedProvisioningFailureWarning()592     private String getManagedProvisioningFailureWarning() {
593         return "\n\n" + getString(R.string.provision_failure_message);
594     }
595 
onProvisioningStep2Result(int requestCode, int resultCode)596     private void onProvisioningStep2Result(int requestCode, int resultCode) {
597         boolean doMode = requestCode == REQUEST_CODE_STEP2_DO;
598         if (resultCode != RESULT_OK) {
599             StringBuilder message = new StringBuilder("onProvisioningStep2Result(): "
600                     + "invalid result code ").append(resultCode);
601             if (doMode) {
602                 message.append(getManagedProvisioningFailureWarning());
603             }
604             showErrorMessage(message.toString());
605             return;
606         }
607 
608         Log.i(TAG, (doMode ? "Device owner" : "Profile owner") + " mode provisioned!");
609         finishSetup();
610     }
611 
resultCodeToString(int resultCode)612     private static String resultCodeToString(int resultCode)  {
613         StringBuilder result = new StringBuilder();
614         switch (resultCode) {
615             case RESULT_OK:
616                 result.append("RESULT_OK");
617                 break;
618             case RESULT_CANCELED:
619                 result.append("RESULT_CANCELED");
620                 break;
621             case RESULT_FIRST_USER:
622                 result.append("RESULT_FIRST_USER");
623                 break;
624             case RESULT_CODE_PROFILE_OWNER_SET:
625                 result.append("RESULT_CODE_PROFILE_OWNER_SET");
626                 break;
627             case RESULT_CODE_DEVICE_OWNER_SET:
628                 result.append("RESULT_CODE_DEVICE_OWNER_SET");
629                 break;
630             default:
631                 result.append("UNKNOWN_CODE");
632         }
633         return result.append('(').append(resultCode).append(')').toString();
634     }
635 
showErrorMessage(String message)636     private void showErrorMessage(String message) {
637         Log.e(TAG, "Error: " + message);
638         mErrorsTextView.setText(message);
639         findViewById(R.id.errors_container).setVisibility(View.VISIBLE);
640     }
641 }
642