• 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 android.app.Activity;
20 import android.app.AlertDialog;
21 import android.app.admin.DevicePolicyManager;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.Intent;
26 import android.content.pm.ApplicationInfo;
27 import android.content.pm.PackageManager;
28 import android.content.pm.ResolveInfo;
29 import android.content.pm.UserInfo;
30 import android.os.Build;
31 import android.os.Bundle;
32 import android.os.PersistableBundle;
33 import android.os.Process;
34 import android.os.SystemProperties;
35 import android.os.UserHandle;
36 import android.os.UserManager;
37 import android.provider.Settings;
38 import android.view.View;
39 import android.widget.ImageView;
40 import android.widget.TextView;
41 
42 import com.android.managedprovisioning.DeleteManagedProfileDialog.DeleteManagedProfileCallback;
43 import com.android.managedprovisioning.UserConsentDialog.ConsentCallback;
44 import com.android.managedprovisioning.Utils.IllegalProvisioningArgumentException;
45 import com.android.managedprovisioning.Utils.MdmPackageInfo;
46 
47 import java.util.List;
48 
49 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
50 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
51 import static com.android.managedprovisioning.EncryptDeviceActivity.EXTRA_RESUME;
52 import static com.android.managedprovisioning.EncryptDeviceActivity.EXTRA_RESUME_TARGET;
53 import static com.android.managedprovisioning.EncryptDeviceActivity.TARGET_PROFILE_OWNER;
54 
55 /**
56  * The activity sets up the environment in which the {@link ProfileOwnerProvisioningActivity} can be run.
57  * It makes sure the device is encrypted, the current launcher supports managed profiles, the
58  * provisioning intent extras are valid, and that the already present managed profile is removed.
59  */
60 public class ProfileOwnerPreProvisioningActivity extends SetupLayoutActivity
61         implements ConsentCallback, DeleteManagedProfileCallback {
62 
63     private static final String MANAGE_USERS_PERMISSION = "android.permission.MANAGE_USERS";
64 
65     // Note: must match the constant defined in HomeSettings
66     private static final String EXTRA_SUPPORT_MANAGED_PROFILES = "support_managed_profiles";
67 
68     // Aliases to start profile owner provisioning with and without MANAGE_USERS permission
69     protected static final ComponentName ALIAS_CHECK_CALLER =
70             new ComponentName("com.android.managedprovisioning",
71                     "com.android.managedprovisioning.ProfileOwnerProvisioningActivity");
72 
73     protected static final ComponentName ALIAS_NO_CHECK_CALLER =
74             new ComponentName("com.android.managedprovisioning",
75                     "com.android.managedprovisioning.ProfileOwnerProvisioningActivityNoCallerCheck");
76 
77     protected static final int PROVISIONING_REQUEST_CODE = 3;
78     protected static final int ENCRYPT_DEVICE_REQUEST_CODE = 2;
79     protected static final int CHANGE_LAUNCHER_REQUEST_CODE = 1;
80 
81     private DeleteManagedProfileDialog mDeleteDialog;
82 
83     private ProvisioningParams mParams;
84     private final MessageParser mParser = new MessageParser();
85 
86     @Override
onCreate(Bundle savedInstanceState)87     protected void onCreate(Bundle savedInstanceState) {
88         super.onCreate(savedInstanceState);
89 
90         initializeLayoutParams(R.layout.user_consent, R.string.setup_work_profile, false);
91         configureNavigationButtons(R.string.set_up, View.INVISIBLE, View.VISIBLE);
92 
93         TextView consentMessageTextView = (TextView) findViewById(R.id.user_consent_message);
94         consentMessageTextView.setText(R.string.company_controls_workspace);
95         TextView mdmInfoTextView = (TextView) findViewById(R.id.mdm_info_message);
96         mdmInfoTextView.setText(R.string.the_following_is_your_mdm);
97 
98         // Check whether system has the required managed profile feature.
99         if (!systemHasManagedProfileFeature()) {
100             showErrorAndClose(R.string.managed_provisioning_not_supported,
101                     "Exiting managed profile provisioning, "
102                     + "managed profiles feature is not available");
103             return;
104         }
105         if (Process.myUserHandle().getIdentifier() != UserHandle.USER_OWNER) {
106             showErrorAndClose(R.string.user_is_not_owner,
107                     "Exiting managed profile provisioning, calling user is not owner.");
108             return;
109         }
110         if (Utils.hasDeviceOwner(this)) {
111             showErrorAndClose(R.string.device_owner_exists,
112                     "Exiting managed profile provisioning, a device owner exists");
113         }
114 
115         // Initialize member variables from the intent, stop if the intent wasn't valid.
116         try {
117             initialize(getIntent(), getPackageName().equals(getCallingPackage()));
118         } catch (IllegalProvisioningArgumentException e) {
119             showErrorAndClose(R.string.managed_provisioning_error_text, e.getMessage());
120             return;
121         }
122 
123         setMdmIcon(mParams.deviceAdminPackageName);
124 
125         // If the caller started us via ALIAS_NO_CHECK_CALLER then they must have permission to
126         // MANAGE_USERS since it is a restricted intent. Otherwise, check the calling package.
127         boolean hasManageUsersPermission = (getComponentName().equals(ALIAS_NO_CHECK_CALLER));
128         if (!hasManageUsersPermission) {
129             // Calling package has to equal the requested device admin package or has to be system.
130             String callingPackage = getCallingPackage();
131             if (callingPackage == null) {
132                 showErrorAndClose(R.string.managed_provisioning_error_text,
133                         "Calling package is null. " +
134                         "Was startActivityForResult used to start this activity?");
135                 return;
136             }
137             if (!callingPackage.equals(mParams.deviceAdminPackageName)
138                     && !packageHasManageUsersPermission(callingPackage)) {
139                 showErrorAndClose(R.string.managed_provisioning_error_text, "Permission denied, "
140                         + "calling package tried to set a different package as profile owner. "
141                         + "The system MANAGE_USERS permission is required.");
142                 return;
143             }
144         }
145 
146         // If there is already a managed profile, setup the profile deletion dialog.
147         // Otherwise, check whether system has reached maximum user limit.
148         int existingManagedProfileUserId = Utils.alreadyHasManagedProfile(this);
149         if (existingManagedProfileUserId != -1) {
150             DevicePolicyManager dpm =
151                     (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
152             createDeleteManagedProfileDialog(dpm, existingManagedProfileUserId);
153         } else if (isMaximumManagedProfilesLimitReached()) {
154             showErrorAndClose(R.string.maximum_user_limit_reached,
155                     "Exiting managed profile provisioning, cannot add more users.");
156         } else {
157             showStartProvisioningButton();
158         }
159     }
160 
161     @Override
onResume()162     protected void onResume() {
163         super.onResume();
164 
165         setTitle(R.string.setup_profile_start_setup);
166         if (Utils.alreadyHasManagedProfile(this) != -1) {
167             showDeleteManagedProfileDialog();
168         }
169     }
170 
171     @Override
onPause()172     protected void onPause() {
173         super.onPause();
174 
175         hideDeleteManagedProfileDialog();
176     }
177 
showStartProvisioningButton()178     private void showStartProvisioningButton() {
179         mNextButton.setVisibility(View.VISIBLE);
180     }
181 
packageHasManageUsersPermission(String pkg)182     private boolean packageHasManageUsersPermission(String pkg) {
183         return PackageManager.PERMISSION_GRANTED == getPackageManager()
184                 .checkPermission(MANAGE_USERS_PERMISSION, pkg);
185     }
186 
systemHasManagedProfileFeature()187     private boolean systemHasManagedProfileFeature() {
188         PackageManager pm = getPackageManager();
189         return pm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
190     }
191 
isMaximumManagedProfilesLimitReached()192     private boolean isMaximumManagedProfilesLimitReached() {
193         UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
194         return !userManager.canAddMoreManagedProfiles();
195     }
196 
currentLauncherSupportsManagedProfiles()197     private boolean currentLauncherSupportsManagedProfiles() {
198         Intent intent = new Intent(Intent.ACTION_MAIN);
199         intent.addCategory(Intent.CATEGORY_HOME);
200 
201         PackageManager pm = getPackageManager();
202         ResolveInfo launcherResolveInfo
203                 = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
204         if (launcherResolveInfo == null) {
205             return false;
206         }
207         try {
208             // If the user has not chosen a default launcher, then launcherResolveInfo will be
209             // referring to the resolver activity. It is fine to create a managed profile in
210             // this case since there will always be at least one launcher on the device that
211             // supports managed profile feature.
212             ApplicationInfo launcherAppInfo = getPackageManager().getApplicationInfo(
213                     launcherResolveInfo.activityInfo.packageName, 0 /* default flags */);
214             return versionNumberAtLeastL(launcherAppInfo.targetSdkVersion);
215         } catch (PackageManager.NameNotFoundException e) {
216             return false;
217         }
218     }
219 
versionNumberAtLeastL(int versionNumber)220     private boolean versionNumberAtLeastL(int versionNumber) {
221         return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
222     }
223 
setMdmIcon(String packageName)224     private void setMdmIcon(String packageName) {
225         MdmPackageInfo packageInfo = Utils.getMdmPackageInfo(getPackageManager(), packageName);
226         if (packageInfo != null) {
227             String appLabel = packageInfo.getAppLabel();
228             ImageView imageView = (ImageView) findViewById(R.id.mdm_icon_view);
229             imageView.setImageDrawable(packageInfo.getPackageIcon());
230             imageView.setContentDescription(
231                     getResources().getString(R.string.mdm_icon_label, appLabel));
232 
233             TextView deviceManagerName = (TextView) findViewById(R.id.device_manager_name);
234             deviceManagerName.setText(appLabel);
235         }
236     }
237 
238     /**
239      * Checks if all required provisioning parameters are provided.
240      * Does not check for extras that are optional such as wifi ssid.
241      * Also checks whether type of admin extras bundle (if present) is PersistableBundle.
242      *
243      * @param intent The intent that started provisioning
244      * @param trusted Whether the intent is trusted or not.
245      */
initialize(Intent intent, boolean trusted)246     private void initialize(Intent intent, boolean trusted)
247             throws IllegalProvisioningArgumentException {
248         mParams = mParser.parseNonNfcIntent(intent, trusted);
249 
250         mParams.deviceAdminComponentName = Utils.findDeviceAdmin(
251                 mParams.deviceAdminPackageName, mParams.deviceAdminComponentName, this);
252         mParams.deviceAdminPackageName = mParams.deviceAdminComponentName.getPackageName();
253 
254     }
255 
256     /**
257      * If the device is encrypted start the service which does the provisioning, otherwise ask for
258      * user consent to encrypt the device.
259      */
checkEncryptedAndStartProvisioningService()260     private void checkEncryptedAndStartProvisioningService() {
261         if (EncryptDeviceActivity.isDeviceEncrypted()
262                 || SystemProperties.getBoolean("persist.sys.no_req_encrypt", false)) {
263 
264             // Notify the user once more that the admin will have full control over the profile,
265             // then start provisioning.
266             UserConsentDialog.newInstance(UserConsentDialog.PROFILE_OWNER)
267                     .show(getFragmentManager(), "UserConsentDialogFragment");
268         } else {
269             Bundle resumeExtras = new Bundle();
270             resumeExtras.putString(EXTRA_RESUME_TARGET, TARGET_PROFILE_OWNER);
271             mParser.addProvisioningParamsToBundle(resumeExtras, mParams);
272             Intent encryptIntent = new Intent(this, EncryptDeviceActivity.class)
273                     .putExtra(EXTRA_RESUME, resumeExtras);
274             startActivityForResult(encryptIntent, ENCRYPT_DEVICE_REQUEST_CODE);
275             // Continue in onActivityResult or after reboot.
276         }
277     }
278 
279     @Override
onDialogConsent()280     public void onDialogConsent() {
281         // For accessibility purposes: we need to talk back only the title of the
282         // next screen after user clicks ok.
283         setTitle("");
284         setupEnvironmentAndProvision();
285     }
286 
287     @Override
onDialogCancel()288     public void onDialogCancel() {
289         // Do nothing.
290     }
291 
292     @Override
onRemoveProfileApproval(int existingManagedProfileUserId)293     public void onRemoveProfileApproval(int existingManagedProfileUserId) {
294         mDeleteDialog = null;
295         UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
296         userManager.removeUser(existingManagedProfileUserId);
297         showStartProvisioningButton();
298     }
299 
300     @Override
onRemoveProfileCancel()301     public void onRemoveProfileCancel() {
302         setResult(Activity.RESULT_CANCELED);
303         finish();
304     }
305 
setupEnvironmentAndProvision()306     private void setupEnvironmentAndProvision() {
307         // Remove any pre-provisioning UI in favour of progress display
308         BootReminder.cancelProvisioningReminder(this);
309 
310         // Check whether the current launcher supports managed profiles.
311         if (!currentLauncherSupportsManagedProfiles()) {
312             showCurrentLauncherInvalid();
313         } else {
314             startProfileOwnerProvisioning();
315         }
316     }
317 
pickLauncher()318     private void pickLauncher() {
319         Intent changeLauncherIntent = new Intent("android.settings.HOME_SETTINGS");
320         changeLauncherIntent.putExtra(EXTRA_SUPPORT_MANAGED_PROFILES, true);
321         startActivityForResult(changeLauncherIntent, CHANGE_LAUNCHER_REQUEST_CODE);
322         // Continue in onActivityResult.
323     }
324 
startProfileOwnerProvisioning()325     private void startProfileOwnerProvisioning() {
326         Intent intent = new Intent(this, ProfileOwnerProvisioningActivity.class);
327         intent.putExtra(ProvisioningParams.EXTRA_PROVISIONING_PARAMS, mParams);
328         startActivityForResult(intent, PROVISIONING_REQUEST_CODE);
329         // Set cross-fade transition animation into the interstitial progress activity.
330         overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
331     }
332 
333     @Override
onActivityResult(int requestCode, int resultCode, Intent data)334     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
335         if (requestCode == ENCRYPT_DEVICE_REQUEST_CODE) {
336             if (resultCode == RESULT_CANCELED) {
337                 ProvisionLogger.loge("User canceled device encryption.");
338                 setResult(Activity.RESULT_CANCELED);
339                 finish();
340             }
341         } else if (requestCode == CHANGE_LAUNCHER_REQUEST_CODE) {
342             if (resultCode == RESULT_CANCELED) {
343                 showCurrentLauncherInvalid();
344             } else if (resultCode == RESULT_OK) {
345                 startProfileOwnerProvisioning();
346             }
347         }
348         if (requestCode == PROVISIONING_REQUEST_CODE) {
349             setResult(resultCode);
350             finish();
351         }
352     }
353 
showCurrentLauncherInvalid()354     private void showCurrentLauncherInvalid() {
355         new AlertDialog.Builder(this)
356                 .setCancelable(false)
357                 .setMessage(R.string.managed_provisioning_not_supported_by_launcher)
358                 .setNegativeButton(R.string.cancel_provisioning,
359                         new DialogInterface.OnClickListener() {
360                             @Override
361                             public void onClick(DialogInterface dialog,int id) {
362                                 dialog.dismiss();
363                                 setResult(Activity.RESULT_CANCELED);
364                                 finish();
365                             }
366                         })
367                 .setPositiveButton(R.string.pick_launcher,
368                         new DialogInterface.OnClickListener() {
369                             @Override
370                             public void onClick(DialogInterface dialog,int id) {
371                                 pickLauncher();
372                             }
373                         })
374                 .show();
375     }
376 
showErrorAndClose(int resourceId, String logText)377     public void showErrorAndClose(int resourceId, String logText) {
378         ProvisionLogger.loge(logText);
379         new AlertDialog.Builder(this)
380                 .setTitle(R.string.provisioning_error_title)
381                 .setMessage(resourceId)
382                 .setCancelable(false)
383                 .setPositiveButton(R.string.device_owner_error_ok,
384                         new DialogInterface.OnClickListener() {
385                             @Override
386                             public void onClick(DialogInterface dialog,int id) {
387                                 // Close activity
388                                 ProfileOwnerPreProvisioningActivity.this.setResult(
389                                         Activity.RESULT_CANCELED);
390                                 ProfileOwnerPreProvisioningActivity.this.finish();
391                             }
392                         })
393                 .show();
394     }
395 
396     /**
397      * Builds a dialog that allows the user to remove an existing managed profile.
398      */
createDeleteManagedProfileDialog(DevicePolicyManager dpm, int existingManagedProfileUserId)399     private void createDeleteManagedProfileDialog(DevicePolicyManager dpm,
400             int existingManagedProfileUserId) {
401         if (mDeleteDialog != null) {
402             return;
403         }
404 
405         ComponentName mdmPackageName = dpm.getProfileOwnerAsUser(existingManagedProfileUserId);
406         String domainName = dpm.getProfileOwnerNameAsUser(existingManagedProfileUserId);
407 
408         mDeleteDialog = DeleteManagedProfileDialog.newInstance(existingManagedProfileUserId,
409                 mdmPackageName, domainName);
410     }
411 
showDeleteManagedProfileDialog()412     private void showDeleteManagedProfileDialog() {
413         if (mDeleteDialog == null) {
414             return;
415         }
416 
417         if (!mDeleteDialog.isVisible()) {
418             mDeleteDialog.show(getFragmentManager(), "DeleteManagedProfileDialogFragment");
419         }
420     }
421 
hideDeleteManagedProfileDialog()422     private void hideDeleteManagedProfileDialog() {
423         if (mDeleteDialog == null) {
424             return;
425         }
426 
427         mDeleteDialog.dismiss();
428         mDeleteDialog = null;
429     }
430 
431     @Override
onBackPressed()432     public void onBackPressed() {
433         new AlertDialog.Builder(this)
434                 .setTitle(R.string.work_profile_setup_later_title)
435                 .setMessage(R.string.work_profile_setup_later_message)
436                 .setCancelable(false)
437                 .setPositiveButton(R.string.work_profile_setup_stop,
438                         new DialogInterface.OnClickListener() {
439                             @Override
440                             public void onClick(DialogInterface dialog,int id) {
441                                 ProfileOwnerPreProvisioningActivity.this.setResult(
442                                         Activity.RESULT_CANCELED);
443                                 ProfileOwnerPreProvisioningActivity.this.finish();
444                             }
445                         })
446                 .setNegativeButton(R.string.work_profile_setup_continue,
447                         new DialogInterface.OnClickListener() {
448                             @Override
449                             public void onClick(DialogInterface dialog, int id) {
450                               // user chose to continue. Do nothing
451                             }
452                         })
453                 .show();
454     }
455 
456     @Override
onNavigateNext()457     public void onNavigateNext() {
458         checkEncryptedAndStartProvisioningService();
459     }
460 }
461