• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2014, 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;
18 
19 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
20 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
21 import static com.android.managedprovisioning.EncryptDeviceActivity.EXTRA_RESUME;
22 import static com.android.managedprovisioning.EncryptDeviceActivity.EXTRA_RESUME_TARGET;
23 import static com.android.managedprovisioning.EncryptDeviceActivity.TARGET_PROFILE_OWNER;
24 
25 import android.app.Activity;
26 import android.app.AlertDialog;
27 import android.content.BroadcastReceiver;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.DialogInterface;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.content.pm.ApplicationInfo;
34 import android.content.pm.PackageManager;
35 import android.content.pm.ResolveInfo;
36 import android.content.pm.PackageManager.NameNotFoundException;
37 import android.content.pm.UserInfo;
38 import android.graphics.drawable.Drawable;
39 import android.os.Build;
40 import android.os.Bundle;
41 import android.os.PersistableBundle;
42 import android.os.Process;
43 import android.os.SystemProperties;
44 import android.os.UserHandle;
45 import android.os.UserManager;
46 import android.support.v4.content.LocalBroadcastManager;
47 import android.text.TextUtils;
48 import android.view.LayoutInflater;
49 import android.view.View;
50 import android.widget.ImageView;
51 import android.widget.TextView;
52 import android.widget.Button;
53 
54 import java.util.List;
55 
56 /**
57  * Managed provisioning sets up a separate profile on a device whose primary user is already set up.
58  * The typical example is setting up a corporate profile that is controlled by their employer on a
59  * users personal device to keep personal and work data separate.
60  *
61  * The activity handles the input validation and UI for managed profile provisioning.
62  * and starts the {@link ManagedProvisioningService}, which runs through the setup steps in an
63  * async task.
64  */
65 // TODO: Proper error handling to report back to the user and potentially the mdm.
66 public class ManagedProvisioningActivity extends Activity {
67 
68     private static final String MANAGE_USERS_PERMISSION = "android.permission.MANAGE_USERS";
69 
70     // Note: must match the constant defined in HomeSettings
71     private static final String EXTRA_SUPPORT_MANAGED_PROFILES = "support_managed_profiles";
72 
73     protected static final String EXTRA_USER_HAS_CONSENTED_PROVISIONING =
74             "com.android.managedprovisioning.EXTRA_USER_HAS_CONSENTED_PROVISIONING";
75 
76     // Aliases to start managed provisioning with and without MANAGE_USERS permission
77     protected static final ComponentName ALIAS_CHECK_CALLER =
78             new ComponentName("com.android.managedprovisioning",
79                     "com.android.managedprovisioning.ManagedProvisioningActivity");
80 
81     protected static final ComponentName ALIAS_NO_CHECK_CALLER =
82             new ComponentName("com.android.managedprovisioning",
83                     "com.android.managedprovisioning.ManagedProvisioningActivityNoCallerCheck");
84 
85     protected static final int ENCRYPT_DEVICE_REQUEST_CODE = 2;
86     protected static final int CHANGE_LAUNCHER_REQUEST_CODE = 1;
87 
88     private String mMdmPackageName;
89     private BroadcastReceiver mServiceMessageReceiver;
90 
91     private View mContentView;
92     private View mMainTextView;
93     private View mProgressView;
94 
95     @Override
onCreate(Bundle savedInstanceState)96     protected void onCreate(Bundle savedInstanceState) {
97         super.onCreate(savedInstanceState);
98 
99         ProvisionLogger.logd("Managed provisioning activity ONCREATE");
100 
101         final LayoutInflater inflater = getLayoutInflater();
102         mContentView = inflater.inflate(R.layout.user_consent, null);
103         mMainTextView = mContentView.findViewById(R.id.main_text_container);
104         mProgressView = mContentView.findViewById(R.id.progress_container);
105         setContentView(mContentView);
106 
107         // Check whether system has the required managed profile feature.
108         if (!systemHasManagedProfileFeature()) {
109             showErrorAndClose(R.string.managed_provisioning_not_supported,
110                     "Exiting managed provisioning, managed profiles feature is not available");
111             return;
112         }
113         if (Process.myUserHandle().getIdentifier() != UserHandle.USER_OWNER) {
114             showErrorAndClose(R.string.user_is_not_owner,
115                     "Exiting managed provisioning, calling user is not owner.");
116             return;
117         }
118 
119         // Setup broadcast receiver for feedback from service.
120         mServiceMessageReceiver = new ServiceMessageReceiver();
121         IntentFilter filter = new IntentFilter();
122         filter.addAction(ManagedProvisioningService.ACTION_PROVISIONING_SUCCESS);
123         filter.addAction(ManagedProvisioningService.ACTION_PROVISIONING_ERROR);
124         LocalBroadcastManager.getInstance(this).registerReceiver(mServiceMessageReceiver, filter);
125 
126         // Initialize member variables from the intent, stop if the intent wasn't valid.
127         try {
128             initialize(getIntent());
129         } catch (ManagedProvisioningFailedException e) {
130             showErrorAndClose(R.string.managed_provisioning_error_text, e.getMessage());
131             return;
132         }
133 
134         setMdmIcon(mMdmPackageName, mContentView);
135 
136         // If the caller started us via ALIAS_NO_CHECK_CALLER then they must have permission to
137         // MANAGE_USERS since it is a restricted intent. Otherwise, check the calling package.
138         boolean hasManageUsersPermission = (getComponentName().equals(ALIAS_NO_CHECK_CALLER));
139         if (!hasManageUsersPermission) {
140             // Calling package has to equal the requested device admin package or has to be system.
141             String callingPackage = getCallingPackage();
142             if (callingPackage == null) {
143                 showErrorAndClose(R.string.managed_provisioning_error_text,
144                         "Calling package is null. " +
145                         "Was startActivityForResult used to start this activity?");
146                 return;
147             }
148             if (!callingPackage.equals(mMdmPackageName)
149                     && !packageHasManageUsersPermission(callingPackage)) {
150                 showErrorAndClose(R.string.managed_provisioning_error_text, "Permission denied, "
151                         + "calling package tried to set a different package as profile owner. "
152                         + "The system MANAGE_USERS permission is required.");
153                 return;
154             }
155         }
156 
157         // If there is already a managed profile, allow the user to cancel or delete it.
158         int existingManagedProfileUserId = alreadyHasManagedProfile();
159         if (existingManagedProfileUserId != -1) {
160             showManagedProfileExistsDialog(existingManagedProfileUserId);
161         } else {
162             showStartProvisioningScreen();
163         }
164     }
165 
showStartProvisioningScreen()166     private void showStartProvisioningScreen() {
167         Button positiveButton = (Button) mContentView.findViewById(R.id.positive_button);
168         positiveButton.setOnClickListener(new View.OnClickListener() {
169             @Override
170             public void onClick(View v) {
171                 checkEncryptedAndStartProvisioningService();
172             }
173         });
174     }
175 
packageHasManageUsersPermission(String pkg)176     private boolean packageHasManageUsersPermission(String pkg) {
177         int packagePermission = this.getPackageManager()
178                 .checkPermission(MANAGE_USERS_PERMISSION, pkg);
179         if (packagePermission == PackageManager.PERMISSION_GRANTED) {
180             return true;
181         } else {
182             return false;
183         }
184     }
185 
systemHasManagedProfileFeature()186     private boolean systemHasManagedProfileFeature() {
187         PackageManager pm = getPackageManager();
188         return pm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
189     }
190 
currentLauncherSupportsManagedProfiles()191     private boolean currentLauncherSupportsManagedProfiles() {
192         Intent intent = new Intent(Intent.ACTION_MAIN);
193         intent.addCategory(Intent.CATEGORY_HOME);
194 
195         PackageManager pm = getPackageManager();
196         ResolveInfo launcherResolveInfo
197                 = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
198         try {
199             ApplicationInfo launcherAppInfo = getPackageManager().getApplicationInfo(
200                     launcherResolveInfo.activityInfo.packageName, 0 /* default flags */);
201             return versionNumberAtLeastL(launcherAppInfo.targetSdkVersion);
202         } catch (PackageManager.NameNotFoundException e) {
203             return false;
204         }
205     }
206 
versionNumberAtLeastL(int versionNumber)207     private boolean versionNumberAtLeastL(int versionNumber) {
208         return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
209     }
210 
211     class ServiceMessageReceiver extends BroadcastReceiver {
212         @Override
onReceive(Context context, Intent intent)213         public void onReceive(Context context, Intent intent) {
214             String action = intent.getAction();
215             if (action.equals(ManagedProvisioningService.ACTION_PROVISIONING_SUCCESS)) {
216                 ProvisionLogger.logd("Successfully provisioned."
217                         + "Finishing ManagedProvisioningActivity");
218                 ManagedProvisioningActivity.this.setResult(Activity.RESULT_OK);
219                 ManagedProvisioningActivity.this.finish();
220                 return;
221             } else if (action.equals(ManagedProvisioningService.ACTION_PROVISIONING_ERROR)) {
222                 String errorLogMessage = intent.getStringExtra(
223                         ManagedProvisioningService.EXTRA_LOG_MESSAGE_KEY);
224                 ProvisionLogger.logd("Error reported: " + errorLogMessage);
225                 showErrorAndClose(R.string.managed_provisioning_error_text, errorLogMessage);
226                 return;
227             }
228         }
229     }
230 
setMdmIcon(String packageName, View contentView)231     private void setMdmIcon(String packageName, View contentView) {
232         if (packageName != null) {
233             PackageManager pm = getPackageManager();
234             try {
235                 ApplicationInfo ai = pm.getApplicationInfo(packageName, /* default flags */ 0);
236                 if (ai != null) {
237                     Drawable packageIcon = pm.getApplicationIcon(packageName);
238                     ImageView imageView = (ImageView) contentView.findViewById(R.id.mdm_icon_view);
239                     imageView.setImageDrawable(packageIcon);
240 
241                     String appLabel = pm.getApplicationLabel(ai).toString();
242                     TextView deviceManagerName = (TextView) contentView
243                             .findViewById(R.id.device_manager_name);
244                     deviceManagerName.setText(appLabel);
245                 }
246             } catch (PackageManager.NameNotFoundException e) {
247                 // Package does not exist, ignore. Should never happen.
248                 ProvisionLogger.loge("Package does not exist. Should never happen.");
249             }
250         }
251     }
252 
253     /**
254      * Checks if all required provisioning parameters are provided.
255      * Does not check for extras that are optional such as wifi ssid.
256      * Also checks whether type of admin extras bundle (if present) is PersistableBundle.
257      *
258      * @param intent The intent that started provisioning
259      */
initialize(Intent intent)260     private void initialize(Intent intent) throws ManagedProvisioningFailedException {
261         // Check if the admin extras bundle is of the right type.
262         try {
263             PersistableBundle bundle = (PersistableBundle) getIntent().getParcelableExtra(
264                     EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE);
265         } catch (ClassCastException e) {
266             throw new ManagedProvisioningFailedException("Extra "
267                     + EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
268                     + " must be of type PersistableBundle.", e);
269         }
270 
271         // Validate package name and check if the package is installed
272         mMdmPackageName = intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
273         if (TextUtils.isEmpty(mMdmPackageName)) {
274             throw new ManagedProvisioningFailedException("Missing intent extra: "
275                     + EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
276         } else {
277             try {
278                 this.getPackageManager().getPackageInfo(mMdmPackageName, 0);
279             } catch (NameNotFoundException e) {
280                 throw new ManagedProvisioningFailedException("Mdm "+ mMdmPackageName
281                         + " is not installed. ", e);
282             }
283         }
284     }
285 
286     @Override
onBackPressed()287     public void onBackPressed() {
288         // TODO: Handle this graciously by stopping the provisioning flow and cleaning up.
289     }
290 
291     @Override
onDestroy()292     public void onDestroy() {
293         LocalBroadcastManager.getInstance(this).unregisterReceiver(mServiceMessageReceiver);
294         super.onDestroy();
295     }
296 
297     /**
298      * If the device is encrypted start the service which does the provisioning, otherwise ask for
299      * user consent to encrypt the device.
300      */
checkEncryptedAndStartProvisioningService()301     private void checkEncryptedAndStartProvisioningService() {
302         if (EncryptDeviceActivity.isDeviceEncrypted()
303                 || SystemProperties.getBoolean("persist.sys.no_req_encrypt", false)) {
304 
305             // Notify the user once more that the admin will have full control over the profile,
306             // then start provisioning.
307             new UserConsentDialog(this, UserConsentDialog.PROFILE_OWNER, new Runnable() {
308                     @Override
309                     public void run() {
310                         setupEnvironmentAndProvision();
311                     }
312                 } /* onUserConsented */ , null /* onCancel */).show(getFragmentManager(),
313                         "UserConsentDialogFragment");
314         } else {
315             Bundle resumeExtras = getIntent().getExtras();
316             resumeExtras.putString(EXTRA_RESUME_TARGET, TARGET_PROFILE_OWNER);
317             Intent encryptIntent = new Intent(this, EncryptDeviceActivity.class)
318                     .putExtra(EXTRA_RESUME, resumeExtras);
319             startActivityForResult(encryptIntent, ENCRYPT_DEVICE_REQUEST_CODE);
320             // Continue in onActivityResult or after reboot.
321         }
322     }
323 
setupEnvironmentAndProvision()324     private void setupEnvironmentAndProvision() {
325         // Remove any pre-provisioning UI in favour of progress display
326         BootReminder.cancelProvisioningReminder(
327                 ManagedProvisioningActivity.this);
328         mProgressView.setVisibility(View.VISIBLE);
329         mMainTextView.setVisibility(View.GONE);
330 
331         // Check whether the current launcher supports managed profiles.
332         if (!currentLauncherSupportsManagedProfiles()) {
333             showCurrentLauncherInvalid();
334         } else {
335             startManagedProvisioningService();
336         }
337     }
338 
pickLauncher()339     private void pickLauncher() {
340         Intent changeLauncherIntent = new Intent("android.settings.HOME_SETTINGS");
341         changeLauncherIntent.putExtra(EXTRA_SUPPORT_MANAGED_PROFILES, true);
342         startActivityForResult(changeLauncherIntent, CHANGE_LAUNCHER_REQUEST_CODE);
343         // Continue in onActivityResult.
344     }
345 
startManagedProvisioningService()346     private void startManagedProvisioningService() {
347         Intent intent = new Intent(this, ManagedProvisioningService.class);
348         intent.putExtras(getIntent());
349         startService(intent);
350     }
351 
352     @Override
onActivityResult(int requestCode, int resultCode, Intent data)353     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
354         if (requestCode == ENCRYPT_DEVICE_REQUEST_CODE) {
355             if (resultCode == RESULT_CANCELED) {
356                 ProvisionLogger.loge("User canceled device encryption.");
357                 setResult(Activity.RESULT_CANCELED);
358                 finish();
359             }
360         } else if (requestCode == CHANGE_LAUNCHER_REQUEST_CODE) {
361             if (resultCode == RESULT_CANCELED) {
362                 showCurrentLauncherInvalid();
363             } else if (resultCode == RESULT_OK) {
364                 startManagedProvisioningService();
365             }
366         }
367     }
368 
showCurrentLauncherInvalid()369     private void showCurrentLauncherInvalid() {
370         new AlertDialog.Builder(this)
371                 .setCancelable(false)
372                 .setMessage(R.string.managed_provisioning_not_supported_by_launcher)
373                 .setNegativeButton(R.string.cancel_provisioning,
374                         new DialogInterface.OnClickListener() {
375                             @Override
376                             public void onClick(DialogInterface dialog,int id) {
377                                 dialog.dismiss();
378                                 setResult(Activity.RESULT_CANCELED);
379                                 finish();
380                             }
381                         })
382                 .setPositiveButton(R.string.pick_launcher,
383                         new DialogInterface.OnClickListener() {
384                             @Override
385                             public void onClick(DialogInterface dialog,int id) {
386                                 pickLauncher();
387                             }
388                         }).show();
389     }
390 
showErrorAndClose(int resourceId, String logText)391     public void showErrorAndClose(int resourceId, String logText) {
392         ProvisionLogger.loge(logText);
393         new ManagedProvisioningErrorDialog(getString(resourceId))
394               .show(getFragmentManager(), "ErrorDialogFragment");
395     }
396 
397     /**
398      * @return The User id of an already existing managed profile or -1 if none
399      * exists
400      */
alreadyHasManagedProfile()401     int alreadyHasManagedProfile() {
402         UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
403         List<UserInfo> profiles = userManager.getProfiles(getUserId());
404         for (UserInfo userInfo : profiles) {
405             if (userInfo.isManagedProfile()) {
406                 return userInfo.getUserHandle().getIdentifier();
407             }
408         }
409         return -1;
410     }
411 
412     /**
413      * Builds a dialog that allows the user to remove an existing managed profile after they were
414      * shown an additional warning.
415      */
showManagedProfileExistsDialog( final int existingManagedProfileUserId)416     private void showManagedProfileExistsDialog(
417             final int existingManagedProfileUserId) {
418 
419         // Before deleting, show a warning dialog
420         DialogInterface.OnClickListener warningListener =
421                 new DialogInterface.OnClickListener() {
422             @Override
423             public void onClick(DialogInterface dialog, int which) {
424                 // Really delete the profile if the user clicks delete on the warning dialog.
425                 final DialogInterface.OnClickListener deleteListener =
426                         new DialogInterface.OnClickListener() {
427                     @Override
428                     public void onClick(DialogInterface dialog, int which) {
429                         UserManager userManager =
430                                 (UserManager) getSystemService(Context.USER_SERVICE);
431                         userManager.removeUser(existingManagedProfileUserId);
432                         showStartProvisioningScreen();
433                     }
434                 };
435                 buildDeleteManagedProfileDialog(
436                         getString(R.string.sure_you_want_to_delete_profile),
437                         deleteListener).show();
438             }
439         };
440 
441         buildDeleteManagedProfileDialog(
442                 getString(R.string.managed_profile_already_present),
443                 warningListener).show();
444     }
445 
buildDeleteManagedProfileDialog(String message, DialogInterface.OnClickListener deleteListener)446     private AlertDialog buildDeleteManagedProfileDialog(String message,
447             DialogInterface.OnClickListener deleteListener) {
448         DialogInterface.OnClickListener cancelListener =
449                 new DialogInterface.OnClickListener() {
450             @Override
451             public void onClick(DialogInterface dialog, int which) {
452                 ManagedProvisioningActivity.this.finish();
453             }
454         };
455 
456         AlertDialog.Builder builder = new AlertDialog.Builder(this);
457         builder.setMessage(message)
458                 .setCancelable(false)
459                 .setPositiveButton(getString(R.string.delete_profile), deleteListener)
460                 .setNegativeButton(getString(R.string.cancel_delete_profile), cancelListener);
461 
462         return builder.create();
463     }
464     /**
465      * Exception thrown when the managed provisioning has failed completely.
466      *
467      * We're using a custom exception to avoid catching subsequent exceptions that might be
468      * significant.
469      */
470     private class ManagedProvisioningFailedException extends Exception {
ManagedProvisioningFailedException(String message)471         public ManagedProvisioningFailedException(String message) {
472             super(message);
473         }
474 
ManagedProvisioningFailedException(String message, Throwable t)475         public ManagedProvisioningFailedException(String message, Throwable t) {
476             super(message, t);
477         }
478     }
479 }
480 
481