OSDN Git Service

Ignore orientation change to preserve ApnEditor. am: f414d58ddf am: 77f75b3859 am...
[android-x86/packages-apps-Settings.git] / src / com / android / settings / TrustedCredentialsDialogBuilder.java
1 /*
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  */
16 package com.android.settings;
17
18 import android.annotation.NonNull;
19 import android.app.Activity;
20 import android.app.AlertDialog;
21 import android.app.admin.DevicePolicyManager;
22 import android.content.DialogInterface;
23 import android.content.pm.UserInfo;
24 import android.net.http.SslCertificate;
25 import android.os.UserHandle;
26 import android.os.UserManager;
27 import android.view.View;
28 import android.view.animation.AnimationUtils;
29 import android.widget.AdapterView;
30 import android.widget.ArrayAdapter;
31 import android.widget.Button;
32 import android.widget.LinearLayout;
33 import android.widget.Spinner;
34
35 import com.android.internal.widget.LockPatternUtils;
36 import com.android.settings.TrustedCredentialsSettings.CertHolder;
37 import com.android.settingslib.RestrictedLockUtils;
38
39 import java.security.cert.X509Certificate;
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.function.IntConsumer;
43
44 class TrustedCredentialsDialogBuilder extends AlertDialog.Builder {
45     public interface DelegateInterface {
46         List<X509Certificate> getX509CertsFromCertHolder(CertHolder certHolder);
47         void removeOrInstallCert(CertHolder certHolder);
48         boolean startConfirmCredentialIfNotConfirmed(int userId,
49                 IntConsumer onCredentialConfirmedListener);
50     }
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) {
141                 onClickEnableOrDisable();
142             }
143         }
144
145         private void onClickOk() {
146             nextOrDismiss();
147         }
148
149         private void onClickTrust() {
150             CertHolder certHolder = getCurrentCertInfo();
151             if (!mDelegate.startConfirmCredentialIfNotConfirmed(certHolder.getUserId(),
152                     this::onCredentialConfirmed)) {
153                 mDpm.approveCaCert(certHolder.getAlias(), certHolder.getUserId(), true);
154                 nextOrDismiss();
155             }
156         }
157
158         private void onClickEnableOrDisable() {
159             final CertHolder certHolder = getCurrentCertInfo();
160             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             }
178         }
179
180         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
188         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
209         /**
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
224         private void updatePositiveButton() {
225             final CertHolder certHolder = getCurrentCertInfo();
226             mNeedsApproval = !certHolder.isSystemCert()
227                     && isUserSecure(certHolder.getUserId())
228                     && !mDpm.isCaCertApproved(certHolder.getAlias(), certHolder.getUserId());
229
230             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
235                     ? 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
322         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 }