Fix for bug 8607049: Improve javascript dialogs for classic

Make a common JsDialogHelper for classic and chromium webview
Improve on before unload dialog

Change-Id: Ib9711e4f95189ef8b2b31fc64aec7c0cb535d0e4
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index a326da2..312af71 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -17,10 +17,8 @@
 package android.webkit;
 
 import android.app.Activity;
-import android.app.AlertDialog;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.net.Uri;
@@ -33,10 +31,6 @@
 import android.provider.Browser;
 import android.util.Log;
 import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.EditText;
-import android.widget.TextView;
 import com.android.internal.R;
 
 import java.net.MalformedURLException;
@@ -92,10 +86,7 @@
     private static final int CREATE_WINDOW                        = 109;
     private static final int CLOSE_WINDOW                         = 110;
     private static final int SAVE_PASSWORD                        = 111;
-    private static final int JS_ALERT                             = 112;
-    private static final int JS_CONFIRM                           = 113;
-    private static final int JS_PROMPT                            = 114;
-    private static final int JS_UNLOAD                            = 115;
+    private static final int JS_DIALOG                            = 112;
     private static final int ASYNC_KEYEVENTS                      = 116;
     private static final int DOWNLOAD_FILE                        = 118;
     private static final int REPORT_ERROR                         = 119;
@@ -566,188 +557,12 @@
                 }
                 break;
 
