Home | History | Annotate | Download | only in browser
      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 
     17 package com.android.browser;
     18 
     19 import android.app.AlertDialog;
     20 import android.content.Context;
     21 import android.content.DialogInterface;
     22 import android.content.res.Configuration;
     23 import android.net.http.SslCertificate;
     24 import android.net.http.SslError;
     25 import android.view.LayoutInflater;
     26 import android.view.View;
     27 import android.webkit.HttpAuthHandler;
     28 import android.webkit.SslErrorHandler;
     29 import android.webkit.WebView;
     30 import android.webkit.WebViewClassic;
     31 import android.widget.LinearLayout;
     32 import android.widget.TextView;
     33 
     34 /**
     35  * Displays page info
     36  *
     37  */
     38 public class PageDialogsHandler {
     39 
     40     private Context mContext;
     41     private Controller mController;
     42     private boolean mPageInfoFromShowSSLCertificateOnError;
     43     private String mUrlCertificateOnError;
     44     private Tab mPageInfoView;
     45     private AlertDialog mPageInfoDialog;
     46 
     47     // as SSLCertificateOnError has different style for landscape / portrait,
     48     // we have to re-open it when configuration changed
     49     private AlertDialog mSSLCertificateOnErrorDialog;
     50     private WebView mSSLCertificateOnErrorView;
     51     private SslErrorHandler mSSLCertificateOnErrorHandler;
     52     private SslError mSSLCertificateOnErrorError;
     53 
     54     // as SSLCertificate has different style for landscape / portrait, we
     55     // have to re-open it when configuration changed
     56     private AlertDialog mSSLCertificateDialog;
     57     private Tab mSSLCertificateView;
     58     private HttpAuthenticationDialog mHttpAuthenticationDialog;
     59 
     60     public PageDialogsHandler(Context context, Controller controller) {
     61         mContext = context;
     62         mController = controller;
     63     }
     64 
     65     public void onConfigurationChanged(Configuration config) {
     66         if (mPageInfoDialog != null) {
     67             mPageInfoDialog.dismiss();
     68             showPageInfo(mPageInfoView,
     69                          mPageInfoFromShowSSLCertificateOnError,
     70                          mUrlCertificateOnError);
     71         }
     72         if (mSSLCertificateDialog != null) {
     73             mSSLCertificateDialog.dismiss();
     74             showSSLCertificate(mSSLCertificateView);
     75         }
     76         if (mSSLCertificateOnErrorDialog != null) {
     77             mSSLCertificateOnErrorDialog.dismiss();
     78             showSSLCertificateOnError(mSSLCertificateOnErrorView,
     79                                       mSSLCertificateOnErrorHandler,
     80                                       mSSLCertificateOnErrorError);
     81         }
     82         if (mHttpAuthenticationDialog != null) {
     83             mHttpAuthenticationDialog.reshow();
     84         }
     85     }
     86 
     87     /**
     88      * Displays an http-authentication dialog.
     89      */
     90     void showHttpAuthentication(final Tab tab, final HttpAuthHandler handler, String host, String realm) {
     91         mHttpAuthenticationDialog = new HttpAuthenticationDialog(mContext, host, realm);
     92         mHttpAuthenticationDialog.setOkListener(new HttpAuthenticationDialog.OkListener() {
     93             public void onOk(String host, String realm, String username, String password) {
     94                 setHttpAuthUsernamePassword(host, realm, username, password);
     95                 handler.proceed(username, password);
     96                 mHttpAuthenticationDialog = null;
     97             }
     98         });
     99         mHttpAuthenticationDialog.setCancelListener(new HttpAuthenticationDialog.CancelListener() {
    100             public void onCancel() {
    101                 handler.cancel();
    102                 mController.onUpdatedSecurityState(tab);
    103                 mHttpAuthenticationDialog = null;
    104             }
    105         });
    106         mHttpAuthenticationDialog.show();
    107     }
    108 
    109     /**
    110      * Set HTTP authentication password.
    111      *
    112      * @param host The host for the password
    113      * @param realm The realm for the password
    114      * @param username The username for the password. If it is null, it means
    115      *            password can't be saved.
    116      * @param password The password
    117      */
    118     public void setHttpAuthUsernamePassword(String host, String realm,
    119                                             String username,
    120                                             String password) {
    121         WebView w = mController.getCurrentTopWebView();
    122         if (w != null) {
    123             w.setHttpAuthUsernamePassword(host, realm, username, password);
    124         }
    125     }
    126 
    127     /**
    128      * Displays a page-info dialog.
    129      * @param tab The tab to show info about
    130      * @param fromShowSSLCertificateOnError The flag that indicates whether
    131      * this dialog was opened from the SSL-certificate-on-error dialog or
    132      * not. This is important, since we need to know whether to return to
    133      * the parent dialog or simply dismiss.
    134      * @param urlCertificateOnError The URL that invokes SSLCertificateError.
    135      * Null when fromShowSSLCertificateOnError is false.
    136      */
    137     void showPageInfo(final Tab tab,
    138             final boolean fromShowSSLCertificateOnError,
    139             final String urlCertificateOnError) {
    140         if (tab == null) return;
    141         final LayoutInflater factory = LayoutInflater.from(mContext);
    142 
    143         final View pageInfoView = factory.inflate(R.layout.page_info, null);
    144 
    145         final WebView view = tab.getWebView();
    146 
    147         String url = fromShowSSLCertificateOnError ? urlCertificateOnError : tab.getUrl();
    148         String title = tab.getTitle();
    149 
    150         if (url == null) {
    151             url = "";
    152         }
    153         if (title == null) {
    154             title = "";
    155         }
    156 
    157         ((TextView) pageInfoView.findViewById(R.id.address)).setText(url);
    158         ((TextView) pageInfoView.findViewById(R.id.title)).setText(title);
    159 
    160         mPageInfoView = tab;
    161         mPageInfoFromShowSSLCertificateOnError = fromShowSSLCertificateOnError;
    162         mUrlCertificateOnError = urlCertificateOnError;
    163 
    164         AlertDialog.Builder alertDialogBuilder =
    165             new AlertDialog.Builder(mContext)
    166             .setTitle(R.string.page_info)
    167             .setIcon(android.R.drawable.ic_dialog_info)
    168             .setView(pageInfoView)
    169             .setPositiveButton(
    170                 R.string.ok,
    171                 new DialogInterface.OnClickListener() {
    172                     public void onClick(DialogInterface dialog,
    173                                         int whichButton) {
    174                         mPageInfoDialog = null;
    175                         mPageInfoView = null;
    176 
    177                         // if we came here from the SSL error dialog
    178                         if (fromShowSSLCertificateOnError) {
    179                             // go back to the SSL error dialog
    180                             showSSLCertificateOnError(
    181                                 mSSLCertificateOnErrorView,
    182                                 mSSLCertificateOnErrorHandler,
    183                                 mSSLCertificateOnErrorError);
    184                         }
    185                     }
    186                 })
    187             .setOnCancelListener(
    188                 new DialogInterface.OnCancelListener() {
    189                     public void onCancel(DialogInterface dialog) {
    190                         mPageInfoDialog = null;
    191                         mPageInfoView = null;
    192 
    193                         // if we came here from the SSL error dialog
    194                         if (fromShowSSLCertificateOnError) {
    195                             // go back to the SSL error dialog
    196                             showSSLCertificateOnError(
    197                                 mSSLCertificateOnErrorView,
    198                                 mSSLCertificateOnErrorHandler,
    199                                 mSSLCertificateOnErrorError);
    200                         }
    201                     }
    202                 });
    203 
    204         // if we have a main top-level page SSL certificate set or a certificate
    205         // error
    206         if (fromShowSSLCertificateOnError ||
    207                 (view != null && view.getCertificate() != null)) {
    208             // add a 'View Certificate' button
    209             alertDialogBuilder.setNeutralButton(
    210                 R.string.view_certificate,
    211                 new DialogInterface.OnClickListener() {
    212                     public void onClick(DialogInterface dialog,
    213                                         int whichButton) {
    214                         mPageInfoDialog = null;
    215                         mPageInfoView = null;
    216 
    217                         // if we came here from the SSL error dialog
    218                         if (fromShowSSLCertificateOnError) {
    219                             // go back to the SSL error dialog
    220                             showSSLCertificateOnError(
    221                                 mSSLCertificateOnErrorView,
    222                                 mSSLCertificateOnErrorHandler,
    223                                 mSSLCertificateOnErrorError);
    224                         } else {
    225                             // otherwise, display the top-most certificate from
    226                             // the chain
    227                             showSSLCertificate(tab);
    228                         }
    229                     }
    230                 });
    231         }
    232 
    233         mPageInfoDialog = alertDialogBuilder.show();
    234     }
    235 
    236     /**
    237      * Displays the main top-level page SSL certificate dialog
    238      * (accessible from the Page-Info dialog).
    239      * @param tab The tab to show certificate for.
    240      */
    241     private void showSSLCertificate(final Tab tab) {
    242 
    243         SslCertificate cert = tab.getWebView().getCertificate();
    244         if (cert == null) {
    245             return;
    246         }
    247 
    248         mSSLCertificateView = tab;
    249         mSSLCertificateDialog = createSslCertificateDialog(cert, tab.getSslCertificateError())
    250                 .setPositiveButton(R.string.ok,
    251                         new DialogInterface.OnClickListener() {
    252                             public void onClick(DialogInterface dialog,
    253                                     int whichButton) {
    254                                 mSSLCertificateDialog = null;
    255                                 mSSLCertificateView = null;
    256 
    257                                 showPageInfo(tab, false, null);
    258                             }
    259                         })
    260                 .setOnCancelListener(
    261                         new DialogInterface.OnCancelListener() {
    262                             public void onCancel(DialogInterface dialog) {
    263                                 mSSLCertificateDialog = null;
    264                                 mSSLCertificateView = null;
    265 
    266                                 showPageInfo(tab, false, null);
    267                             }
    268                         })
    269                 .show();
    270     }
    271 
    272     /**
    273      * Displays the SSL error certificate dialog.
    274      * @param view The target web-view.
    275      * @param handler The SSL error handler responsible for cancelling the
    276      * connection that resulted in an SSL error or proceeding per user request.
    277      * @param error The SSL error object.
    278      */
    279     void showSSLCertificateOnError(
    280             final WebView view, final SslErrorHandler handler,
    281             final SslError error) {
    282 
    283         SslCertificate cert = error.getCertificate();
    284         if (cert == null) {
    285             return;
    286         }
    287 
    288         mSSLCertificateOnErrorHandler = handler;
    289         mSSLCertificateOnErrorView = view;
    290         mSSLCertificateOnErrorError = error;
    291         mSSLCertificateOnErrorDialog = createSslCertificateDialog(cert, error)
    292                 .setPositiveButton(R.string.ok,
    293                         new DialogInterface.OnClickListener() {
    294                             public void onClick(DialogInterface dialog,
    295                                     int whichButton) {
    296                                 mSSLCertificateOnErrorDialog = null;
    297                                 mSSLCertificateOnErrorView = null;
    298                                 mSSLCertificateOnErrorHandler = null;
    299                                 mSSLCertificateOnErrorError = null;
    300 
    301                                 WebViewClassic.fromWebView(view).getWebViewClient().
    302                                         onReceivedSslError(view, handler, error);
    303                             }
    304                         })
    305                  .setNeutralButton(R.string.page_info_view,
    306                         new DialogInterface.OnClickListener() {
    307                             public void onClick(DialogInterface dialog,
    308                                     int whichButton) {
    309                                 mSSLCertificateOnErrorDialog = null;
    310 
    311                                 // do not clear the dialog state: we will
    312                                 // need to show the dialog again once the
    313                                 // user is done exploring the page-info details
    314 
    315                                 showPageInfo(mController.getTabControl()
    316                                         .getTabFromView(view),
    317                                         true,
    318                                         error.getUrl());
    319                             }
    320                         })
    321                 .setOnCancelListener(
    322                         new DialogInterface.OnCancelListener() {
    323                             public void onCancel(DialogInterface dialog) {
    324                                 mSSLCertificateOnErrorDialog = null;
    325                                 mSSLCertificateOnErrorView = null;
    326                                 mSSLCertificateOnErrorHandler = null;
    327                                 mSSLCertificateOnErrorError = null;
    328 
    329                                 WebViewClassic.fromWebView(view).getWebViewClient().
    330                                         onReceivedSslError(view, handler, error);
    331                             }
    332                         })
    333                 .show();
    334     }
    335 
    336     /*
    337      * Creates an AlertDialog to display the given certificate. If error is
    338      * null, text is added to state that the certificae is valid and the icon
    339      * is set accordingly. If error is non-null, it must relate to the supplied
    340      * certificate. In this case, error is used to add text describing the
    341      * problems with the certificate and a different icon is used.
    342      */
    343     private AlertDialog.Builder createSslCertificateDialog(SslCertificate certificate,
    344             SslError error) {
    345         View certificateView = certificate.inflateCertificateView(mContext);
    346         final LinearLayout placeholder =
    347                 (LinearLayout)certificateView.findViewById(com.android.internal.R.id.placeholder);
    348 
    349         LayoutInflater factory = LayoutInflater.from(mContext);
    350         int iconId;
    351 
    352         if (error == null) {
    353             iconId = R.drawable.ic_dialog_browser_certificate_secure;
    354             LinearLayout table = (LinearLayout)factory.inflate(R.layout.ssl_success, placeholder);
    355             TextView successString = (TextView)table.findViewById(R.id.success);
    356             successString.setText(com.android.internal.R.string.ssl_certificate_is_valid);
    357         } else {
    358             iconId = R.drawable.ic_dialog_browser_certificate_partially_secure;
    359             if (error.hasError(SslError.SSL_UNTRUSTED)) {
    360                 addError(factory, placeholder, R.string.ssl_untrusted);
    361             }
    362             if (error.hasError(SslError.SSL_IDMISMATCH)) {
    363                 addError(factory, placeholder, R.string.ssl_mismatch);
    364             }
    365             if (error.hasError(SslError.SSL_EXPIRED)) {
    366                 addError(factory, placeholder, R.string.ssl_expired);
    367             }
    368             if (error.hasError(SslError.SSL_NOTYETVALID)) {
    369                 addError(factory, placeholder, R.string.ssl_not_yet_valid);
    370             }
    371             if (error.hasError(SslError.SSL_DATE_INVALID)) {
    372                 addError(factory, placeholder, R.string.ssl_date_invalid);
    373             }
    374             if (error.hasError(SslError.SSL_INVALID)) {
    375                 addError(factory, placeholder, R.string.ssl_invalid);
    376             }
    377             // The SslError should always have at least one type of error and we
    378             // should explicitly handle every type of error it supports. We
    379             // therefore expect the condition below to never be hit. We use it
    380             // as as safety net in case a new error type is added to SslError
    381             // without the logic above being updated accordingly.
    382             if (placeholder.getChildCount() == 0) {
    383                 addError(factory, placeholder, R.string.ssl_unknown);
    384             }
    385         }
    386 
    387         return new AlertDialog.Builder(mContext)
    388                 .setTitle(com.android.internal.R.string.ssl_certificate)
    389                 .setIcon(iconId)
    390                 .setView(certificateView);
    391     }
    392 
    393     private void addError(LayoutInflater inflater, LinearLayout parent, int error) {
    394         TextView textView = (TextView) inflater.inflate(R.layout.ssl_warning,
    395                 parent, false);
    396         textView.setText(error);
    397         parent.addView(textView);
    398     }
    399 }
    400