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