-            case JS_ALERT:
+            case JS_DIALOG:
                 if (mWebChromeClient != null) {
                     final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
-                    final JsResult res = receiver.mJsResult;
-                    String message = msg.getData().getString("message");
-                    String url = msg.getData().getString("url");
-                    if (!mWebChromeClient.onJsAlert(mWebView.getWebView(), url, message,
-                            res)) {
-                        if (!canShowAlertDialog()) {
-                            res.cancel();
-                            receiver.setReady();
-                            break;
-                        }
-                        new AlertDialog.Builder(mContext)
-                                .setTitle(getJsDialogTitle(url))
-                                .setMessage(message)
-                                .setPositiveButton(R.string.ok,
-                                        new DialogInterface.OnClickListener() {
-                                            public void onClick(
-                                                    DialogInterface dialog,
-                                                    int which) {
-                                                res.confirm();
-                                            }
-                                        })
-                                .setOnCancelListener(
-                                        new DialogInterface.OnCancelListener() {
-                                            public void onCancel(
-                                                    DialogInterface dialog) {
-                                                res.cancel();
-                                            }
-                                        })
-                                .show();
-                    }
-                    receiver.setReady();
-                }
-                break;
-
-            case JS_CONFIRM:
-                if (mWebChromeClient != null) {
-                    final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
-                    final JsResult res = receiver.mJsResult;
-                    String message = msg.getData().getString("message");
-                    String url = msg.getData().getString("url");
-                    if (!mWebChromeClient.onJsConfirm(mWebView.getWebView(), url, message,
-                            res)) {
-                        if (!canShowAlertDialog()) {
-                            res.cancel();
-                            receiver.setReady();
-                            break;
-                        }
-                        new AlertDialog.Builder(mContext)
-                                .setTitle(getJsDialogTitle(url))
-                                .setMessage(message)
-                                .setPositiveButton(R.string.ok,
-                                        new DialogInterface.OnClickListener() {
-                                            public void onClick(
-                                                    DialogInterface dialog,
-                                                    int which) {
-                                                res.confirm();
-                                            }})
-                                .setNegativeButton(R.string.cancel,
-                                        new DialogInterface.OnClickListener() {
-                                            public void onClick(
-                                                    DialogInterface dialog,
-                                                    int which) {
-                                                res.cancel();
-                                            }})
-                                .setOnCancelListener(
-                                        new DialogInterface.OnCancelListener() {
-                                            public void onCancel(
-                                                    DialogInterface dialog) {
-                                                res.cancel();
-                                            }
-                                        })
-                                .show();
-                    }
-                    // Tell the JsResult that it is ready for client
-                    // interaction.
-                    receiver.setReady();
-                }
-                break;
-
-            case JS_PROMPT:
-                if (mWebChromeClient != null) {
-                    final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
-                    final JsPromptResult res = receiver.mJsResult;
-                    String message = msg.getData().getString("message");
-                    String defaultVal = msg.getData().getString("default");
-                    String url = msg.getData().getString("url");
-                    if (!mWebChromeClient.onJsPrompt(mWebView.getWebView(), url, message,
-                                defaultVal, res)) {
-                        if (!canShowAlertDialog()) {
-                            res.cancel();
-                            receiver.setReady();
-                            break;
-                        }
-                        final LayoutInflater factory = LayoutInflater
-                                .from(mContext);
-                        final View view = factory.inflate(R.layout.js_prompt,
-                                null);
-                        final EditText v = (EditText) view
-                                .findViewById(R.id.value);
-                        v.setText(defaultVal);
-                        ((TextView) view.findViewById(R.id.message))
-                                .setText(message);
-                        new AlertDialog.Builder(mContext)
-                                .setTitle(getJsDialogTitle(url))
-                                .setView(view)
-                                .setPositiveButton(R.string.ok,
-                                        new DialogInterface.OnClickListener() {
-                                            public void onClick(
-                                                    DialogInterface dialog,
-                                                    int whichButton) {
-                                                res.confirm(v.getText()
-                                                        .toString());
-                                            }
-                                        })
-                                .setNegativeButton(R.string.cancel,
-                                        new DialogInterface.OnClickListener() {
-                                            public void onClick(
-                                                    DialogInterface dialog,
-                                                    int whichButton) {
-                                                res.cancel();
-                                            }
-                                        })
-                                .setOnCancelListener(
-                                        new DialogInterface.OnCancelListener() {
-                                            public void onCancel(
-                                                    DialogInterface dialog) {
-                                                res.cancel();
-                                            }
-                                        })
-                                .show();
-                    }
-                    // Tell the JsResult that it is ready for client
-                    // interaction.
-                    receiver.setReady();
-                }
-                break;
-
-            case JS_UNLOAD:
-                if (mWebChromeClient != null) {
-                    final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
-                    final JsResult res = receiver.mJsResult;
-                    String message = msg.getData().getString("message");
-                    String url = msg.getData().getString("url");
-                    if (!mWebChromeClient.onJsBeforeUnload(mWebView.getWebView(), url,
-                            message, res)) {
-                        if (!canShowAlertDialog()) {
-                            res.cancel();
-                            receiver.setReady();
-                            break;
-                        }
-                        final String m = mContext.getString(
-                                R.string.js_dialog_before_unload, message);
-                        new AlertDialog.Builder(mContext)
-                                .setMessage(m)
-                                .setPositiveButton(R.string.ok,
-                                        new DialogInterface.OnClickListener() {
-                                            public void onClick(
-                                                    DialogInterface dialog,
-                                                    int which) {
-                                                res.confirm();
-                                            }
-                                        })
-                                .setNegativeButton(R.string.cancel,
-                                        new DialogInterface.OnClickListener() {
-                                            public void onClick(
-                                                    DialogInterface dialog,
-                                                    int which) {
-                                                res.cancel();
-                                            }
-                                        })
-                                .setOnCancelListener(
-                                        new DialogInterface.OnCancelListener() {
-                                            @Override
-                                            public void onCancel(
-                                                    DialogInterface dialog) {
-                                                res.cancel();
-                                            }
-                                        })
-                                .show();
+                    JsDialogHelper helper = new JsDialogHelper(receiver.mJsResult, msg);
+                    if (!helper.invokeCallback(mWebChromeClient, mWebView.getWebView())) {
+                        helper.showDialog(mContext);
                     }
                     receiver.setReady();
                 }
@@ -757,7 +572,7 @@
                 if(mWebChromeClient != null) {
                     final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
                     final JsResult res = receiver.mJsResult;
-                    if(mWebChromeClient.onJsTimeout()) {
+                    if (mWebChromeClient.onJsTimeout()) {
                         res.confirm();
                     } else {
                         res.cancel();
@@ -895,24 +710,6 @@
         sendMessage(obtainMessage(SWITCH_OUT_HISTORY));
     }
 
-    private String getJsDialogTitle(String url) {
-        String title = url;
-        if (URLUtil.isDataUrl(url)) {
-            // For data: urls, we just display 'JavaScript' similar to Safari.
-            title = mContext.getString(R.string.js_dialog_title_default);
-        } else {
-            try {
-                URL aUrl = new URL(url);
-                // For example: "The page at 'http://www.mit.edu' says:"
-                title = mContext.getString(R.string.js_dialog_title,
-                        aUrl.getProtocol() + "://" + aUrl.getHost());
-            } catch (MalformedURLException ex) {
-                // do nothing. just use the url as the title
-            }
-        }
-        return title;
-    }
-
     //--------------------------------------------------------------------------
     // WebViewClient functions.
     // NOTE: shouldOverrideKeyEvent is never called from the WebCore thread so
@@ -1332,9 +1129,10 @@
             return;
         }
         JsResultReceiver result = new JsResultReceiver();
-        Message alert = obtainMessage(JS_ALERT, result);
+        Message alert = obtainMessage(JS_DIALOG, result);
         alert.getData().putString("message", message);
         alert.getData().putString("url", url);
+        alert.getData().putInt("type", JsDialogHelper.ALERT);
         sendMessageToUiThreadSync(alert);
     }
 
@@ -1345,9 +1143,10 @@
             return false;
         }
         JsResultReceiver result = new JsResultReceiver();
