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