一、需求背景

新项目开发,需预置“天翼云电脑”app,云电脑app界面里其实就是盒子端接入的鼠标和键盘外设,来操作云端的windows系统桌面;

云电脑客户端使用的android系统本地的鼠标光标,而远端光标(云桌面windows系统里的鼠标光标)发生变更时会把新的光标图标传递给客户端,让客户端使用这个图标更新本地鼠标光标;

但是android7.0以下应用层没有可以变更鼠标光标的API,所以如果设备时android7.0以下的系统,需要厂家另行添加变更本地鼠标光标的API供云电脑app调用;

例如如下图这种情况:

当鼠标移动到窗口边缘时,需要将鼠标光标替换为双箭头图标

image.png

image.png

二、需求接口定义

系统与应用约定,通过AIDL实现

setPointerIcon(Bitmap bitmap, int hotSpotX, int hotSpotY)和clear()

两个函数,来实现替换和恢复系统光标

// IPointerIconService.aidl

package com.chinatelecom.clouddesk;

import android.graphics.Bitmap;

// Declare any non-default types here with import statements

interface IPointerIconService {

/*

** 设置自定义的鼠标光标图片

** @params:

** bitmap: 自定义光标图片。目前云电脑使用的鼠标标准鼠标光标图标大小为32*32左右,其大小不会超过100*100。

** hotSpotX: 图标在鼠标光标X轴绝对位置上的相对偏移量

** hotSpotY: 图标在鼠标光标Y轴绝对位置上的相对偏移量

*/

void setPointerIcon(in Bitmap bitmap, int hotSpotX, int hotSpotY);

//清除自定义鼠标图片的引用,恢复使用系统默认的鼠标图标光标

void clear();

}

期望的绑定方式:

Intent intent = new Intent("com.chinatelecom.clouddesk.IPointerIconService");

intent.setPackage("com.chinatelecom.clouddesk");

if (!bindService(intent, mConnection, Context.BIND_AUTO_CREATE)) {

Log.e("tanz", "Could not bind to IPointerIconService with "+intent);

}

注:系统侧需调测bitmap状态为recycled以及bitmap状态为null时的情形,以免出现此类异常时系统稳定性出问题。

三、需求实现思路

阅读源码发现,系统鼠标光标是在开机时由

frameworks\base\services\input\InputReader.cpp

中调用obtainPointerController函数创建指针控制器

image.png

\frameworks\base\services\jni\com_android_server_input_InputManagerService.cpp

的obtainPointerController函数中创建指针控制器,并给指针控制器赋值光标bitmap图标资源

9505430-63bfb23999f169c6.png

从上图可以看出PointerIcon实例是通过 JNI Native callback回调到InputManagerService.java中的getPointerIcon()函数来获取;

image.png

而且通过读上述阅读源码发现,com_android_server_input_InputManagerService.cpp中PointerController只实例化一次

那么我们就有了大致思路:

云电脑通过aidl将bitmap和偏移量传递AIDL服务

AIDL服务通过广播将bitmap和偏移量传递给InputManagerService.java

InputManagerService中使用拿到的bitmap和偏移量,创建新的PointerIcon

然后InputManagerService.java通过JNI通知C层刷新鼠标光标

C层中的单例PointerController去set新创建的PointerIcon即可完成光标替换

四、功能具体实现

步骤一:实现AIDL服务和接口函数

实现AIDL服务和clear()、setPointerIcon()两个函数

clear()函数:发“com.pointer.clear”广播

setPointerIcon()函数:发“com.pointer.change”广播

并将bitmap、hotSpotX、hotSpotY通过Bundle传递

package com.chinatelecom.clouddesk;

import android.app.Service;

import android.content.Context;

import android.content.Intent;

import android.graphics.Bitmap;

import android.os.Bundle;

import android.os.IBinder;

import android.os.RemoteException;

import android.util.Log;

/**

* 作者:libeibei

* 创建日期:20201109

* 类说明:

* API:setPointerIcon for telecom clouddesk

* logcat -v time |grep -e InputManager -e InputReader -e PointerController

**/