-        Message confirm = obtainMessage(JS_CONFIRM, result);
+        Message confirm = obtainMessage(JS_DIALOG, result);
         confirm.getData().putString("message", message);
         confirm.getData().putString("url", url);
+        confirm.getData().putInt("type", JsDialogHelper.CONFIRM);
         sendMessageToUiThreadSync(confirm);
         return result.mJsResult.getResult();
     }
@@ -1359,10 +1158,11 @@
             return null;
         }
         JsResultReceiver result = new JsResultReceiver();
-        Message prompt = obtainMessage(JS_PROMPT, result);
+        Message prompt = obtainMessage(JS_DIALOG, result);
         prompt.getData().putString("message", message);
         prompt.getData().putString("default", defaultValue);
         prompt.getData().putString("url", url);
+        prompt.getData().putInt("type", JsDialogHelper.PROMPT);
         sendMessageToUiThreadSync(prompt);
         return result.mJsResult.getStringResult();
     }
@@ -1374,10 +1174,11 @@
             return true;
         }
         JsResultReceiver result = new JsResultReceiver();
-        Message confirm = obtainMessage(JS_UNLOAD, result);
-        confirm.getData().putString("message", message);
-        confirm.getData().putString("url", url);
-        sendMessageToUiThreadSync(confirm);
+        Message unload = obtainMessage(JS_DIALOG, result);
+        unload.getData().putString("message", message);
+        unload.getData().putString("url", url);
+        unload.getData().putInt("type", JsDialogHelper.UNLOAD);
+        sendMessageToUiThreadSync(unload);
         return result.mJsResult.getResult();
     }
 
@@ -1595,16 +1396,6 @@
         sendMessage(msg);
     }
 
