Android:系统自定义鼠标样式切换
一、APP通过View修改鼠标样式
app view上修改鼠标样式比较简单,通过 hover event 获取鼠标坐标并使用如下方法修改为自定义图片:
getWindow().getDecorView().setPointerIcon(PointerIcon.load(getResources(), R.drawable.pointer_spot_touch_icon));
imageView = (ImageView) findViewById(R.id.image_view); imageView.setOnHoverListener(new View.OnHoverListener() { @SuppressLint({"SetTextI18n", "ResourceType"}) @Override public boolean onHover(View v, MotionEvent event) { int what = event.getAction(); textX.setText("X : " + event.getX()); textY.setText("Y : " + event.getY()); switch(what){ case MotionEvent.ACTION_HOVER_ENTER: //鼠标进入view Log.i(TAG, "bottom ACTION_HOVER_ENTER..."); mOrgPI = getWindow().getDecorView().getPointerIcon(); getWindow().getDecorView().setPointerIcon(PointerIcon.load(getResources(), R.drawable.pointer_spot_touch_icon)); break; case MotionEvent.ACTION_HOVER_MOVE: //鼠标在view上 Log.i(TAG, "bottom ACTION_HOVER_MOVE..."); break; case MotionEvent.ACTION_HOVER_EXIT: //鼠标离开view Log.i(TAG, "bottom ACTION_HOVER_EXIT..."); getWindow().getDecorView().setPointerIcon(mOrgPI); break; } return false; } }); }
其中pointer_spot_touch_icon.xml 需要声明为 pointer-icon :
<?xml version="1.0" encoding="utf-8"?> <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" android:bitmap="@drawable/pointer_red_dot_arrow" android:hotSpotX="6dp" android:hotSpotY="6dp" />
但是app修改鼠标样式的view关闭后,鼠标样式会恢复成默认的黑箭头,因此不依赖APP去动态切换鼠标样式需要在framework层修改系统源码实现。
二、framework层添加自定义鼠标样式并通过按键切换
(1)添加自定义样式资源
系统图标资源在 frameworks/base/core/res/res/drawable-mdpi/ 目录,其中 pointer_arrow.png、pointer_arrow_large.png 是系统默认的黑色箭头,
pointer_arrow_red_dot.png、pointer_arrow_red_dot_large.png 是自己添加的红点样式图片:
然后在 frameworks/base/core/res/res/drawable/ 目录添加对应的xml:
pointer_arrow_red_dot_icon.xml
<?xml version="1.0" encoding="utf-8"?> <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" android:bitmap="@drawable/pointer_arrow_red_dot" android:hotSpotX="5dp" android:hotSpotY="5dp" />
pointer_arrow_red_dot_large_icon.xml
<?xml version="1.0" encoding="utf-8"?> <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" android:bitmap="@drawable/pointer_arrow_red_dot_large" android:hotSpotX="10dp" android:hotSpotY="10dp" />
修改 frameworks/base/core/res/res/values/styles.xml 添加资源配置,注意名字的匹配!
修改 frameworks/base/core/res/res/values/attrs.xml 引用资源:
(2)Java 层获取资源
修改 frameworks/base/core/java/android/view/PointerIcon.java ,添加如下定义:
在 getSystemIconTypeIndex(int type) 函数中返回之前配置的资源:
(3)c++层添加对应的id并加载资源
修改 frameworks/base/core/jni/android_view_PointerIcon.h
* Pointer icon styles. * Must match the definition in android.view.PointerIcon. */ enum { POINTER_ICON_STYLE_CUSTOM = -1, POINTER_ICON_STYLE_NULL = 0, POINTER_ICON_STYLE_ARROW = 1000, POINTER_ICON_STYLE_CONTEXT_MENU = 1001, POINTER_ICON_STYLE_HAND = 1002, POINTER_ICON_STYLE_HELP = 1003, POINTER_ICON_STYLE_WAIT = 1004, POINTER_ICON_STYLE_CELL = 1006, POINTER_ICON_STYLE_CROSSHAIR = 1007, POINTER_ICON_STYLE_TEXT = 1008, POINTER_ICON_STYLE_VERTICAL_TEXT = 1009, POINTER_ICON_STYLE_ALIAS = 1010, POINTER_ICON_STYLE_COPY = 1011, POINTER_ICON_STYLE_NO_DROP = 1012, POINTER_ICON_STYLE_ALL_SCROLL = 1013, POINTER_ICON_STYLE_HORIZONTAL_DOUBLE_ARROW = 1014, POINTER_ICON_STYLE_VERTICAL_DOUBLE_ARROW = 1015, POINTER_ICON_STYLE_TOP_RIGHT_DOUBLE_ARROW = 1016, POINTER_ICON_STYLE_TOP_LEFT_DOUBLE_ARROW = 1017, POINTER_ICON_STYLE_ZOOM_IN = 1018, POINTER_ICON_STYLE_ZOOM_OUT = 1019, POINTER_ICON_STYLE_GRAB = 1020, POINTER_ICON_STYLE_GRABBING = 1021, POINTER_ICON_STYLE_SPOT_HOVER = 2000, POINTER_ICON_STYLE_SPOT_TOUCH = 2001, POINTER_ICON_STYLE_SPOT_ANCHOR = 2002, POINTER_ICON_STYLE_REDDOT = 10001, //增加自定义样式的枚举定义,与上面 PointerIcon.java 中的变量对应 };
修改 frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp ,加载到自定义枚举变量对应的图片资源:
void NativeInputManager::loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources, std::map<int32_t, PointerAnimation>* outAnimationResources) { JNIEnv* env = jniEnv(); for (int iconId = POINTER_ICON_STYLE_CONTEXT_MENU; iconId <= POINTER_ICON_STYLE_REDDOT; ++iconId) { PointerIcon pointerIcon; loadSystemIconAsSpriteWithPointerIcon( env, mContextObj, iconId, &pointerIcon, &((*outResources)[iconId])); if (!pointerIcon.bitmapFrames.empty()) { PointerAnimation& animationData = (*outAnimationResources)[iconId]; size_t numFrames = pointerIcon.bitmapFrames.size() + 1; animationData.durationPerFrame = milliseconds_to_nanoseconds(pointerIcon.durationPerFrame); animationData.animationFrames.reserve(numFrames); animationData.animationFrames.push_back(SpriteIcon( pointerIcon.bitmap, pointerIcon.hotSpotX, pointerIcon.hotSpotY)); for (size_t i = 0; i < numFrames - 1; ++i) { animationData.animationFrames.push_back(SpriteIcon( pointerIcon.bitmapFrames[i], pointerIcon.hotSpotX, pointerIcon.hotSpotY)); } } } loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_NULL, &((*outResources)[POINTER_ICON_STYLE_NULL])); }
(4)按键切换鼠标样式
按键事件处理在 frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java 的 interceptKeyBeforeDispatching 函数中
...... String keytype = event.getDevice().getName(); int vendorId = event.getDevice().getVendorId(); int productId = event.getDevice().getProductId(); Log.d(TAG, "onKeyDown send keycode : " + keyCode + ", keytype : " + keytype + ", vendorId : " + vendorId + ", productId : " + productId); if (event.getAction() == KeyEvent.ACTION_UP && vendorId == 6421 && productId == 4146) { //过滤指定VID PID 的HID设备按键值 /* @id: *TYPE_NULL = 0; *TYPE_ARROW = 1000; *TYPE_OEM_FIRST = 10000; *TYPE_ARROW_REDDOT = 10001; */ switch (keyCode) { case 139: // air mouse InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_ARROW_REDDOT); //设置指定id的鼠标样式 SystemProperties.set("persist.sys.lxl.mouse_id", "10001"); //通过属性记录当前样式ID,目的是防止鼠标样式被其他界面更新,后面会介绍到 break; default: break; } }
......
其中 setPointerIconType() 方法实现在 frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
// Binder call @Override public void setPointerIconType(int iconId) { nativeSetPointerIconType(mPtr, iconId); }
接着调用到 native层的方法,实现在 frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
static void nativeSetPointerIconType(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint iconId) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); im->setPointerIconType(iconId); }
void NativeInputManager::setPointerIconType(int32_t iconId) { AutoMutex _l(mLock); sp<PointerController> controller = mLocked.pointerController.promote(); if (controller != NULL) { controller->updatePointerIcon(iconId); } }
updatePointerIcon()方法实现在 frameworks/base/libs/input/PointerController.cpp 中:
void PointerController::updatePointerIcon(int32_t iconId) { AutoMutex _l(mLock); //lxl add for custom mouse icon start const int32_t customIconId = mPolicy->getCustomPointerIconId(); if(customIconId >= 0) { iconId = customIconId; } //lxl add for custom mouse icon end if (mLocked.requestedPointerType != iconId) { mLocked.requestedPointerType = iconId; mLocked.presentationChanged = true; updatePointerLocked(); } }
其中就是通过 getCustomPointerIconId() 去获取当前属性id,强制更新为自定义的样式:
int32_t NativeInputManager::getCustomPointerIconId() { //lxl add for custom mouse icon start int mVaule; char mgetVal[PROPERTY_VALUE_MAX+1]={0}; property_get("persist.sys.lxl.mouse_id",mgetVal,""); //例如鼠标悬浮在按钮上时会变成小手样式,需要使用到前面按键时设定的属性值,强制更新为自定义鼠标样式。 mVaule = atoi(mgetVal); switch (mVaule) { case POINTER_ICON_STYLE_REDDOT: case POINTER_ICON_STYLE_NULL: return mVaule; default: break; } //lxl add for custom mouse icon end return POINTER_ICON_STYLE_CUSTOM; }
(5)隐藏鼠标样式接口
同样在frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp 中添加一个JNI方法:
//lxl add for custom mouse icon start static void android_server_InputManager_nativefadeMouse(JNIEnv* env, jclass clazz,jlong ptr){ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); int mID; char *mgetID=new char[PROPERTY_VALUE_MAX]; property_get("sys.ID.mID",mgetID,0); mID=atoi(mgetID); static sp<PointerControllerInterface>mPointerController=im->obtainPointerController(mID); ALOGI("Fade mouse by user"); //start to dispatchMouse mPointerController->setPresentation( PointerControllerInterface::PRESENTATION_POINTER); mPointerController->fade(PointerControllerInterface::TRANSITION_IMMEDIATE); } //lxl add for custom mouse icon end
并添加到 JNINativeMethod gInputManagerMethods[] 数组中去:
//lxl add for custom mouse icon start { "nativefadeMouse", "(J)V", (void*) android_server_InputManager_nativefadeMouse}, //lxl add for custom mouse icon end
然后在 frameworks/base/services/core/java/com/android/server/input/InputManagerService.java 即可声明使用:
//lxl add for custom mouse icon start private static native void nativefadeMouse(long ptr); public void fadeMouse(){ nativefadeMouse(mPtr); } //lxl add for custom mouse icon end
例如在 systemRunning() 中注册一个广播监听 USB 设备的插拔,在指定VID PID的HID设备拔除时调用fade接口隐藏鼠标:
//lxl add for custom mouse icon start filter = new IntentFilter(); filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Bundle bundle = intent.getExtras(); String action = intent.getAction(); String idStr = bundle.getString("id"); Slog.i(TAG, "onReceive : " + idStr); if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) { // USB设备接入 Log.d(TAG, "onReceive: " + "ACTION_USB_DEVICE_ATTACHED"); UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (null == device) { Log.w(TAG, "null usb device"); return; } Log.d(TAG, "--->getDeviceName:"+device.getDeviceName() + ",getVendorId:"+device.getVendorId() + ",getProductId:"+device.getProductId()); int count = device.getConfigurationCount(); for (int i = 0; i < count; i++) { UsbConfiguration configuration = device.getConfiguration(i); if (null == configuration) { Log.w(TAG, "null usb configuration"); return; } int interfaceCount = configuration.getInterfaceCount(); for (int j = 0; j < interfaceCount; j++) { UsbInterface usbInterface = configuration.getInterface(j); if (null != usbInterface && usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) { Log.d(TAG, "onReceive: " + "A hid device connected, mId: "+usbInterface.getId() + ", mName: " + usbInterface.getName()); } } } } else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) { // USB设备移除 Log.d(TAG, "onReceive: " + "ACTION_USB_DEVICE_DETACHED"); UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (null == device) { Log.w(TAG, "null usb device"); return; } Log.d(TAG, "--->getDeviceName:"+device.getDeviceName() + ",getVendorId:"+device.getVendorId() + ",getProductId:"+device.getProductId()); int count = device.getConfigurationCount(); for (int i = 0; i < count; i++) { UsbConfiguration configuration = device.getConfiguration(i); if (null == configuration) { Log.w(TAG, "null usb configuration"); return; } int interfaceCount = configuration.getInterfaceCount(); for (int j = 0; j < interfaceCount; j++) { UsbInterface usbInterface = configuration.getInterface(j); if (null != usbInterface && usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) { Log.d(TAG, "onReceive: " + "A hid device disconnected, mId: "+usbInterface.getId() + ", mName: " + usbInterface.getName()); if(device.getVendorId() == 6421 && device.getProductId() == 4146){ fadeMouse(); // 隐藏鼠标 } } } } } } }, filter, null, mHandler); //lxl add for custom mouse icon end
(6)HID设备数据监听
实际项目中,需要监测HID设备数据上报状态,所以在 frameworks/native/services/inputflinger/EventHub.cpp 中添加了一个线程+定时器进行实时监测,超时则认为设备移除,恢复默认鼠标样式。
定时器中通过am发送广播给framework层更新鼠标样式(可参考InputManagerService.java 中 USB 设备插拔广播添加),定时器方法如下:
//lxl add for custom mouse icon start static pthread_t mTimerThread = -1; static int mCountDown = 0; static bool mThreadRun = false;
// 定时器处理函数 static void handle(union sigval v) { time_t t; char p[32]; time(&t); strftime(p, sizeof(p), "%T", localtime(&t)); ALOGV("%s thread %lu, val = %d, mCountDown = %d , mThreadRun = %d\n", p, pthread_self(), v.sival_int, mCountDown, mThreadRun); mCountDown--; if(mCountDown <= 0){ mCountDown = 0; mThreadRun = false; } return; }
// 线程启动函数 static void* cursorTimer(void* p) { struct sigevent evp; struct itimerspec ts; timer_t timer; String8 sys_cmd; int ret; memset (&evp, 0, sizeof (evp)); evp.sigev_value.sival_ptr = &timer; evp.sigev_notify = SIGEV_THREAD; evp.sigev_notify_function = handle; evp.sigev_value.sival_int = 3; // handle() args ret = timer_create(CLOCK_REALTIME, &evp, &timer); // 创建定时器 if(ret) ALOGV("cursorTimer Create");// send broadcast to update icon when get air mouse data sys_cmd = "am broadcast -a com.lxl.action.mouseicon --es id \"ENABLE\" -f 0x01000000"; system(sys_cmd); ts.it_interval.tv_sec = 1; ts.it_interval.tv_nsec = 0; ts.it_value.tv_sec = 3; ts.it_value.tv_nsec = 0; ret = timer_settime(timer, TIMER_ABSTIME, &ts, NULL); if(ret) ALOGV("timer_settime"); while(mThreadRun){ usleep(500000); ALOGV("cursorTimer is running..."); } ret = timer_delete (timer); if(!ret) ALOGV("cursorTimer Delete");// send broadcast to update icon when get air mouse data sys_cmd = "am broadcast -a com.lxl.action.mouseicon --es id \"DISABLE\" -f 0x01000000"; system(sys_cmd); mTimerThread = -1; ALOGV("cursorTimer Exit"); return NULL; } //lxl add for custom mouse icon end
在EventHub::getEvents() 方法中创建该线程:
......
//lxl add for custom mouse icon start if(device->identifier.vendor == 0x1915 && device->identifier.product == 0x1032) { if(iev.type == 2 && (iev.code == 1 || iev.code == 0)) { // cursor data mCountDown = 30; // 有数据上传则重置定时器倒计时30s // creat timer thread for sending broadcast to reset icon if(mTimerThread < 0) { mThreadRun = true; int ret = pthread_create(&mTimerThread, NULL, &cursorTimer, NULL); // 创建定时器线程 if(!ret) ALOGV("Create air mouse timer thread successed"); } } else if(iev.type==1 && iev.code==67 && iev.value == 1) { // air mouse key down if(mCountDown > 0) { // air mouse is enable, then disable right now mThreadRun = false; // Let timer thread exit mCountDown = 0; } } } //lxl add for custom mouse icon end
......
以上实现方式和交互逻辑可根据实际项目需求合理设计~
posted on 2019-11-27 23:37 sheldon_blogs 阅读(6204) 评论(1) 编辑 收藏 举报