1 /*
2  * Copyright (C) 2013 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 android.webkit;
18 
19 import android.annotation.SystemApi;
20 import android.app.Activity;
21 import android.app.AlertDialog;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.os.Message;
25 import android.util.Log;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.widget.EditText;
29 import android.widget.TextView;
30 
31 import java.net.MalformedURLException;
32 import java.net.URL;
33 
34 /**
35  * Helper class to create JavaScript dialogs. It is used by
36  * different WebView implementations.
37  *
38  * @hide
39  */
40 @SystemApi
41 public class JsDialogHelper {
42 
43     private static final String TAG = "JsDialogHelper";
44 
45     // Dialog types
46     public static final int ALERT   = 1;
47     public static final int CONFIRM = 2;
48     public static final int PROMPT  = 3;
49     public static final int UNLOAD  = 4;
50 
51     private final String mDefaultValue;
52     private final JsPromptResult mResult;
53     private final String mMessage;
54     private final int mType;
55     private final String mUrl;
56 
JsDialogHelper(JsPromptResult result, int type, String defaultValue, String message, String url)57     public JsDialogHelper(JsPromptResult result, int type, String defaultValue, String message,
58             String url) {
59         mResult = result;
60         mDefaultValue = defaultValue;
61         mMessage = message;
62         mType = type;
63         mUrl = url;
64     }
65 
JsDialogHelper(JsPromptResult result, Message msg)66     public JsDialogHelper(JsPromptResult result, Message msg) {
67         mResult = result;
68         mDefaultValue = msg.getData().getString("default");
69         mMessage = msg.getData().getString("message");
70         mType = msg.getData().getInt("type");
71         mUrl = msg.getData().getString("url");
72     }
73 
invokeCallback(WebChromeClient client, WebView webView)74     public boolean invokeCallback(WebChromeClient client, WebView webView) {
75         switch (mType) {
76             case ALERT:
77                 return client.onJsAlert(webView, mUrl, mMessage, mResult);
78             case CONFIRM:
79                 return client.onJsConfirm(webView, mUrl, mMessage, mResult);
80             case UNLOAD:
81                 return client.onJsBeforeUnload(webView, mUrl, mMessage, mResult);
82             case PROMPT:
83                 return client.onJsPrompt(webView, mUrl, mMessage, mDefaultValue, mResult);
84             default:
85                 throw new IllegalArgumentException("Unexpected type: " + mType);
86         }
87     }
88 
showDialog(Context context)89     public void showDialog(Context context) {
90         if (!canShowAlertDialog(context)) {
91             Log.w(TAG, "Cannot create a dialog, the WebView context is not an Activity");
92             mResult.cancel();
93             return;
94         }
95 
96         String title, displayMessage;
97         int positiveTextId, negativeTextId;
98         if (mType == UNLOAD) {
99             title = context.getString(com.android.internal.R.string.js_dialog_before_unload_title);
100             displayMessage = context.getString(
101                     com.android.internal.R.string.js_dialog_before_unload, mMessage);
102             positiveTextId = com.android.internal.R.string.js_dialog_before_unload_positive_button;
103             negativeTextId = com.android.internal.R.string.js_dialog_before_unload_negative_button;
104         } else {
105             title = getJsDialogTitle(context);
106             displayMessage = mMessage;
107             positiveTextId = com.android.internal.R.string.ok;
108             negativeTextId = com.android.internal.R.string.cancel;
109         }
110         AlertDialog.Builder builder = new AlertDialog.Builder(context);
111         builder.setTitle(title);
112         builder.setOnCancelListener(new CancelListener());
113         if (mType != PROMPT) {
114             builder.setMessage(displayMessage);
115             builder.setPositiveButton(positiveTextId, new PositiveListener(null));
116         } else {
117             final View view = LayoutInflater.from(context).inflate(
118                     com.android.internal.R.layout.js_prompt, null);
119             EditText edit = ((EditText) view.findViewById(com.android.internal.R.id.value));
120             edit.setText(mDefaultValue);
121             builder.setPositiveButton(positiveTextId, new PositiveListener(edit));
122             ((TextView) view.findViewById(com.android.internal.R.id.message)).setText(mMessage);
123             builder.setView(view);
124         }
125         if (mType != ALERT) {
126             builder.setNegativeButton(negativeTextId, new CancelListener());
127         }
128         builder.show();
129     }
130 
131     private class CancelListener implements DialogInterface.OnCancelListener,
132             DialogInterface.OnClickListener {
133         @Override
onCancel(DialogInterface dialog)134         public void onCancel(DialogInterface dialog) {
135             mResult.cancel();
136         }
137         @Override
onClick(DialogInterface dialog, int which)138         public void onClick(DialogInterface dialog, int which) {
139             mResult.cancel();
140         }
141     }
142 
143     private class PositiveListener implements DialogInterface.OnClickListener {
144         private final EditText mEdit;
145 
PositiveListener(EditText edit)146         public PositiveListener(EditText edit) {
147             mEdit = edit;
148         }
149 
150         @Override
onClick(DialogInterface dialog, int which)151         public void onClick(DialogInterface dialog, int which) {
152             if (mEdit == null) {
153                 mResult.confirm();
154             } else {
155                 mResult.confirm(mEdit.getText().toString());
156             }
157         }
158     }
159 
getJsDialogTitle(Context context)160     private String getJsDialogTitle(Context context) {
161         String title = mUrl;
162         if (URLUtil.isDataUrl(mUrl)) {
163             // For data: urls, we just display 'JavaScript' similar to Chrome.
164             title = context.getString(com.android.internal.R.string.js_dialog_title_default);
165         } else {
166             try {
167                 URL alertUrl = new URL(mUrl);
168                 // For example: "The page at 'http://www.mit.edu' says:"
169                 title = context.getString(com.android.internal.R.string.js_dialog_title,
170                         alertUrl.getProtocol() + "://" + alertUrl.getHost());
171             } catch (MalformedURLException ex) {
172                 // do nothing. just use the url as the title
173             }
174         }
175         return title;
176     }
177 
canShowAlertDialog(Context context)178     private static boolean canShowAlertDialog(Context context) {
179         // We can only display the alert dialog if mContext is
180         // an Activity context.
181         // FIXME: Should we display dialogs if mContext does
182         // not have the window focus (e.g. if the user is viewing
183         // another Activity when the alert should be displayed) ?
184         // See bug 3166409
185         return context instanceof Activity;
186     }
187 }
188