PageDialogsHandler.java revision db6ff8999159f386ea8a99d980ce533b717fca78
1/*
2 * Copyright (C) 2010 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
17package com.android.browser;
18
19import android.app.AlertDialog;
20import android.content.Context;
21import android.content.DialogInterface;
22import android.content.res.Configuration;
23import android.net.http.SslCertificate;
24import android.net.http.SslError;
25import android.text.format.DateFormat;
26import android.view.LayoutInflater;
27import android.view.View;
28import android.webkit.HttpAuthHandler;
29import android.webkit.SslErrorHandler;
30import android.webkit.WebView;
31import android.widget.LinearLayout;
32import android.widget.TextView;
33
34import java.util.Date;
35
36/**
37 * Displays page info
38 *
39 */
40public class PageDialogsHandler {
41
42    private Context mContext;
43    private Controller mController;
44    private boolean mPageInfoFromShowSSLCertificateOnError;
45    private String mUrlCertificateOnError;
46    private Tab mPageInfoView;
47    private AlertDialog mPageInfoDialog;
48
49    // as SSLCertificateOnError has different style for landscape / portrait,
50    // we have to re-open it when configuration changed
51    private AlertDialog mSSLCertificateOnErrorDialog;
52    private WebView mSSLCertificateOnErrorView;
53    private SslErrorHandler mSSLCertificateOnErrorHandler;
54    private SslError mSSLCertificateOnErrorError;
55
56    // as SSLCertificate has different style for landscape / portrait, we
57    // have to re-open it when configuration changed
58    private AlertDialog mSSLCertificateDialog;
59    private Tab mSSLCertificateView;
60    private HttpAuthenticationDialog mHttpAuthenticationDialog;
61
62    public PageDialogsHandler(Context context, Controller controller) {
63        mContext = context;
64        mController = controller;
65    }
66
67    public void onConfigurationChanged(Configuration config) {
68        if (mPageInfoDialog != null) {
69            mPageInfoDialog.dismiss();
70            showPageInfo(mPageInfoView,
71                         mPageInfoFromShowSSLCertificateOnError,
72                         mUrlCertificateOnError);
73        }
74        if (mSSLCertificateDialog != null) {
75            mSSLCertificateDialog.dismiss();
76            showSSLCertificate(mSSLCertificateView);
77        }
78        if (mSSLCertificateOnErrorDialog != null) {
79            mSSLCertificateOnErrorDialog.dismiss();
80            showSSLCertificateOnError(mSSLCertificateOnErrorView,
81                                      mSSLCertificateOnErrorHandler,
82                                      mSSLCertificateOnErrorError);
83        }
84        if (mHttpAuthenticationDialog != null) {
85            mHttpAuthenticationDialog.reshow();
86        }
87    }
88
89    /**
90     * Displays an http-authentication dialog.
91     */
92    void showHttpAuthentication(final Tab tab, final HttpAuthHandler handler, String host, String realm) {
93        mHttpAuthenticationDialog = new HttpAuthenticationDialog(mContext, host, realm);
94        mHttpAuthenticationDialog.setOkListener(new HttpAuthenticationDialog.OkListener() {
95            public void onOk(String host, String realm, String username, String password) {
96                setHttpAuthUsernamePassword(host, realm, username, password);
97                handler.proceed(username, password);
98                mHttpAuthenticationDialog = null;
99            }
100        });
101        mHttpAuthenticationDialog.setCancelListener(new HttpAuthenticationDialog.CancelListener() {
102            public void onCancel() {
103                handler.cancel();
104                mController.onUpdatedLockIcon(tab);
105                mHttpAuthenticationDialog = null;
106            }
107        });
108        mHttpAuthenticationDialog.show();
109    }
110
111    /**
112     * Set HTTP authentication password.
113     *
114     * @param host The host for the password
115     * @param realm The realm for the password
116     * @param username The username for the password. If it is null, it means
117     *            password can't be saved.
118     * @param password The password
119     */
120    public void setHttpAuthUsernamePassword(String host, String realm,
121                                            String username,
122                                            String password) {
123        WebView w = mController.getCurrentTopWebView();
124        if (w != null) {
125            w.setHttpAuthUsernamePassword(host, realm, username, password);
126        }
127    }
128
129    /**
130     * Displays a page-info dialog.
131     * @param tab The tab to show info about
132     * @param fromShowSSLCertificateOnError The flag that indicates whether
133     * this dialog was opened from the SSL-certificate-on-error dialog or
134     * not. This is important, since we need to know whether to return to
135     * the parent dialog or simply dismiss.
136     * @param urlCertificateOnError The URL that invokes SSLCertificateError.
137     * Null when fromShowSSLCertificateOnError is false.
138     */
139    void showPageInfo(final Tab tab,
140            final boolean fromShowSSLCertificateOnError,
141            final String urlCertificateOnError) {
142        final LayoutInflater factory = LayoutInflater.from(mContext);
143
144        final View pageInfoView = factory.inflate(R.layout.page_info, null);
145
146        final WebView view = tab.getWebView();
147
148        String url = fromShowSSLCertificateOnError ? urlCertificateOnError : tab.getUrl();
149        String title = tab.getTitle();
150
151        if (url == null) {
152            url = "";
153        }
154        if (title == null) {
155            title = "";
156        }
157
158        ((TextView) pageInfoView.findViewById(R.id.address)).setText(url);
159        ((TextView) pageInfoView.findViewById(R.id.title)).setText(title);
160
161        mPageInfoView = tab;
162        mPageInfoFromShowSSLCertificateOnError = fromShowSSLCertificateOnError;
163        mUrlCertificateOnError = urlCertificateOnError;
164
165        AlertDialog.Builder alertDialogBuilder =
166            new AlertDialog.Builder(mContext)
167            .setTitle(R.string.page_info)
168            .setIcon(android.R.drawable.ic_dialog_info)
169            .setView(pageInfoView)
170            .setPositiveButton(
171                R.string.ok,
172                new DialogInterface.OnClickListener() {
173                    public void onClick(DialogInterface dialog,
174                                        int whichButton) {
175                        mPageInfoDialog = null;
176                        mPageInfoView = null;
177
178                        // if we came here from the SSL error dialog
179                        if (fromShowSSLCertificateOnError) {
180                            // go back to the SSL error dialog
181                            showSSLCertificateOnError(
182                                mSSLCertificateOnErrorView,
183                                mSSLCertificateOnErrorHandler,
184                                mSSLCertificateOnErrorError);
185                        }
186                    }
187                })
188            .setOnCancelListener(
189                new DialogInterface.OnCancelListener() {
190                    public void onCancel(DialogInterface dialog) {
191                        mPageInfoDialog = null;
192                        mPageInfoView = null;
193
194                        // if we came here from the SSL error dialog
195                        if (fromShowSSLCertificateOnError) {
196                            // go back to the SSL error dialog
197                            showSSLCertificateOnError(
198                                mSSLCertificateOnErrorView,
199                                mSSLCertificateOnErrorHandler,
200                                mSSLCertificateOnErrorError);
201                        }
202                    }
203                });
204
205        // if we have a main top-level page SSL certificate set or a certificate
206        // error
207        if (fromShowSSLCertificateOnError ||
208                (view != null && view.getCertificate() != null)) {
209            // add a 'View Certificate' button
210            alertDialogBuilder.setNeutralButton(
211                R.string.view_certificate,
212                new DialogInterface.OnClickListener() {
213                    public void onClick(DialogInterface dialog,
214                                        int whichButton) {
215                        mPageInfoDialog = null;
216                        mPageInfoView = null;
217
218                        // if we came here from the SSL error dialog
219                        if (fromShowSSLCertificateOnError) {
220                            // go back to the SSL error dialog
221                            showSSLCertificateOnError(
222                                mSSLCertificateOnErrorView,
223                                mSSLCertificateOnErrorHandler,
224                                mSSLCertificateOnErrorError);
225                        } else {
226                            // otherwise, display the top-most certificate from
227                            // the chain
228                            if (view.getCertificate() != null) {
229                                showSSLCertificate(tab);
230                            }
231                        }
232                    }
233                });
234        }
235
236        mPageInfoDialog = alertDialogBuilder.show();
237    }
238
239    /**
240     * Displays the main top-level page SSL certificate dialog
241     * (accessible from the Page-Info dialog).
242     * @param tab The tab to show certificate for.
243     */
244    private void showSSLCertificate(final Tab tab) {
245        final View certificateView =
246                inflateCertificateView(tab.getWebView().getCertificate());
247        if (certificateView == null) {
248            return;
249        }
250
251        LayoutInflater factory = LayoutInflater.from(mContext);
252
253        final LinearLayout placeholder =
254                (LinearLayout)certificateView.findViewById(R.id.placeholder);
255
256        LinearLayout ll = (LinearLayout) factory.inflate(
257            R.layout.ssl_success, placeholder);
258        ((TextView)ll.findViewById(R.id.success))
259            .setText(R.string.ssl_certificate_is_valid);
260
261        mSSLCertificateView = tab;
262        mSSLCertificateDialog =
263            new AlertDialog.Builder(mContext)
264                .setTitle(R.string.ssl_certificate).setIcon(
265                    R.drawable.ic_dialog_browser_certificate_secure)
266                .setView(certificateView)
267                .setPositiveButton(R.string.ok,
268                        new DialogInterface.OnClickListener() {
269                            public void onClick(DialogInterface dialog,
270                                    int whichButton) {
271                                mSSLCertificateDialog = null;
272                                mSSLCertificateView = null;
273
274                                showPageInfo(tab, false, null);
275                            }
276                        })
277                .setOnCancelListener(
278                        new DialogInterface.OnCancelListener() {
279                            public void onCancel(DialogInterface dialog) {
280                                mSSLCertificateDialog = null;
281                                mSSLCertificateView = null;
282
283                                showPageInfo(tab, false, null);
284                            }
285                        })
286                .show();
287    }
288
289    /**
290     * Displays the SSL error certificate dialog.
291     * @param view The target web-view.
292     * @param handler The SSL error handler responsible for cancelling the
293     * connection that resulted in an SSL error or proceeding per user request.
294     * @param error The SSL error object.
295     */
296    void showSSLCertificateOnError(
297            final WebView view, final SslErrorHandler handler,
298            final SslError error) {
299
300        final View certificateView =
301            inflateCertificateView(error.getCertificate());
302        if (certificateView == null) {
303            return;
304        }
305
306        LayoutInflater factory = LayoutInflater.from(mContext);
307
308        final LinearLayout placeholder =
309                (LinearLayout)certificateView.findViewById(R.id.placeholder);
310
311        if (error.hasError(SslError.SSL_UNTRUSTED)) {
312            LinearLayout ll = (LinearLayout)factory
313                .inflate(R.layout.ssl_warning, placeholder);
314            ((TextView)ll.findViewById(R.id.warning))
315                .setText(R.string.ssl_untrusted);
316        }
317
318        if (error.hasError(SslError.SSL_IDMISMATCH)) {
319            LinearLayout ll = (LinearLayout)factory
320                .inflate(R.layout.ssl_warning, placeholder);
321            ((TextView)ll.findViewById(R.id.warning))
322                .setText(R.string.ssl_mismatch);
323        }
324
325        if (error.hasError(SslError.SSL_EXPIRED)) {
326            LinearLayout ll = (LinearLayout)factory
327                .inflate(R.layout.ssl_warning, placeholder);
328            ((TextView)ll.findViewById(R.id.warning))
329                .setText(R.string.ssl_expired);
330        }
331
332        if (error.hasError(SslError.SSL_NOTYETVALID)) {
333            LinearLayout ll = (LinearLayout)factory
334                .inflate(R.layout.ssl_warning, placeholder);
335            ((TextView)ll.findViewById(R.id.warning))
336                .setText(R.string.ssl_not_yet_valid);
337        }
338
339        mSSLCertificateOnErrorHandler = handler;
340        mSSLCertificateOnErrorView = view;
341        mSSLCertificateOnErrorError = error;
342        mSSLCertificateOnErrorDialog =
343            new AlertDialog.Builder(mContext)
344                .setTitle(R.string.ssl_certificate).setIcon(
345                    R.drawable.ic_dialog_browser_certificate_partially_secure)
346                .setView(certificateView)
347                .setPositiveButton(R.string.ok,
348                        new DialogInterface.OnClickListener() {
349                            public void onClick(DialogInterface dialog,
350                                    int whichButton) {
351                                mSSLCertificateOnErrorDialog = null;
352                                mSSLCertificateOnErrorView = null;
353                                mSSLCertificateOnErrorHandler = null;
354                                mSSLCertificateOnErrorError = null;
355
356                                view.getWebViewClient().onReceivedSslError(
357                                                view, handler, error);
358                            }
359                        })
360                 .setNeutralButton(R.string.page_info_view,
361                        new DialogInterface.OnClickListener() {
362                            public void onClick(DialogInterface dialog,
363                                    int whichButton) {
364                                mSSLCertificateOnErrorDialog = null;
365
366                                // do not clear the dialog state: we will
367                                // need to show the dialog again once the
368                                // user is done exploring the page-info details
369
370                                showPageInfo(mController.getTabControl()
371                                        .getTabFromView(view),
372                                        true,
373                                        error.getUrl());
374                            }
375                        })
376                .setOnCancelListener(
377                        new DialogInterface.OnCancelListener() {
378                            public void onCancel(DialogInterface dialog) {
379                                mSSLCertificateOnErrorDialog = null;
380                                mSSLCertificateOnErrorView = null;
381                                mSSLCertificateOnErrorHandler = null;
382                                mSSLCertificateOnErrorError = null;
383
384                                view.getWebViewClient().onReceivedSslError(
385                                                view, handler, error);
386                            }
387                        })
388                .show();
389    }
390
391    /**
392     * Inflates the SSL certificate view (helper method).
393     * @param certificate The SSL certificate.
394     * @return The resultant certificate view with issued-to, issued-by,
395     * issued-on, expires-on, and possibly other fields set.
396     * If the input certificate is null, returns null.
397     */
398    private View inflateCertificateView(SslCertificate certificate) {
399        if (certificate == null) {
400            return null;
401        }
402
403        LayoutInflater factory = LayoutInflater.from(mContext);
404
405        View certificateView = factory.inflate(
406            R.layout.ssl_certificate, null);
407
408        // issued to:
409        SslCertificate.DName issuedTo = certificate.getIssuedTo();
410        if (issuedTo != null) {
411            ((TextView) certificateView.findViewById(R.id.to_common))
412                .setText(issuedTo.getCName());
413            ((TextView) certificateView.findViewById(R.id.to_org))
414                .setText(issuedTo.getOName());
415            ((TextView) certificateView.findViewById(R.id.to_org_unit))
416                .setText(issuedTo.getUName());
417        }
418
419        // issued by:
420        SslCertificate.DName issuedBy = certificate.getIssuedBy();
421        if (issuedBy != null) {
422            ((TextView) certificateView.findViewById(R.id.by_common))
423                .setText(issuedBy.getCName());
424            ((TextView) certificateView.findViewById(R.id.by_org))
425                .setText(issuedBy.getOName());
426            ((TextView) certificateView.findViewById(R.id.by_org_unit))
427                .setText(issuedBy.getUName());
428        }
429
430        // issued on:
431        String issuedOn = formatCertificateDate(
432            certificate.getValidNotBeforeDate());
433        ((TextView) certificateView.findViewById(R.id.issued_on))
434            .setText(issuedOn);
435
436        // expires on:
437        String expiresOn = formatCertificateDate(
438            certificate.getValidNotAfterDate());
439        ((TextView) certificateView.findViewById(R.id.expires_on))
440            .setText(expiresOn);
441
442        return certificateView;
443    }
444
445    /**
446     * Formats the certificate date to a properly localized date string.
447     * @return Properly localized version of the certificate date string and
448     * the "" if it fails to localize.
449     */
450    private String formatCertificateDate(Date certificateDate) {
451      if (certificateDate == null) {
452          return "";
453      }
454      String formattedDate = DateFormat.getDateFormat(mContext)
455              .format(certificateDate);
456      if (formattedDate == null) {
457          return "";
458      }
459      return formattedDate;
460    }
461
462}
463