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 PageDialogsHandler(Context context, Controller controller)59 public PageDialogsHandler(Context context, Controller controller) { 60 mContext = context; 61 mController = controller; 62 } 63 onConfigurationChanged(Configuration config)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 */ showHttpAuthentication(final Tab tab, final HttpAuthHandler handler, String host, String realm)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 */ setHttpAuthUsernamePassword(String host, String realm, String username, String password)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 */ showPageInfo(final Tab tab, final boolean fromShowSSLCertificateOnError, final String urlCertificateOnError)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 */ showSSLCertificate(final Tab tab)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 */ showSSLCertificateOnError( final WebView view, final SslErrorHandler handler, final SslError error)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 */ createSslCertificateDialog(SslCertificate certificate, SslError error)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 addError(LayoutInflater inflater, LinearLayout parent, int error)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