-    boolean canShowAlertDialog() {
-        // We can only display the alert dialog if mContext is
-        // an Activity context.
-        // FIXME: Should we display dialogs if mContext does
-        // not have the window focus (e.g. if the user is viewing
-        // another Activity when the alert should be displayed?
-        // See bug 3166409
-        return mContext instanceof Activity;
-    }
-
     private synchronized void sendMessageToUiThreadSync(Message msg) {
         sendMessage(msg);
         WebCoreThreadWatchdog.pause();
diff --git a/core/java/android/webkit/JsDialogHelper.java b/core/java/android/webkit/JsDialogHelper.java
new file mode 100644
index 0000000..bb0339e
--- /dev/null
+++ b/core/java/android/webkit/JsDialogHelper.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Message;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Helper class to create JavaScript dialogs. It is used by
+ * different WebView implementations.
+ *
+ * @hide Helper class for internal use
+ */
+public class JsDialogHelper {
+
+    private static final String TAG = "JsDialogHelper";
+
+    // Dialog types
+    public static final int ALERT   = 1;
+    public static final int CONFIRM = 2;
+    public static final int PROMPT  = 3;
+    public static final int UNLOAD  = 4;
+
+    private final String mDefaultValue;
+    private final JsPromptResult mResult;
+    private final String mMessage;
+    private final int mType;
+    private final String mUrl;
+
+    public JsDialogHelper(JsPromptResult result, int type, String defaultValue, String message,
+            String url) {
+        mResult = result;
+        mDefaultValue = defaultValue;
+        mMessage = message;
+        mType = type;
+        mUrl = url;
+    }
+
+    public JsDialogHelper(JsPromptResult result, Message msg) {
+        mResult = result;
+        mDefaultValue = msg.getData().getString("default");
+        mMessage = msg.getData().getString("message");
+        mType = msg.getData().getInt("type");
+        mUrl = msg.getData().getString("url");
+    }
+
+    public boolean invokeCallback(WebChromeClient client, WebView webView) {
+        switch (mType) {
+            case ALERT:
+                return client.onJsAlert(webView, mUrl, mMessage, mResult);
+            case CONFIRM:
+                return client.onJsConfirm(webView, mUrl, mMessage, mResult);
+            case UNLOAD:
+                return client.onJsBeforeUnload(webView, mUrl, mMessage, mResult);
+            case PROMPT:
+                return client.onJsPrompt(webView, mUrl, mMessage, mDefaultValue, mResult);
+            default:
+                throw new IllegalArgumentException("Unexpected type: " + mType);
+        }
+    }
+
+    public void showDialog(Context context) {
+        if (!canShowAlertDialog(context)) {
+            Log.w(TAG, "Cannot create a dialog, the WebView context is not an Activity");
+            mResult.cancel();
+            return;
+        }
+
+        String title, displayMessage;
+        int positiveTextId, negativeTextId;
+        if (mType == UNLOAD) {
+            title = context.getString(com.android.internal.R.string.js_dialog_before_unload_title);
+            displayMessage = context.getString(
+                    com.android.internal.R.string.js_dialog_before_unload, mMessage);
+            positiveTextId = com.android.internal.R.string.js_dialog_before_unload_positive_button;
+            negativeTextId = com.android.internal.R.string.js_dialog_before_unload_negative_button;
+        } else {
+            title = getJsDialogTitle(context);
+            displayMessage = mMessage;
+            positiveTextId = com.android.internal.R.string.ok;
+            negativeTextId = com.android.internal.R.string.cancel;
+        }
+        AlertDialog.Builder builder = new AlertDialog.Builder(context);
+        builder.setTitle(title);
+        builder.setOnCancelListener(new CancelListener());
+        if (mType != PROMPT) {
+            builder.setMessage(displayMessage);
+            builder.setPositiveButton(positiveTextId, new PositiveListener(null));
+        } else {
+            final View view = LayoutInflater.from(context).inflate(
+                    com.android.internal.R.layout.js_prompt, null);
+            EditText edit = ((EditText) view.findViewById(com.android.internal.R.id.value));
+            edit.setText(mDefaultValue);
+            builder.setPositiveButton(positiveTextId, new PositiveListener(edit));
+            ((TextView) view.findViewById(com.android.internal.R.id.message)).setText(mMessage);
+            builder.setView(view);
+        }
+        if (mType != ALERT) {
+            builder.setNegativeButton(negativeTextId, new CancelListener());
+        }
+        builder.show();
+    }
+
+    private class CancelListener implements DialogInterface.OnCancelListener,
+            DialogInterface.OnClickListener {
+        @Override
+        public void onCancel(DialogInterface dialog) {
+            mResult.cancel();
+        }
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+            mResult.cancel();
+        }
+    }
+
+    private class PositiveListener implements DialogInterface.OnClickListener {
+        private final EditText mEdit;
+
+        public PositiveListener(EditText edit) {
+            mEdit = edit;
+        }
+
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+            if (mEdit == null) {
+                mResult.confirm();
+            } else {
+                mResult.confirm(mEdit.getText().toString());
+            }
+        }
+    }
+
+    private String getJsDialogTitle(Context context) {
+        String title = mUrl;
+        if (URLUtil.isDataUrl(mUrl)) {
+            // For data: urls, we just display 'JavaScript' similar to Chrome.
+            title = context.getString(com.android.internal.R.string.js_dialog_title_default);
+        } else {
+            try {
+                URL alertUrl = new URL(mUrl);
+                // For example: "The page at 'http://www.mit.edu' says:"
+                title = context.getString(com.android.internal.R.string.js_dialog_title,
+                        alertUrl.getProtocol() + "://" + alertUrl.getHost());
+            } catch (MalformedURLException ex) {
+                // do nothing. just use the url as the title
+            }
+        }
+        return title;
+    }
+
+    private static boolean canShowAlertDialog(Context context) {
+        // We can only display the alert dialog if mContext is
+        // an Activity context.
+        // FIXME: Should we display dialogs if mContext does
+        // not have the window focus (e.g. if the user is viewing
+        // another Activity when the alert should be displayed) ?
+        // See bug 3166409
+        return context instanceof Activity;
+    }
+}
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4a15967..aea0803 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2406,9 +2406,14 @@
     <string name="js_dialog_title">The page at \"<xliff:g id="title">%s</xliff:g>\" says:</string>
     <!-- Default title for a javascript dialog -->
     <string name="js_dialog_title_default">JavaScript</string>
