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         if (tab == null) return;
    140         final LayoutInflater factory = LayoutInflater.from(mContext);
    141 
    142         final View pageInfoView = factory.inflate(R.layout.page_info, null);
    143 
    144         final WebView view = tab.getWebView();
    145 
    146         String url = fromShowSSLCertificateOnError ? urlCertificateOnError : tab.getUrl();
    147         String title = tab.getTitle();
    148 
    149         if (url == null) {
    150             url = "";
    151         }
    152         if (title == null) {
    153             title = "";
    154         }
    155 
    156         ((TextView) pageInfoView.findViewById(R.id.address)).setText(url);
    157         ((TextView) pageInfoView.findViewById(R.id.title)).setText(title);
    158 
    159         mPageInfoView = tab;
    160         mPageInfoFromShowSSLCertificateOnError = fromShowSSLCertificateOnError;
    161         mUrlCertificateOnError = urlCertificateOnError;
    162 
    163         AlertDialog.Builder alertDialogBuilder =
    164             new AlertDialog.Builder(mContext)
    165             .setTitle(R.string.page_info)
    166             .setIcon(android.R.drawable.ic_dialog_info)
    167             .setView(pageInfoView)
    168             .setPositiveButton(
    169                 R.string.ok,
    170                 new DialogInterface.OnClickListener() {
    171                     public void onClick(DialogInterface dialog,
    172                                         int whichButton) {
    173                         mPageInfoDialog = null;
    174                         mPageInfoView = null;
    175 
    176                         // if we came here from the SSL error dialog
    177                         if (fromShowSSLCertificateOnError) {
    178                             // go back to the SSL error dialog
    179                             showSSLCertificateOnError(
    180                                 mSSLCertificateOnErrorView,
    181                                 mSSLCertificateOnErrorHandler,
    182                                 mSSLCertificateOnErrorError);
    183                         }
    184                     }
    185                 })
    186             .setOnCancelListener(
    187                 new DialogInterface.OnCancelListener() {
    188                     public void onCancel(DialogInterface dialog) {
    189                         mPageInfoDialog = null;
    190                         mPageInfoView = null;
    191 
    192                         // if we came here from the SSL error dialog
    193                         if (fromShowSSLCertificateOnError) {
    194                             // go back to the SSL error dialog
    195                             showSSLCertificateOnError(
    196                                 mSSLCertificateOnErrorView,
    197                                 mSSLCertificateOnErrorHandler,
    198                                 mSSLCertificateOnErrorError);
    199                         }
    200                     }
    201                 });
    202 
    203         // if we have a main top-level page SSL certificate set or a certificate
    204         // error
    205         if (fromShowSSLCertificateOnError ||
    206                 (view != null && view.getCertificate() != null)) {
    207             // add a 'View Certificate' button
    208             alertDialogBuilder.setNeutralButton(
    209                 R.string.view_certificate,
    210                 new DialogInterface.OnClickListener() {
    211                     public void onClick(DialogInterface dialog,
    212                                         int whichButton) {
    213                         mPageInfoDialog = null;
    214                         mPageInfoView = null;
    215 
    216                         // if we came here from the SSL error dialog
    217                         if (fromShowSSLCertificateOnError) {
    218                             // go back to the SSL error dialog
    219                             showSSLCertificateOnError(
    220                                 mSSLCertificateOnErrorView,
    221                                 mSSLCertificateOnErrorHandler,
    222                                 mSSLCertificateOnErrorError);
    223                         } else {
    224                             // otherwise, display the top-most certificate from
    225                             // the chain
    226                             showSSLCertificate(tab);
    227                         }
    228                     }
    229                 });
    230         }
    231 
    232         mPageInfoDialog = alertDialogBuilder.show();
    233     }
    234 
    235     /**
    236      * Displays the main top-level page SSL certificate dialog
    237      * (accessible from the Page-Info dialog).
    238      * @param tab The tab to show certificate for.
    239      */
    240     private void showSSLCertificate(final Tab tab) {
    241 
    242         SslCertificate cert = tab.getWebView().getCertificate();
    243         if (cert == null) {
    244             return;
    245         }
    246 
    247         mSSLCertificateView = tab;
    248         mSSLCertificateDialog = createSslCertificateDialog(cert, tab.getSslCertificateError())
    249                 .setPositiveButton(R.string.ok,
    250                         new DialogInterface.OnClickListener() {
    251                             public void onClick(DialogInterface dialog,
    252                                     int whichButton) {
    253                                 mSSLCertificateDialog = null;
    254                                 mSSLCertificateView = null;
    255 
    256                                 showPageInfo(tab, false, null);
    257                             }
    258                         })
    259                 .setOnCancelListener(
    260                         new DialogInterface.OnCancelListener() {
    261                             public void onCancel(DialogInterface dialog) {
    262                                 mSSLCertificateDialog = null;
    263                                 mSSLCertificateView = null;
    264 
    265                                 showPageInfo(tab, false, null);
    266                             }
    267                         })
    268                 .show();
    269     }
    270 
    271     /**
    272      * Displays the SSL error certificate dialog.
    273      * @param view The target web-view.
    274      * @param handler The SSL error handler responsible for cancelling the
    275      * connection that resulted in an SSL error or proceeding per user request.
    276      * @param error The SSL error object.
    277      */
    278     void showSSLCertificateOnError(
    279             final WebView view, final SslErrorHandler handler,
    280             final SslError error) {
    281 
    282         SslCertificate cert = error.getCertificate();
    283         if (cert == null) {
    284             return;
    285         }
    286 
    287         mSSLCertificateOnErrorHandler = handler;
    288         mSSLCertificateOnErrorView = view;
    289         mSSLCertificateOnErrorError = error;
    290         mSSLCertificateOnErrorDialog = createSslCertificateDialog(cert, error)
    291                 .setPositiveButton(R.string.ok,
    292                         new DialogInterface.OnClickListener() {
    293                             public void onClick(DialogInterface dialog,
    294                                     int whichButton) {
    295                                 mSSLCertificateOnErrorDialog = null;
    296                                 mSSLCertificateOnErrorView = null;
    297                                 mSSLCertificateOnErrorHandler = null;
    298                                 mSSLCertificateOnErrorError = null;
    299 
    300                                 ((BrowserWebView) view).getWebViewClient().
    301                                         onReceivedSslError(view, handler, error);
    302                             }
    303                         })
    304                  .setNeutralButton(R.string.page_info_view,
    305                         new DialogInterface.OnClickListener() {
    306                             public void onClick(DialogInterface dialog,
    307                                     int whichButton) {
    308                                 mSSLCertificateOnErrorDialog = null;
    309 
    310                                 // do not clear the dialog state: we will
    311                                 // need to show the dialog again once the
    312                                 // user is done exploring the page-info details
    313 
    314                                 showPageInfo(mController.getTabControl()
    315                                         .getTabFromView(view),
    316                                         true,
    317                                         error.getUrl());
    318                             }
    319                         })
    320                 .setOnCancelListener(
    321                         new DialogInterface.OnCancelListener() {
    322                             public void onCancel(DialogInterface dialog) {
    323                                 mSSLCertificateOnErrorDialog = null;
    324                                 mSSLCertificateOnErrorView = null;
    325                                 mSSLCertificateOnErrorHandler = null;
    326                                 mSSLCertificateOnErrorError = null;
    327 
    328                                 ((BrowserWebView) view).getWebViewClient().
    329                                         onReceivedSslError(view, handler, error);
    330                             }
    331                         })
    332                 .show();
    333     }
    334 
    335     /*
    336      * Creates an AlertDialog to display the given certificate. If error is
    337      * null, text is added to state that the certificae is valid and the icon
    338      * is set accordingly. If error is non-null, it must relate to the supplied
    339      * certificate. In this case, error is used to add text describing the
    340      * problems with the certificate and a different icon is used.
    341      */
    342     private AlertDialog.Builder createSslCertificateDialog(SslCertificate certificate,
    343             SslError error) {
    344         View certificateView = certificate.inflateCertificateView(mContext);
    345         final LinearLayout placeholder =
    346                 (LinearLayout)certificateView.findViewById(com.android.internal.R.id.placeholder);
    347 
    348         LayoutInflater factory = LayoutInflater.from(mContext);
    349         int iconId;
    350 
    351         if (error == null) {
    352             iconId = R.drawable.ic_dialog_browser_certificate_secure;
    353             LinearLayout table = (LinearLayout)factory.inflate(R.layout.ssl_success, placeholder);
    354             TextView successString = (TextView)table.findViewById(R.id.success);
    355             successString.setText(com.android.internal.R.string.ssl_certificate_is_valid);
    356         } else {
    357             iconId = R.drawable.ic_dialog_browser_certificate_partially_secure;
    358             if (error.hasError(SslError.SSL_UNTRUSTED)) {
    359                 addError(factory, placeholder, R.string.ssl_untrusted);
    360             }
    361             if (error.hasError(SslError.SSL_IDMISMATCH)) {
    362                 addError(factory, placeholder, R.string.ssl_mismatch);
    363             }
    364             if (error.hasError(SslError.SSL_EXPIRED)) {
    365                 addError(factory, placeholder, R.string.ssl_expired);
    366             }
    367             if (error.hasError(SslError.SSL_NOTYETVALID)) {
    368                 addError(factory, placeholder, R.string.ssl_not_yet_valid);
    369             }
    370             if (error.hasError(SslError.SSL_DATE_INVALID)) {
    371                 addError(factory, placeholder, R.string.ssl_date_invalid);
    372             }
    373             if (error.hasError(SslError.SSL_INVALID)) {
    374                 addError(factory, placeholder, R.string.ssl_invalid);
    375             }
    376             // The SslError should always have at least one type of error and we
    377             // should explicitly handle every type of error it supports. We
    378             // therefore expect the condition below to never be hit. We use it
    379             // as as safety net in case a new error type is added to SslError
    380             // without the logic above being updated accordingly.
    381             if (placeholder.getChildCount() == 0) {
    382                 addError(factory, placeholder, R.string.ssl_unknown);
    383             }
    384         }
    385 
    386         return new AlertDialog.Builder(mContext)
    387                 .setTitle(com.android.internal.R.string.ssl_certificate)
    388                 .setIcon(iconId)
    389                 .setView(certificateView);
    390     }
    391 
    392     private void addError(LayoutInflater inflater, LinearLayout parent, int error) {
    393         TextView textView = (TextView) inflater.inflate(R.layout.ssl_warning,
    394                 parent, false);
    395         textView.setText(error);
    396         parent.addView(textView);
    397     }
    398 }
    399