blob: ac7e602aab1d1c36c0d037a374a0b6a29ff288c9 [file] [log] [blame]
/*
* Copyright 2014, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.managedprovisioning;
import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.SystemProperties;
import android.os.UserManager;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Button;
import java.util.List;
/**
* Managed provisioning sets up a separate profile on a device whose primary user is already set up.
* The typical example is setting up a corporate profile that is controlled by their employer on a
* users personal device to keep personal and work data separate.
*
* The activity handles the input validation and UI for managed profile provisioning.
* and starts the {@link ManagedProvisioningService}, which runs through the setup steps in an
* async task.
*/
// TODO: Proper error handling to report back to the user and potentially the mdm.
public class ManagedProvisioningActivity extends Activity {
private static final String MANAGE_USERS_PERMISSION = "android.permission.MANAGE_USERS";
private static final String EXTRA_USER_HAS_CONSENTED_PROVISIONING =
"com.android.managedprovisioning.EXTRA_USER_HAS_CONSENTED_PROVISIONING";
// TODO remove these when the new constant values are in use in all relevant places.
protected static final String EXTRA_LEGACY_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME =
"deviceAdminPackageName";
protected static final String EXTRA_LEGACY_PROVISIONING_DEFAULT_MANAGED_PROFILE_NAME =
"defaultManagedProfileName";
protected static final int ENCRYPT_DEVICE_REQUEST_CODE = 2;
private String mMdmPackageName;
private BroadcastReceiver mServiceMessageReceiver;
private boolean mUserConsented;
private View mMainTextView;
private View mProgressView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ProvisionLogger.logd("Managed provisioning activity ONCREATE");
PackageManager pm = getPackageManager();
if (!pm.hasSystemFeature(PackageManager.FEATURE_MANAGED_PROFILES)) {
showErrorAndClose(R.string.managed_provisioning_not_supported,
"Exiting managed provisioning, managed profiles feature is not available");
return;
}
// Setup broadcast receiver for feedback from service.
mServiceMessageReceiver = new ServiceMessageReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(ManagedProvisioningService.ACTION_PROVISIONING_SUCCESS);
filter.addAction(ManagedProvisioningService.ACTION_PROVISIONING_ERROR);
LocalBroadcastManager.getInstance(this).registerReceiver(mServiceMessageReceiver, filter);
// Initialize member variables from the intent, stop if the intent wasn't valid.
try {
initialize(getIntent());
} catch (ManagedProvisioningFailedException e) {
showErrorAndClose(R.string.managed_provisioning_error_text, e.getMessage());
return;
}
final LayoutInflater inflater = getLayoutInflater();
final View contentView = inflater.inflate(R.layout.user_consent, null);
mMainTextView = contentView.findViewById(R.id.main_text_container);
mProgressView = contentView.findViewById(R.id.progress_container);
setContentView(contentView);
setMdmIcon(mMdmPackageName, contentView);
// Don't continue if a managed profile already exists
if (alreadyHasManagedProfile()) {
showErrorAndClose(R.string.managed_profile_already_present,
"The device already has a managed profile, nothing to do.");
return;
}
// Don't continue if the caller tries to skip user consent without permission.
boolean needsPermission = getIntent().hasExtra(EXTRA_USER_HAS_CONSENTED_PROVISIONING);
if (needsPermission && !callerHasUserConsentPermission()) {
showErrorAndClose(R.string.managed_provisioning_error_text, "Permission denied,"
+ "you need MANAGE_USERS permission to skip user consent");
return;
}
// Skip the user consent if user has previously consented.
mUserConsented = getIntent().getBooleanExtra(EXTRA_USER_HAS_CONSENTED_PROVISIONING, false);
if (mUserConsented) {
checkEncryptedAndStartProvisioningService();
} else {
Button positiveButton = (Button) contentView.findViewById(R.id.positive_button);
positiveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
checkEncryptedAndStartProvisioningService();
}
});
}
}
/**
* Only system apps with the permission manage users can claim that the user consented.
*/
private boolean callerHasUserConsentPermission() {
String callingPackage = getCallingPackage();
if (callingPackage == null) {
ProvisionLogger.loge("Calling package is null. "
+ "Was startActivityForResult used to start this activity?");
return false;
}
int callingPackagePermission = this.getPackageManager().checkPermission
(MANAGE_USERS_PERMISSION, callingPackage);
if (callingPackagePermission == PackageManager.PERMISSION_GRANTED) {
return true;
} else {
return false;
}
}
class ServiceMessageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(ManagedProvisioningService.ACTION_PROVISIONING_SUCCESS)) {
ProvisionLogger.logd("Successfully provisioned");
ManagedProvisioningActivity.this.setResult(Activity.RESULT_OK);
ManagedProvisioningActivity.this.finish();
return;
} else if (action.equals(ManagedProvisioningService.ACTION_PROVISIONING_ERROR)) {
String errorLogMessage = intent.getStringExtra(
ManagedProvisioningService.EXTRA_LOG_MESSAGE_KEY);
ProvisionLogger.logd("Error reported: " + errorLogMessage);
showErrorAndClose(R.string.managed_provisioning_error_text, errorLogMessage);
return;
}
}
}
private void setMdmIcon(String packageName, View contentView) {
if (packageName != null) {
PackageManager pm = getPackageManager();
try {
ApplicationInfo ai = pm.getApplicationInfo(packageName, /* default flags */ 0);
if (ai != null) {
Drawable packageIcon = pm.getApplicationIcon(packageName);
ImageView imageView = (ImageView) contentView.findViewById(R.id.mdm_icon_view);
imageView.setImageDrawable(packageIcon);
String appLabel = pm.getApplicationLabel(ai).toString();
TextView deviceManagerName = (TextView) contentView
.findViewById(R.id.device_manager_name);
deviceManagerName.setText(appLabel);
}
} catch (PackageManager.NameNotFoundException e) {
// Package does not exist, ignore. Should never happen.
ProvisionLogger.loge("Package does not exist. Should never happen.");
}
}
}
/**
* Checks if all required provisioning parameters are provided.
* Does not check for extras that are optional such as the email address.
*
* @param intent The intent that started provisioning
*/
private void initialize(Intent intent) throws ManagedProvisioningFailedException {
// Validate package name and check if the package is installed
mMdmPackageName = getMdmPackageName(intent);
if (TextUtils.isEmpty(mMdmPackageName)) {
throw new ManagedProvisioningFailedException("Missing intent extra: "
+ EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
} else {
try {
this.getPackageManager().getPackageInfo(mMdmPackageName, 0);
} catch (NameNotFoundException e) {
throw new ManagedProvisioningFailedException("Mdm "+ mMdmPackageName
+ " is not installed. " + e);
}
}
}
private String getMdmPackageName(Intent intent) {
String name = intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
if (TextUtils.isEmpty(name)) {
name = intent.getStringExtra(EXTRA_LEGACY_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
}
return name;
}
@Override
public void onBackPressed() {
// TODO: Handle this graciously by stopping the provisioning flow and cleaning up.
}
@Override
public void onDestroy() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mServiceMessageReceiver);
super.onDestroy();
}
/**
* If the device is encrypted start the service which does the provisioning, otherwise ask for
* user consent to encrypt the device.
*/
private void checkEncryptedAndStartProvisioningService() {
if (EncryptDeviceActivity.isDeviceEncrypted()
|| SystemProperties.getBoolean("persist.sys.no_req_encrypt", false)) {
// Remove any pre-provisioning UI in favour of progress display
BootReminder.cancelProvisioningReminder(this);
mProgressView.setVisibility(View.VISIBLE);
mMainTextView.setVisibility(View.GONE);
Intent intent = new Intent(this, ManagedProvisioningService.class);
intent.putExtras(getIntent());
startService(intent);
} else {
Bundle resumeExtras = getIntent().getExtras();
resumeExtras.putString(EncryptDeviceActivity.EXTRA_RESUME_TARGET,
EncryptDeviceActivity.TARGET_PROFILE_OWNER);
Intent encryptIntent = new Intent(this, EncryptDeviceActivity.class)
.putExtra(EncryptDeviceActivity.EXTRA_RESUME, resumeExtras);
startActivityForResult(encryptIntent, ENCRYPT_DEVICE_REQUEST_CODE);
// Continue in onActivityResult or after reboot.
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == ENCRYPT_DEVICE_REQUEST_CODE) {
if (resultCode == RESULT_CANCELED) {
ProvisionLogger.loge("User canceled device encryption.");
setResult(Activity.RESULT_CANCELED);
finish();
}
}
}
public void showErrorAndClose(int resourceId, String logText) {
ProvisionLogger.loge(logText);
new ManagedProvisioningErrorDialog(getString(resourceId))
.show(getFragmentManager(), "ErrorDialogFragment");
}
boolean alreadyHasManagedProfile() {
UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
List<UserInfo> profiles = userManager.getProfiles(getUserId());
for (UserInfo userInfo : profiles) {
if (userInfo.isManagedProfile()) return true;
}
return false;
}
/**
* Exception thrown when the managed provisioning has failed completely.
*
* We're using a custom exception to avoid catching subsequent exceptions that might be
* significant.
*/
private class ManagedProvisioningFailedException extends Exception {
public ManagedProvisioningFailedException(String message) {
super(message);
}
}
}