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