blob: 03414bdc2e1482673e592b16a22368cf918f8a8b [file] [log] [blame]
Victor Changd7d0e1b2016-04-05 20:01:24 +01001/*
2 * Copyright (C) 2016 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 */
16package com.android.settings;
17
18import android.annotation.NonNull;
19import android.app.Activity;
20import android.app.AlertDialog;
21import android.app.admin.DevicePolicyManager;
22import android.content.DialogInterface;
Victor Changf2e02db2016-04-21 15:45:59 +010023import android.content.pm.UserInfo;
Victor Changd7d0e1b2016-04-05 20:01:24 +010024import android.net.http.SslCertificate;
25import android.os.UserHandle;
26import android.os.UserManager;
27import android.view.View;
28import android.view.animation.AnimationUtils;
29import android.widget.AdapterView;
30import android.widget.ArrayAdapter;
31import android.widget.Button;
32import android.widget.LinearLayout;
33import android.widget.Spinner;
34
Victor Changf2e02db2016-04-21 15:45:59 +010035import com.android.internal.widget.LockPatternUtils;
Victor Changd7d0e1b2016-04-05 20:01:24 +010036import com.android.settings.TrustedCredentialsSettings.CertHolder;
Victor Chang71d1fed2016-05-31 22:07:20 +010037import com.android.settingslib.RestrictedLockUtils;
Victor Changd7d0e1b2016-04-05 20:01:24 +010038
39import java.security.cert.X509Certificate;
40import java.util.ArrayList;
41import java.util.List;
Victor Chang45ca9062016-05-23 19:47:38 +010042import java.util.function.IntConsumer;
Victor Changd7d0e1b2016-04-05 20:01:24 +010043
44class TrustedCredentialsDialogBuilder extends AlertDialog.Builder {
45 public interface DelegateInterface {
46 List<X509Certificate> getX509CertsFromCertHolder(CertHolder certHolder);
47 void removeOrInstallCert(CertHolder certHolder);
Victor Chang45ca9062016-05-23 19:47:38 +010048 boolean startConfirmCredentialIfNotConfirmed(int userId,
49 IntConsumer onCredentialConfirmedListener);
Victor Changd7d0e1b2016-04-05 20:01:24 +010050 }
51
52 private final DialogEventHandler mDialogEventHandler;
53
54 public TrustedCredentialsDialogBuilder(Activity activity, DelegateInterface delegate) {
55 super(activity);
56 mDialogEventHandler = new DialogEventHandler(activity, delegate);
57
58 initDefaultBuilderParams();
59 }
60
61 public TrustedCredentialsDialogBuilder setCertHolder(CertHolder certHolder) {
62 return setCertHolders(certHolder == null ? new CertHolder[0]
63 : new CertHolder[]{certHolder});
64 }
65
66 public TrustedCredentialsDialogBuilder setCertHolders(@NonNull CertHolder[] certHolders) {
67 mDialogEventHandler.setCertHolders(certHolders);
68 return this;
69 }
70
71 @Override
72 public AlertDialog create() {
73 AlertDialog dialog = super.create();
74 dialog.setOnShowListener(mDialogEventHandler);
75 mDialogEventHandler.setDialog(dialog);
76 return dialog;
77 }
78
79 private void initDefaultBuilderParams() {
80 setTitle(com.android.internal.R.string.ssl_certificate);
81 setView(mDialogEventHandler.mRootContainer);
82
83 // Enable buttons here. The actual labels and listeners are configured in nextOrDismiss
84 setPositiveButton(R.string.trusted_credentials_trust_label, null);
85 setNegativeButton(android.R.string.ok, null);
86 }
87
88 private static class DialogEventHandler implements DialogInterface.OnShowListener,
89 View.OnClickListener {
90 private static final long OUT_DURATION_MS = 300;
91 private static final long IN_DURATION_MS = 200;
92
93 private final Activity mActivity;
94 private final DevicePolicyManager mDpm;
95 private final UserManager mUserManager;
96 private final DelegateInterface mDelegate;
97 private final LinearLayout mRootContainer;
98
99 private int mCurrentCertIndex = -1;
100 private AlertDialog mDialog;
101 private Button mPositiveButton;
102 private Button mNegativeButton;
103 private boolean mNeedsApproval;
104 private CertHolder[] mCertHolders = new CertHolder[0];
105 private View mCurrentCertLayout = null;
106
107 public DialogEventHandler(Activity activity, DelegateInterface delegate) {
108 mActivity = activity;
109 mDpm = activity.getSystemService(DevicePolicyManager.class);
110 mUserManager = activity.getSystemService(UserManager.class);
111 mDelegate = delegate;
112
113 mRootContainer = new LinearLayout(mActivity);
114 mRootContainer.setOrientation(LinearLayout.VERTICAL);
115 }
116
117 public void setDialog(AlertDialog dialog) {
118 mDialog = dialog;
119 }
120
121 public void setCertHolders(CertHolder[] certHolder) {
122 mCertHolders = certHolder;
123 }
124
125 @Override
126 public void onShow(DialogInterface dialogInterface) {
127 // Config the display content only when the dialog is shown because the
128 // positive/negative buttons don't exist until the dialog is shown
129 nextOrDismiss();
130 }
131
132 @Override
133 public void onClick(View view) {
134 if (view == mPositiveButton) {
135 if (mNeedsApproval) {
136 onClickTrust();
137 } else {
138 onClickOk();
139 }
140 } else if (view == mNegativeButton) {
Robin Leebed85592016-09-01 18:35:00 +0100141 onClickEnableOrDisable();
Victor Changd7d0e1b2016-04-05 20:01:24 +0100142 }
143 }
144
145 private void onClickOk() {
146 nextOrDismiss();
147 }
148
149 private void onClickTrust() {
150 CertHolder certHolder = getCurrentCertInfo();
Victor Chang45ca9062016-05-23 19:47:38 +0100151 if (!mDelegate.startConfirmCredentialIfNotConfirmed(certHolder.getUserId(),
152 this::onCredentialConfirmed)) {
Victor Chang01f4dbc2016-05-09 16:13:10 +0100153 mDpm.approveCaCert(certHolder.getAlias(), certHolder.getUserId(), true);
154 nextOrDismiss();
155 }
Victor Changd7d0e1b2016-04-05 20:01:24 +0100156 }
157
Robin Leebed85592016-09-01 18:35:00 +0100158 private void onClickEnableOrDisable() {
Victor Changd7d0e1b2016-04-05 20:01:24 +0100159 final CertHolder certHolder = getCurrentCertInfo();
Robin Leebed85592016-09-01 18:35:00 +0100160 DialogInterface.OnClickListener onConfirm = new DialogInterface.OnClickListener() {
161 @Override
162 public void onClick(DialogInterface dialog, int id) {
163 mDelegate.removeOrInstallCert(certHolder);
164 nextOrDismiss();
165 }
166 };
167 if (certHolder.isSystemCert()) {
168 // Removing system certs is reversible, so skip confirmation.
169 onConfirm.onClick(null, -1);
170 } else {
171 new AlertDialog.Builder(mActivity)
172 .setMessage(R.string.trusted_credentials_remove_confirmation)
173 .setPositiveButton(android.R.string.yes, onConfirm)
174 .setNegativeButton(android.R.string.no, null)
175 .show();
176
177 }
Victor Changd7d0e1b2016-04-05 20:01:24 +0100178 }
179
Victor Chang45ca9062016-05-23 19:47:38 +0100180 private void onCredentialConfirmed(int userId) {
181 if (mDialog.isShowing() && mNeedsApproval && getCurrentCertInfo() != null
182 && getCurrentCertInfo().getUserId() == userId) {
183 // Treat it as user just clicks "trust" for this cert
184 onClickTrust();
185 }
186 }
187
Victor Changd7d0e1b2016-04-05 20:01:24 +0100188 private CertHolder getCurrentCertInfo() {
189 return mCurrentCertIndex < mCertHolders.length ? mCertHolders[mCurrentCertIndex] : null;
190 }
191
192 private void nextOrDismiss() {
193 mCurrentCertIndex++;
194 // find next non-null cert or dismiss
195 while (mCurrentCertIndex < mCertHolders.length && getCurrentCertInfo() == null) {
196 mCurrentCertIndex++;
197 }
198
199 if (mCurrentCertIndex >= mCertHolders.length) {
200 mDialog.dismiss();
201 return;
202 }
203
204 updateViewContainer();
205 updatePositiveButton();
206 updateNegativeButton();
207 }
208
Victor Changf2e02db2016-04-21 15:45:59 +0100209 /**
210 * @return true if current user or parent user is guarded by screenlock
211 */
212 private boolean isUserSecure(int userId) {
213 final LockPatternUtils lockPatternUtils = new LockPatternUtils(mActivity);
214 if (lockPatternUtils.isSecure(userId)) {
215 return true;
216 }
217 UserInfo parentUser = mUserManager.getProfileParent(userId);
218 if (parentUser == null) {
219 return false;
220 }
221 return lockPatternUtils.isSecure(parentUser.id);
222 }
223
Victor Changd7d0e1b2016-04-05 20:01:24 +0100224 private void updatePositiveButton() {
225 final CertHolder certHolder = getCurrentCertInfo();
Victor Changf2e02db2016-04-21 15:45:59 +0100226 mNeedsApproval = !certHolder.isSystemCert()
227 && isUserSecure(certHolder.getUserId())
228 && !mDpm.isCaCertApproved(certHolder.getAlias(), certHolder.getUserId());
Victor Changd7d0e1b2016-04-05 20:01:24 +0100229
Victor Chang71d1fed2016-05-31 22:07:20 +0100230 final boolean isProfileOrDeviceOwner = RestrictedLockUtils.getProfileOrDeviceOwner(
231 mActivity, certHolder.getUserId()) != null;
232
233 // Show trust button only when it requires consumer user (non-PO/DO) to approve
234 CharSequence displayText = mActivity.getText(!isProfileOrDeviceOwner && mNeedsApproval
Victor Changd7d0e1b2016-04-05 20:01:24 +0100235 ? R.string.trusted_credentials_trust_label
236 : android.R.string.ok);
237 mPositiveButton = updateButton(DialogInterface.BUTTON_POSITIVE, displayText);
238 }
239
240 private void updateNegativeButton() {
241 final CertHolder certHolder = getCurrentCertInfo();
242 final boolean showRemoveButton = !mUserManager.hasUserRestriction(
243 UserManager.DISALLOW_CONFIG_CREDENTIALS,
244 new UserHandle(certHolder.getUserId()));
245 CharSequence displayText = mActivity.getText(getButtonLabel(certHolder));
246 mNegativeButton = updateButton(DialogInterface.BUTTON_NEGATIVE, displayText);
247 mNegativeButton.setVisibility(showRemoveButton ? View.VISIBLE : View.GONE);
248 }
249
250 /**
251 * mDialog.setButton doesn't trigger text refresh since mDialog has been shown.
252 * It's invoked only in case mDialog is refreshed.
253 * setOnClickListener is invoked to avoid dismiss dialog onClick
254 */
255 private Button updateButton(int buttonType, CharSequence displayText) {
256 mDialog.setButton(buttonType, displayText, (DialogInterface.OnClickListener) null);
257 Button button = mDialog.getButton(buttonType);
258 button.setText(displayText);
259 button.setOnClickListener(this);
260 return button;
261 }
262
263
264 private void updateViewContainer() {
265 CertHolder certHolder = getCurrentCertInfo();
266 LinearLayout nextCertLayout = getCertLayout(certHolder);
267
268 // Displaying first cert doesn't require animation
269 if (mCurrentCertLayout == null) {
270 mCurrentCertLayout = nextCertLayout;
271 mRootContainer.addView(mCurrentCertLayout);
272 } else {
273 animateViewTransition(nextCertLayout);
274 }
275 }
276
277 private LinearLayout getCertLayout(final CertHolder certHolder) {
278 final ArrayList<View> views = new ArrayList<View>();
279 final ArrayList<String> titles = new ArrayList<String>();
280 List<X509Certificate> certificates = mDelegate.getX509CertsFromCertHolder(certHolder);
281 if (certificates != null) {
282 for (X509Certificate certificate : certificates) {
283 SslCertificate sslCert = new SslCertificate(certificate);
284 views.add(sslCert.inflateCertificateView(mActivity));
285 titles.add(sslCert.getIssuedTo().getCName());
286 }
287 }
288
289 ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(mActivity,
290 android.R.layout.simple_spinner_item,
291 titles);
292 arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
293 Spinner spinner = new Spinner(mActivity);
294 spinner.setAdapter(arrayAdapter);
295 spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
296 @Override
297 public void onItemSelected(AdapterView<?> parent, View view, int position,
298 long id) {
299 for (int i = 0; i < views.size(); i++) {
300 views.get(i).setVisibility(i == position ? View.VISIBLE : View.GONE);
301 }
302 }
303
304 @Override
305 public void onNothingSelected(AdapterView<?> parent) {
306 }
307 });
308
309 LinearLayout certLayout = new LinearLayout(mActivity);
310 certLayout.setOrientation(LinearLayout.VERTICAL);
311 certLayout.addView(spinner);
312 for (int i = 0; i < views.size(); ++i) {
313 View certificateView = views.get(i);
314 // Show first cert by default
315 certificateView.setVisibility(i == 0 ? View.VISIBLE : View.GONE);
316 certLayout.addView(certificateView);
317 }
318
319 return certLayout;
320 }
321
Victor Changd7d0e1b2016-04-05 20:01:24 +0100322 private static int getButtonLabel(CertHolder certHolder) {
323 return certHolder.isSystemCert() ? ( certHolder.isDeleted()
324 ? R.string.trusted_credentials_enable_label
325 : R.string.trusted_credentials_disable_label )
326 : R.string.trusted_credentials_remove_label;
327 }
328
329 /* Animation code */
330 private void animateViewTransition(final View nextCertView) {
331 animateOldContent(new Runnable() {
332 @Override
333 public void run() {
334 addAndAnimateNewContent(nextCertView);
335 }
336 });
337 }
338
339 private void animateOldContent(Runnable callback) {
340 // Fade out
341 mCurrentCertLayout.animate()
342 .alpha(0)
343 .setDuration(OUT_DURATION_MS)
344 .setInterpolator(AnimationUtils.loadInterpolator(mActivity,
345 android.R.interpolator.fast_out_linear_in))
346 .withEndAction(callback)
347 .start();
348 }
349
350 private void addAndAnimateNewContent(View nextCertLayout) {
351 mCurrentCertLayout = nextCertLayout;
352 mRootContainer.removeAllViews();
353 mRootContainer.addView(nextCertLayout);
354
355 mRootContainer.addOnLayoutChangeListener( new View.OnLayoutChangeListener() {
356 @Override
357 public void onLayoutChange(View v, int left, int top, int right, int bottom,
358 int oldLeft, int oldTop, int oldRight, int oldBottom) {
359 mRootContainer.removeOnLayoutChangeListener(this);
360
361 // Animate slide in from the right
362 final int containerWidth = mRootContainer.getWidth();
363 mCurrentCertLayout.setTranslationX(containerWidth);
364 mCurrentCertLayout.animate()
365 .translationX(0)
366 .setInterpolator(AnimationUtils.loadInterpolator(mActivity,
367 android.R.interpolator.linear_out_slow_in))
368 .setDuration(IN_DURATION_MS)
369 .start();
370 }
371 });
372 }
373 }
374}