diff --git a/cloudphone/src/main/aidl/com/huawei/cloudphone/ICASAidlListener.aidl b/cloudphone/src/main/aidl/com/huawei/cloudphone/ICASAidlListener.aidl index c3a9fee9fd9cd60f08b207ac4e63c48fd09ab62a..948d7c2316a2a804ca1e90ed68b79cf0d8eaba4b 100644 --- a/cloudphone/src/main/aidl/com/huawei/cloudphone/ICASAidlListener.aidl +++ b/cloudphone/src/main/aidl/com/huawei/cloudphone/ICASAidlListener.aidl @@ -28,4 +28,6 @@ interface ICASAidlListener { void onChannelDataRecv(in byte[] data); void onVirtualDevDataRecv(in byte[] data); + + void onImeMsgRecv(in byte[] data); } diff --git a/cloudphone/src/main/cpp/CasController.cpp b/cloudphone/src/main/cpp/CasController.cpp index 2b5a948d9ae579c52a4548931db18aca595a9fbe..198516aad03f5db4a05f8925fd683f08863a2c55 100644 --- a/cloudphone/src/main/cpp/CasController.cpp +++ b/cloudphone/src/main/cpp/CasController.cpp @@ -68,6 +68,7 @@ CasController::CasController() m_virtualDeviceStream = nullptr; m_controlStream = nullptr; m_channelStream = nullptr; + m_imeDataStream = nullptr; m_cmdController = nullptr; m_streamParseThread = nullptr; m_heartbeatThread = nullptr; @@ -89,6 +90,7 @@ CasController::~CasController() m_virtualDeviceStream = nullptr; m_controlStream = nullptr; m_channelStream = nullptr; + m_imeDataStream = nullptr; m_cmdController = nullptr; m_streamParseThread = nullptr; m_heartbeatThread = nullptr; @@ -405,6 +407,7 @@ bool CasController::CreateWorkers(ANativeWindow *nativeWindow, bool needVideoDec m_streamParser->SetServiceHandle(CasMsgType::Audio, m_audioPacketStream); m_streamParser->SetServiceHandle(CasMsgType::Channel, m_channelStream); m_streamParser->SetServiceHandle(CasMsgType::VirtualDevice, m_virtualDeviceStream); + m_streamParser->SetServiceHandle(CasMsgType::ImeData, m_imeDataStream); if (needVideoDecode) { m_videoDecodeThread = new (std::nothrow) CasVideoHDecodeThread(nativeWindow, m_frameType); @@ -606,6 +609,12 @@ bool CasController::InitDataStream() return false; } + m_imeDataStream = new (std::nothrow) CasDataPipe(); + if (m_imeDataStream == nullptr) { + ERR("Failed to new ime data packet stream."); + return false; + } + return true; } @@ -635,6 +644,10 @@ bool CasController::ClearDataStream() m_virtualDeviceStream->Clear(); } + if (m_imeDataStream != nullptr) { + m_imeDataStream->Clear(); + } + INFO("Succeed to clear data stream "); return true; } @@ -666,6 +679,11 @@ bool CasController::CloseDataStream() m_channelStream = nullptr; } + if (m_imeDataStream != nullptr) { + delete m_imeDataStream; + m_imeDataStream = nullptr; + } + INFO("Succeed to close data stream "); return true; } @@ -768,6 +786,11 @@ int CasController::JniRecvData(CasMsgType type, uint8_t *data, int length) pPkt = m_virtualDeviceStream->GetNextPkt(); } break; + case CasMsgType::ImeData: + if (m_imeDataStream != nullptr) { + pPkt = m_imeDataStream->GetNextPkt(); + } + break; default: ERR("Invalid type %d, length %d.", type, length); return 0; diff --git a/cloudphone/src/main/cpp/CasController.h b/cloudphone/src/main/cpp/CasController.h index 02b111d3c22a35edff790c46435d07e8c6e664be..835a8837519408dcaaa8ea91bd22b2e4cb44946e 100644 --- a/cloudphone/src/main/cpp/CasController.h +++ b/cloudphone/src/main/cpp/CasController.h @@ -126,6 +126,7 @@ private: CasDataPipe *m_controlStream = nullptr; CasDataPipe *m_channelStream = nullptr; CasDataPipe *m_virtualDeviceStream = nullptr; + CasDataPipe *m_imeDataStream = nullptr; CasCmdController *m_cmdController = nullptr; CasHeartbeatController *m_heartbeatController = nullptr; diff --git a/cloudphone/src/main/cpp/CasJniBridge.cpp b/cloudphone/src/main/cpp/CasJniBridge.cpp index 8c4afd104b5af1845488d2bd633147963b79bc22..94faf4440edfaf1736b851db8546d26776d6fd70 100644 --- a/cloudphone/src/main/cpp/CasJniBridge.cpp +++ b/cloudphone/src/main/cpp/CasJniBridge.cpp @@ -174,7 +174,6 @@ extern "C" JNIEXPORT jint JNICALL JNI(sendData)(JNIEnv *env, jclass clazz, jbyte int ret = gJniApiCtrl->JniSendData((CasMsgType)type, data, length); env->ReleaseByteArrayElements(jData, (jbyte *)data, JNI_COMMIT); return ret; - return 0; } extern "C" JNIEXPORT jboolean JNICALL JNI(sendTouchEvent)(JNIEnv *env, jclass, jint id, jint action, jint x, jint y, diff --git a/cloudphone/src/main/cpp/cas_common/CasMsg.h b/cloudphone/src/main/cpp/cas_common/CasMsg.h index b4a5d9a2f55f93a3fb16e8ecb703dbd9944cdfab..fcea1f5be0777b34083a66d444822351f2e52fe8 100644 --- a/cloudphone/src/main/cpp/cas_common/CasMsg.h +++ b/cloudphone/src/main/cpp/cas_common/CasMsg.h @@ -31,6 +31,7 @@ enum CasMsgType : uint8_t { HeartBeat = 8, Orientation = 9, Recorder = 11, + ImeData = 14, KeyEventInput = 15, MotionEventInput = 16, VirtualDevice = 20, @@ -53,6 +54,7 @@ enum CasMsgType : uint8_t { #define CAS_MSG_CHECKSUM_VIDEO GET_CAS_CHECKSUM(CasMsgType::Video) #define CAS_MSG_CHECKSUM_VERIFY GET_CAS_CHECKSUM(CasMsgType::Verify) #define CAS_MSG_CHECKSUM_HEARTBEAT GET_CAS_CHECKSUM(CasMsgType::HeartBeat) +#define CAS_MSG_CHECKSUM_IMEDATA GET_CAS_CHECKSUM(CasMsgType::ImeData) #define CAS_MSG_CHECKSUM_KEYEVENTINPUT GET_CAS_CHECKSUM(CasMsgType::KeyEventInput) #define CAS_MSG_CHECKSUM_MOTIONEVENTINPUT GET_CAS_CHECKSUM(CasMsgType::MotionEventInput) #define CAS_MSG_CHECKSUM_CHANNEL GET_CAS_CHECKSUM(CasMsgType::Channel) diff --git a/cloudphone/src/main/cpp/cas_stream/CasStreamBuildSender.cpp b/cloudphone/src/main/cpp/cas_stream/CasStreamBuildSender.cpp index 12576549c179d3c4516fd0ff9a2e54938aa51692..92bbf8a97847c424df782db2945b48f1be80f778 100644 --- a/cloudphone/src/main/cpp/cas_stream/CasStreamBuildSender.cpp +++ b/cloudphone/src/main/cpp/cas_stream/CasStreamBuildSender.cpp @@ -63,6 +63,9 @@ int CasStreamBuildSender::SendDataToServer(CasMsgType type, const void *buf, siz case (HeartBeat): msgHead.checksum = CAS_MSG_CHECKSUM_HEARTBEAT; break; + case (ImeData): + msgHead.checksum = CAS_MSG_CHECKSUM_IMEDATA; + break; case (KeyEventInput): msgHead.checksum = CAS_MSG_CHECKSUM_KEYEVENTINPUT; break; diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/apiimpl/CloudPhoneImeMgr.java b/cloudphone/src/main/java/com/huawei/cloudphone/apiimpl/CloudPhoneImeMgr.java new file mode 100644 index 0000000000000000000000000000000000000000..16a07a224befd4cc1006f0a73b4aa54eb17c16f6 --- /dev/null +++ b/cloudphone/src/main/java/com/huawei/cloudphone/apiimpl/CloudPhoneImeMgr.java @@ -0,0 +1,193 @@ +package com.huawei.cloudphone.apiimpl; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Color; +import android.os.Handler; +import android.os.Looper; +import android.text.Editable; +import android.text.InputFilter; +import android.text.TextWatcher; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.PopupWindow; +import android.widget.RelativeLayout; + +import com.huawei.cloudphone.common.CASLog; + +public class CloudPhoneImeMgr { + private static final String TAG = "CloudPhoneImeMgr"; + + private static final int TEXT_SIZE = 14; + private static final int TEXT_MAX_LEN = 40; + private static final int TEXT_MAX_LINES = 1; + private static final int BOX_INPUT_HEIGHT = 50; + private static final int BUTTON_WIDTH = 65; + private static final int BUTTON_HEIGHT = 40; + private static final int IME_MSG_SHOW = 0; + private static final int IME_MSG_HIDE = 1; + private static final int IME_MSG_TEXT = 2; + private static final int IME_MSG_HEADER_LEN = 3; + + private ViewGroup mRootViewGroup; + private ViewGroup mImeViewGroup; + private Context mContext; + private EditText mImeEditText; + private PopupWindow mPopWindow; + private CloudPhoneTextWatchListener mTextWatchListener; + + @SuppressLint("Range") + public CloudPhoneImeMgr(Context context, ViewGroup viewGroup) { + mContext = context; + mRootViewGroup = viewGroup; + + mImeViewGroup = new RelativeLayout(context); + RelativeLayout.LayoutParams imeViewGroupLp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, dip2px(BOX_INPUT_HEIGHT)); + imeViewGroupLp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); + mImeViewGroup.setLayoutParams(imeViewGroupLp); + mImeViewGroup.setBackgroundColor(Color.WHITE); + mImeViewGroup.setAlpha(50); + + mImeEditText = new EditText(context); + RelativeLayout.LayoutParams imeEditTextLp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT); + mImeEditText.setLayoutParams(imeEditTextLp); + mImeEditText.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); + mImeEditText.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); + mImeEditText.setHint("请输入"); + mImeEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX, dip2px(TEXT_SIZE)); + mImeEditText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(TEXT_MAX_LEN)}); + mImeEditText.setMaxLines(TEXT_MAX_LINES); + mImeViewGroup.addView(mImeEditText); + + Button button = new Button(context); + RelativeLayout.LayoutParams marginLp = new RelativeLayout.LayoutParams(dip2px(BUTTON_WIDTH), dip2px(BUTTON_HEIGHT)); + marginLp.setMargins(0, dip2px(5), dip2px(10), 0); + RelativeLayout.LayoutParams buttonLp = new RelativeLayout.LayoutParams(marginLp); + buttonLp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); + button.setLayoutParams(buttonLp); + button.setText("下一步"); + button.setTextSize(TypedValue.COMPLEX_UNIT_PX, dip2px(12)); + mImeViewGroup.addView(button); + + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + hide(); + } + }); + + mImeEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + byte[] data = charSequence.toString().getBytes(); + byte[] msg = new byte[IME_MSG_HEADER_LEN + data.length]; + msg[0] = IME_MSG_TEXT; + msg[1] = (byte) ((data.length >> 8) & 0xFF); + msg[2] = (byte) (data.length & 0XFF); + + System.arraycopy(data, 0, msg, 3, data.length); + if (mTextWatchListener != null) { + mTextWatchListener.onTextChange(msg); + } + } + + @Override + public void afterTextChanged(Editable editable) { + } + }); + + mPopWindow = new PopupWindow(mImeViewGroup, WindowManager.LayoutParams.MATCH_PARENT, dip2px(BOX_INPUT_HEIGHT), true); + mPopWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); + mPopWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + mPopWindow.setOutsideTouchable(true); + mPopWindow.setTouchable(true); + } + + public int show(String text) { + if (mPopWindow.isShowing()) { + return 0; + } + if (text != null && text.length() > 0) { + mImeEditText.getText().replace(0, mImeEditText.length(), text); + } else { + mImeEditText.getText().clear(); + } + mPopWindow.setFocusable(true); + mImeEditText.requestFocus(); + mPopWindow.showAtLocation(mRootViewGroup, Gravity.BOTTOM, 0, 0); + + new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { + @Override + public void run() { + InputMethodManager inputMethodManager = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.showSoftInput(mImeEditText, 0); + } + }, 100); + return 0; + } + + public synchronized int hide() { + if (!mPopWindow.isShowing()) { + return 0; + } + mPopWindow.dismiss(); + InputMethodManager inputMethodManager = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.hideSoftInputFromWindow(mImeEditText.getWindowToken(), 0); + return 0; + } + + public void processImeMsg(byte[] data) { + if (data == null) { + return; + } + final byte type = data[0]; + int msgLen = (data[1] << 8) | (data[2] & 0xFF); + String text = null; + if (msgLen > 0) { + text = new String(data,3, msgLen); + } + final String finalText = text; + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + switch (type) { + case IME_MSG_SHOW: + showKeyBoard(true, finalText); + break; + case IME_MSG_HIDE: + showKeyBoard(false, null); + default: + break; + } + } + }); + } + + public void setTextWatchListener(CloudPhoneTextWatchListener listener) { + mTextWatchListener = listener; + } + + private void showKeyBoard(boolean isShow, String text) { + if (isShow) { + this.show(text); + } else { + this.hide(); + } + } + + private int dip2px(float dipValue) { + final float scale = mContext.getResources().getDisplayMetrics().density; + return (int) (dipValue * scale + 0.5f); + } +} \ No newline at end of file diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/apiimpl/CloudPhoneImpl.java b/cloudphone/src/main/java/com/huawei/cloudphone/apiimpl/CloudPhoneImpl.java index 3bec08737e92e67189c08ca396dceee313e72f9d..ffee3d77f21273ee0409f1f235728a2890947c5b 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/apiimpl/CloudPhoneImpl.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/apiimpl/CloudPhoneImpl.java @@ -90,6 +90,7 @@ public class CloudPhoneImpl implements ICloudPhone { private static final int CMD_RECONNECT = 16; private static final int CMD_SET_MEDIA_CONFIG = 17; private static final byte CUSTOM_DATA = 18; + private static final byte IME_DATA =14; private final Object mCloudPhoneLock = new Object(); private CASClient mCASClient = null; @@ -132,6 +133,8 @@ public class CloudPhoneImpl implements ICloudPhone { private long initTime = 0; + private CloudPhoneImeMgr mImeMgr; + public CloudPhoneImpl() { mCurrentState = STATE_DEINIT; } @@ -488,6 +491,9 @@ public class CloudPhoneImpl implements ICloudPhone { FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); mSurfaceView.setLayoutParams(layoutParams); + + mImeMgr = new CloudPhoneImeMgr(mContext, views); + mImeMgr.setTextWatchListener(new TextWatchListener()); } }); } @@ -788,6 +794,12 @@ public class CloudPhoneImpl implements ICloudPhone { mVirtualDevDataListener.onRecvVirtualDevData(data, data.length); } } + + public void onImeMsgRecv(byte[] data) throws RemoteException { + if (mImeMgr != null) { + mImeMgr.processImeMsg(data); + } + } } private class BackgroundTimerTask extends TimerTask { @@ -861,4 +873,11 @@ public class CloudPhoneImpl implements ICloudPhone { } } + class TextWatchListener implements CloudPhoneTextWatchListener { + @Override + public void onTextChange(byte[] data) { + mCASClient.sendDataToIme(data); + } + } + } \ No newline at end of file diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/apiimpl/CloudPhoneTextWatchListener.java b/cloudphone/src/main/java/com/huawei/cloudphone/apiimpl/CloudPhoneTextWatchListener.java new file mode 100644 index 0000000000000000000000000000000000000000..3fb505f10ca3b2fa012fc08f5da042230a6a6da9 --- /dev/null +++ b/cloudphone/src/main/java/com/huawei/cloudphone/apiimpl/CloudPhoneTextWatchListener.java @@ -0,0 +1,5 @@ +package com.huawei.cloudphone.apiimpl; + +public interface CloudPhoneTextWatchListener { + void onTextChange(byte[] data); +} diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/JNIWrapper.java b/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/JNIWrapper.java index ccf80ac4da73ba1dc62e37e38f8d975328651e78..0ea60bdabcb5872f44cddc716d3088eb9337bc9c 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/JNIWrapper.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/JNIWrapper.java @@ -46,6 +46,7 @@ public class JNIWrapper { public static final byte RECORDER = 11; + public static final byte IMEDATA = 14; public static final byte RECONNECT = 15; public static final byte RECONNECT_HEAD = 16; public static final byte NOTIFY = 17; diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/service/CASClient.java b/cloudphone/src/main/java/com/huawei/cloudphone/service/CASClient.java index 1bef8cda875dc655246e317844453d6f74e214fb..512428c2701e2424aded5b715f0908c09a91ea65 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/service/CASClient.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/service/CASClient.java @@ -17,6 +17,7 @@ package com.huawei.cloudphone.service; import static com.huawei.cloudphone.jniwrapper.JNIWrapper.CHANNEL; +import static com.huawei.cloudphone.jniwrapper.JNIWrapper.IMEDATA; import android.os.IBinder; import android.os.RemoteException; @@ -339,4 +340,16 @@ public class CASClient { CASLog.e(TAG, "failed to send data to virtual device."); } } + + public void sendDataToIme(byte[] data) { + if (mCasInterface == null) { + return; + } + try { + mCasInterface.sendData(IMEDATA, data); + } catch (RemoteException e) { + CASLog.e(TAG, "failed to send data to virtual device."); + } + } + } diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/service/CasProcessor.java b/cloudphone/src/main/java/com/huawei/cloudphone/service/CasProcessor.java index a189ad4b15396a8c10dbc037bb37746b23c8f17d..1d9de02af8ea451a626cb325837b1df27d41ef36 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/service/CasProcessor.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/service/CasProcessor.java @@ -88,6 +88,7 @@ public class CasProcessor extends ICASAidlInterface.Stub { private ICASAidlListener mListener = null; private NewRotationDirectionPacket mNewRotationDirPkt; private NewVirtualDevDataPacket mVirtualDevDataPkt; + private NewImeDataPacket mImeDataPkt; @Override public void init() throws RemoteException { @@ -216,11 +217,13 @@ public class CasProcessor extends ICASAidlInterface.Stub { mNewRotationDirPkt = new NewRotationDirectionPacket(); mChannelDataCallback = new NewChannelDataPacket(); mVirtualDevDataPkt = new NewVirtualDevDataPacket(); + mImeDataPkt = new NewImeDataPacket(); mUpstreamReceiveDispatcher.addNewPacketCallback((byte) JNIWrapper.ORIENTATION, mNewRotationDirPkt); mUpstreamReceiveDispatcher.addNewPacketCallback((byte) JNIWrapper.AUDIO, mAudioTrackerCallback); mUpstreamReceiveDispatcher.addNewPacketCallback((byte) JNIWrapper.CHANNEL, mChannelDataCallback); mUpstreamReceiveDispatcher.addNewPacketCallback((byte) JNIWrapper.VIRTUAL_DEVICE_DATA, mVirtualDevDataPkt); + mUpstreamReceiveDispatcher.addNewPacketCallback((byte) JNIWrapper.IMEDATA, mImeDataPkt); mUpstreamReceiveDispatcher.start(); return true; } @@ -232,6 +235,7 @@ public class CasProcessor extends ICASAidlInterface.Stub { mUpstreamReceiveDispatcher.deleteNewPacketCallback((byte) JNIWrapper.ORIENTATION); mUpstreamReceiveDispatcher.deleteNewPacketCallback((byte) JNIWrapper.CHANNEL); mUpstreamReceiveDispatcher.deleteNewPacketCallback((byte) JNIWrapper.VIRTUAL_DEVICE_DATA); + mUpstreamReceiveDispatcher.deleteNewPacketCallback((byte) JNIWrapper.IMEDATA); mUpstreamReceiveDispatcher.stopBlocked(); mAudioTrackerCallback.closeAudioTrack(); mUpstreamReceiveDispatcher = null; @@ -353,4 +357,17 @@ public class CasProcessor extends ICASAidlInterface.Stub { } } } + + private class NewImeDataPacket implements NewPacketCallback { + @Override + public void onNewPacket(byte[] data) { + if (mListener != null) { + try { + mListener.onImeMsgRecv(data); + } catch (RemoteException e) { + CASLog.e(TAG, "call onImeMsgRecv failed."); + } + } + } + } }