Adding hooks for Android dialogs upon console.prompt() and similar.

Adapting code from Android's JsDialogHelper, a private class in the
Android source code.
This commit is contained in:
Andrew n marshall
2018-10-10 14:08:32 -07:00
parent 6608860a23
commit 09958368e2
6 changed files with 243 additions and 1 deletions

View File

@@ -1,5 +1,6 @@
package com.google.blockly.android.webview;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -16,11 +17,13 @@ import android.webkit.WebView;
public class BlocklyWebViewFragment extends Fragment {
protected @Nullable WebView mWebView = null;
@SuppressLint("SetJavaScriptEnabled")
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
mWebView = new WebView(inflater.getContext());
mWebView.setWebChromeClient(new WebChromeClient());
WebSettings webSettings = mWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
mWebView.loadUrl("file:///android_asset/blockly/webview.html");

View File

@@ -0,0 +1,163 @@
/*
* 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 com.google.blockly.android.webview;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Message;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.WebView;
import android.widget.EditText;
import android.widget.TextView;
import com.example.blocklywebview.R;
/**
* Helper class to create JavaScript dialogs.
* Adapted from android-9.0.0_r10/core/java/android/webkit/JsDialogHelper.java.
* Removes dialog title (page domain) and uses a larger prompt message area than original.
*/
public class JsDialogHelper {
private static final String TAG = "JsDialogHelper";
// Dialog types
/** An alert dialog, for console.alert(..). */
public static final int ALERT = 1;
/** An alert dialog, for console.confirm(..). */
public static final int CONFIRM = 2;
/** An alert dialog, for console.prompt(..). */
public static final int PROMPT = 3;
private final @Nullable String mDefaultValue;
private final JsResult mResult;
private final String mMessage;
private final int mType;
private final String mUrl;
public JsDialogHelper(JsResult result, int type, @Nullable String defaultValue,
String message, String url) {
if (type == PROMPT && !(result instanceof JsPromptResult)) {
throw new IllegalArgumentException("JsDialogHelper PROMPT requires JsPromptResult");
}
mResult = result;
mDefaultValue = defaultValue;
mMessage = message;
mType = type;
mUrl = url;
}
public JsDialogHelper(JsResult 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 PROMPT:
return client.onJsPrompt(webView, mUrl, mMessage, mDefaultValue, (JsPromptResult) 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;
}
final EditText edit;
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setOnCancelListener(new CancelListener());
if (mType != PROMPT) {
edit = null;
builder.setMessage(mMessage);
builder.setPositiveButton(android.R.string.ok, new PositiveListener(null));
} else {
final View view = LayoutInflater.from(context).inflate(R.layout.js_prompt, null);
edit = view.findViewById(R.id.js_prompt_value);
edit.setText(mDefaultValue);
builder.setPositiveButton(android.R.string.ok, new PositiveListener(edit));
((TextView) view.findViewById(R.id.js_prompt_message)).setText(mMessage);
builder.setView(view);
// TODO: Open keyboard and place text cursor.
}
if (mType != ALERT) {
builder.setNegativeButton(android.R.string.cancel, new CancelListener());
}
final AlertDialog dialog = builder.show();
if (edit != null) { // Is it a prompt dialog?
edit.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
// TODO: Accept input, close keyboard, close dialog
return true;
}
return false;
}
});
}
}
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 {
((JsPromptResult) mResult).confirm(mEdit.getText().toString());
}
}
}
private static boolean canShowAlertDialog(Context context) {
return context instanceof Activity;
}
}

View File

@@ -0,0 +1,32 @@
package com.google.blockly.android.webview;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.WebView;
/**
* Provides native hooks for JavaScript console dialog functions.
*/
public class WebChromeClient extends android.webkit.WebChromeClient {
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
new JsDialogHelper(result, JsDialogHelper.ALERT, null, message, url)
.showDialog(view.getContext());
return true;
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
new JsDialogHelper(result, JsDialogHelper.CONFIRM, null, message, url)
.showDialog(view.getContext());
return true;
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue,
JsPromptResult result) {
new JsDialogHelper(result, JsDialogHelper.PROMPT, defaultValue, message, url)
.showDialog(view.getContext());
return true;
}
}

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
>
<TextView android:id="@+id/js_prompt_message"
style="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dip"
/>
<EditText android:id="@+id/js_prompt_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:inputType="text"
android:selectAllOnFocus="true"
android:scrollHorizontally="true"
android:layout_marginTop="6dip"
/>
</LinearLayout>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Title for a JavaScript dialog. "The page at <url of current page> says:" -->
<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>
</resources>

View File

@@ -2,7 +2,7 @@
<!-- HTML file to host Blockly in a mobile WebView. -->
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<!--<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">-->
<style type="text/css">
html, body, #blocklyDiv {
border: 0;