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