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
17package com.android.browser;
18
19import android.app.AlertDialog;
20import android.content.Context;
21import android.content.DialogInterface;
22import android.content.res.Configuration;
23import android.net.http.SslCertificate;
24import android.net.http.SslError;
25import android.view.LayoutInflater;
26import android.view.View;
27import android.webkit.HttpAuthHandler;
28import android.webkit.SslErrorHandler;
29import android.webkit.WebView;
30import android.webkit.WebViewClassic;
31import android.widget.LinearLayout;
32import android.widget.TextView;
33
34/**
35 * Displays page info
36 *
37 */
38public 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