/* * Copyright (C) 2015 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.afwsamples.testdpc; import android.accounts.Account; import android.annotation.TargetApi; import android.app.Activity; import android.app.Fragment; import android.app.admin.DevicePolicyManager; import android.content.ClipData; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.res.ColorStateList; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.PersistableBundle; import android.support.v4.os.BuildCompat; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.CheckBox; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.RadioGroup; import android.widget.TextView; import android.widget.Toast; import com.afwsamples.testdpc.common.ColorPicker; import com.afwsamples.testdpc.common.LaunchIntentUtil; import com.afwsamples.testdpc.common.ProvisioningStateUtil; import com.afwsamples.testdpc.common.Util; import com.android.setupwizardlib.SetupWizardLayout; import com.android.setupwizardlib.view.NavigationBar; import java.security.SecureRandom; import java.util.Arrays; import java.util.List; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOGO_URI; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_MAIN_COLOR; /** * This {@link Fragment} shows the UI that allows the user to start the setup of a managed profile * or configuration of a device-owner if the device is in an appropriate state. */ public class SetupManagementFragment extends Fragment implements NavigationBar.NavigationBarListener, View.OnClickListener, ColorPicker.OnColorSelectListener, RadioGroup.OnCheckedChangeListener { // Tag for creating this fragment. This tag can be used to retrieve this fragment. public static final String FRAGMENT_TAG = "SetupManagementFragment"; private static final int REQUEST_PROVISION_MANAGED_PROFILE = 1; private static final int REQUEST_PROVISION_DEVICE_OWNER = 2; private static final int REQUEST_GET_LOGO = 3; private TextView mSetupManagementMessage; private RadioGroup mSetupOptions; private Button mNavigationNextButton; private CheckBox mSkipUserConsent; private CheckBox mSkipEncryption; private CheckBox mKeepAccountMigrated; private ImageButton mParamsIndicator; private View mParamsView; private static final int[] STATE_EXPANDED = new int[] {R.attr.state_expanded}; private static final int[] STATE_COLLAPSED = new int[] {-R.attr.state_expanded}; private ImageView mColorPreviewView; private TextView mColorValue; private ImageView mLogoPreviewView; private TextView mLogoValue; private int mCurrentColor; private Uri mLogoUri = null; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (savedInstanceState != null) { mLogoUri = (Uri) savedInstanceState.getParcelable(EXTRA_PROVISIONING_LOGO_URI); mCurrentColor = savedInstanceState.getInt(EXTRA_PROVISIONING_MAIN_COLOR); } else { mLogoUri = resourceToUri(getActivity(), R.drawable.ic_launcher); mCurrentColor = getResources().getColor(R.color.teal); } // Use setupwizard theme final Context contextThemeWrapper = new ContextThemeWrapper(getActivity(), R.style.SetupTheme); LayoutInflater themeInflater = inflater.cloneInContext(contextThemeWrapper); View view = themeInflater.inflate(R.layout.setup_management_fragment, container, false); SetupWizardLayout layout = (SetupWizardLayout) view.findViewById(R.id.setup_wizard_layout); NavigationBar navigationBar = layout.getNavigationBar(); navigationBar.setNavigationBarListener(this); navigationBar.getBackButton().setText(R.string.exit); mNavigationNextButton = navigationBar.getNextButton(); mNavigationNextButton.setText(R.string.setup_label); mSetupManagementMessage = (TextView) view.findViewById(R.id.setup_management_message_id); mSetupOptions = (RadioGroup) view.findViewById(R.id.setup_options); mSetupOptions.setOnCheckedChangeListener(this); mSkipUserConsent = (CheckBox) view.findViewById(R.id.skip_user_consent); mKeepAccountMigrated = (CheckBox) view.findViewById(R.id.keep_account_migrated); mSkipEncryption = (CheckBox) view.findViewById(R.id.skip_encryption); mParamsView = view.findViewById(R.id.params); mParamsIndicator = (ImageButton) view.findViewById(R.id.params_indicator); mParamsIndicator.setOnClickListener(this); view.findViewById(R.id.color_select_button).setOnClickListener(this); mColorValue = (TextView) view.findViewById(R.id.selected_color_value); mColorValue.setText(String.format(ColorPicker.COLOR_STRING_FORMATTER, mCurrentColor)); mColorPreviewView = (ImageView) view.findViewById(R.id.preview_color); mColorPreviewView.setImageTintList(ColorStateList.valueOf(mCurrentColor)); Intent launchIntent = getActivity().getIntent(); if (LaunchIntentUtil.isSynchronousAuthLaunch(launchIntent)) { Account addedAccount = LaunchIntentUtil.getAddedAccount(launchIntent); if (addedAccount != null) { view.findViewById(R.id.managed_account_desc).setVisibility(View.VISIBLE); // Show the user which account needs management. TextView managedAccountName = (TextView) view.findViewById( R.id.managed_account_name); managedAccountName.setVisibility(View.VISIBLE); managedAccountName.setText(addedAccount.name); } else { // This is not an expected case, sync-auth is triggered by an account being added so // we expect to be told which account to migrate in the launch intent. We don't // finish() here as it's still technically feasible to continue. Toast.makeText(getActivity(), R.string.invalid_launch_intent_no_account, Toast.LENGTH_LONG).show(); } } return view; } @Override public void onSaveInstanceState(Bundle outState) { outState.putParcelable(EXTRA_PROVISIONING_LOGO_URI, mLogoUri); outState.putInt(EXTRA_PROVISIONING_MAIN_COLOR, mCurrentColor); super.onSaveInstanceState(outState); } @Override public void onResume() { super.onResume(); getActivity().getActionBar().hide(); if (setProvisioningMethodsVisibility()) { // The extra logo uri and color are supported only from N if (BuildCompat.isAtLeastN()) { getView().findViewById(R.id.params_title).setVisibility(View.VISIBLE); if (canAnAppHandleGetContent()) { getView().findViewById( R.id.choose_logo_item_layout).setVisibility(View.VISIBLE); getView().findViewById(R.id.logo_select_button).setOnClickListener(this); mLogoValue = (TextView) getView().findViewById(R.id.selected_logo_value); mLogoPreviewView = (ImageView) getView().findViewById(R.id.preview_logo); } setProvisioningModeSpecificUI(); } } else { showNoProvisioningPossibleUI(); } } /** * On R.id.setup_options are changed */ @Override public void onCheckedChanged(RadioGroup radioGroup, int i) { setProvisioningModeSpecificUI(); } private void setProvisioningModeSpecificUI() { final int setUpOptionId = mSetupOptions.getCheckedRadioButtonId(); final boolean isManagedProfileAction = setUpOptionId == R.id.setup_managed_profile; final boolean isManagedDeviceAction = setUpOptionId == R.id.setup_device_owner; mSkipUserConsent.setVisibility(BuildCompat.isAtLeastO() && isManagedProfileAction && Util.isDeviceOwner(getActivity()) ? View.VISIBLE : View.GONE); mKeepAccountMigrated.setVisibility(BuildCompat.isAtLeastO() && isManagedProfileAction ? View.VISIBLE : View.GONE); mSkipEncryption.setVisibility((isManagedProfileAction && BuildCompat.isAtLeastN()) || (isManagedDeviceAction && Util.isAtLeastM()) ? View.VISIBLE : View.GONE); // If TestDpc is already a device owner, but can create a managed profile, show a different // message. if (Util.isDeviceOwner(getActivity())) { mSetupManagementMessage.setText(R.string.setup_management_message_for_do); } } private void maybeLaunchProvisioning(String intentAction, int requestCode) { Activity activity = getActivity(); Intent intent = new Intent(intentAction); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME, DeviceAdminReceiver.getComponentName(getActivity())); } else { intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME, getActivity().getPackageName()); } if (!maybeSpecifyNExtras(intent)) { // Unable to handle user-input - can't continue. return; } PersistableBundle adminExtras = new PersistableBundle(); maybeSpecifySyncAuthExtras(intent, adminExtras); maybePassAffiliationIds(intent, adminExtras); specifySkipUserConsent(intent); specifyKeepAccountMigrated(intent); specifySkipEncryption(intent); specifyDefaultDisclaimers(intent); if (adminExtras.size() > 0) { intent.putExtra(EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE, adminExtras); } if (intent.resolveActivity(activity.getPackageManager()) != null) { startActivityForResult(intent, requestCode); } else { Toast.makeText(activity, R.string.provisioning_not_supported, Toast.LENGTH_SHORT) .show(); } } private void maybeSpecifySyncAuthExtras(Intent intent, PersistableBundle adminExtras) { Activity activity = getActivity(); Intent launchIntent = activity.getIntent(); if (!LaunchIntentUtil.isSynchronousAuthLaunch(launchIntent)) { // Don't do anything if this isn't a sync-auth flow. return; } Account accountToMigrate = LaunchIntentUtil.getAddedAccount(launchIntent); if (accountToMigrate != null) { // EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE only supported in API 22+. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { // Configure the account to migrate into the managed profile if setup // completes. intent.putExtra(EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE, accountToMigrate); } else { Toast.makeText(activity, R.string.migration_not_supported, Toast.LENGTH_SHORT) .show(); } } // Perculate launch intent extras through to DeviceAdminReceiver so they can be used there. LaunchIntentUtil.prepareDeviceAdminExtras(launchIntent, adminExtras); } private void maybePassAffiliationIds(Intent intent, PersistableBundle adminExtras) { if (Util.isDeviceOwner(getActivity()) && ACTION_PROVISION_MANAGED_PROFILE.equals(intent.getAction()) && BuildCompat.isAtLeastO()) { passAffiliationIds(intent, adminExtras); } } @TargetApi(Build.VERSION_CODES.O) private void passAffiliationIds(Intent intent, PersistableBundle adminExtras) { ComponentName admin = DeviceAdminReceiver.getComponentName(getActivity()); DevicePolicyManager dpm = (DevicePolicyManager) getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE); List<String> ids = dpm.getAffiliationIds(admin); String affiliationId = null; if (ids.size() == 0) { SecureRandom randomGenerator = new SecureRandom(); affiliationId = Integer.toString(randomGenerator.nextInt(1000000)); dpm.setAffiliationIds(admin, Arrays.asList(affiliationId)); } else { affiliationId = ids.get(0); } adminExtras.putString(LaunchIntentUtil.EXTRA_AFFILIATION_ID, affiliationId); } /** * @return true if we can launch the intent */ private boolean maybeSpecifyNExtras(Intent intent) { if (BuildCompat.isAtLeastN()) { specifyLogoUri(intent); specifyColor(intent); } return true; } // TODO: replace with O SDK API private static final String EXTRA_PROVISIONING_DISCLAIMERS = "android.app.extra.PROVISIONING_DISCLAIMERS"; private static final String EXTRA_PROVISIONING_DISCLAIMER_HEADER = "android.app.extra.PROVISIONING_DISCLAIMER_HEADER"; private static final String EXTRA_PROVISIONING_DISCLAIMER_CONTENT = "android.app.extra.PROVISIONING_DISCLAIMER_CONTENT"; private void specifyDefaultDisclaimers(Intent intent) { if (BuildCompat.isAtLeastO()) { Bundle emmBundle = new Bundle(); emmBundle.putString(EXTRA_PROVISIONING_DISCLAIMER_HEADER, getString(R.string.default_disclaimer_emm_name)); emmBundle.putParcelable(EXTRA_PROVISIONING_DISCLAIMER_CONTENT, resourceToUri(getActivity(), R.raw.emm_disclaimer)); Bundle companyBundle = new Bundle(); companyBundle.putString(EXTRA_PROVISIONING_DISCLAIMER_HEADER, getString(R.string.default_disclaimer_company_name)); companyBundle.putParcelable(EXTRA_PROVISIONING_DISCLAIMER_CONTENT, resourceToUri(getActivity(), R.raw.company_disclaimer)); intent.putExtra(EXTRA_PROVISIONING_DISCLAIMERS, new Bundle[] { emmBundle, companyBundle }); } } private static Uri resourceToUri(Context context, int resID) { return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.getResources().getResourcePackageName(resID) + '/' + context.getResources().getResourceTypeName(resID) + '/' + context.getResources().getResourceEntryName(resID) ); } private void specifySkipUserConsent(Intent intent) { if (BuildCompat.isAtLeastO() && ACTION_PROVISION_MANAGED_PROFILE.equals(intent.getAction()) && mSkipUserConsent.getVisibility() == View.VISIBLE) { // TODO: use action string in Android SDK intent.putExtra("android.app.extra.PROVISIONING_SKIP_USER_CONSENT", mSkipUserConsent.isChecked()); } } private void specifyKeepAccountMigrated(Intent intent) { if (BuildCompat.isAtLeastO() && ACTION_PROVISION_MANAGED_PROFILE.equals(intent.getAction()) && mKeepAccountMigrated.getVisibility() == View.VISIBLE) { // TODO: use action string in Android SDK intent.putExtra("android.app.extra.PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION", mKeepAccountMigrated.isChecked()); } } private void specifySkipEncryption(Intent intent) { if (mSkipEncryption.getVisibility() == View.VISIBLE) { intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION, mSkipEncryption.isChecked()); } } private void specifyLogoUri(Intent intent) { intent.putExtra(EXTRA_PROVISIONING_LOGO_URI, mLogoUri); if (mLogoUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setClipData(ClipData.newUri(getActivity().getContentResolver(), "", mLogoUri)); } } private void specifyColor(Intent intent) { intent.putExtra(EXTRA_PROVISIONING_MAIN_COLOR, mCurrentColor); } private void showNoProvisioningPossibleUI() { mNavigationNextButton.setVisibility(View.GONE); TextView textView = (TextView) getView().findViewById(R.id.setup_management_message_id); textView.setText(Util.isDeviceOwner(getActivity()) ? R.string.provisioning_not_possible_for_do : R.string.provisioning_not_possible); } /** * Set visibility of all provisioning methods * * @return false if none of the provisioning method is visible */ private boolean setProvisioningMethodsVisibility() { boolean hasProvisioningOption = false; hasProvisioningOption |= setVisibility(ACTION_PROVISION_MANAGED_PROFILE, R.id.setup_managed_profile); hasProvisioningOption |= setVisibility(ACTION_PROVISION_MANAGED_DEVICE, R.id.setup_device_owner); return hasProvisioningOption; } private boolean setVisibility(String action, int radioButtonId) { final int visibility = ProvisioningStateUtil.isProvisioningAllowed(getActivity(), action) ? View.VISIBLE : View.GONE; getView().findViewById(radioButtonId).setVisibility(visibility); return visibility == View.VISIBLE; } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { Activity activity = getActivity(); switch (requestCode) { case REQUEST_PROVISION_MANAGED_PROFILE: case REQUEST_PROVISION_DEVICE_OWNER: if (resultCode == Activity.RESULT_OK) { // Success, finish the enclosing activity. NOTE: Only finish once we're done // here, as in synchronous auth cases we don't want the user to return to the // Android setup wizard or add-account flow prematurely. activity.setResult(Activity.RESULT_OK); activity.finish(); } else { // Something went wrong (either provisioning failed, or the user backed out). // Let the user decide how to proceed. Toast.makeText(activity, R.string.provisioning_failed_or_cancelled, Toast.LENGTH_SHORT).show(); } break; case REQUEST_GET_LOGO: if (data != null && data.getData() != null) { mLogoUri = data.getData(); mLogoValue.setText(mLogoUri.getLastPathSegment()); Util.updateImageView(getActivity(), mLogoPreviewView, mLogoUri); } break; } } @Override public void onClick(View view) { switch (view.getId()) { case R.id.params_indicator : if (mParamsView.getVisibility() == View.VISIBLE) { mParamsView.setVisibility(View.GONE); mParamsIndicator.setImageState(STATE_COLLAPSED, false); } else { mParamsIndicator.setImageState(STATE_EXPANDED, false); mParamsView.setVisibility(View.VISIBLE); } break; case R.id.color_select_button: ColorPicker.newInstance(mCurrentColor, FRAGMENT_TAG, "provisioningColor") .show(getFragmentManager(), "colorPicker"); break; case R.id.logo_select_button: startActivityForResult(getGetContentIntent(), REQUEST_GET_LOGO); break; } } @Override public void onColorSelected(int colorValue, String id) { mCurrentColor = colorValue; mColorValue.setText(String.format(ColorPicker.COLOR_STRING_FORMATTER, colorValue)); mColorPreviewView.setImageTintList(ColorStateList.valueOf(colorValue)); } @Override public void onNavigateBack() { getActivity().onBackPressed(); } @Override public void onNavigateNext() { if (mSetupOptions.getCheckedRadioButtonId() == R.id.setup_managed_profile) { maybeLaunchProvisioning(ACTION_PROVISION_MANAGED_PROFILE, REQUEST_PROVISION_MANAGED_PROFILE); } else { maybeLaunchProvisioning(ACTION_PROVISION_MANAGED_DEVICE, REQUEST_PROVISION_DEVICE_OWNER); } } private Intent getGetContentIntent() { Intent getContent = new Intent(Intent.ACTION_GET_CONTENT); getContent.setType("image/*"); return getContent; } private boolean canAnAppHandleGetContent() { return getGetContentIntent().resolveActivity(getActivity().getPackageManager()) != null; } }