Merge "Add com.android.virt.accessor_demo" into main
diff --git a/ferrochrome_app/AndroidManifest.xml b/ferrochrome_app/AndroidManifest.xml
index 826fcc4..7afffe5 100644
--- a/ferrochrome_app/AndroidManifest.xml
+++ b/ferrochrome_app/AndroidManifest.xml
@@ -23,6 +23,16 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+ <activity android:name=".OpenUrlActivity"
+ android:theme="@android:style/Theme.NoDisplay"
+ android:launchMode="singleTask"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.SEND" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="text/*" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/ferrochrome_app/java/com/android/virtualization/ferrochrome/FerrochromeActivity.java b/ferrochrome_app/java/com/android/virtualization/ferrochrome/FerrochromeActivity.java
index 5006413..2df5cab 100644
--- a/ferrochrome_app/java/com/android/virtualization/ferrochrome/FerrochromeActivity.java
+++ b/ferrochrome_app/java/com/android/virtualization/ferrochrome/FerrochromeActivity.java
@@ -55,11 +55,18 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ if (!isTaskRoot()) {
+ // In case we launched this activity multiple times, only start one instance of this
+ // activity by only starting this as the root activity in task.
+ finish();
+ Log.w(TAG, "Not starting because not task root");
+ return;
+ }
setContentView(R.layout.activity_ferrochrome);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
// Find VM Launcher
- Intent intent = new Intent(ACTION_VM_LAUNCHER);
+ Intent intent = new Intent(ACTION_VM_LAUNCHER).setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PackageManager pm = getPackageManager();
List<ResolveInfo> resolveInfos =
pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
diff --git a/ferrochrome_app/java/com/android/virtualization/ferrochrome/OpenUrlActivity.java b/ferrochrome_app/java/com/android/virtualization/ferrochrome/OpenUrlActivity.java
new file mode 100644
index 0000000..c32d017
--- /dev/null
+++ b/ferrochrome_app/java/com/android/virtualization/ferrochrome/OpenUrlActivity.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 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.android.virtualization.ferrochrome;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+public class OpenUrlActivity extends Activity {
+ private static final String TAG = OpenUrlActivity.class.getSimpleName();
+
+ private static final String ACTION_VM_OPEN_URL = "android.virtualization.VM_OPEN_URL";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ boolean isRoot = isTaskRoot();
+ finish();
+ if (!Intent.ACTION_SEND.equals(getIntent().getAction())) {
+ return;
+ }
+ String text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
+ if (text == null) {
+ return;
+ }
+ Uri uri = Uri.parse(text);
+ if (uri == null) {
+ return;
+ }
+ String scheme = uri.getScheme();
+ if (!("http".equals(scheme) || "https".equals(scheme) || "mailto".equals(scheme))) {
+ Log.e(TAG, "Unsupported URL scheme: " + scheme);
+ return;
+ }
+ Log.i(TAG, "Sending " + scheme + " URL to VM");
+ if (isRoot) {
+ Log.w(
+ TAG,
+ "Cannot open URL without starting "
+ + FerrochromeActivity.class.getSimpleName()
+ + " first, starting it now");
+ startActivity(
+ new Intent(this, FerrochromeActivity.class).setAction(Intent.ACTION_MAIN));
+ return;
+ }
+ startActivity(
+ new Intent(ACTION_VM_OPEN_URL)
+ .setFlags(
+ Intent.FLAG_ACTIVITY_SINGLE_TOP
+ | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP)
+ .putExtra(Intent.EXTRA_TEXT, text));
+ }
+}
diff --git a/ferrochrome_app/vm_config.json.template b/ferrochrome_app/vm_config.json.template
index 6e1992f..d1a7cfa 100644
--- a/ferrochrome_app/vm_config.json.template
+++ b/ferrochrome_app/vm_config.json.template
@@ -33,5 +33,9 @@
"backend": "virglrenderer",
"context_types": ["virgl2"]
},
+ "display": {
+ "scale": "0.77",
+ "refresh_rate": "30"
+ },
"console_input_device": "hvc0"
}
diff --git a/tests/ferrochrome/ferrochrome.sh b/tests/ferrochrome/ferrochrome.sh
index 210548a..32db59c 100755
--- a/tests/ferrochrome/ferrochrome.sh
+++ b/tests/ferrochrome/ferrochrome.sh
@@ -32,6 +32,9 @@
fecr_clean_up() {
trap - INT
+ # Reset screen always on
+ adb shell svc power stayon false
+
if [[ -d ${fecr_dir} && -z ${fecr_keep} ]]; then
rm -rf ${fecr_dir}
fi
@@ -134,33 +137,8 @@
fi
echo "Ensure screen unlocked"
-
-try_unlock=0
-while [[ "${try_unlock}" -le "${TRY_UNLOCK_MAX}" ]]; do
- screen_state=$(adb shell dumpsys nfc | sed -n 's/^mScreenState=\(.*\)$/\1/p')
- case "${screen_state}" in
- "ON_UNLOCKED")
- break
- ;;
- "ON_LOCKED")
- # Disclaimer: This can unlock phone only if unlock method is swipe (default after FDR)
- adb shell input keyevent KEYCODE_MENU
- ;;
- "OFF_LOCKED"|"OFF_UNLOCKED")
- adb shell input keyevent KEYCODE_WAKEUP
- ;;
- *)
- echo "Unknown screen state. Continue to boot, but may fail"
- break
- ;;
- esac
- sleep 1
- try_unlock=$((try_unlock+1))
-done
-if [[ "${try_unlock}" -gt "${TRY_UNLOCK_MAX}" ]]; then
- >&2 echo "Failed to unlock screen. Try again after manual unlock"
- exit 1
-fi
+adb shell svc power stayon true
+adb shell wm dismiss-keyguard
echo "Starting ferrochrome"
adb shell am start-activity -a ${ACTION_NAME} > /dev/null
diff --git a/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java b/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java
index 69527be..af68eb3 100644
--- a/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java
+++ b/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java
@@ -33,6 +33,8 @@
private static final String CUTTLEFISH_DEVICE_PREFIX = "vsoc_";
private static final String CUTTLEFISH_ARM64_DEVICE_PREFIX = "vsoc_arm64";
+ private static final String GOLDFISH_DEVICE_PREFIX = "emu64";
+ private static final String GOLDFISH_ARM64_DEVICE_PREFIX = "emu64a";
private static final String USER_BUILD_TYPE = "user";
private static final String HWASAN_SUFFIX = "_hwasan";
@@ -65,6 +67,23 @@
}
/**
+ * @return whether the device is a cuttlefish device.
+ */
+ public boolean isGoldfish() {
+ String vendorDeviceName = getProperty(KEY_VENDOR_DEVICE);
+ return vendorDeviceName != null && vendorDeviceName.startsWith(GOLDFISH_DEVICE_PREFIX);
+ }
+
+ /**
+ * @return whether the device is a cuttlefish device running on 64 bit Arm.
+ */
+ public boolean isGoldfishArm64() {
+ String vendorDeviceName = getProperty(KEY_VENDOR_DEVICE);
+ return vendorDeviceName != null
+ && vendorDeviceName.startsWith(GOLDFISH_ARM64_DEVICE_PREFIX);
+ }
+
+ /**
* @return whether the build is HWASAN.
*/
public boolean isHwasan() {
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index 91ff4d8..8169376 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -81,6 +81,14 @@
return getDeviceProperties().isCuttlefishArm64();
}
+ public static boolean isGoldfish() {
+ return getDeviceProperties().isGoldfish();
+ }
+
+ private static boolean isGoldfishArm64() {
+ return getDeviceProperties().isGoldfishArm64();
+ }
+
public static boolean isHwasan() {
return getDeviceProperties().isHwasan();
}
@@ -246,10 +254,11 @@
.that(KERNEL_VERSION)
.isNotEqualTo("5.4");
- // Cuttlefish on Arm 64 doesn't and cannot support any form of virtualization, so there's
- // no point running any of these tests.
- assume().withMessage("Virtualization not supported on Arm64 Cuttlefish. b/341889915")
- .that(isCuttlefishArm64())
+ // Cuttlefish/Goldfish on Arm 64 doesn't and cannot support any form of virtualization,
+ // so there's no point running any of these tests.
+ assume().withMessage("Virtualization not supported on Arm64 Cuttlefish/Goldfish."
+ + " b/341889915")
+ .that(isCuttlefishArm64() || isGoldfishArm64())
.isFalse();
}
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 658b1bb..1465e73 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -1219,7 +1219,8 @@
// TODO(b/325094712): VMs on CF with same payload have the same secret. This is because
// `instance-id` which is input to DICE is contained in DT which is missing in CF.
assumeFalse(
- "Cuttlefish doesn't support device tree under /proc/device-tree", isCuttlefish());
+ "Cuttlefish/Goldfish doesn't support device tree under /proc/device-tree",
+ isCuttlefish() || isGoldfish());
grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
VirtualMachineConfig normalConfig =
@@ -1693,7 +1694,8 @@
// TODO(b/325094712): VMs on CF with same payload have the same secret. This is because
// `instance-id` which is input to DICE is contained in DT which is missing in CF.
assumeFalse(
- "Cuttlefish doesn't support device tree under /proc/device-tree", isCuttlefish());
+ "Cuttlefish/Goldfish doesn't support device tree under /proc/device-tree",
+ isCuttlefish() || isGoldfish());
VirtualMachineConfig config =
newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
@@ -2401,7 +2403,8 @@
assumeSupportedDevice();
// TODO(b/325094712): Boot fails with vendor partition in Cuttlefish.
assumeFalse(
- "Cuttlefish doesn't support device tree under /proc/device-tree", isCuttlefish());
+ "Cuttlefish/Goldfish doesn't support device tree under /proc/device-tree",
+ isCuttlefish() || isGoldfish());
// TODO(b/317567210): Boot fails with vendor partition in HWASAN enabled microdroid
// after introducing verification based on DT and fstab in microdroid vendor partition.
assumeFalse(
diff --git a/vmbase/src/console.rs b/vmbase/src/console.rs
index bbbcb07..cd05250 100644
--- a/vmbase/src/console.rs
+++ b/vmbase/src/console.rs
@@ -15,11 +15,8 @@
//! Console driver for 8250 UART.
use crate::uart::Uart;
-use core::{
- cell::OnceCell,
- fmt::{write, Arguments, Write},
-};
-use spin::mutex::SpinMutex;
+use core::fmt::{write, Arguments, Write};
+use spin::{mutex::SpinMutex, Once};
// Arbitrary limit on the number of consoles that can be registered.
//
@@ -28,12 +25,8 @@
static CONSOLES: [SpinMutex<Option<Uart>>; MAX_CONSOLES] =
[SpinMutex::new(None), SpinMutex::new(None), SpinMutex::new(None), SpinMutex::new(None)];
-static ADDRESSES: [SpinMutex<OnceCell<usize>>; MAX_CONSOLES] = [
- SpinMutex::new(OnceCell::new()),
- SpinMutex::new(OnceCell::new()),
- SpinMutex::new(OnceCell::new()),
- SpinMutex::new(OnceCell::new()),
-];
+static ADDRESSES: [Once<usize>; MAX_CONSOLES] =
+ [Once::new(), Once::new(), Once::new(), Once::new()];
/// Index of the console used by default for logging.
pub const DEFAULT_CONSOLE_INDEX: usize = 0;
@@ -52,7 +45,7 @@
pub unsafe fn init(base_addresses: &[usize]) {
for (i, &base_address) in base_addresses.iter().enumerate() {
// Remember the valid address, for emergency console accesses.
- ADDRESSES[i].lock().set(base_address).expect("console::init() called more than once");
+ ADDRESSES[i].call_once(|| base_address);
// Initialize the console driver, for normal console accesses.
let mut console = CONSOLES[i].lock();
@@ -78,8 +71,7 @@
/// This is intended for use in situations where the UART may be in an unknown state or the global
/// instance may be locked, such as in an exception handler or panic handler.
pub fn ewriteln(n: usize, format_args: Arguments) {
- let Some(cell) = ADDRESSES[n].try_lock() else { return };
- let Some(addr) = cell.get() else { return };
+ let Some(addr) = ADDRESSES[n].get() else { return };
// SAFETY: addr contains the base of a mapped UART, passed in init().
let mut uart = unsafe { Uart::new(*addr) };
diff --git a/vmlauncher_app/AndroidManifest.xml b/vmlauncher_app/AndroidManifest.xml
index e7a23dc..c6ab1f2 100644
--- a/vmlauncher_app/AndroidManifest.xml
+++ b/vmlauncher_app/AndroidManifest.xml
@@ -17,8 +17,9 @@
android:exported="true">
<intent-filter>
<action android:name="android.virtualization.VM_LAUNCHER" />
+ <action android:name="android.virtualization.VM_OPEN_URL" />
<category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
+ </intent-filter>
</activity>
</application>
diff --git a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
index 694aa57..0b93968 100644
--- a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
+++ b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
@@ -17,14 +17,13 @@
package com.android.virtualization.vmlauncher;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.ParcelFileDescriptor.AutoCloseInputStream;
-import static android.os.ParcelFileDescriptor.AutoCloseOutputStream;
import static android.system.virtualmachine.VirtualMachineConfig.CPU_TOPOLOGY_MATCH_HOST;
import android.Manifest.permission;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
+import android.content.Intent;
import android.crosvm.ICrosvmAndroidDisplayService;
import android.graphics.PixelFormat;
import android.graphics.Rect;
@@ -45,7 +44,6 @@
import android.system.virtualmachine.VirtualMachineManager;
import android.util.DisplayMetrics;
import android.util.Log;
-import android.view.Display;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
@@ -64,6 +62,8 @@
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -81,12 +81,17 @@
public class MainActivity extends Activity implements InputManager.InputDeviceListener {
private static final String TAG = "VmLauncherApp";
private static final String VM_NAME = "my_custom_vm";
+
private static final boolean DEBUG = true;
+ private static final int RECORD_AUDIO_PERMISSION_REQUEST_CODE = 101;
+
+ private static final String ACTION_VM_LAUNCHER = "android.virtualization.VM_LAUNCHER";
+ private static final String ACTION_VM_OPEN_URL = "android.virtualization.VM_OPEN_URL";
+
private ExecutorService mExecutorService;
private VirtualMachine mVirtualMachine;
private CursorHandler mCursorHandler;
private ClipboardManager mClipboardManager;
- private static final int RECORD_AUDIO_PERMISSION_REQUEST_CODE = 101;
private VirtualMachineConfig createVirtualMachineConfig(String jsonPath) {
VirtualMachineConfig.Builder configBuilder =
@@ -200,20 +205,32 @@
customImageConfigBuilder.setGpuConfig(gpuConfigBuilder.build());
}
- configBuilder.setMemoryBytes(8L * 1024 * 1024 * 1024 /* 8 GB */);
+ long memoryMib = 1024; // 1GB by default
+ if (json.has("memory_mib")) {
+ memoryMib = json.getLong("memory_mib");
+ }
+ configBuilder.setMemoryBytes(memoryMib * 1024 * 1024);
+
WindowMetrics windowMetrics = getWindowManager().getCurrentWindowMetrics();
- Rect windowSize = windowMetrics.getBounds();
- int dpi = (int) (DisplayMetrics.DENSITY_DEFAULT * windowMetrics.getDensity());
+ float dpi = DisplayMetrics.DENSITY_DEFAULT * windowMetrics.getDensity();
+ int refreshRate = (int) getDisplay().getRefreshRate();
+ if (json.has("display")) {
+ JSONObject display = json.getJSONObject("display");
+ if (display.has("scale")) {
+ dpi *= (float) display.getDouble("scale");
+ }
+ if (display.has("refresh_rate")) {
+ refreshRate = display.getInt("refresh_rate");
+ }
+ }
+ int dpiInt = (int) dpi;
DisplayConfig.Builder displayConfigBuilder = new DisplayConfig.Builder();
+ Rect windowSize = windowMetrics.getBounds();
displayConfigBuilder.setWidth(windowSize.right);
displayConfigBuilder.setHeight(windowSize.bottom);
- displayConfigBuilder.setHorizontalDpi(dpi);
- displayConfigBuilder.setVerticalDpi(dpi);
-
- Display display = getDisplay();
- if (display != null) {
- displayConfigBuilder.setRefreshRate((int) display.getRefreshRate());
- }
+ displayConfigBuilder.setHorizontalDpi(dpiInt);
+ displayConfigBuilder.setVerticalDpi(dpiInt);
+ displayConfigBuilder.setRefreshRate(refreshRate);
customImageConfigBuilder.setDisplayConfig(displayConfigBuilder.build());
customImageConfigBuilder.useTouch(true);
@@ -310,6 +327,12 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ String action = getIntent().getAction();
+ if (!ACTION_VM_LAUNCHER.equals(action)) {
+ finish();
+ Log.e(TAG, "onCreate unsupported intent action: " + action);
+ return;
+ }
checkAndRequestRecordAudioPermission();
mExecutorService = Executors.newCachedThreadPool();
try {
@@ -556,10 +579,11 @@
Log.d(TAG, "destroyed");
}
- private static final int CLIPBOARD_SHARING_SERVER_PORT = 3580;
+ private static final int DATA_SHARING_SERVICE_PORT = 3580;
private static final byte READ_CLIPBOARD_FROM_VM = 0;
private static final byte WRITE_CLIPBOARD_TYPE_EMPTY = 1;
private static final byte WRITE_CLIPBOARD_TYPE_TEXT_PLAIN = 2;
+ private static final byte OPEN_URL = 3;
private ClipboardManager getClipboardManager() {
if (mClipboardManager == null) {
@@ -581,16 +605,9 @@
return header.array();
}
- private ParcelFileDescriptor connectClipboardSharingServer() {
- ParcelFileDescriptor pfd;
- try {
- // TODO(349702313): Consider when clipboard sharing server is started to run in VM.
- pfd = mVirtualMachine.connectVsock(CLIPBOARD_SHARING_SERVER_PORT);
- } catch (VirtualMachineException e) {
- Log.d(TAG, "cannot connect to the clipboard sharing server", e);
- return null;
- }
- return pfd;
+ private ParcelFileDescriptor connectDataSharingService() throws VirtualMachineException {
+ // TODO(349702313): Consider when clipboard sharing server is started to run in VM.
+ return mVirtualMachine.connectVsock(DATA_SHARING_SERVICE_PORT);
}
private boolean writeClipboardToVm() {
@@ -604,64 +621,58 @@
byte[] header =
constructClipboardHeader(
WRITE_CLIPBOARD_TYPE_TEXT_PLAIN, text.getBytes().length + 1);
- ParcelFileDescriptor pfd = connectClipboardSharingServer();
- if (pfd == null) {
- Log.d(TAG, "file descriptor of ClipboardSharingServer is null");
- return false;
- }
- try (OutputStream stream = new AutoCloseOutputStream(pfd)) {
+ try (ParcelFileDescriptor pfd = connectDataSharingService();
+ OutputStream stream = new FileOutputStream(pfd.getFileDescriptor())) {
stream.write(header);
stream.write(text.getBytes());
stream.write('\0');
- stream.flush();
Log.d(TAG, "successfully wrote clipboard data to the VM");
return true;
- } catch (IOException e) {
+ } catch (IOException | VirtualMachineException e) {
Log.e(TAG, "failed to write clipboard data to the VM", e);
return false;
}
}
+ private byte[] readExactly(InputStream stream, int len) throws IOException {
+ byte[] buf = stream.readNBytes(len);
+ if (buf.length != len) {
+ throw new IOException("Cannot read enough bytes");
+ }
+ return buf;
+ }
+
private boolean readClipboardFromVm() {
byte[] request = constructClipboardHeader(READ_CLIPBOARD_FROM_VM, 0);
- ParcelFileDescriptor pfd = connectClipboardSharingServer();
- if (pfd == null) {
- Log.d(TAG, "file descriptor of ClipboardSharingServer is null");
- return false;
- }
- try (OutputStream output = new AutoCloseOutputStream(pfd.dup())) {
- output.write(request);
- output.flush();
- Log.d(TAG, "successfully send request to the VM for reading clipboard");
- } catch (IOException e) {
- Log.e(TAG, "failed to send request to the VM for read clipboard", e);
- try {
- pfd.close();
- } catch (IOException err) {
- Log.e(TAG, "failed to close file descriptor", err);
+ try (ParcelFileDescriptor pfd = connectDataSharingService()) {
+ try (OutputStream output = new FileOutputStream(pfd.getFileDescriptor())) {
+ output.write(request);
+ Log.d(TAG, "successfully send request to the VM for reading clipboard");
+ } catch (IOException e) {
+ Log.e(TAG, "failed to send request to the VM for read clipboard");
+ throw e;
}
- return false;
- }
-
- try (InputStream input = new AutoCloseInputStream(pfd)) {
- ByteBuffer header = ByteBuffer.wrap(input.readNBytes(8));
- header.order(ByteOrder.LITTLE_ENDIAN);
- switch (header.get(0)) {
- case WRITE_CLIPBOARD_TYPE_EMPTY:
- Log.d(TAG, "clipboard data in VM is empty");
- return true;
- case WRITE_CLIPBOARD_TYPE_TEXT_PLAIN:
- int dataSize = header.getInt(4);
- String text_data =
- new String(input.readNBytes(dataSize), StandardCharsets.UTF_8);
- getClipboardManager().setPrimaryClip(ClipData.newPlainText(null, text_data));
- Log.d(TAG, "successfully received clipboard data from VM");
- return true;
- default:
- Log.e(TAG, "unknown clipboard response type");
- return false;
+ try (InputStream input = new FileInputStream(pfd.getFileDescriptor())) {
+ ByteBuffer header = ByteBuffer.wrap(readExactly(input, 8));
+ header.order(ByteOrder.LITTLE_ENDIAN);
+ switch (header.get(0)) {
+ case WRITE_CLIPBOARD_TYPE_EMPTY:
+ Log.d(TAG, "clipboard data in VM is empty");
+ return true;
+ case WRITE_CLIPBOARD_TYPE_TEXT_PLAIN:
+ int dataSize = header.getInt(4);
+ String text_data =
+ new String(readExactly(input, dataSize), StandardCharsets.UTF_8);
+ getClipboardManager()
+ .setPrimaryClip(ClipData.newPlainText(null, text_data));
+ Log.d(TAG, "successfully received clipboard data from VM");
+ return true;
+ default:
+ Log.e(TAG, "unknown clipboard response type");
+ return false;
+ }
}
- } catch (IOException e) {
+ } catch (IOException | VirtualMachineException e) {
Log.e(TAG, "failed to receive clipboard content from the VM", e);
return false;
}
@@ -676,16 +687,46 @@
surfaceView.requestPointerCapture();
}
if (mVirtualMachine != null) {
- if (hasFocus) {
- Log.d(TAG, "writing clipboard of host device into VM");
- writeClipboardToVm();
- } else {
- Log.d(TAG, "reading clipboard of VM");
- readClipboardFromVm();
+ try {
+ if (hasFocus) {
+ Log.d(TAG, "writing clipboard of host device into VM");
+ writeClipboardToVm();
+ } else {
+ Log.d(TAG, "reading clipboard of VM");
+ readClipboardFromVm();
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "read/write clipboard error", e);
}
}
}
+ @Override
+ protected void onNewIntent(Intent intent) {
+ String action = intent.getAction();
+ if (!ACTION_VM_OPEN_URL.equals(action)) {
+ Log.e(TAG, "onNewIntent unsupported intent action: " + action);
+ return;
+ }
+ Log.d(TAG, "onNewIntent intent action: " + action);
+ String text = intent.getStringExtra(Intent.EXTRA_TEXT);
+ if (text != null) {
+ mExecutorService.execute(
+ () -> {
+ byte[] data = text.getBytes();
+ try (ParcelFileDescriptor pfd = connectDataSharingService();
+ OutputStream stream =
+ new FileOutputStream(pfd.getFileDescriptor())) {
+ stream.write(constructClipboardHeader(OPEN_URL, data.length));
+ stream.write(data);
+ Log.d(TAG, "Successfully sent URL to the VM");
+ } catch (IOException | VirtualMachineException e) {
+ Log.e(TAG, "Failed to send URL to the VM", e);
+ }
+ });
+ }
+ }
+
@FunctionalInterface
public interface RemoteExceptionCheckedFunction<T> {
void apply(T t) throws RemoteException;