1 /* 2 * Copyright 2022 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 package com.example.androidx.webkit; 17 18 import android.annotation.SuppressLint; 19 import android.app.Activity; 20 import android.net.Uri; 21 import android.os.Bundle; 22 import android.text.SpannableString; 23 import android.text.Spanned; 24 import android.text.TextUtils; 25 import android.text.style.AbsoluteSizeSpan; 26 import android.webkit.WebView; 27 import android.webkit.WebViewClient; 28 import android.widget.ArrayAdapter; 29 import android.widget.CheckBox; 30 import android.widget.Spinner; 31 import android.widget.TextView; 32 33 import androidx.appcompat.app.AppCompatActivity; 34 import androidx.webkit.WebMessageCompat; 35 import androidx.webkit.WebMessagePortCompat; 36 import androidx.webkit.WebViewCompat; 37 import androidx.webkit.WebViewFeature; 38 39 import com.google.common.base.Charsets; 40 import com.google.common.io.ByteStreams; 41 42 import org.jspecify.annotations.NonNull; 43 import org.jspecify.annotations.Nullable; 44 45 import java.io.IOException; 46 import java.io.InputStream; 47 import java.nio.ByteBuffer; 48 49 /** 50 * An {@link Activity} to exercise WebMessageCompat related functionality. 51 */ 52 public class WebMessageCompatActivity extends AppCompatActivity { 53 private static final String TYPE_STRING = "String"; 54 private static final String TYPE_ARRAY_BUFFER = "ArrayBuffer"; 55 private static final String[] MESSAGE_TYPES = {TYPE_STRING, TYPE_ARRAY_BUFFER}; 56 private static final boolean ARRAY_BUFFER_FEATURE_ENABLED = 57 WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER); 58 59 private WebView mWebView; 60 private TextView mTextView, mPerfTextView; 61 private CheckBox mCheckBox; 62 private WebMessagePortCompat mPort; 63 private Spinner mSpinner; 64 private int mMessageCount = 0; 65 private int mExpectedCount = 0; 66 private long mTimeStamp; 67 createNativeTitle()68 static CharSequence createNativeTitle() { 69 final String title = "Native View\n"; 70 SpannableString ss = new SpannableString(title); 71 ss.setSpan(new AbsoluteSizeSpan(55, true), 0, title.length() - 1, 72 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 73 return ss; 74 } 75 76 @SuppressLint("SetJavascriptEnabled") 77 @Override onCreate(@ullable Bundle savedInstanceState)78 protected void onCreate(@Nullable Bundle savedInstanceState) { 79 super.onCreate(savedInstanceState); 80 setContentView(R.layout.activity_web_message_compat); 81 setTitle(R.string.web_message_compat_activity_title); 82 WebkitHelpers.appendWebViewVersionToTitle(this); 83 if (!WebViewFeature.isFeatureSupported(WebViewFeature.POST_WEB_MESSAGE)) { 84 WebkitHelpers.showMessageInActivity(WebMessageCompatActivity.this, 85 R.string.webkit_api_not_available); 86 return; 87 } 88 89 mWebView = findViewById(R.id.webview); 90 mWebView.getSettings().setJavaScriptEnabled(true); 91 mWebView.setWebViewClient(new MyWebViewClient()); 92 mTextView = findViewById(R.id.textview); 93 mPerfTextView = findViewById(R.id.textview_perf); 94 mCheckBox = findViewById(R.id.checkbox_window_message); 95 mSpinner = findViewById(R.id.message_type_spinner); 96 final ArrayAdapter<String> adapter = new ArrayAdapter<>(this, 97 android.R.layout.simple_spinner_dropdown_item, MESSAGE_TYPES); 98 mSpinner.setAdapter(adapter); 99 // If GET_PAYLOAD feature is not supported, disable the type selection spinner. 100 mSpinner.setEnabled(ARRAY_BUFFER_FEATURE_ENABLED); 101 102 try (InputStream is = getAssets().open("www/web_message_compat.html")) { 103 String webContent = new String(ByteStreams.toByteArray(is), Charsets.UTF_8); 104 mWebView.loadDataWithBaseURL("https://example.com", webContent, "text/html", null, 105 null); 106 } catch (IOException e) { 107 throw new RuntimeException(e); 108 } 109 findViewById(R.id.button_send).setOnClickListener(view -> { 110 mExpectedCount = mMessageCount + 5000; 111 mTimeStamp = System.currentTimeMillis(); 112 mSpinner.setEnabled(false); 113 sendMessage(); 114 }); 115 } 116 sendMessage()117 private void sendMessage() { 118 if (mMessageCount >= mExpectedCount) { 119 return; 120 } 121 final String selectedType = (String) mSpinner.getSelectedItem(); 122 final WebMessageCompat message; 123 switch (selectedType) { 124 case TYPE_STRING: 125 message = new WebMessageCompat(String.valueOf(mMessageCount + 1)); 126 break; 127 case TYPE_ARRAY_BUFFER: 128 byte[] bytes = 129 ByteBuffer.allocate(Integer.BYTES).putInt(mMessageCount + 1).array(); 130 message = new WebMessageCompat(bytes); 131 break; 132 default: 133 // Should never happen. 134 throw new RuntimeException("Invalid type."); 135 } 136 if (mCheckBox.isChecked()) { 137 WebViewCompat.postWebMessage(mWebView, message, Uri.EMPTY); 138 } else { 139 mPort.postMessage(message); 140 } 141 } 142 refreshNativeText()143 private void refreshNativeText() { 144 mTextView.setText(TextUtils.concat(createNativeTitle(), String.valueOf(mMessageCount), 145 " messages received")); 146 } 147 refreshPerfText()148 private void refreshPerfText() { 149 mPerfTextView.setText("Average time over 5000 messages: " 150 + (System.currentTimeMillis() - mTimeStamp) / 5000.0 + " ms"); 151 } 152 setupMessagePort()153 private void setupMessagePort() { 154 WebMessagePortCompat[] ports = WebViewCompat.createWebMessageChannel(mWebView); 155 WebViewCompat.postWebMessage(mWebView, 156 new WebMessageCompat("setup", new WebMessagePortCompat[]{ports[0]}), Uri.EMPTY); 157 mPort = ports[1]; 158 mPort.setWebMessageCallback(new WebMessagePortCompat.WebMessageCallbackCompat() { 159 @Override 160 public void onMessage(@NonNull WebMessagePortCompat port, 161 @Nullable WebMessageCompat message) { 162 switch (message.getType()) { 163 case WebMessageCompat.TYPE_STRING: 164 mMessageCount = Integer.parseInt(message.getData()); 165 break; 166 case WebMessageCompat.TYPE_ARRAY_BUFFER: 167 mMessageCount = ByteBuffer.wrap(message.getArrayBuffer()).getInt(); 168 break; 169 default: 170 // Should never happen 171 throw new RuntimeException("Invalid type: " + message.getType()); 172 } 173 if (mMessageCount % 100 == 0) { 174 refreshNativeText(); 175 } 176 if (mMessageCount == mExpectedCount) { 177 refreshPerfText(); 178 mSpinner.setEnabled(ARRAY_BUFFER_FEATURE_ENABLED); 179 } 180 sendMessage(); 181 } 182 }); 183 } 184 185 private class MyWebViewClient extends WebViewClient { 186 @Override onPageFinished(WebView view, String url)187 public void onPageFinished(WebView view, String url) { 188 super.onPageFinished(view, url); 189 setupMessagePort(); 190 view.setWebViewClient(null); 191 } 192 } 193 } 194