public class PointerIconService extends Service {

private static final String TAG = "CH_InputManager";

Context mContext = null;

Intent intent = null;

Bundle bundle = null;

@Override

public IBinder onBind(Intent intent) {

Log.i(TAG, "-----> onBind() ……");

mContext = PointerIconService.this.getApplicationContext();

return new PointerIconStub();

}

@Override

public boolean onUnbind(Intent intent) {

return super.onUnbind(intent);

}

private class PointerIconStub extends IPointerIconService.Stub {

@Override

public void clear() throws RemoteException {

Log.i(TAG, "-----> clear()");

intent = new Intent("com.pointer.clear");

sendBroadcast(intent);

}

@Override

public void setPointerIcon(Bitmap bitmap, int hotSpotX, int hotSpotY) throws RemoteException {

Log.i(TAG, "-----> setPointerIcon()");

if (bitmap == null) {

Log.i(TAG, "-----> bitmap is null , cancel");

} else if (bitmap.isRecycled()) {

Log.i(TAG, "-----> bitmap is isRecycled , cancel");

} else {

intent = new Intent("com.pointer.change");

bundle = new Bundle();

bundle.putParcelable("bitmap", bitmap);

bundle.putFloat("hotSpotX", (float) hotSpotX);

bundle.putFloat("hotSpotY", (float) hotSpotY);

intent.putExtras(bundle);

sendBroadcast(intent);

}

}

}

}

步骤二:InputManagerService接收广播

新增静态变量:static int Icon_Type = 0;

当接收到clear()函数发送的“com.pointer.clear”广播时

将Icon_Type = 0

当接收到setPointerIcon()函数发送的“com.pointer.change”广播时

将Icon_Type = 1

并调用setSystemUiVisibility()Native函数

(这里也可以新增Jni native函数来通知下面,不过我这边测试发现调用setSystemUiVisibility不影响其他功能就直接用这个函数了)

来通知com_android_server_input_InputManagerService.cpp中的

PointerIconController来更新Icon

frameworks\base\services\java\com\android\server\input\InputManagerService.java

// libeibei add for change pointer begin

private void registerPointerReceiver() {

IntentFilter pointerFilter = new IntentFilter();

pointerFilter.addAction("com.pointer.clear");

pointerFilter.addAction("com.pointer.change");

mContext.registerReceiver(pointerReceiver, pointerFilter);

}

static int Icon_Type = 0;

static Bitmap bitmap = null;

static float hotSpotX = 0;

static float hotSpotY = 0;

static Bundle mBundle = null;

BroadcastReceiver pointerReceiver = new BroadcastReceiver() {

@Override

public void onReceive(Context context, Intent intent) {

if (intent.getAction().equals("com.pointer.clear")) {

Log.i(TAG, "-----> onReceive clear");

Icon_Type = 0;

bitmap = null;

hotSpotX = 0;

hotSpotY = 0;

setSystemUiVisibility(0);

}

if (intent.getAction().equals("com.pointer.change")) {

Log.i(TAG, "-----> onReceive change");

mBundle = intent.getExtras();

if (mBundle != null) {

Icon_Type = 1;

bitmap = (Bitmap) mBundle.getParcelable("bitmap");

hotSpotX = mBundle.getFloat("hotSpotX");

hotSpotY = mBundle.getFloat("hotSpotY");

Log.i(TAG, "-----> hotSpotX = " + hotSpotX + ",hotSpotY = " + hotSpotY);

setSystemUiVisibility(1);

} else {

Log.i(TAG, "-----> mBundle = null");

Icon_Type = 0;

bitmap = null;

hotSpotX = 0;

hotSpotY = 0;

setSystemUiVisibility(0);

}

}

}

};

// libeibei add for change pointer end

并修改getPointerIcon()函数

当Icon_Type=1时获取使用bundle传递的bitmap创建新的图标

当Icon_Type=0时获取系统默认光标

将getPointerIcon()函数按照上述思路修改,如下:

image.png

步骤三:修改com_android_server_input_InputManagerService.cpp更新PointerIcon

image.png

有之前上面源码分析可知 obtainPointerController函数中

pointerController只创建一次,且由锁持有

所以我们在函数中获取需要获取pointerController实例时,要加锁

注意1:这也是我步骤二中没有新建JNI接口的原因,查看源码发现setSystemUiVisibility()函数已经加锁,并对pointerController进行操作,所以直接在setSystemUiVisibility做了修改

注意2:当然具体项目不同可能代码有差异,如果修改setSystemUiVisibility()函数会对系统有影响,就需要自己实现JNI接口了,总之新实现的函数别忘了持有锁

setSystemUiVisibility()函数修改前

image.png

setSystemUiVisibility()函数修改后

获取pointerController实例,对controller进行setPointerIcon操作来替换资源

image.png

自测试界面,调用两个接口测试OK:

ezgif-7-386343925533.gif

以上为Android 4.4.2 系统,添加自定义系统鼠标光标接口的分析流程和实现步骤

不同android版本可能这部分代码有些差异,但是总体思路不会变化

天翼云电脑的对接绕不开鼠标光标问题,应该后续很多电信的项目有对接云电脑的需求

如有其他友商需实现,可参考该实现方法

