From aa894978dadbd2fb4116a95444aad8a5a5363496 Mon Sep 17 00:00:00 2001 From: ahs Date: Thu, 21 Jul 2022 11:22:05 +0530 Subject: [PATCH] Initial commit for clipboard-agent Signed-off-by: ahs --- Android.bp | 36 ++ AndroidManifest.xml | 38 ++ jni/Android.bp | 56 +++ jni/DispatchHelper.cpp | 139 +++++++ jni/DispatchHelper.h | 45 +++ jni/VsockClientImpl.cpp | 246 ++++++++++++ jni/VsockClientImpl.h | 85 +++++ jni/VsockMsgDispatcher.cpp | 352 ++++++++++++++++++ jni/VsockMsgDispatcher.h | 173 +++++++++ .../clipboardagent/AppstatusComponent.java | 136 +++++++ .../intel/clipboardagent/ClipboardAgent.java | 42 +++ .../clipboardagent/ClipboardComponent.java | 84 +++++ .../clipboardagent/ClipboardService.java | 171 +++++++++ .../intel/clipboardagent/DispatchHelper.java | 42 +++ .../clipboardagent/GuestVsockCommService.java | 49 +++ .../intel/clipboardagent/MainActivity.java | 31 ++ src/com/intel/clipboardagent/Vsock.java | 74 ++++ .../intel/clipboardagent/VsockAddress.java | 46 +++ .../intel/clipboardagent/VsockBaseVSock.java | 65 ++++ .../intel/clipboardagent/VsockClientImpl.java | 42 +++ .../clipboardagent/VsockInputStream.java | 55 +++ .../clipboardagent/VsockOutputStream.java | 51 +++ 22 files changed, 2058 insertions(+) create mode 100755 Android.bp create mode 100755 AndroidManifest.xml create mode 100755 jni/Android.bp create mode 100755 jni/DispatchHelper.cpp create mode 100755 jni/DispatchHelper.h create mode 100755 jni/VsockClientImpl.cpp create mode 100755 jni/VsockClientImpl.h create mode 100755 jni/VsockMsgDispatcher.cpp create mode 100755 jni/VsockMsgDispatcher.h create mode 100755 src/com/intel/clipboardagent/AppstatusComponent.java create mode 100755 src/com/intel/clipboardagent/ClipboardAgent.java create mode 100755 src/com/intel/clipboardagent/ClipboardComponent.java create mode 100755 src/com/intel/clipboardagent/ClipboardService.java create mode 100755 src/com/intel/clipboardagent/DispatchHelper.java create mode 100755 src/com/intel/clipboardagent/GuestVsockCommService.java create mode 100755 src/com/intel/clipboardagent/MainActivity.java create mode 100755 src/com/intel/clipboardagent/Vsock.java create mode 100755 src/com/intel/clipboardagent/VsockAddress.java create mode 100755 src/com/intel/clipboardagent/VsockBaseVSock.java create mode 100755 src/com/intel/clipboardagent/VsockClientImpl.java create mode 100755 src/com/intel/clipboardagent/VsockInputStream.java create mode 100755 src/com/intel/clipboardagent/VsockOutputStream.java diff --git a/Android.bp b/Android.bp new file mode 100755 index 0000000..9157286 --- /dev/null +++ b/Android.bp @@ -0,0 +1,36 @@ +// +// Copyright (C) 2018 The Android Open Source Project +// Copyright (C) 2021 Intel Corporation. +// +// 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. +// + +// This makefile shows how to build a shared library and an activity that +// bundles the shared library and calls it using JNI. + +android_app { + name: "ClipboardAgent", + srcs: ["**/*.java"], + // JNI library built from C++ source code + jni_libs: ["libVsockMsgDispatch", "libVsocketClientImpl"], + optimize: { + enabled: false, + }, + sdk_version: "system_current", + dex_preopt: { + enabled: false, + }, + privileged: true, + // To match the signature + certificate: "platform", +} diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100755 index 0000000..4d7c85a --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jni/Android.bp b/jni/Android.bp new file mode 100755 index 0000000..2440d09 --- /dev/null +++ b/jni/Android.bp @@ -0,0 +1,56 @@ +// +// 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. +// + +// This makefile supplies the rules for building a library of JNI code for +// use by our example of how to bundle a shared library with an APK. + +cc_library_shared { + name: "libVsocketClientImpl", + // All of the source files that we will compile. + srcs: ["VsockClientImpl.cpp"], + // All of the shared libraries we link against. + // liblog is used to print trace log in C plus plus source code. + shared_libs: ["liblog"], + // No static libraries. + header_libs: ["jni_headers"], + static_libs: [], + cflags: [ + "-Wall", + "-Werror", + ], + // We cannot use stl:"none" here to link libc++ dynamically because + // it caused "'iostream' file not found" build issue. + stl: "c++_static", + sdk_version: "current", +} + +cc_library_shared { + name: "libVsockMsgDispatch", + srcs: [ + "VsockMsgDispatcher.cpp", + "DispatchHelper.cpp", + ], + header_libs: ["jni_headers"], + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + "-Wno-unused-label", + ], + shared_libs: ["libbase", "liblog"], + sdk_version: "current", +} + diff --git a/jni/DispatchHelper.cpp b/jni/DispatchHelper.cpp new file mode 100755 index 0000000..f2aba1a --- /dev/null +++ b/jni/DispatchHelper.cpp @@ -0,0 +1,139 @@ +#include "VsockMsgDispatcher.h" +#include "DispatchHelper.h" +#include +#include + +using namespace vsock; +std::map< std::string, std::vector > comp_msg_map { + {"ClipboardComponent", {MSG_TYPE_CLIPBOARD}}, + {"AppstatusComponent", {MSG_TYPE_APPSTATUS}} +}; +std::map< std::string, jclass > jclass_map; + +static JavaVM* gVm = nullptr; +JNIEnv* getenv() { + JNIEnv *env = nullptr; + int getEnvStat = gVm->GetEnv((void **)&env, JNI_VERSION_1_6); + if (getEnvStat == JNI_EDETACHED) { + if (gVm->AttachCurrentThread(&env, NULL) != 0) { + LOGIT("Failed to attach"); + } + } else if (getEnvStat == JNI_OK) { + // + } else if (getEnvStat == JNI_EVERSION) { + LOGIT("GetEnv: version not supported"); + } + return env; +} + +class JavaComponent:public Component { + public: + std::string java_class_name; + std::vector msg_list; + JavaComponent(std::string name) { + std::map< std::string, std::vector >::iterator it; + java_class_name = name; + it = comp_msg_map.find(name); + if (it != comp_msg_map.end()) { + msg_list = it->second; + } + } + virtual ~JavaComponent(){ + JNIEnv* env = getenv(); + jclass reqClass = GetJClass(); + jobject singleInstance = GetSingletonInstance(reqClass); + jmethodID reqMethod = env->GetMethodID(reqClass, "stop", "()V"); + env->CallVoidMethod(singleInstance, reqMethod); + + } + virtual void init() { + JNIEnv* env = getenv(); + jclass reqClass = GetJClass(); + jobject singleInstance = GetSingletonInstance(reqClass); + jmethodID reqMethod = env->GetMethodID(reqClass, "init", "()V"); + env->CallVoidMethod(singleInstance, reqMethod); + } + + virtual void ProcessMsg(Message& msg, uint64_t hndl) { + //LOGIT("Process msg - %s\n", msg.payload); + JNIEnv *env = getenv(); + jclass reqClass = GetJClass(); + jobject singleInstance = GetSingletonInstance(reqClass); + jmethodID reqMethod = env->GetMethodID(reqClass, "processMsg", "(Ljava/lang/String;J)V"); + jstring str = env->NewStringUTF(msg.payload); + env->CallVoidMethod(singleInstance, reqMethod, str, static_cast(hndl)); + } + private: + jclass GetJClass() { + std::map< std::string, jclass >::iterator it; + jclass reqClass = nullptr; + it = jclass_map.find(java_class_name.c_str()); + if (it != jclass_map.end()) { + reqClass = it->second; + } + return reqClass; + } + + jobject GetSingletonInstance(jclass reqClass) { + JNIEnv *env = getenv(); + std::string sig = "()Lcom/intel/clipboardagent/"+java_class_name+";"; + jmethodID instMethod = env->GetStaticMethodID(reqClass, "getInstance", sig.c_str()); + jobject singleInstance = env->CallStaticObjectMethod(reqClass, instMethod); + return singleInstance; + } +}; + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { + JNIEnv *env; + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { + return JNI_ERR; + } + jclass tmp = nullptr; + tmp = env->FindClass("com/intel/clipboardagent/ClipboardComponent"); + if (tmp!= nullptr) { + jclass_map.insert({"ClipboardComponent", (jclass)env->NewGlobalRef(tmp)}); + } + tmp = env->FindClass("com/intel/clipboardagent/AppstatusComponent"); + if (tmp!= nullptr) { + jclass_map.insert({"AppstatusComponent", (jclass)env->NewGlobalRef(tmp)}); + } + return JNI_VERSION_1_6; +} + + +JNIEXPORT void JNICALL Java_com_intel_clipboardagent_DispatchHelper_registerComponent(JNIEnv *env, jobject thisObject, jstring className) { + MsgDispatcher* dispatcher = MsgDispatcher::getInstance(); + env->GetJavaVM(&gVm); + std::string name = env->GetStringUTFChars(className, 0); + JavaComponent* javaComponent = new JavaComponent(name); + dispatcher->RegisterComponent(javaComponent->msg_list, javaComponent); +} + + +JNIEXPORT void JNICALL Java_com_intel_clipboardagent_DispatchHelper_sendMsg(JNIEnv *env, jobject thisObject, jstring className, jstring msg, jlong handle) { + MsgDispatcher* dispatcher = MsgDispatcher::getInstance(); + std::string payload = env->GetStringUTFChars(msg, 0); + int size = env->GetStringUTFLength(msg); + std::vector msg_list; + std::map< std::string, std::vector >::iterator it; + std::string name = env->GetStringUTFChars(className, 0); + it = comp_msg_map.find(name); + if (it != comp_msg_map.end()) { + msg_list = it->second; + } + if (handle == 0) { + handle = dispatcher->GetHandleForMsgType(msg_list.front()); + } + dispatcher->SendMsg(handle, msg_list.front(), payload.c_str(), size); +} + + +JNIEXPORT void JNICALL Java_com_intel_clipboardagent_DispatchHelper_start(JNIEnv *env, jobject thisObject) { + MsgDispatcher* dispatcher = MsgDispatcher::getInstance(); + dispatcher->Start(); +} + +JNIEXPORT void JNICALL Java_com_intel_clipboardagent_DispatchHelper_stop(JNIEnv *env, jobject thisObject) { + MsgDispatcher* dispatcher = MsgDispatcher::getInstance(); + dispatcher->Stop(); +} diff --git a/jni/DispatchHelper.h b/jni/DispatchHelper.h new file mode 100755 index 0000000..3b67be4 --- /dev/null +++ b/jni/DispatchHelper.h @@ -0,0 +1,45 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_intel_clipboardagent_DispatchHelper */ + +#ifndef _Included_com_intel_clipboardagent_DispatchHelper +#define _Included_com_intel_clipboardagent_DispatchHelper +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_intel_clipboardagent_DispatchHelper + * Method: registerComponent + * Signature: (Ljava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_com_intel_clipboardagent_DispatchHelper_registerComponent + (JNIEnv *, jobject, jstring); + +/* + * Class: com_intel_clipboardagent_DispatchHelper + * Method: sendMsg + * Signature: (Ljava/lang/String;Ljava/lang/String;J)V + */ +JNIEXPORT void JNICALL Java_com_intel_clipboardagent_DispatchHelper_sendMsg + (JNIEnv *, jobject, jstring, jstring, jlong); + +/* + * Class: com_intel_clipboardagent_DispatchHelper + * Method: start + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_com_intel_clipboardagent_DispatchHelper_start + (JNIEnv *, jobject); + +/* + * Class: com_intel_clipboardagent_DispatchHelper + * Method: stop + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_com_intel_clipboardagent_DispatchHelper_stop + (JNIEnv *, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jni/VsockClientImpl.cpp b/jni/VsockClientImpl.cpp new file mode 100755 index 0000000..11c0ce7 --- /dev/null +++ b/jni/VsockClientImpl.cpp @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2021 Intel Corporation + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define JVM_IO_INTR (-2) +#ifndef bufferFER_LEN +#define bufferFER_LEN 65536 +#endif +#ifndef min +#define min(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#define LOG_TAG "vsock" +#include + +#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) +#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) +#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) + +#define DATA_SIZE_LENGTH 4 +#define MAX_CHUNK_LENGTH 8192 +#define MAX_DATA_LENGTH 512*1024 + +static const char *vsockClientImplPath = "com/intel/clipboardagent/VsockClientImpl"; +static const char *vsockAddressPath = "com/intel/clipboardagent/VsockAddress"; +static const char *javaConnException = "java/net/ConnectException"; +static const char *javaIntrIOException = "java/io/InterruptedIOException"; +static const char *sunConnResetException = "sun/net/ConnectionResetException"; + +int read_from_vsock(JNIEnv* env, int sockfd, uint8_t* bytes, uint32_t size) { + int nread = (jint) recv(sockfd, bytes, size, 0); + if (nread <= 0) { + if (nread < 0 && errno != ENOTCONN) { + env->ThrowNew(env->FindClass(javaConnException), + ("vsock read: Read failed with error no: " + std::to_string(errno)).c_str()); + } else { + env->ThrowNew(env->FindClass(javaConnException), + ("vsock read: Connection is closed by peer.")); + } + return nread; + } + return nread; +} + +bool write_to_vsock(JNIEnv* env, int sockfd, uint8_t* bytes, uint32_t size) { + int n = (int)send(sockfd, bytes, size, 0); + if (n == JVM_IO_INTR) { + env->ThrowNew(env->FindClass(javaIntrIOException), 0); + } else if (n <= 0){ + if (errno == ECONNRESET) { + env->ThrowNew(env->FindClass(sunConnResetException), "vsock write: Connection reset"); + } else { + env->ThrowNew(env->FindClass(javaConnException), "vsock write: Write failed"); + } + return false; + } else if (n != size) { + env->ThrowNew(env->FindClass(javaConnException), "vsock write: Failed to write complete msg"); + return false; + } + return true; +} + +JNIEXPORT void JNICALL Java_com_intel_clipboardagent_VsockClientImpl_socketCreate + (JNIEnv *env, jobject thisObject) { + int sock = socket(AF_VSOCK, SOCK_STREAM, 0); + + jclass implement = env->FindClass(vsockClientImplPath); + jfieldID fdField = env->GetFieldID(implement, "fd", "I"); + env->SetIntField(thisObject, fdField, sock); +} + +JNIEXPORT void JNICALL Java_com_intel_clipboardagent_VsockClientImpl_connect + (JNIEnv *env, jobject thisObject, jobject addr) { + jclass implement = env->FindClass(vsockClientImplPath); + jfieldID fdField = env->GetFieldID(implement, "fd", "I"); + int sock = (int)env->GetIntField(thisObject, fdField); + + if (sock == -1) { + env->ThrowNew(env->FindClass(javaConnException), "vsock: Socket is closed"); + return; + } + + jclass vsockAddress = env->FindClass(vsockAddressPath); + jfieldID cidField = env->GetFieldID(vsockAddress, "cid", "I"); + jfieldID portField = env->GetFieldID(vsockAddress, "port", "I"); + + + struct sockaddr_vm sock_addr; + std::memset(&sock_addr, 0, sizeof(struct sockaddr_vm)); + sock_addr.svm_family = AF_VSOCK; + sock_addr.svm_port = (int)env->GetIntField(addr, portField); + sock_addr.svm_cid = (int)env->GetIntField(addr, cidField); + int status = connect(sock, (struct sockaddr *) &sock_addr, sizeof(struct sockaddr_vm)); + if (status != 0) { + if (errno == EALREADY || errno == EISCONN ) { + env->ThrowNew(env->FindClass(javaConnException), + ("Connect failed: " + std::to_string(errno)).c_str()); + } + } +} + +JNIEXPORT void JNICALL Java_com_intel_clipboardagent_VsockClientImpl_close + (JNIEnv *env, jobject thisObject) { + jclass implement = env->FindClass(vsockClientImplPath); + jfieldID fdField = env->GetFieldID(implement, "fd", "I"); + int s = (int)env->GetIntField(thisObject, fdField); + + if (s == -1) { + env->ThrowNew(env->FindClass(javaConnException), "vsock close: Socket is already closed."); + return; + } + + int status = close(s); + + env->SetIntField(thisObject, fdField, -1); + if (status != 0) { + env->ThrowNew(env->FindClass(javaConnException), + ("Close failed: " + std::to_string(errno)).c_str()); + } +} + +JNIEXPORT void JNICALL Java_com_intel_clipboardagent_VsockClientImpl_write + (JNIEnv * env, jobject thisObject, jbyteArray b, jint offset, jint len) { + jclass implement = env->FindClass(vsockClientImplPath); + jfieldID fdField = env->GetFieldID(implement, "fd", "I"); + int s = (int)env->GetIntField(thisObject, fdField); + + if (s == -1) { + env->ThrowNew(env->FindClass(javaConnException), "vsock write: Socket is already closed."); + return; + } + + + // Send the actual data + char buffer[MAX_CHUNK_LENGTH]; + while(len > 0) { + int chunkLen = min(MAX_CHUNK_LENGTH, len); + + env->GetByteArrayRegion(b, offset, chunkLen, (jbyte *)buffer); + if(!write_to_vsock(env, s, (uint8_t*)buffer, chunkLen)) { + return; + } + len -= chunkLen; + offset += chunkLen; + } + return; +} + +JNIEXPORT jint JNICALL Java_com_intel_clipboardagent_VsockClientImpl_read + (JNIEnv * env, jobject thisObject, jbyteArray b, jint off, jint len) { + jclass implement = env->FindClass(vsockClientImplPath); + jfieldID fdField = env->GetFieldID(implement, "fd", "I"); + int s = (int)env->GetIntField(thisObject, fdField); + + if (s == -1) { + env->ThrowNew(env->FindClass(javaConnException), "vsock read: Socket is already closed"); + return -1; + } + uint8_t buffer[MAX_CHUNK_LENGTH]; + uint32_t remaining = len; + while (remaining > 0) { + int nread = 0; + uint32_t chunkLen = min(remaining, MAX_CHUNK_LENGTH); + if ((nread = read_from_vsock(env, s, buffer, chunkLen)) <= 0) { + ALOGE("vsock read: Failed to read complete msg"); + } + env->SetByteArrayRegion(b, off, nread, (jbyte *)buffer); + remaining -= nread; + off += nread; + } + + return (jint)len; +} + +JNIEXPORT void JNICALL Java_com_intel_clipboardagent_VsockClientImpl_writeInt + (JNIEnv *env, jobject thisObject, jint length) { + jclass implement = env->FindClass(vsockClientImplPath); + jfieldID fdField = env->GetFieldID(implement, "fd", "I"); + int s = (int)env->GetIntField(thisObject, fdField); + + if (s == -1) { + env->ThrowNew(env->FindClass(javaConnException), "vsock read: Socket is already closed"); + return; + } + + { + uint32_t size = length; + size = htonl(size); + uint8_t* buffer = (uint8_t*)&size; + if (!write_to_vsock(env, s, buffer, DATA_SIZE_LENGTH)) { + return; + } + } +} + + +JNIEXPORT jint JNICALL Java_com_intel_clipboardagent_VsockClientImpl_readInt + (JNIEnv *env, jobject thisObject) { + jclass implement = env->FindClass(vsockClientImplPath); + jfieldID fdField = env->GetFieldID(implement, "fd", "I"); + int s = (int)env->GetIntField(thisObject, fdField); + + if (s == -1) { + env->ThrowNew(env->FindClass(javaConnException), "vsock read: Socket is already closed"); + return -1; + } + + uint32_t size = 0; + { + uint8_t buffer[DATA_SIZE_LENGTH + 1] = {0}; + if (read_from_vsock(env, s, buffer, DATA_SIZE_LENGTH) != DATA_SIZE_LENGTH) { + ALOGE("vsock read: Failed to read data size."); + return -1; + } + size = *(uint32_t*)buffer; + size = ntohl(size); + } + return (jint)size; +} diff --git a/jni/VsockClientImpl.h b/jni/VsockClientImpl.h new file mode 100755 index 0000000..efab088 --- /dev/null +++ b/jni/VsockClientImpl.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2021 Intel Corporation + * + * 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. + */ + +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_intel_clipboardagent_VsockClientImpl */ + +#ifndef _Included_com_intel_clipboardagent_VsockClientImpl +#define _Included_com_intel_clipboardagent_VsockClientImpl +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_intel_clipboardagent_VsockClientImpl + * Method: socketCreate + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_com_intel_clipboardagent_VsockClientImpl_socketCreate + (JNIEnv *, jobject); + +/* + * Class: com_intel_clipboardagent_VsockClientImpl + * Method: connect + * Signature: (Lcom/intel/clipboardagent/VsockAddress;)V + */ +JNIEXPORT void JNICALL Java_com_intel_clipboardagent_VsockClientImpl_connect + (JNIEnv *, jobject, jobject); + +/* + * Class: com_intel_clipboardagent_VsockClientImpl + * Method: close + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_com_intel_clipboardagent_VsockClientImpl_close + (JNIEnv *, jobject); + +/* + * Class: com_intel_clipboardagent_VsockClientImpl + * Method: write + * Signature: ([BII)V + */ +JNIEXPORT void JNICALL Java_com_intel_clipboardagent_VsockClientImpl_write + (JNIEnv *, jobject, jbyteArray, jint, jint); + +/* + * Class: com_intel_clipboardagent_VsockClientImpl + * Method: read + * Signature: ([BII)I + */ +JNIEXPORT jint JNICALL Java_com_intel_clipboardagent_VsockClientImpl_read + (JNIEnv *, jobject, jbyteArray, jint, jint); + +/* + * Class: com_intel_clipboardagent_VsockClientImpl + * Method: writeInt + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_com_intel_clipboardagent_VsockClientImpl_writeInt + (JNIEnv *, jobject, jint); + +/* + * Class: com_intel_clipboardagent_VsockClientImpl + * Method: readInt + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_com_intel_clipboardagent_VsockClientImpl_readInt + (JNIEnv *, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jni/VsockMsgDispatcher.cpp b/jni/VsockMsgDispatcher.cpp new file mode 100755 index 0000000..daee35f --- /dev/null +++ b/jni/VsockMsgDispatcher.cpp @@ -0,0 +1,352 @@ +#include "VsockMsgDispatcher.h" +#include +#include +#include +#include +#include + +namespace vsock { +#ifdef MSG_SERVER +static const bool gServer = true; +#else +static const bool gServer = false; +#endif + +#define VMADDR_CID_HOST 2 +#define VSOCK_PORT 77788 +#define CHUNK_SIZE 8192 + +MsgDispatcher *MsgDispatcher::instance = nullptr; +MsgDispatcher::MsgDispatcher() { + std::vector empty_vector; + fd_ = socket(AF_VSOCK, SOCK_STREAM, 0); + if (fd_ <= 0) { + ERR("Socket Create:"); + } + nclients_ = 0; + m_bStop = false; + msg_type_map[MSG_TYPE_INVALID] = empty_vector; +} + +bool MsgDispatcher::Start() { + struct sockaddr_vm sock_addr; + memset(&sock_addr, 0, sizeof(struct sockaddr_vm)); + sock_addr.svm_family = AF_VSOCK; + sock_addr.svm_port = VSOCK_PORT; + sock_addr.svm_cid = VMADDR_CID_HOST; + + if (gServer) { + int ret; + if ((ret = bind(fd_, (struct sockaddr*)&sock_addr, sizeof(sock_addr))) < 0) { + ERR("Socket bind:"); + return ret; + } + + //Bind succesful, so start accepting connections on a separate thread + Channel* channel = new Channel(this, fd_, 0); + channels_.emplace_back(channel); + LOGIT("Start Listen thread\n"); + CHECK_PTHREAD_CALL(pthread_create, (&t_main_, nullptr, &Listen, channel), "Failed to create server listener thread"); + + } else { + bool bConnected = true; + bConnected = connect(fd_, (struct sockaddr *) &sock_addr, sizeof(struct sockaddr_vm)); + while(!bConnected) { + LOGIT("Failed to connect to server. Waiting to try again..."); + sleep(1); + ERR("Socket connect:"); + bConnected = connect(fd_, (struct sockaddr *) &sock_addr, sizeof(struct sockaddr_vm)); + } + + //Connected to server, start msg xchange + Channel* channel = new Channel(this, fd_, 0); + channels_.emplace_back(channel); + LOGIT("Start Run thread\n"); + CHECK_PTHREAD_CALL(pthread_create, (&t_main_, nullptr, &Run, channel), "Failed to create leader thread"); + } + + //Create workers + for (int i = 0; i < WORKER_POOL; ++i) { + LOGIT("Start worker thread-%d\n", i); + CHECK_PTHREAD_CALL(pthread_create, (&t_workers_[i], nullptr, &WorkerStart, this), "Failed to create worker thread"); + } + + return true; +} + +void* MsgDispatcher::Listen(void* arg) { + Channel* channel = reinterpret_cast(arg); + MsgDispatcher* dispatcher = channel->dispatcher_; + + struct sockaddr_vm sock_addr; + memset(&sock_addr, 0, sizeof(struct sockaddr_vm)); + sock_addr.svm_family = AF_VSOCK; + sock_addr.svm_port = VSOCK_PORT; + sock_addr.svm_cid = VMADDR_CID_HOST; + size_t sock_addr_len = sizeof(struct sockaddr_vm); + + if (!gServer) { + return (void*)-1; + } + + int client_fd = -1; + + while (!dispatcher->m_bStop) { + if (listen(channel->fd_, 2) < 0) { + ERR("Socket listen:"); + } + //TODO: Make accept non-blocking + if((client_fd = accept(channel->fd_, (struct sockaddr*)&sock_addr, (socklen_t*)&sock_addr_len)) < 0) { + // if (errno == EAGAIN || errno == EWOULDBLOCK) { + // sleep(1); + // } else { + ERR("Socket accept:"); + // } + return (void *)-1; + } + if (dispatcher->t_clients_.size() == MAX_CLIENT_CONNECTIONS) { + close(client_fd); + LOGIT("Max connections exceeded. Closing client connection."); + continue; + } + // start msg xchange + pthread_t thread; + dispatcher->nclients_++; + Channel* channel = new Channel(dispatcher, client_fd, dispatcher->nclients_); + dispatcher->channels_.emplace_back(channel); + CHECK_PTHREAD_CALL(pthread_create, (&thread, nullptr, &Run, channel), "Failed to create client handler thread"); + dispatcher->t_clients_[dispatcher->nclients_] = thread; + } + pthread_exit(nullptr); + return (void*)0; +} + +void* MsgDispatcher::Run(void* arg) { + Channel* channel = reinterpret_cast(arg); + MsgDispatcher* dispatcher = channel->dispatcher_; + if (gServer) { + SendCapabilities(channel); + } + dispatcher->InitComponentsIfReq(); + MessageHeader hdr; + //TODO: Make recv non-blocking + do { + int nread = (int) recv(channel->fd_, &hdr, sizeof(MessageHeader), 0); + if (nread != sizeof(MessageHeader)) { + LOGIT("Failed to recv header:%d", nread); + ERR("Socket recv header:"); + break; + } + int remaining = ntohl(hdr._size); + //LOGIT("Reading payload - %d", remaining); + // allocate a buffer to read the payload + char* payload = new char[remaining + 1]; + payload[remaining] = 0; + Message msg = {hdr, payload}; + while (nread > 0 && remaining != 0) { + nread = (int) recv(channel->fd_, payload, (remaining > CHUNK_SIZE) ? CHUNK_SIZE : remaining, 0); + payload += nread; + remaining -= nread; + } + + if (nread < 0) { + LOGIT("Failed to recv... will reconnect...\n"); + ERR("Socket recv:"); + break; + } + //LOGIT("Recvd payload - %s\n", payload); + switch(hdr._type) { + MSG_TYPE_CAPABILITIES: SaveCapabilities(channel); + if (!gServer) { + SendCapabilities(channel); + } + break; + default: if (ValidateMsg(channel, hdr)) { + //LOGIT("Enqueue message"); + Enqueue(channel, msg); + } else { + // Msg dropped + } + break; + } + + } while (!dispatcher->m_bStop); + + // 0 is reserved for the main channel + if (channel->id_ > 0) { + //Mark the end of this thread + dispatcher->t_clients_.erase(channel->id_); + //Close the channel + std::lock_guard lock(dispatcher->q_lock_); + for (auto it = dispatcher->channels_.begin(); it != dispatcher->channels_.end(); ++it) { + if (*it == channel) { + dispatcher->channels_.erase(it); + delete channel; + break; + } + } + } else if (!dispatcher->m_bStop) { + //We may want to reconnect + close(channel->fd_); + dispatcher->fd_ = socket(AF_VSOCK, SOCK_STREAM, 0); + sleep(1); + bool bConnected = true; + struct sockaddr_vm sock_addr; + memset(&sock_addr, 0, sizeof(struct sockaddr_vm)); + sock_addr.svm_family = AF_VSOCK; + sock_addr.svm_port = VSOCK_PORT; + sock_addr.svm_cid = VMADDR_CID_HOST; + bConnected = connect(dispatcher->fd_, (struct sockaddr *) &sock_addr, sizeof(struct sockaddr_vm)); + while(!bConnected) { + LOGIT("Failed to connect to server. Waiting to try again..."); + ERR("Socket connect:"); + sleep(1); + bConnected = connect(dispatcher->fd_, (struct sockaddr *) &sock_addr, sizeof(struct sockaddr_vm)); + } + channel->fd_ = dispatcher->fd_; + return Run(arg); + } + pthread_exit(nullptr); + return (void*)0; +} + +void* MsgDispatcher::WorkerStart(void* arg) { + MsgDispatcher* dispatcher = reinterpret_cast(arg); + while (!dispatcher->m_bStop) { + Message msg; + Channel* channel = nullptr; + bool bMsg = false; + { + std::lock_guard lock(dispatcher->q_lock_); + for (auto& chnl : dispatcher->channels_) { + if (chnl->msg_queue.size() > 0) { + msg = chnl->msg_queue.front(); + chnl->msg_queue.pop(); + channel = chnl; + bMsg = true; + } + } + } + if (bMsg) { + for (auto& component : dispatcher->GetComponentsForMsgType(msg.header._type)) { + //LOGIT("Worker Dispatch msg - %s\n", msg.payload); + component->ProcessMsg(msg, reinterpret_cast(channel)); + } + delete msg.payload; + msg.payload = nullptr; + channel = nullptr; + } else { + sleep(1); + } + } + pthread_exit(nullptr); + return (void*)0; +} + +void MsgDispatcher::Enqueue(Channel* channel, Message& msg) { + std::queue& mq = channel->msg_queue; + // No need of locks, only a single thread does the queing + while (mq.size() >= Q_SIZE) { + // Sleep for the queue to free up + sleep(1); + } + + mq.push(msg); +} + +void MsgDispatcher::Stop() { + m_bStop = true; + for (auto it = t_clients_.begin(); it != t_clients_.end(); ++it) { + CHECK_PTHREAD_CALL(pthread_join, (it->second, nullptr), "Failed to join on client thread"); + } + for (int i = 0; i < WORKER_POOL; ++i) { + CHECK_PTHREAD_CALL(pthread_join, (t_workers_[i], nullptr), "Failed to join on worker thread"); + } + CHECK_PTHREAD_CALL(pthread_join, (t_main_, nullptr), "Failed to join on the main thread"); + + //Clear all the channels + std::lock_guard lock(q_lock_); + for(auto& channel : channels_) { + delete channel; + } + channels_.clear(); + for(auto& comp : components_) { + delete comp; + } + components_.clear(); + +} + +bool MsgDispatcher::SendMsg(uint64_t hndl, MSG_TYPE msg_type, const char* buffer, int size) { + MessageHeader header; + Channel* channel = reinterpret_cast(hndl); + bool bValidChannel = false; + for (auto it = channels_.begin(); it != channels_.end(); ++it) { + if (*it == channel) { + bValidChannel = true; + break; + } + } + if (!bValidChannel) { + return false; + } + + header._ver = MSG_VERSION; + header._type = msg_type; + header._id = 0; + header._size = htonl(size); + int nsent = (int)send(channel->fd_, &header, sizeof(MessageHeader), 0); + if (nsent != sizeof(MessageHeader)) { + LOGIT("Failed to send header:%d", nsent); + ERR("Socket send hdr:"); + return false; + } + while (nsent > 0 && size > 0) { + nsent = (int)send(channel->fd_, buffer, (size > CHUNK_SIZE) ? CHUNK_SIZE : size, 0); + size -= nsent; + buffer += nsent; + } + if (nsent < 0) { + ERR("Socket send:"); + } + return (nsent >= 0); +} + +std::vector& MsgDispatcher::GetComponentsForMsgType(MSG_TYPE msg_type) { + auto search = msg_type_map.find(msg_type); + if (search != msg_type_map.end()) { + return search->second; + } else { + return msg_type_map.find(MSG_TYPE_INVALID)->second; + } +} + +void MsgDispatcher::RegisterComponent(std::vector& msg_list, Component* component) { + components_.emplace_back(component); + for (auto type : msg_list) { + if (type == MSG_TYPE_INVALID) { + continue; + } + auto search = msg_type_map.find(type); + if (search != msg_type_map.end()) { + search->second.emplace_back(component); + } else { + std::vector comp_vector; + comp_vector.emplace_back(component); + msg_type_map[type] = comp_vector; + } + } +} + +uint64_t MsgDispatcher::GetHandleForMsgType(MSG_TYPE msg_type) { + return reinterpret_cast(channels_.back()); +} + +void MsgDispatcher::InitComponentsIfReq() { + for (auto component : components_) { + component->setDispatcher(this); + component->Initialize(); + } +} + +} diff --git a/jni/VsockMsgDispatcher.h b/jni/VsockMsgDispatcher.h new file mode 100755 index 0000000..4b9071f --- /dev/null +++ b/jni/VsockMsgDispatcher.h @@ -0,0 +1,173 @@ +#ifndef VSOCK_MSG_DISPATCHER_H +#define VSOCK_MSG_DISPATCHER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NODEBUG 0 //set this to 0 to turn on logging + +#ifdef __ANDROID__ +#define LOG_TAG "vsock" +#include +#include + +#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) +#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) +#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) + +#ifdef NODEBUG +#define LOGIT(...) +#else +#define LOGIT(...) ALOGV(__VA_ARGS__) +#endif +#else +#define CHECK_PTHREAD_CALL(call, args, what) \ + do { \ + int rc = call args; \ + if (rc != 0) { \ + errno = rc; \ + std::cout << #call << " failed for " << (what); \ + exit(-1); \ + } \ + } while (false) +#ifdef NODEBUG +#define LOGIT(...) +#else +#define LOGIT(...) printf(__VA_ARGS__); +#endif +#endif + +#define ERR(msg) perror(msg); + +#define MSG_VERSION 1 +#define MAX_CLIENT_CONNECTIONS 4 +#define WORKER_POOL 4 +#define Q_SIZE 32 + +namespace vsock { + +enum MSG_TYPE : char { + MSG_TYPE_CAPABILITY, + MSG_TYPE_CLIPBOARD, + MSG_TYPE_APPSTATUS, + MSG_TYPE_INVALID, +}; + + +struct MessageHeader { + char _ver; + MSG_TYPE _type; + int _id; + int _size; +}; + +struct Message { + MessageHeader header; + char* payload; +}; + +class MsgDispatcher; +class Component { + public: + Component () { m_bInitDone = false; } + MsgDispatcher* GetDispatcher() { return dispatcher_; } + + virtual ~Component() {} + virtual void ProcessMsg(Message& msg, uint64_t hndl) {} + virtual void init() {} + private: + void setDispatcher(MsgDispatcher* dispatcher) { dispatcher_ = dispatcher; } + void Initialize() { + if (!m_bInitDone) { + init(); + } + m_bInitDone = true; + } + bool m_bInitDone; + MsgDispatcher* dispatcher_; + friend class MsgDispatcher; +}; + + +class MsgDispatcher { + public: + MsgDispatcher(); + bool Start(); + void Stop(); + void RegisterComponent(std::vector& msg_list, Component* component); + bool SendMsg(uint64_t hndl, MSG_TYPE msg_type, const char* buffer, int size); + uint64_t GetHandleForMsgType(MSG_TYPE msg_type); + static MsgDispatcher *instance; + static MsgDispatcher *getInstance() { + if (instance == nullptr) { + instance = new MsgDispatcher(); + } + return instance; + } + private: + + class Channel { + public: + Channel(MsgDispatcher* dispatcher, int fd, int id) { + dispatcher_ = dispatcher; + fd_ = fd; + id_ = id; + } + + ~Channel() { + close(fd_); + while(!msg_queue.empty()) { + auto msg = msg_queue.front(); + delete msg.payload; + msg.payload = nullptr; + msg_queue.pop(); + } + } + private: + MsgDispatcher* dispatcher_; + int fd_; + int id_; + std::queue msg_queue; + friend class MsgDispatcher; + }; + + static void Enqueue(Channel* channel, Message& msg); + + static void* Listen(void* arg); + static void* Run(void* arg); + static void* WorkerStart(void* arg); + + static void SaveCapabilities(Channel* ) {} + static void SendCapabilities(Channel* ) {} + + static bool ValidateMsg(Channel* channel, MessageHeader hdr) { return true; } + void InitComponentsIfReq(); + std::vector& GetComponentsForMsgType(MSG_TYPE msg_type); + + //Dont close, managed by a channel + int fd_; + bool m_bStop; + pthread_t t_main_; + pthread_t t_workers_[WORKER_POOL]; + std::map t_clients_; + int nclients_; + std::vector channels_; + std::vector components_; + std::map> msg_type_map; + + std::mutex client_lock_; + std::mutex q_lock_; +}; +} + + +#endif //VSOCK_MSG_DISPATCHER_H diff --git a/src/com/intel/clipboardagent/AppstatusComponent.java b/src/com/intel/clipboardagent/AppstatusComponent.java new file mode 100755 index 0000000..324e248 --- /dev/null +++ b/src/com/intel/clipboardagent/AppstatusComponent.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2021 Intel Corporation. + * + * 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.intel.clipboardagent; + +import android.app.Service; +import android.content.Intent; +import android.util.Log; +import java.util.HashMap; +import java.util.List; +import com.intel.clipboardagent.DispatchHelper; +import android.content.Context; +import android.app.ActivityManager; +import android.app.ActivityManager.RecentTaskInfo; +import android.content.pm.PackageManager; + +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED; +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE; + +public class AppstatusComponent { + private static final String TAG = "AppstatusComponent"; + private static AppstatusComponent single_instance = null; + private DispatchHelper dH; + private ActivityManager mActivityManager; + private HashMap uidPrevImpMap = new HashMap(); + private static final int FOREGROUND_IMPORTANCE_CUTOFF = ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; + + private AppstatusComponent(){ + } + + public static AppstatusComponent getInstance() { + if (single_instance == null) { + single_instance = new AppstatusComponent(); + } + return single_instance; + } + + public void init() { + dH = DispatchHelper.getInstance(); + mActivityManager = (ActivityManager) dH.mContext.getSystemService(Context.ACTIVITY_SERVICE); + mActivityManager.addOnUidImportanceListener(mOnUidImportanceListener, FOREGROUND_IMPORTANCE_CUTOFF); + + } + + public void stop() { + if (mActivityManager != null) { + mActivityManager.removeOnUidImportanceListener(mOnUidImportanceListener); + } + } + + private String getPackageName(int uid) { + String packageName = ""; + String[] packages = dH.mContext.getPackageManager().getPackagesForUid(uid); + if (packages == null) { + // Log.d(TAG, "No package is associated with that uid, do nothing"); + } else if (packages.length == 1) { + packageName = packages[0]; + } else { + //Log.d(TAG, "Multiple packages associated with the uid, should see what to do"); + } + return packageName; + } + + private boolean isHomeForeground(){ + try { + int homeId = dH.mContext.getPackageManager().getPackageUid("com.android.launcher3", 0); + if (mActivityManager.getUidImportance(homeId) == IMPORTANCE_FOREGROUND) + return true; + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return false; + } + + private boolean killLG(String appName) { + List recentTasks = mActivityManager.getRecentTasks(10, 0); + boolean kill = true; + for (ActivityManager.RecentTaskInfo taskInfo : recentTasks) { + //if ((taskInfo.baseActivity != null) && taskInfo.isRunning && taskInfo.isVisible() && appName.equals(taskInfo.baseActivity.getPackageName())) { + if ((taskInfo.baseActivity != null) && taskInfo.isRunning && appName.equals(taskInfo.baseActivity.getPackageName())) { + kill = false; + break; + } + } + return kill; + } + + private final ActivityManager.OnUidImportanceListener mOnUidImportanceListener = + new ActivityManager.OnUidImportanceListener() { + @Override + public void onUidImportance(final int uid, final int importance){ + String appName = getPackageName(uid); + if (appName.isEmpty()) { + //Log.d(TAG, "No app associated with uid, so return"); + return; + } + if (uidPrevImpMap.containsKey(uid)) { + int prevImp = uidPrevImpMap.get(uid); + if (prevImp == IMPORTANCE_FOREGROUND) { + if (importance == IMPORTANCE_GONE) { + dH.sendMsg("AppstatusComponent", appName, 0); + } else if(importance >= IMPORTANCE_VISIBLE) { // && importance <= IMPORTANCE_CACHED) { + if (killLG(appName)) { + dH.sendMsg("AppstatusComponent", appName, 0); + } + } + } + if (importance == IMPORTANCE_GONE) { + Log.d(TAG, "App with uid " + uid + " killed, remove from the map"); + uidPrevImpMap.remove(uid); + } else { + uidPrevImpMap.put(uid, importance); + } + } else { + uidPrevImpMap.put(uid, importance); + } + } + }; + +} diff --git a/src/com/intel/clipboardagent/ClipboardAgent.java b/src/com/intel/clipboardagent/ClipboardAgent.java new file mode 100755 index 0000000..32fbb6e --- /dev/null +++ b/src/com/intel/clipboardagent/ClipboardAgent.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2021 Intel Corporation. + * + * 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.intel.clipboardagent; + +import android.app.Application; +import android.content.Intent; +import android.util.Log; +import com.intel.clipboardagent.GuestVsockCommService; +import com.intel.clipboardagent.ClipboardService; + +public class ClipboardAgent extends Application { + private static final String TAG = "ClipboardAgent"; + private static final String SERVICE_NAME = "ClipboardAgent"; + + + public void onCreate() { + super.onCreate(); + + startService(new Intent(this, ClipboardService.class)); + startService(new Intent(this, GuestVsockCommService.class)); + } + + public void onTerminate() { + super.onTerminate(); + } + +} diff --git a/src/com/intel/clipboardagent/ClipboardComponent.java b/src/com/intel/clipboardagent/ClipboardComponent.java new file mode 100755 index 0000000..dc7a468 --- /dev/null +++ b/src/com/intel/clipboardagent/ClipboardComponent.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2021 Intel Corporation. + * + * 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.intel.clipboardagent; + +import android.app.Service; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Intent; +import android.util.Log; +import com.intel.clipboardagent.DispatchHelper; +import android.content.Context; +import static android.content.Context.CLIPBOARD_SERVICE; + +public class ClipboardComponent { + private static final String TAG = "ClipboardComponent"; + private static final String CLIPBOARD_SERVICE_LABEL = "IntelClipboardService"; + private static ClipboardComponent single_instance = null; + private ClipboardManager mClipboardManager; + private DispatchHelper dH; + private long mChannelHandle = 0; + + private ClipboardComponent(){ + } + + public static ClipboardComponent getInstance() { + if (single_instance == null) { + single_instance = new ClipboardComponent(); + } + return single_instance; + } + + public void init() { + dH = DispatchHelper.getInstance(); + mClipboardManager = + (ClipboardManager) dH.mContext.getSystemService(CLIPBOARD_SERVICE); + mClipboardManager.addPrimaryClipChangedListener( + mOnPrimaryClipChangedListener); + } + + public void stop() { + if (mClipboardManager != null) { + mClipboardManager.removePrimaryClipChangedListener( + mOnPrimaryClipChangedListener); + } + } + + + private final ClipboardManager.OnPrimaryClipChangedListener mOnPrimaryClipChangedListener = + new ClipboardManager.OnPrimaryClipChangedListener() { + @Override + public void onPrimaryClipChanged() { + ClipData mclipData = mClipboardManager.getPrimaryClip(); + // This clip originated from the same service, suppress it. + if (CLIPBOARD_SERVICE_LABEL.equals(mclipData.getDescription().getLabel())) { + return; + } + CharSequence mText = mclipData.getItemAt(0).getText(); + dH.sendMsg("ClipboardComponent", mText.toString(), mChannelHandle); + } + }; + + public void processMsg(String content, long handle) { + ClipData mclipData = mClipboardManager.getPrimaryClip(); + mclipData = ClipData.newPlainText(CLIPBOARD_SERVICE_LABEL, content); + mClipboardManager.setPrimaryClip(mclipData); + mChannelHandle = handle; + } + +} diff --git a/src/com/intel/clipboardagent/ClipboardService.java b/src/com/intel/clipboardagent/ClipboardService.java new file mode 100755 index 0000000..23d39de --- /dev/null +++ b/src/com/intel/clipboardagent/ClipboardService.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2021 Intel Corporation. + * + * 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.intel.clipboardagent; + +import android.app.Service; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Intent; +import android.os.IBinder; +import android.util.Log; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import com.intel.clipboardagent.VsockClientImpl; +import com.intel.clipboardagent.VsockAddress; + +public class ClipboardService extends Service{ + private static final String TAG = "ClipboardAgent"; + private static final String CLIPBOARD_SERVICE_LABEL = "IntelClipboardService"; + private static final int DEFAULT_DATA_LENGTH = 4096; + private static final int MAX_DATA_LENGTH = 512*1024; + private ExecutorService mThreadPool = Executors.newSingleThreadExecutor(); + private ClipboardManager mClipboardManager; + private Vsock mVsock; + private VsockAddress mVsockAddress; + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + + mClipboardManager = + (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + mClipboardManager.addPrimaryClipChangedListener( + mOnPrimaryClipChangedListener); + // TODO: remove hard code on vsock port + mVsockAddress = new VsockAddress(VsockAddress.VMADDR_CID_HOST, 77777); + mVsock = new Vsock(mVsockAddress); + + mThreadPool.execute(new HandleHostVsockContent()); + } + + @Override + public void onDestroy() { + super.onDestroy(); + + if (mClipboardManager != null) { + mClipboardManager.removePrimaryClipChangedListener( + mOnPrimaryClipChangedListener); + } + try { + mVsock.close(); + } catch (IOException exception) { + Log.e(TAG, "Error on closing Vsock: " + exception.getMessage()); + } + + } + + private final ClipboardManager.OnPrimaryClipChangedListener mOnPrimaryClipChangedListener = + new ClipboardManager.OnPrimaryClipChangedListener() { + @Override + public void onPrimaryClipChanged() { + ClipData mclipData = mClipboardManager.getPrimaryClip(); + // This clip originated from the same service, suppress it. + if (CLIPBOARD_SERVICE_LABEL.equals(mclipData.getDescription().getLabel())) { + return; + } + CharSequence mText = mclipData.getItemAt(0).getText(); + byte[] mBytes = mText.toString().getBytes(StandardCharsets.UTF_8); + try{ + mVsock.getOutputStream().writeInt(mBytes.length); + int writeLength = (mBytes.length < MAX_DATA_LENGTH) ? mBytes.length : MAX_DATA_LENGTH; + // If Clipboard is cleared, nothing to send + if (writeLength > 0) { + mVsock.getOutputStream().write(mBytes, 0, writeLength); + } + } catch (IOException exception) { + Log.e(TAG, "Error on handling clipboard data: " + exception.getMessage()); + } + } + }; + + // Class HandleHostVsockContent should receive vsock data from remote host + private class HandleHostVsockContent implements Runnable { + private static final String TAG = "ClipboardAgent"; + + private HandleHostVsockContent() { + } + + @Override + public void run() { + // TODO: Data length is hard code here for 4096. + byte[] buffer = new byte[DEFAULT_DATA_LENGTH]; + try { + mVsock.connect(); + } catch (IOException exception) { + Log.e(TAG, "Failed to connect: " + exception.getMessage()); + } + while (true) { + boolean bReconnect = false; + byte[] bytes = buffer; + String content = ""; + try { + int length = mVsock.getInputStream().readInt(); + if (length < 0 || length > MAX_DATA_LENGTH) { + Log.wtf(TAG, "Unexpected data size :"+length, new Exception("Unexpected data size")); + continue; + } + + if (length > DEFAULT_DATA_LENGTH) { + bytes = new byte[length]; + } + + if (length > 0) { + mVsock.getInputStream().read(bytes, 0, length); + content = new String(bytes, 0, length, StandardCharsets.UTF_8); + } + ClipData mclipData = mClipboardManager.getPrimaryClip(); + mclipData = ClipData.newPlainText(CLIPBOARD_SERVICE_LABEL, content); + mClipboardManager.setPrimaryClip(mclipData); + + } catch (IOException exception) { + if (exception.toString().contains("Connection reset") || + exception.toString().contains("Connection is closed by peer")) { + Log.e(TAG, "Connection reset, attempting to reconnect"); + bReconnect = true; + } else { + Log.e(TAG, "Error on handling host Vsock: " + exception.getMessage()); + } + } + if (bReconnect) { + try { + mVsock.close(); + } catch (IOException e) { + Log.e(TAG, "Failed to close vsock: " + e.getMessage()); + } + try { + mVsock = new Vsock(mVsockAddress); + mVsock.connect(); + Thread.sleep(1000); + } catch (IOException e) { + Log.e(TAG, "Error reconnecting... " + e.getMessage()); + } catch (InterruptedException x) {} + } + } + } + } +} diff --git a/src/com/intel/clipboardagent/DispatchHelper.java b/src/com/intel/clipboardagent/DispatchHelper.java new file mode 100755 index 0000000..d2c82ac --- /dev/null +++ b/src/com/intel/clipboardagent/DispatchHelper.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2021 Intel Corporation. + * + * 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.intel.clipboardagent; +import android.content.Context; + +public class DispatchHelper { + static { + System.loadLibrary("VsockMsgDispatch"); + } + private static final String TAG = "DispatchHelper"; + private static DispatchHelper single_instance = null; + public Context mContext; + private DispatchHelper() { + } + public static DispatchHelper getInstance() { + if (single_instance == null) { + single_instance = new DispatchHelper(); + } + return single_instance; + } + + public native void registerComponent(String className); + public native void sendMsg(String className, String msg, long handle); + public native void start(); + public native void stop(); + +} diff --git a/src/com/intel/clipboardagent/GuestVsockCommService.java b/src/com/intel/clipboardagent/GuestVsockCommService.java new file mode 100755 index 0000000..f916e12 --- /dev/null +++ b/src/com/intel/clipboardagent/GuestVsockCommService.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2021 Intel Corporation. + * + * 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.intel.clipboardagent; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.util.Log; +import com.intel.clipboardagent.DispatchHelper; +import android.content.Context; + +public class GuestVsockCommService extends Service{ + private DispatchHelper dH; + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + dH = DispatchHelper.getInstance(); + dH.mContext = this.getApplicationContext(); + dH.registerComponent("ClipboardComponent"); + dH.registerComponent("AppstatusComponent"); + dH.start(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + dH.stop(); + } +} diff --git a/src/com/intel/clipboardagent/MainActivity.java b/src/com/intel/clipboardagent/MainActivity.java new file mode 100755 index 0000000..158be8d --- /dev/null +++ b/src/com/intel/clipboardagent/MainActivity.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2021 Intel Corporation. + * + * 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.intel.clipboardagent; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; + +public class MainActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + finish(); + } +} diff --git a/src/com/intel/clipboardagent/Vsock.java b/src/com/intel/clipboardagent/Vsock.java new file mode 100755 index 0000000..1a7455d --- /dev/null +++ b/src/com/intel/clipboardagent/Vsock.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2021 Intel Corporation. + * + * 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.intel.clipboardagent; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.SocketException; + +public final class Vsock extends VsockBaseVSock implements Closeable { + private boolean connected = false; + private VsockOutputStream outputStream; + private VsockInputStream inputStream; + private VsockAddress mAddress; + + public Vsock() { + } + + public Vsock(VsockAddress address) { + mAddress = address; + } + + public void connect() throws SocketException { + if (isClosed()) { + throw new SocketException("Socket closed"); + } + if (connected) { + throw new SocketException("Socket already connected"); + } + getImplementation().connect(mAddress); + connected = true; + } + + public synchronized VsockOutputStream getOutputStream() throws IOException { + if (isClosed()) { + throw new SocketException("VSock is closed"); + } + if (outputStream == null) { + outputStream = new VsockOutputStream(getImplementation()); + } + return outputStream; + } + + public synchronized VsockInputStream getInputStream() throws IOException { + if (isClosed()) { + throw new SocketException("VSock is closed"); + } + if (inputStream == null) { + inputStream = new VsockInputStream(getImplementation()); + } + return inputStream; + } + + void postAccept() { + created = true; + connected = true; + } +} diff --git a/src/com/intel/clipboardagent/VsockAddress.java b/src/com/intel/clipboardagent/VsockAddress.java new file mode 100755 index 0000000..99e831a --- /dev/null +++ b/src/com/intel/clipboardagent/VsockAddress.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2021 Intel Corporation. + * + * 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.intel.clipboardagent; + +import java.net.SocketAddress; +import java.util.Objects; + +public final class VsockAddress extends SocketAddress { + public static final int VMADDR_CID_ANY = -1; + public static final int VMADDR_CID_HYPERVISOR = 0; + public static final int VMADDR_CID_RESERVED = 1; + public static final int VMADDR_CID_HOST = 2; + public static final int VMADDR_CID_PARENT = 3; + + public static final int VMADDR_PORT_ANY = -1; + final int cid; + final int port; + + public VsockAddress(int cid, int port) { + this.cid = cid; + this.port = port; + } + + public int getCid() { + return cid; + } + + public int getPort() { + return port; + } +} diff --git a/src/com/intel/clipboardagent/VsockBaseVSock.java b/src/com/intel/clipboardagent/VsockBaseVSock.java new file mode 100755 index 0000000..bde6fc9 --- /dev/null +++ b/src/com/intel/clipboardagent/VsockBaseVSock.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2021 Intel Corporation. + * + * 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.intel.clipboardagent; + +import java.io.Closeable; +import java.io.IOException; +import java.net.SocketException; + +abstract class VsockBaseVSock implements Closeable { + protected final Object closeLock = new Object(); + protected boolean closed = false; + protected boolean created = false; + + private VsockClientImpl implementation; + + private void createImplementation() throws SocketException { + implementation = new VsockClientImpl(); + implementation.create(); + created = true; + } + + protected VsockClientImpl getImplementation() throws SocketException { + if (!created) { + createImplementation(); + } + return implementation; + } + + protected VsockClientImpl setImplementation() throws SocketException { + if(implementation == null) { + implementation = new VsockClientImpl(); + } + return implementation; + } + + @Override + public synchronized void close() throws IOException { + synchronized (closeLock) { + if (isClosed()) + return; + if (created) + getImplementation().close(); + closed = true; + } + } + + protected boolean isClosed() { + return closed; + } +} diff --git a/src/com/intel/clipboardagent/VsockClientImpl.java b/src/com/intel/clipboardagent/VsockClientImpl.java new file mode 100755 index 0000000..57caf8c --- /dev/null +++ b/src/com/intel/clipboardagent/VsockClientImpl.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2021 Intel Corporation. + * + * 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.intel.clipboardagent; + +import java.net.*; +import java.io.*; + +public class VsockClientImpl { + static { + System.loadLibrary("VsocketClientImpl"); + } + + int fd = -1; + + void create() throws SocketException { + socketCreate(); + } + + native void socketCreate() throws SocketException; + native void connect(VsockAddress address) throws SocketException; + native void close() throws IOException; + native void write(byte[] b, int off, int len) throws IOException; + native int read(byte[] b, int off, int len) throws IOException; + + native void writeInt(int value) throws IOException; + native int readInt() throws IOException; +} diff --git a/src/com/intel/clipboardagent/VsockInputStream.java b/src/com/intel/clipboardagent/VsockInputStream.java new file mode 100755 index 0000000..0ab8fcf --- /dev/null +++ b/src/com/intel/clipboardagent/VsockInputStream.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2021 Intel Corporation. + * + * 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.intel.clipboardagent; + +import java.io.IOException; +import java.io.InputStream; + +public final class VsockInputStream extends InputStream { + private final VsockClientImpl vSock; + private byte[] temp; + + public VsockInputStream (VsockClientImpl vSock) { + this.vSock = vSock; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return vSock.read(b, off, len); + } + + @Override + public int read() throws IOException { + temp = new byte[1]; + int n = read(temp, 0, 1); + if (n <= 0) { + return -1; + } + return temp[0]; + } + + public int readInt() throws IOException { + return vSock.readInt(); + } + + @Override + public void close() throws IOException { + vSock.close(); + super.close(); + } +} diff --git a/src/com/intel/clipboardagent/VsockOutputStream.java b/src/com/intel/clipboardagent/VsockOutputStream.java new file mode 100755 index 0000000..6e2bdb6 --- /dev/null +++ b/src/com/intel/clipboardagent/VsockOutputStream.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2021 Intel Corporation. + * + * 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.intel.clipboardagent; + +import java.io.IOException; +import java.io.OutputStream; + +public final class VsockOutputStream extends OutputStream { + private final VsockClientImpl vSock; + private final byte[] temp = new byte[1]; + + VsockOutputStream(VsockClientImpl vSock) { + this.vSock = vSock; + } + + @Override + public void write(int b) throws IOException { + temp[0] = (byte) b; + this.write(temp, 0, 1); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + vSock.write(b, off, len); + } + + public void writeInt(int value) throws IOException { + vSock.writeInt(value); + } + + @Override + public void close() throws IOException { + vSock.close(); + super.close(); + } +}