Android 4.4.2 系統(tǒng) 自定義 鼠標 光標 替換 接口實現

一养叛、需求背景

新項目開發(fā),需預置“天翼云電腦”app宰翅,云電腦app界面里其實就是盒子端接入的鼠標和鍵盤外設弃甥,來操作云端的windows系統(tǒng)桌面;
云電腦客戶端使用的android系統(tǒng)本地的鼠標光標汁讼,而遠端光標(云桌面windows系統(tǒng)里的鼠標光標)發(fā)生變更時會把新的光標圖標傳遞給客戶端淆攻,讓客戶端使用這個圖標更新本地鼠標光標;
但是android7.0以下應用層沒有可以變更鼠標光標的API嘿架,所以如果設備時android7.0以下的系統(tǒng)瓶珊,需要廠家另行添加變更本地鼠標光標的API供云電腦app調用;

例如如下圖這種情況:
當鼠標移動到窗口邊緣時耸彪,需要將鼠標光標替換為雙箭頭圖標


image.png

image.png

二伞芹、需求接口定義

系統(tǒng)與應用約定,通過AIDL實現
setPointerIcon(Bitmap bitmap, int hotSpotX, int hotSpotY)和clear()
兩個函數蝉娜,來實現替換和恢復系統(tǒng)光標

// 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);

     //清除自定義鼠標圖片的引用,恢復使用系統(tǒng)默認的鼠標圖標光標
     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);
}

注:系統(tǒng)側需調測bitmap狀態(tài)為recycled以及bitmap狀態(tài)為null時的情形南缓,以免出現此類異常時系統(tǒng)穩(wěn)定性出問題。

三荧呐、需求實現思路

閱讀源碼發(fā)現汉形,系統(tǒng)鼠標光標是在開機時由
frameworks\base\services\input\InputReader.cpp
中調用obtainPointerController函數創(chuàng)建指針控制器


image.png

\frameworks\base\services\jni\com_android_server_input_InputManagerService.cpp
的obtainPointerController函數中創(chuàng)建指針控制器,并給指針控制器賦值光標bitmap圖標資源


9505430-63bfb23999f169c6.png

從上圖可以看出PointerIcon實例是通過 JNI Native callback回調到InputManagerService.java中的getPointerIcon()函數來獲缺恫概疆;


image.png

而且通過讀上述閱讀源碼發(fā)現,com_android_server_input_InputManagerService.cpp中PointerController只實例化一次

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

  • 云電腦通過aidl將bitmap和偏移量傳遞AIDL服務
  • AIDL服務通過廣播將bitmap和偏移量傳遞給InputManagerService.java
  • InputManagerService中使用拿到的bitmap和偏移量峰搪,創(chuàng)建新的PointerIcon
  • 然后InputManagerService.java通過JNI通知C層刷新鼠標光標
  • C層中的單例PointerController去set新創(chuàng)建的PointerIcon即可完成光標替換

四届案、功能具體實現

步驟一:實現AIDL服務和接口函數
實現AIDL服務和clear()、setPointerIcon()兩個函數

clear()函數:發(fā)“com.pointer.clear”廣播

setPointerIcon()函數:發(fā)“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
 * 創(chuàng)建日期: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接收廣播
新增靜態(tài)變量:static int Icon_Type = 0;
當接收到clear()函數發(fā)送的“com.pointer.clear”廣播時
將Icon_Type = 0
當接收到setPointerIcon()函數發(fā)送的“com.pointer.change”廣播時
將Icon_Type = 1

并調用setSystemUiVisibility()Native函數
(這里也可以新增Jni native函數來通知下面,不過我這邊測試發(fā)現調用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創(chuàng)建新的圖標
當Icon_Type=0時獲取系統(tǒng)默認光標
將getPointerIcon()函數按照上述思路修改咐蚯,如下:

image.png

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

image.png

有之前上面源碼分析可知 obtainPointerController函數中
pointerController只創(chuàng)建一次童漩,且由鎖持有
所以我們在函數中獲取需要獲取pointerController實例時,要加鎖

注意1:這也是我步驟二中沒有新建JNI接口的原因春锋,查看源碼發(fā)現setSystemUiVisibility()函數已經加鎖矫膨,并對pointerController進行操作,所以直接在setSystemUiVisibility做了修改
注意2:當然具體項目不同可能代碼有差異期奔,如果修改setSystemUiVisibility()函數會對系統(tǒng)有影響侧馅,就需要自己實現JNI接口了,總之新實現的函數別忘了持有鎖

setSystemUiVisibility()函數修改前


image.png

setSystemUiVisibility()函數修改后
獲取pointerController實例呐萌,對controller進行setPointerIcon操作來替換資源


image.png

自測試界面馁痴,調用兩個接口測試OK:


ezgif-7-386343925533.gif

以上為Android 4.4.2 系統(tǒng),添加自定義系統(tǒng)鼠標光標接口的分析流程和實現步驟
不同android版本可能這部分代碼有些差異肺孤,但是總體思路不會變化
天翼云電腦的對接繞不開鼠標光標問題罗晕,應該后續(xù)很多電信的項目有對接云電腦的需求
如有其他友商需實現,可參考該實現方法

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末赠堵,一起剝皮案震驚了整個濱河市小渊,隨后出現的幾起案子,更是在濱河造成了極大的恐慌茫叭,老刑警劉巖酬屉,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異揍愁,居然都是意外死亡呐萨,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門吗垮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來垛吗,“玉大人,你說我怎么就攤上這事烁登∏犹耄” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵饵沧,是天一觀的道長锨络。 經常有香客問我,道長狼牺,這世上最難降的妖魔是什么羡儿? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮是钥,結果婚禮上掠归,老公的妹妹穿的比我還像新娘缅叠。我一直安慰自己,他們只是感情好虏冻,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布肤粱。 她就那樣靜靜地躺著,像睡著了一般厨相。 火紅的嫁衣襯著肌膚如雪领曼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天蛮穿,我揣著相機與錄音庶骄,去河邊找鬼。 笑死践磅,一個胖子當著我的面吹牛单刁,可吹牛的內容都是我干的。 我是一名探鬼主播音诈,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼幻碱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了细溅?” 一聲冷哼從身側響起褥傍,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎喇聊,沒想到半個月后恍风,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡誓篱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年朋贬,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窜骄。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡锦募,死狀恐怖,靈堂內的尸體忽然破棺而出邻遏,到底是詐尸還是另有隱情糠亩,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布准验,位于F島的核電站赎线,受9級特大地震影響,放射性物質發(fā)生泄漏糊饱。R本人自食惡果不足惜垂寥,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧滞项,春花似錦狭归、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至律杠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間竞惋,已是汗流浹背柜去。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拆宛,地道東北人嗓奢。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像浑厚,于是被迫代替她去往敵國和親股耽。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內容