Android 4.4.2 系統(tǒng) 自定義 鼠標(biāo) 光標(biāo) 替換 接口實(shí)現(xiàn)

一、需求背景

新項(xiàng)目開發(fā),需預(yù)置“天翼云電腦”app,云電腦app界面里其實(shí)就是盒子端接入的鼠標(biāo)和鍵盤外設(shè),來操作云端的windows系統(tǒng)桌面;
云電腦客戶端使用的android系統(tǒng)本地的鼠標(biāo)光標(biāo),而遠(yuǎn)端光標(biāo)(云桌面windows系統(tǒng)里的鼠標(biāo)光標(biāo))發(fā)生變更時會把新的光標(biāo)圖標(biāo)傳遞給客戶端,讓客戶端使用這個圖標(biāo)更新本地鼠標(biāo)光標(biāo);
但是android7.0以下應(yīng)用層沒有可以變更鼠標(biāo)光標(biāo)的API,所以如果設(shè)備時android7.0以下的系統(tǒng),需要廠家另行添加變更本地鼠標(biāo)光標(biāo)的API供云電腦app調(diào)用;

例如如下圖這種情況:
當(dāng)鼠標(biāo)移動到窗口邊緣時,需要將鼠標(biāo)光標(biāo)替換為雙箭頭圖標(biāo)


image.png

image.png

二、需求接口定義

系統(tǒng)與應(yīng)用約定,通過AIDL實(shí)現(xiàn)
setPointerIcon(Bitmap bitmap, int hotSpotX, int hotSpotY)和clear()
兩個函數(shù),來實(shí)現(xiàn)替換和恢復(fù)系統(tǒng)光標(biāo)

// IPointerIconService.aidl
package com.chinatelecom.clouddesk;

import android.graphics.Bitmap;
// Declare any non-default types here with import statements

interface IPointerIconService {
    /*
     ** 設(shè)置自定義的鼠標(biāo)光標(biāo)圖片
     ** @params:
     ** bitmap: 自定義光標(biāo)圖片。目前云電腦使用的鼠標(biāo)標(biāo)準(zhǔn)鼠標(biāo)光標(biāo)圖標(biāo)大小為32*32左右,其大小不會超過100*100。
     ** hotSpotX: 圖標(biāo)在鼠標(biāo)光標(biāo)X軸絕對位置上的相對偏移量
     ** hotSpotY: 圖標(biāo)在鼠標(biāo)光標(biāo)Y軸絕對位置上的相對偏移量
     */
     void setPointerIcon(in Bitmap bitmap, int hotSpotX, int hotSpotY);

     //清除自定義鼠標(biāo)圖片的引用,恢復(fù)使用系統(tǒng)默認(rèn)的鼠標(biāo)圖標(biāo)光標(biāo)
     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)側(cè)需調(diào)測bitmap狀態(tài)為recycled以及bitmap狀態(tài)為null時的情形,以免出現(xiàn)此類異常時系統(tǒng)穩(wěn)定性出問題。

三、需求實(shí)現(xiàn)思路

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


image.png

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


9505430-63bfb23999f169c6.png

從上圖可以看出PointerIcon實(shí)例是通過 JNI Native callback回調(diào)到InputManagerService.java中的getPointerIcon()函數(shù)來獲?。?/p>

image.png

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

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

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

四、功能具體實(shí)現(xiàn)

步驟一:實(shí)現(xiàn)AIDL服務(wù)和接口函數(shù)
實(shí)現(xiàn)AIDL服務(wù)和clear()、setPointerIcon()兩個函數(shù)

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

setPointerIcon()函數(shù):發(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;
當(dāng)接收到clear()函數(shù)發(fā)送的“com.pointer.clear”廣播時
將Icon_Type = 0
當(dāng)接收到setPointerIcon()函數(shù)發(fā)送的“com.pointer.change”廣播時
將Icon_Type = 1

并調(diào)用setSystemUiVisibility()Native函數(shù)
(這里也可以新增Jni native函數(shù)來通知下面,不過我這邊測試發(fā)現(xiàn)調(diào)用setSystemUiVisibility不影響其他功能就直接用這個函數(shù)了)
來通知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()函數(shù)
當(dāng)Icon_Type=1時獲取使用bundle傳遞的bitmap創(chuàng)建新的圖標(biāo)
當(dāng)Icon_Type=0時獲取系統(tǒng)默認(rèn)光標(biāo)
將getPointerIcon()函數(shù)按照上述思路修改,如下:

image.png

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

image.png

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

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

setSystemUiVisibility()函數(shù)修改前


image.png

setSystemUiVisibility()函數(shù)修改后
獲取pointerController實(shí)例,對controller進(jìn)行setPointerIcon操作來替換資源


image.png

自測試界面,調(diào)用兩個接口測試OK:


ezgif-7-386343925533.gif

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容