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