| /* | |
| * Copyright (C) 2016 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.settings; | |
| import android.annotation.NonNull; | |
| import android.app.Activity; | |
| import android.app.AlertDialog; | |
| import android.app.admin.DevicePolicyManager; | |
| import android.content.DialogInterface; | |
| import android.net.http.SslCertificate; | |
| import android.os.UserHandle; | |
| import android.os.UserManager; | |
| import android.view.View; | |
| import android.view.animation.AnimationUtils; | |
| import android.widget.AdapterView; | |
| import android.widget.ArrayAdapter; | |
| import android.widget.Button; | |
| import android.widget.LinearLayout; | |
| import android.widget.Spinner; | |
| import com.android.settings.TrustedCredentialsSettings.CertHolder; | |
| import java.security.cert.X509Certificate; | |
| import java.util.ArrayList; | |
| import java.util.List; | |
| class TrustedCredentialsDialogBuilder extends AlertDialog.Builder { | |
| public interface DelegateInterface { | |
| List<X509Certificate> getX509CertsFromCertHolder(CertHolder certHolder); | |
| void removeOrInstallCert(CertHolder certHolder); | |
| } | |
| private final DialogEventHandler mDialogEventHandler; | |
| public TrustedCredentialsDialogBuilder(Activity activity, DelegateInterface delegate) { | |
| super(activity); | |
| mDialogEventHandler = new DialogEventHandler(activity, delegate); | |
| initDefaultBuilderParams(); | |
| } | |
| public TrustedCredentialsDialogBuilder setCertHolder(CertHolder certHolder) { | |
| return setCertHolders(certHolder == null ? new CertHolder[0] | |
| : new CertHolder[]{certHolder}); | |
| } | |
| public TrustedCredentialsDialogBuilder setCertHolders(@NonNull CertHolder[] certHolders) { | |
| mDialogEventHandler.setCertHolders(certHolders); | |
| return this; | |
| } | |
| @Override | |
| public AlertDialog create() { | |
| AlertDialog dialog = super.create(); | |
| dialog.setOnShowListener(mDialogEventHandler); | |
| mDialogEventHandler.setDialog(dialog); | |
| return dialog; | |
| } | |
| private void initDefaultBuilderParams() { | |
| setTitle(com.android.internal.R.string.ssl_certificate); | |
| setView(mDialogEventHandler.mRootContainer); | |
| // Enable buttons here. The actual labels and listeners are configured in nextOrDismiss | |
| setPositiveButton(R.string.trusted_credentials_trust_label, null); | |
| setNegativeButton(android.R.string.ok, null); | |
| } | |
| private static class DialogEventHandler implements DialogInterface.OnShowListener, | |
| View.OnClickListener { | |
| private static final long OUT_DURATION_MS = 300; | |
| private static final long IN_DURATION_MS = 200; | |
| private final Activity mActivity; | |
| private final DevicePolicyManager mDpm; | |
| private final UserManager mUserManager; | |
| private final DelegateInterface mDelegate; | |
| private final LinearLayout mRootContainer; | |
| private int mCurrentCertIndex = -1; | |
| private AlertDialog mDialog; | |
| private Button mPositiveButton; | |
| private Button mNegativeButton; | |
| private boolean mNeedsApproval; | |
| private CertHolder[] mCertHolders = new CertHolder[0]; | |
| private View mCurrentCertLayout = null; | |
| public DialogEventHandler(Activity activity, DelegateInterface delegate) { | |
| mActivity = activity; | |
| mDpm = activity.getSystemService(DevicePolicyManager.class); | |
| mUserManager = activity.getSystemService(UserManager.class); | |
| mDelegate = delegate; | |
| mRootContainer = new LinearLayout(mActivity); | |
| mRootContainer.setOrientation(LinearLayout.VERTICAL); | |
| } | |
| public void setDialog(AlertDialog dialog) { | |
| mDialog = dialog; | |
| } | |
| public void setCertHolders(CertHolder[] certHolder) { | |
| mCertHolders = certHolder; | |
| } | |
| @Override | |
| public void onShow(DialogInterface dialogInterface) { | |
| // Config the display content only when the dialog is shown because the | |
| // positive/negative buttons don't exist until the dialog is shown | |
| nextOrDismiss(); | |
| } | |
| @Override | |
| public void onClick(View view) { | |
| if (view == mPositiveButton) { | |
| if (mNeedsApproval) { | |
| onClickTrust(); | |
| } else { | |
| onClickOk(); | |
| } | |
| } else if (view == mNegativeButton) { | |
| onClickRemove(); | |
| } | |
| } | |
| private void onClickOk() { | |
| nextOrDismiss(); | |
| } | |
| private void onClickTrust() { | |
| CertHolder certHolder = getCurrentCertInfo(); | |
| mDpm.approveCaCert(certHolder.getAlias(), certHolder.getUserId(), true); | |
| nextOrDismiss(); | |
| } | |
| private void onClickRemove() { | |
| final CertHolder certHolder = getCurrentCertInfo(); | |
| new AlertDialog.Builder(mActivity) | |
| .setMessage(getButtonConfirmation(certHolder)) | |
| .setPositiveButton(android.R.string.yes, | |
| new DialogInterface.OnClickListener() { | |
| @Override | |
| public void onClick(DialogInterface dialog, int id) { | |
| mDelegate.removeOrInstallCert(certHolder); | |
| dialog.dismiss(); | |
| nextOrDismiss(); | |
| } | |
| }) | |
| .setNegativeButton(android.R.string.no, null) | |
| .show(); | |
| } | |
| private CertHolder getCurrentCertInfo() { | |
| return mCurrentCertIndex < mCertHolders.length ? mCertHolders[mCurrentCertIndex] : null; | |
| } | |
| private void nextOrDismiss() { | |
| mCurrentCertIndex++; | |
| // find next non-null cert or dismiss | |
| while (mCurrentCertIndex < mCertHolders.length && getCurrentCertInfo() == null) { | |
| mCurrentCertIndex++; | |
| } | |
| if (mCurrentCertIndex >= mCertHolders.length) { | |
| mDialog.dismiss(); | |
| return; | |
| } | |
| updateViewContainer(); | |
| updatePositiveButton(); | |
| updateNegativeButton(); | |
| } | |
| private void updatePositiveButton() { | |
| final CertHolder certHolder = getCurrentCertInfo(); | |
| mNeedsApproval = !certHolder.isSystemCert() && | |
| !mDpm.isCaCertApproved(certHolder.getAlias(), certHolder.getUserId()); | |
| // The ok button is optional. User can still dismiss the dialog by other means. | |
| // Display it only when trust button is not displayed, because we want users to | |
| // either remove or trust a CA cert when the cert is installed by DPC app. | |
| CharSequence displayText = mActivity.getText(mNeedsApproval | |
| ? R.string.trusted_credentials_trust_label | |
| : android.R.string.ok); | |
| mPositiveButton = updateButton(DialogInterface.BUTTON_POSITIVE, displayText); | |
| } | |
| private void updateNegativeButton() { | |
| final CertHolder certHolder = getCurrentCertInfo(); | |
| final boolean showRemoveButton = !mUserManager.hasUserRestriction( | |
| UserManager.DISALLOW_CONFIG_CREDENTIALS, | |
| new UserHandle(certHolder.getUserId())); | |
| CharSequence displayText = mActivity.getText(getButtonLabel(certHolder)); | |
| mNegativeButton = updateButton(DialogInterface.BUTTON_NEGATIVE, displayText); | |
| mNegativeButton.setVisibility(showRemoveButton ? View.VISIBLE : View.GONE); | |
| } | |
| /** | |
| * mDialog.setButton doesn't trigger text refresh since mDialog has been shown. | |
| * It's invoked only in case mDialog is refreshed. | |
| * setOnClickListener is invoked to avoid dismiss dialog onClick | |
| */ | |
| private Button updateButton(int buttonType, CharSequence displayText) { | |
| mDialog.setButton(buttonType, displayText, (DialogInterface.OnClickListener) null); | |
| Button button = mDialog.getButton(buttonType); | |
| button.setText(displayText); | |
| button.setOnClickListener(this); | |
| return button; | |
| } | |
| private void updateViewContainer() { | |
| CertHolder certHolder = getCurrentCertInfo(); | |
| LinearLayout nextCertLayout = getCertLayout(certHolder); | |
| // Displaying first cert doesn't require animation | |
| if (mCurrentCertLayout == null) { | |
| mCurrentCertLayout = nextCertLayout; | |
| mRootContainer.addView(mCurrentCertLayout); | |
| } else { | |
| animateViewTransition(nextCertLayout); | |
| } | |
| } | |
| private LinearLayout getCertLayout(final CertHolder certHolder) { | |
| final ArrayList<View> views = new ArrayList<View>(); | |
| final ArrayList<String> titles = new ArrayList<String>(); | |
| List<X509Certificate> certificates = mDelegate.getX509CertsFromCertHolder(certHolder); | |
| if (certificates != null) { | |
| for (X509Certificate certificate : certificates) { | |
| SslCertificate sslCert = new SslCertificate(certificate); | |
| views.add(sslCert.inflateCertificateView(mActivity)); | |
| titles.add(sslCert.getIssuedTo().getCName()); | |
| } | |
| } | |
| ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(mActivity, | |
| android.R.layout.simple_spinner_item, | |
| titles); | |
| arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); | |
| Spinner spinner = new Spinner(mActivity); | |
| spinner.setAdapter(arrayAdapter); | |
| spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { | |
| @Override | |
| public void onItemSelected(AdapterView<?> parent, View view, int position, | |
| long id) { | |
| for (int i = 0; i < views.size(); i++) { | |
| views.get(i).setVisibility(i == position ? View.VISIBLE : View.GONE); | |
| } | |
| } | |
| @Override | |
| public void onNothingSelected(AdapterView<?> parent) { | |
| } | |
| }); | |
| LinearLayout certLayout = new LinearLayout(mActivity); | |
| certLayout.setOrientation(LinearLayout.VERTICAL); | |
| certLayout.addView(spinner); | |
| for (int i = 0; i < views.size(); ++i) { | |
| View certificateView = views.get(i); | |
| // Show first cert by default | |
| certificateView.setVisibility(i == 0 ? View.VISIBLE : View.GONE); | |
| certLayout.addView(certificateView); | |
| } | |
| return certLayout; | |
| } | |
| private static int getButtonConfirmation(CertHolder certHolder) { | |
| return certHolder.isSystemCert() ? ( certHolder.isDeleted() | |
| ? R.string.trusted_credentials_enable_confirmation | |
| : R.string.trusted_credentials_disable_confirmation ) | |
| : R.string.trusted_credentials_remove_confirmation; | |
| } | |
| private static int getButtonLabel(CertHolder certHolder) { | |
| return certHolder.isSystemCert() ? ( certHolder.isDeleted() | |
| ? R.string.trusted_credentials_enable_label | |
| : R.string.trusted_credentials_disable_label ) | |
| : R.string.trusted_credentials_remove_label; | |
| } | |
| /* Animation code */ | |
| private void animateViewTransition(final View nextCertView) { | |
| animateOldContent(new Runnable() { | |
| @Override | |
| public void run() { | |
| addAndAnimateNewContent(nextCertView); | |
| } | |
| }); | |
| } | |
| private void animateOldContent(Runnable callback) { | |
| // Fade out | |
| mCurrentCertLayout.animate() | |
| .alpha(0) | |
| .setDuration(OUT_DURATION_MS) | |
| .setInterpolator(AnimationUtils.loadInterpolator(mActivity, | |
| android.R.interpolator.fast_out_linear_in)) | |
| .withEndAction(callback) | |
| .start(); | |
| } | |
| private void addAndAnimateNewContent(View nextCertLayout) { | |
| mCurrentCertLayout = nextCertLayout; | |
| mRootContainer.removeAllViews(); | |
| mRootContainer.addView(nextCertLayout); | |
| mRootContainer.addOnLayoutChangeListener( new View.OnLayoutChangeListener() { | |
| @Override | |
| public void onLayoutChange(View v, int left, int top, int right, int bottom, | |
| int oldLeft, int oldTop, int oldRight, int oldBottom) { | |
| mRootContainer.removeOnLayoutChangeListener(this); | |
| // Animate slide in from the right | |
| final int containerWidth = mRootContainer.getWidth(); | |
| mCurrentCertLayout.setTranslationX(containerWidth); | |
| mCurrentCertLayout.animate() | |
| .translationX(0) | |
| .setInterpolator(AnimationUtils.loadInterpolator(mActivity, | |
| android.R.interpolator.linear_out_slow_in)) | |
| .setDuration(IN_DURATION_MS) | |
| .start(); | |
| } | |
| }); | |
| } | |
| } | |
| } |