-    <!-- Message in a javascript dialog asking if the user wishes to leave the
-             current page -->
-    <string name="js_dialog_before_unload">Navigate away from this page?\n\n<xliff:g id="message">%s</xliff:g>\n\nTouch OK to continue, or Cancel to stay on the current page.</string>
+    <!-- Title for the unload javascript dialog -->
+    <string name="js_dialog_before_unload_title">Confirm Navigation</string>
+    <!-- Text for the positive button on the unload javascript dialog -->
+    <string name="js_dialog_before_unload_positive_button">Leave this Page</string>
+    <!-- Text for the negative button on the unload javascript dialog -->
+    <string name="js_dialog_before_unload_negative_button">Stay on this Page</string>
+    <!-- Message in a javascript dialog asking if the user wishes to leave the current page -->
+    <string name="js_dialog_before_unload"><xliff:g id="message">%s</xliff:g>\n\nAre you sure you want to navigate away from this page?</string>
 
     <!-- Title of the WebView save password dialog.  If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. -->
     <string name="save_password_label">Confirm</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ced0851..563d28c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -549,6 +549,9 @@
   <java-symbol type="string" name="ime_action_search" />
   <java-symbol type="string" name="ime_action_send" />
   <java-symbol type="string" name="invalidPin" />
+  <java-symbol type="string" name="js_dialog_before_unload_positive_button" />
+  <java-symbol type="string" name="js_dialog_before_unload_negative_button" />
+  <java-symbol type="string" name="js_dialog_before_unload_title" />
   <java-symbol type="string" name="js_dialog_before_unload" />
   <java-symbol type="string" name="js_dialog_title" />
   <java-symbol type="string" name="js_dialog_title_default" />