android 4.4 自定义广播,Android 4.4.2 系统 自定义 鼠标 光标 替换 接口实现相关推荐

  1. android 增加一条广播,Android中BroadcastReceiver广播使用及注意点

    Android中的广播用途很广,是四大组件之一.在android中可以看到它的各种应用,从系统发出的广播,用户自定义的广播等. 这里详细记录下广播的分类以及使用方法. 广播,是由两方面组成一个流程:广 ...

  2. android 静态注册wifi广播,Android中BroadcastReceiver详解

    BroadcastReceiver是什么? Android app可以发送广播也可以接收系统或者其它app发送的广播,是发送/订阅的设计模式.这些广播被发送当重要的事件发生的时候.例如,安卓系统发送广 ...

  3. android o 跨进程广播,[Android] Android O 广播限制

    问题 因为项目需要迁移到8.0平台,发现有一个系统应用打不开,从log发现如下描述: BroadcastQueue: Background execution not allowed: receivi ...

  4. android挂断电话广播,android实现接通和挂断电话

    android实现接通和挂断电话 发布时间:2020-08-21 01:52:02 来源:脚本之家 阅读:230 作者:WillenWu 本文实例为大家分享了android实现接通和挂断电话的具体代码 ...

  5. android nfc刷卡广播,Android关于NFC的简单使用

    这项功能是googleandroid2.3才有的.NFC 中文名无线射频技术是一项比蓝牙还要方便的一项技术 不需要人为手动操作即可完成数据的交换 只需要遵循相应的协议即可 下面为google提供的一个 ...

  6. android文档来电广播,Android系统广播(来电示例)系统广播大全

    电话广播 打开 AndroidManifest.xml 配置获取响铃.电话的权限. 在 application 节点内,添加,广播接受配置 xml Java 代码 建立一个类,继承BroadcastR ...

  7. android nsd和udp广播,Android网络服务发现(NSD)使用

    Android网络服务发现(NSD)使用 NSD(NsdManager)是Android SDK中自带的类库,可以集成直接使用. 使用 NSD服务需要(android4.1及以上) minSdkVer ...

  8. android 进lanucher的广播,Android开机优化之调整Launcher的加载时间

    前言 如前面两篇文章所描述的, 我们对Android系统的开机时间优化有了一个比较全面的了解,以及一些常用的调试手段(bootchart等),在这篇文章中我们先来看看如下这个问题,首先看一张图: 如上 ...

  9. android 注册两次广播,android 4.4 动态注册+静态注册电话短信广播后收到两次onReceive回调的问题...

    在android4.4手机里,先静态注册一次广播,以后在代码里动态注册一次该广播,会同时收到两次onReceive回调,我在htc 802d手机上测试(使用官方升级版,升级到4.4)测试android ...

最新文章

  1. Dynamics CRM 开启EmailRouter日志记录
  2. 对未来计算机的设想1000字,未来的世界作文1000字
  3. PHP rss聚合,利用PHP和AJAX创建RSS聚合器
  4. Windows Embedded Compact 7新特性
  5. myeclipse各菜单项说明
  6. 浅谈​与彼得原理和责任管理有关的小故事
  7. 23种设计模式之组合模式
  8. JavaScript doT模板引擎
  9. Leetcode:11.container-with-most-water(盛水最多的容器)
  10. 地图比例尺与空间分辨率之间的关系_宝马如何查看自己的车机系统是不是idrive几的?宝马主机与idrive系统和导航地图之间的对应关系!...
  11. 顺序存储二叉树和线索化二叉树
  12. Capture Modules 车载网络报文捕获(监听)模块(低时延、802.1AS时钟同步)
  13. 为什么Kindle不支持epub?
  14. 鸿蒙系统不能自动连wifi,Hi3861_WiFi IoT工程:WiFi自动连接
  15. VMware虚拟机安装教程图解,虚拟机详细使用教程
  16. 数据库产品-易用性问题
  17. uniapp网上商城排坑专业户
  18. MYC归来(2)第三次测试
  19. 暴风影音5免去广告的小技巧
  20. Elon Musk最感性专访:我期待失败,也期待真爱

热门文章

  1. 微信公众号接入天行机器人案例和方法
  2. ps常用的扣图工具有哪些,都有哪些方法
  3. 买二手台式机用软件能测试出好坏吗,如何鉴别二手台式机好坏,请大侠鉴次配置兼容性...
  4. et200s模块接线图讲解_西门子ET200S 1 STEP 步进模块使用入门.doc
  5. ARM或将在英美同时上市;国务院出台方案加大培育粤港澳高新技术产业;Meta将在香港试点元宇宙 | 每日大事件...
  6. 你离电影级别的PPT页面,只差一个操作指南
  7. hackmap-[常见的文件解析漏洞总结]
  8. 各类个性化图标在线制作总汇
  9. 织梦 通过后台管理文件
  10. Mbed Studio编辑L-Tek FF-LPC546XX