精华内容
下载资源
问答
  • 2020-10-31 11:39:30

    当我们给Android设备外接键盘时,某些自定义按键点击时可能无法唤醒屏幕,但又需要做到可以唤醒屏幕,要唤醒需使用到电源管理中PowerManager,可以自定义一个PowerManager类对象,在执行自定义按键按下操作处,添加相关处理:

    PowerManger mPowerManager = new PowerManager();
    mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
    

    源码中关于PowerManager类的成员方法userActivity有说明第二个参数传递false值时可以唤醒屏幕,感兴趣的可以自行查阅下。

    更多相关内容
  • 需求:定制物理按键可以转换成别的按键/打开其他应用/发送intent例如:按下 VOLUME_DOWN -> Back key按下 VOLUME_DOWN -> 打开系统某个应用按下 VOLUME_DOWN -> 发送特定intent简单分析:首先想到的是...

    需求:定制物理按键可以转换成别的按键/打开其他应用/发送intent

    例如:

    按下 VOLUME_DOWN -> Back key

    按下 VOLUME_DOWN -> 打开系统某个应用

    按下 VOLUME_DOWN -> 发送特定intent

    简单分析:

    首先想到的是PhoneWindowManager的interceptKeyBeforeQueueing 去拦截按键,其次:

    一. 需要系统级service及数据库保证按键转换的映射,保证重启仍然生效

    二. 需要一个APP支持转换逻辑

    我们都知道Android系统的启动是先加载Linux kernel,再启动init进程,创建SystemServer,然后在SystemServer 启动各种Service让系统跑起来。所以我们也要创建一个系统Service. 由于代码量的问题,就不全部上传了。

    1). 自定义系统serviceframeworks/base/core/java/android/app/hijackbutton/IHiJackService.aidl

    package android.app.hijackbutton;

    import android.view.KeyEvent;

    import android.app.hijackbutton.HijackingKeys;

    import android.app.hijackbutton.HiJackData;

    /**

    * {@hide}

    */

    interface IHiJackService {

    HijackingKeys[] getHijackingKeys(); //获取需要转换的按键 供上层app显示【1】

    int setAllHiJackData(in HiJackData[] dataList);//设置键值对应/打开系统某个应用/发送特定intent【2】

    HiJackData[] getAllHiJackData(); //得到所有的key的键值对应,供上层app显示【3】

    int hijackingKey(inout KeyEvent event, boolean useCache); //按键转换【4】

    }

    自定义系统service实现:frameworks/base/services/core/java/com/android/server/hijackbutton/HiJackService.java

    //功能1:创建数据库,并且初始化HijackManager 即初始化数据库。

    public HiJackService(Context context) {

    mContext = context;

    mThread = new HandlerThread(TAG);

    mThread.start();

    mHandler = new Handler(mThread.getLooper()) {

    @Override

    public void handleMessage(Message msg) {

    switch (msg.what) {

    case INIT_MSG: {

    if (mHiJackManager != null) {

    mHiJackManager.initialization();

    }

    break;

    }

    default: {

    break;

    }

    }

    }

    };

    mHiJackManager = new HiJackManager(context, new HiJackDBHelper(context));

    mHiJackManager.open();

    mHandler.sendEmptyMessageDelayed(INIT_MSG, 500);

    }

    // 功能2: 按键转换【4】

    public int hijackingKey(KeyEvent event, boolean useCache) throws RemoteException {

    hijackingMappingKey(event, useCache);

    return event.getKeyCode();

    }

    private boolean hijackingMappingKey(KeyEvent event, boolean useCache) {

    HiJackData data = mHiJackManager.selData(event.getKeyCode());

    returnKeyCode = data.getConvertKeyCode();

    event.hijackingKeyCode(returnKeyCode); //转换按键

    return true;

    }

    KeyEvent的处理:frameworks/base/core/java/android/view/KeyEvent.java

    public KeyEvent hijackingKeyCode(int code) {

    //直接转换了按键

    mKeyCode = code;

    return this;

    }

    被转换的按键://需要被转换的按键 【1】

    frameworks/base/core/java/android/app/hijackbutton/HijackingKeys.aidl

    frameworks/base/core/java/android/app/hijackbutton/HijackingKeys.java

    关联数据库://和HiJackService 相关联的数据库.

    frameworks/base/core/java/android/app/hijackbutton/HiJackDBHelper.java

    //定义的字段

    public static final String HIJACK_ID = "_id";

    public static final String HIJACK_LABEL = "_label";

    public static final String HIJACK_DEFAULT_KEYCODE = "_default_keycode"; //默认keycode

    public static final String HIJACK_DEFAULT_SYMBOL = "_default_symbol"; //默认按键名

    public static final String HIJACK_CONVERT_KEYCODE = "_convert_keycode";//转换后keycode

    public static final String HIJACK_CONVERT_SYMBOL = "_convert_symbol";  //转换后按键名

    数据库的对象封装:frameworks/base/core/java/android/app/hijackbutton/HiJackData.aidl

    frameworks/base/core/java/android/app/hijackbutton/HiJackData.java

    // 对HiJackDBHelper的对象封装。

    private long mID = 0;

    private String mLabel = "";

    private int mDefaultKeyCode = 0;

    private String mDefaultSymbol = "";

    private int mConvertKeyCode = 0;

    private String mConvertSymbol = "";

    添加service到SystemServer:frameworks/base/services/java/com/android/server/SystemServer.java

    //系统服务的添加 startOtherServices():

    traceBeginAndSlog("StartHijackService");

    mHijack = new HiJackService(context);

    ServiceManager.addService(Context.HIJACK_SERVICE, mHijack);

    traceEnd();

    这样就完成了系统service的添加。

    最后在关键点的使用这个service的hijackingKey()的转换功能:

    frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java

    interceptKeyBeforeDispatching 中调用 【4】

    frameworks/base/core/java/android/view/ViewRootImpl.java

    ViewPreImeInputStage.processKeyEvent 调用 【4】

    转换按键APP:

    初始界面:

    8433b7a66c09

    gpio-key 被转换的按键:

    这个list 通过解析手机 /system/usr/keylayout/gpio-key.kl获取,或者根据需求直接写死

    convert key 转换成的按键:

    这个在HiJackService初始化数据库的时候,初始化每个按键的初始值。选择不同的按键转换后,会保存映射。

    点击具体的item转换:

    8433b7a66c09

    第一部分的list:

    【1】通过读取 frameworks/base/core/res/res/values/config.xml 中自定义的config_hijackingKeys数组,得到需要被定制的key:

    "NoAction:KEYCODE_UNKNOWN:0:::0"

    "HomeKey:KEYCODE_HOME:3:::0"

    "VolumeUp:KEYCODE_VOLUME_UP:24:::0"

    "F13:KEYCODE_F13:900:::0"

    ............

    其中的:::是分隔符

    第二部分:

    额外添加三个功能:

    RunApplication   点击按键运行某个app

    //获取所有应用packagemApplist = mContext.getPackageManager().queryIntentActivities(intent, 0);

    //运行选择的应用Intent intent = new Intent(Intent.ACTION_RUN);

    intent.setComponent(new ComponentName(data.getPackageNameOfExecuteApp(),

    data.getActivityNameOfExecuteApp()));

    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK

    | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);

    mContext.startActivity(intent);

    Broadcastkey,CustomIntent也是同样原理。

    最后在总结一下流程:

    启动流程:

    reboot->systemSever->HiJackService->  init database  and restore key Map->  TransformButtons APP  -> choose convet key  -> apply

    特殊按键流程:

    press key -> PhoneWindowManager  interceptKeyBeforeQueueing and interceptKeyBeforeDispatching -> HiJackService.hijackingKey() -> KeyEvent.hijackingKeyCode() // 特殊按键转换例如homekey  backkey

    普通按键:

    inputDispatcher -> WindowInputEventReceiver -> WindowManagerService.dispatchInputEvent-> ViewRootImpl-> ViewRootImpl.enqueueInputEvent-> ViewRootImpl.doProcessInputEvents ->>ViewPreImeInputStage.processKeyEvent ->hijackingKey -> View dispatchKeyEvent-> KeyEvent.java dispatch()   //普通按键转换,例如自定义的F13

    对了, 上传了APP的源码以供参考

    http://download.csdn.net/download/zghlezh/10134989

    展开全文
  • 一、给自定义按键添加广播 修改PhoneWindowManager.java中的interceptKeyBeforeDispatching方法 /frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @Override public long ...
  • 网上存在一些关于Android系统添加自定义按键的文章,但大多针对Android2.3和4.0系统的,许多文件都已经变动位置了,这两天我总结了一些,写出来欢迎大家交流与指正: Android系统通过*.kl文件将Linux按键传给上层,...

    网上存在一些关于Android系统添加自定义按键的文章,但大多针对Android2.3和4.0系统的,许多文件都已经变动位置了,这两天我总结了一些,写出来欢迎大家交流与指正:


         Android系统通过*.kl文件将Linux按键传给上层,最新的Android4.4已经不再默认qwerty.kl文件了,代之的是frameworks/base/data/keyboards/Generic.kl文件,当然如果定义板级键值文件,还是以rk29-keypad.kl文件优先。

     

        在3288/device/rockchip/rksdk目录下的rk29-keypad.kl文件中有相关键值的定义:

    1. key 59    MENU
    2. key 102   HOME
    3. key 114   VOLUME_DOWN
    4. key 115   VOLUME_UP
    5. key 116   POWE                WAKE
    6. key 143   NOTIFICATION       WAKE
    7. key 158   BACK
    8. key 212   CAMERA
    9. key 217   SEARCH

    复制代码

    说明:

    WAKE: 当设备睡眠时按下此键,设备将被唤醒,按键事件将会被发送到应用程序。

    WAKE_DROPPED: 当设备睡眠时按下此键,设备将被唤醒,而按键事件不会被发送到应用程序。

     

           瑞星微的按键分为GPIO和ADC两种,定义在/kernel/arch/arm/boot/dts目录下的firefly-rk3288.dts文件:

    1. &adc {        
    2.           status = "okay";
    3.  
    4.           key {               
    5.                  compatible = "rockchip, key";               
    6.                  io-channels = <&adc 1>;
    7.  
    8.                  vol-up-key {                       
    9.                          linux, code = <115>;                       
    10.                          label = "volume up";                       
    11.                          rockchip, adc_value = <1>;               
    12.                  };
    13.  
    14.                  vol-down-key {                       
    15.                         linux, code = <114>;                       
    16.                         label = "volume down";                       
    17.                         rockchip, adc_value = <170>;               
    18.                  };
    19.                  power-key {                        
    20.                        gpios = <&gpio0 GPIO_A4 GPIO_ACTIVE_LOW>;                        
    21.                        linux, code = <116>;                     
    22.                        label = "power";                       
    23.                        gpio-key, wakeup;  // 具有唤醒功能               
    24.                  };
    25.  
    26.                  menu-key {                        
    27.                        linux, code = <139>;                        
    28.                        label = "menu";                       
    29.                        rockchip, adc_value = <355>;         
    30.                  };
    31.                  home-key {               
    32.                       linux, code = <102>;
    33.                       label = "home";
    34.                       rockchip, adc_value = <746>;
    35.                 };
    36.                 back-key {
    37.                       linux, code = <158>;
    38.                       label = "back";
    39.                       rockchip, adc_value = <560>;
    40.                };
    41.                camera-key {
    42.                       linux, code = <212>;
    43.                       label = "camera";
    44.                       rockchip, adc_value = <450>;
    45.               };         
    46.       };

    复制代码

           先在linux内核中添加新的按键值(当然不用宏定义直接写数值也可以),网上一般写在/include/linux/input/input.h文件中添加,实际3.10内核在/include/uapi/ linux/input.h文件中:

    #defineKEY_RESERVED     0

    #define KEY_ESC         1

    #define KEY_1           2

    ...

    #defineKEY_MY           250

     

          对于Android层次,在rk29-keypad.kl文件中增加按键值后,还需要修改一些文件,系统才能处理,首先修
    改KeycodeLabels.h文件,4.4版本将其移到了frameworks/native/include/input目录下,对KEYCODES数组添加按键码:
    static const KeycodeLabelKEYCODES[] = {
      ...
      { "MY_KEYS" ,250},

      { NULL,0 }
              最后一定要以NULL结尾,其中的TV_KEYMOUSE_LEFT按键就是瑞星微添加的,再去frameworks/native/include/android/keycode.h文件中添加:enum {
       AKEYCODE_UNKNOWN         = 0,
        ....
        ....
    添加
        AKEYCODE_MY_KEYS  =250,


           同样AKEYCODE_TV_KEYMOUSE_LEFT宏也是瑞星微添加的,如果要添加系统按键值,还需要修改

    frameworks/base/libs/ui/input.java(android4.4已变为frameworks/native/libs/input/input.cpp)文件:

    boolKeyEvent::hasDefaultAction(int32_t keyCode)

    {

        switch (keyCode) {

            case AKEYCODE_HOME:

            case AKEYCODE_BACK:

            ...

            case AKEYCODE_MY_KEYS:

                return true;

     

            还有下面的:

    boolKeyEvent::isSystemKey(int32_t keyCode) {

        switch (keyCode) {

            case AKEYCODE_MENU:

            ...

            case AKEYCODE_MY_KEYS:

                return true;

        }

            return false;

    }

     

           还要修改frameworks/base/core/java/android/view/KeyEvent.java文件:
    public staticfinal int KEYCODE_TV_MEDIA_PAUSE = 249;

    public staticfinal int KEYCODE_MY_KEYS        = 250;

    private staticfinal int LAST_KEYCODE = KEYCODE_MY_KEYS;


           注意一定要修改LAST_KEYCODE,下面的populateKeycodeSymbolicNames数组也需要添加:

    names.append(KEYCODE_MY_KEYS, " KEYCODE_MY_KEYS ");

     

           修改frameworks/base/core/res/res/values/attr.xml文件:

    <!--This enum provides the same keycode values as can be found in      

    {@link android.view.KeyEvent}. --><attrname="keycode">

           <enum name="KEYCODE_UNKNOWN"value="0" />

           ...

           <enumname="KEYCODE_TV_MEDIA_PAUSE" value="249" />

           <enum name="KEYCODE_MY_KEYS" value="250" /></attr>

     

           对于网上说的/external/webkit/Source/WebKit/android/plugins/ANPKeyCodes.h文件已经移除。

     

           以上文件都改完之后frameworks/base/api/current.txt也要随之更新,如果未更新,可运行 makeupdate-api 进行更新

           如果对新键值进行处理,可以通过获取相应的keycode,对它进行处理。对于按键事件的处理一般如下文件中:
    frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java

     

    @Override   

    public intinterceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn)

    {

      ...

      switch (keyCode) {

          case KeyEvent.KEYCODE_VOLUME_DOWN:

          ...

          case KeyEvent. KEYCODE_MY_KEYS:  // 添加处理代码

    展开全文
  • Android9 framework 按键音调用流程及替换原生按键音、调节按键音音量方法 摘要:按键音的总体逻辑是先找到系统中按键音的资源,然后调用SoundPool.load让系统加载音频资源,加载成功后在onLoadComplete回调中会返回...

    一、按键音调用流程

    摘要:按键音播放的总体逻辑是先找到系统中按键音的资源,然后调用SoundPool.load让系统加载音频资源,加载成功后在onLoadComplete回调中会返回一个非0的soundID ,用于播放时指定特定的音频,最后在需要播放按键音的时候直接根据soundID播放

    1.Android按键音接口

    Android按键音只有两个常用接口,分别是:

    1. 原生设置APP中SoundFragment.java调用的设置按键音开关的接口:mAudioManager.loadSoundEffects()和mAudioManager.unloadSoundEffects()
        private void setSoundEffectsEnabled(boolean enabled) {
                mAudioManager = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE); //1
            if (enabled) {
                mAudioManager.loadSoundEffects();   
            } else {
                mAudioManager.unloadSoundEffects();
            }
            Settings.System.putInt(getActivity().getContentResolver(),
                    Settings.System.SOUND_EFFECTS_ENABLED, enabled ? 1 : 0);
        }
    
    

    先调用AudioManager的loadSoundEffects方法,然后会调用到AudioService的loadSoundEffects方法

    1. View.java中播放按键音的接口:playSoundEffect
        public boolean performClick() {
            // We still need to call this method to handle the cases where performClick() was called
            // externally, instead of through performClickInternal()
            notifyAutofillManagerOnClick();
    
            final boolean result;
            final ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnClickListener != null) {
                playSoundEffect(SoundEffectConstants.CLICK);//调用会经过ViewRootImpl.java,最终调用到AudioService中
                li.mOnClickListener.onClick(this);
                result = true;
            } else {
                result = false;
            }
    
            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    
            notifyEnterOrExitForAutoFillIfNeeded(true);
    
            return result;
        }
    

    最终会调用到AudioService的playSoundEffect方法

    2.onLoadSoundEffects()方法

    上述的两个方法调用到AudioService之后,分别通过sendMsg向handler发送MSG_LOAD_SOUND_EFFECTS和MSG_PLAY_SOUND_EFFECT信息,handler在收到信息后会进行相应的操作,但是不管是哪个操作,都会调用到onLoadSoundEffects()方法

    loadSoundEffects的调用流程(非重点):

        public boolean loadSoundEffects() {
            int attempts = 3;
            LoadSoundEffectReply reply = new LoadSoundEffectReply();
    
            synchronized (reply) {
            //调用sendMsg方法
                sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, reply, 0);
                while ((reply.mStatus == 1) && (attempts-- > 0)) {
                    try {
                        reply.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);
                    } catch (InterruptedException e) {
                        Log.w(TAG, "loadSoundEffects Interrupted while waiting sound pool loaded.");
                    }
                }
            }
            return (reply.mStatus == 0);
        }
    
    //sendMsg方法是对handler.sendMessageAtTime的封装
        private static void sendMsg(Handler handler, int msg,
                int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) {
    
            if (existingMsgPolicy == SENDMSG_REPLACE) {
                handler.removeMessages(msg);
            } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
                return;
            }
            synchronized (mLastDeviceConnectMsgTime) {
                long time = SystemClock.uptimeMillis() + delay;
    
                if (msg == MSG_SET_A2DP_SRC_CONNECTION_STATE ||
                    msg == MSG_SET_A2DP_SINK_CONNECTION_STATE ||
                    msg == MSG_SET_HEARING_AID_CONNECTION_STATE ||
                    msg == MSG_SET_WIRED_DEVICE_CONNECTION_STATE ||
                    msg == MSG_A2DP_DEVICE_CONFIG_CHANGE ||
                    msg == MSG_BTA2DP_DOCK_TIMEOUT) {
                    if (mLastDeviceConnectMsgTime >= time) {
                      // add a little delay to make sure messages are ordered as expected
                      time = mLastDeviceConnectMsgTime + 30;
                    }
                    mLastDeviceConnectMsgTime = time;
                }
    
                handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time);
            }
        }
    
    
    //在handleMessage中处理消息
    @Override
            public void handleMessage(Message msg) {
    	......
    	                case MSG_PLAY_SOUND_EFFECT:
    	                //调用onPlaySoundEffect
                        onPlaySoundEffect(msg.arg1, msg.arg2);
                        break;
    }
    
    
    
            private void onPlaySoundEffect(int effectType, int volume) {
                synchronized (mSoundEffectsLock) {
    
    				//最终会调用到onLoadSoundEffects
                    onLoadSoundEffects();
                    	......
             }
    

    playSoundEffect的调用流程(非重点):

        public void playSoundEffect(int effectType) {
            playSoundEffectVolume(effectType, -1.0f);
        }
    
    
        public void playSoundEffectVolume(int effectType, float volume) {
            // do not try to play the sound effect if the system stream is muted
            if (isStreamMutedByRingerOrZenMode(STREAM_SYSTEM)) {
                return;
            }
    
            if (effectType >= AudioManager.NUM_SOUND_EFFECTS || effectType < 0) {
                Log.w(TAG, "AudioService effectType value " + effectType + " out of range");
                return;
            }
    
            sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SENDMSG_QUEUE,
                    effectType, (int) (volume * 1000), null, 0);
        }
    
    
    
    //在handleMessage中处理消息
    @Override
            public void handleMessage(Message msg) {
    	......
                    case MSG_PLAY_SOUND_EFFECT:
                        onPlaySoundEffect(msg.arg1, msg.arg2);
                        break;
    }
    
    
    
            private void onPlaySoundEffect(int effectType, int volume) {
                synchronized (mSoundEffectsLock) {
    
                    onLoadSoundEffects();
                    ......
                    }
    

    如上所述最终都会调用onLoadSoundEffects方法
    在onLoadSoundEffects方法中主要完成以下几件事:

    1. 调用loadTouchSoundAssets方法解析XML文件,获得音源文件名,初始化数组,将音源文件与数组中元素一一对应
    2. 补全音源文件路径,调用SoundPool.load方法
    3. 将SoundPool.load方法返回的sampleId保存在数组中,作为之后play方法的参数

    先来看loadTouchSoundAssets方法,代码如下:

        private void loadTouchSoundAssets() {
            XmlResourceParser parser = null;
    
            // only load assets once.
            //SOUND_EFFECT_FILES是一个存放字符串的List,里面存放的是音频资源的名称
            if (!SOUND_EFFECT_FILES.isEmpty()) {
                return;
            }
    
    		//此方法执行:
    		//1.SOUND_EFFECT_FILES.add("Effect_Tick.ogg"); 向SOUND_EFFECT_FILES添加一个音频资源的名称
    		//2.初始化一个二维数组SOUND_EFFECT_FILES_MAP。行数为10,列数为2,第一列都为0,第二列都为-1
            loadTouchSoundAssetDefaults();
    
            try {
            	//获得XML对象
                parser = mContext.getResources().getXml(com.android.internal.R.xml.audio_assets);
    
                XmlUtils.beginDocument(parser, TAG_AUDIO_ASSETS);
                //getAttributeValue方法用于获取传入的Attribute名称对应的Value,这里是"1.0"
                String version = parser.getAttributeValue(null, ATTR_VERSION);
                boolean inTouchSoundsGroup = false;
    
                if (ASSET_FILE_VERSION.equals(version)) {
                    while (true) {
                    	//nextElement方法用于切换到XML的下一层
                        XmlUtils.nextElement(parser);
                        //获取当前parser的名称,这里是"group"
                        String element = parser.getName();
                        if (element == null) {
                            break;
                        }
                        if (element.equals(TAG_GROUP)) {
                            String name = parser.getAttributeValue(null, ATTR_GROUP_NAME);
                            if (GROUP_TOUCH_SOUNDS.equals(name)) {
                                inTouchSoundsGroup = true;
                                break;
                            }
                        }
                    }
                    //遍历XML中剩下的所有元素
                    while (inTouchSoundsGroup) {
                        XmlUtils.nextElement(parser);
                        String element = parser.getName();
                        if (element == null) {
                            break;
                        }
                        if (element.equals(TAG_ASSET)) {
                            String id = parser.getAttributeValue(null, ATTR_ASSET_ID);
                            String file = parser.getAttributeValue(null, ATTR_ASSET_FILE);
                            int fx;
    
                            try {
                            	//getField的对象是.class文件(.java文件的预编译产物,只进行一些变量即宏的替换),这里即AudioManager.class
                            	//根据传入的id获得AudioManager.class中对应的对象,例如传入的是"FX_KEY_CLICK",得到的是AudioManager中定义的public static final int FX_KEY_CLICK = 0
                                Field field = AudioManager.class.getField(id);
                                fx = field.getInt(null);
                            } catch (Exception e) {
                                Log.w(TAG, "Invalid touch sound ID: "+id);
                                continue;
                            }
    
    						//根据之前XML中读取的file取出其在SOUND_EFFECT_FILES的位置
    						//此时SOUND_EFFECT_FILES只有一个元素即"Effect_Tick.ogg"
    						//如果不存在则加入到SOUND_EFFECT_FILES中
                            int i = SOUND_EFFECT_FILES.indexOf(file);
                            if (i == -1) {
                                i = SOUND_EFFECT_FILES.size();
                                SOUND_EFFECT_FILES.add(file);
                            }
                            SOUND_EFFECT_FILES_MAP[fx][0] = i;
                        } else {
                            break;
                        }
                    }
                }
            } catch (Resources.NotFoundException e) {
                Log.w(TAG, "audio assets file not found", e);
            } catch (XmlPullParserException e) {
                Log.w(TAG, "XML parser exception reading touch sound assets", e);
            } catch (IOException e) {
                Log.w(TAG, "I/O exception reading touch sound assets", e);
            } finally {
                if (parser != null) {
                    parser.close();
                }
            }
        }
    
    
        private void loadTouchSoundAssetDefaults() {
            SOUND_EFFECT_FILES.add("Effect_Tick.ogg");
            for (int i = 0; i < AudioManager.NUM_SOUND_EFFECTS; i++) {
                SOUND_EFFECT_FILES_MAP[i][0] = 0;
                SOUND_EFFECT_FILES_MAP[i][1] = -1;
            }
        }
    
    

    经过loadTouchSoundAssets初始化后,SOUND_EFFECT_FILES数组为:

    { "Effect_Tick.ogg" , "KeypressStandard.ogg" , "KeypressSpacebar.ogg" ,
     "KeypressDelete.ogg" , "KeypressReturn.ogg" , "KeypressInvalid.ogg" }
    

    SOUND_EFFECT_FILES_MAP数组为:

    {{0, -1}, {0, -1}, {0, -1}, {0, -1}, {0, -1}, {1, -1}, {2, -1}, {3, -1}, {4, -1}, {5, -1}}
    

    再来看真正的onLoadSoundEffects方法:

            private boolean onLoadSoundEffects() {
                int status;
    
                synchronized (mSoundEffectsLock) {
                    if (!mSystemReady) {
                        Log.w(TAG, "onLoadSoundEffects() called before boot complete");
                        return false;
                    }
    
                    if (mSoundPool != null) {
                        return true;
                    }
    
                    loadTouchSoundAssets();//根据XML文件初始化数组,如上所述
    
    				//初始化SoundPool
                    mSoundPool = new SoundPool.Builder()
                            .setMaxStreams(NUM_SOUNDPOOL_CHANNELS)
                            .setAudioAttributes(new AudioAttributes.Builder()
                                .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
                                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                                .build())
                            .build();
                    mSoundPoolCallBack = null;
                    mSoundPoolListenerThread = new SoundPoolListenerThread();
                    //这个线程以及下面的代码主要是去设置SoundPoolCallback,不求甚解
                    mSoundPoolListenerThread.start();
                    int attempts = 3;
                    while ((mSoundPoolCallBack == null) && (attempts-- > 0)) {
                        try {
                            // Wait for mSoundPoolCallBack to be set by the other thread
                            mSoundEffectsLock.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);
                        } catch (InterruptedException e) {
                            Log.w(TAG, "Interrupted while waiting sound pool listener thread.");
                        }
                    }
    
                    if (mSoundPoolCallBack == null) {
                        Log.w(TAG, "onLoadSoundEffects() SoundPool listener or thread creation error");
                        if (mSoundPoolLooper != null) {
                            mSoundPoolLooper.quit();
                            mSoundPoolLooper = null;
                        }
                        mSoundPoolListenerThread = null;
                        mSoundPool.release();
                        mSoundPool = null;
                        return false;
                    }
                    /*
                     * poolId table: The value -1 in this table indicates that corresponding
                     * file (same index in SOUND_EFFECT_FILES[] has not been loaded.
                     * Once loaded, the value in poolId is the sample ID and the same
                     * sample can be reused for another effect using the same file.
                     */
                     //创建一个和SOUND_EFFECT_FILES一样大的数组并将元素初始化为-1
                    int[] poolId = new int[SOUND_EFFECT_FILES.size()];
                    for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.size(); fileIdx++) {
                        poolId[fileIdx] = -1;
                    }
                    /*
                     * Effects whose value in SOUND_EFFECT_FILES_MAP[effect][1] is -1 must be loaded.
                     * If load succeeds, value in SOUND_EFFECT_FILES_MAP[effect][1] is > 0:
                     * this indicates we have a valid sample loaded for this effect.
                     */
    
                    int numSamples = 0;
                    for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
                        // Do not load sample if this effect uses the MediaPlayer
                        if (SOUND_EFFECT_FILES_MAP[effect][1] == 0) {
                            continue;
                        }
                        //第一次走到这里时这个判断一定为真,因为poolId中所有元素都为-1
                        if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == -1) {
                        	//getSoundEffectFilePath会根据SOUND_EFFECT_FILES中的内容补全出音频文件的具体路径
                            String filePath = getSoundEffectFilePath(effect);
                            //调用SoundPool.load方法,返回的sampleId被保存在SOUND_EFFECT_FILES_MAP和poolId中
                            int sampleId = mSoundPool.load(filePath, 0);
                            if (sampleId <= 0) {
                                Log.w(TAG, "Soundpool could not load file: "+filePath);
                            } else {
                                SOUND_EFFECT_FILES_MAP[effect][1] = sampleId;
                                poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = sampleId;
                                numSamples++;
                            }
                        } else {
                            SOUND_EFFECT_FILES_MAP[effect][1] =
                                    poolId[SOUND_EFFECT_FILES_MAP[effect][0]];
                        }
                    }
                    // wait for all samples to be loaded
                    if (numSamples > 0) {
                        mSoundPoolCallBack.setSamples(poolId);
    
                        attempts = 3;
                        status = 1;
                        while ((status == 1) && (attempts-- > 0)) {
                            try {
                                mSoundEffectsLock.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);
                                status = mSoundPoolCallBack.status();
                            } catch (InterruptedException e) {
                                Log.w(TAG, "Interrupted while waiting sound pool callback.");
                            }
                        }
                    } else {
                        status = -1;
                    }
    
                    if (mSoundPoolLooper != null) {
                        mSoundPoolLooper.quit();
                        mSoundPoolLooper = null;
                    }
                    mSoundPoolListenerThread = null;
                    if (status != 0) {
                        Log.w(TAG,
                                "onLoadSoundEffects(), Error "+status+ " while loading samples");
                        for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
                            if (SOUND_EFFECT_FILES_MAP[effect][1] > 0) {
                                SOUND_EFFECT_FILES_MAP[effect][1] = -1;
                            }
                        }
    
                        mSoundPool.release();
                        mSoundPool = null;
                    }
                }
                return (status == 0);
            }
    
    
            private String getSoundEffectFilePath(int effectType) {
                String filePath = Environment.getProductDirectory() + SOUND_EFFECTS_PATH
                        + SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]);
                if (!new File(filePath).isFile()) {
                    filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH
                            + SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]);
                }
                Log.d(TAG, "SoundEffectFilePath is : "+filePath);
                return filePath;
            }
    
    

    代码中难懂的部分基本上都有注释,核心其实就是为SoundPool的load方法准备参数,其中有些数组嵌套的部分比较绕,但是只要把数组都写出来就一目了然了

    onLoadSoundEffects基本上就是loadSoundEffects的全部内容,最后再来看一下onPlaySoundEffect的剩余部分

            private void onPlaySoundEffect(int effectType, int volume) {
                synchronized (mSoundEffectsLock) {
    
                    onLoadSoundEffects();
    
                    if (mSoundPool == null) {
                        return;
                    }
                    float volFloat;
                    // use default if volume is not specified by caller
                    if (volume < 0) {
                       volFloat = (float)Math.pow(10, (float)sSoundEffectVolumeDb/20);
                    } else {
                        volFloat = volume / 1000.0f;
                    }
    
                    if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) {
                    	//调用SoundPool的play方法
                        mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1],
                                            volFloat, volFloat, 0, 0, 1.0f);
                        Log.w(TAG, "Touch tone played");
                    } else {
                        MediaPlayer mediaPlayer = new MediaPlayer();
                        try {
                            String filePath = getSoundEffectFilePath(effectType);
                            mediaPlayer.setDataSource(filePath);
                            mediaPlayer.setAudioStreamType(AudioSystem.STREAM_SYSTEM);
                            mediaPlayer.prepare();
                            mediaPlayer.setVolume(volFloat);
                            mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
                                public void onCompletion(MediaPlayer mp) {
                                    cleanupPlayer(mp);
                                }
                            });
                            mediaPlayer.setOnErrorListener(new OnErrorListener() {
                                public boolean onError(MediaPlayer mp, int what, int extra) {
                                    cleanupPlayer(mp);
                                    return true;
                                }
                            });
                            mediaPlayer.start();
                        } catch (IOException ex) {
                            Log.w(TAG, "MediaPlayer IOException: "+ex);
                        } catch (IllegalArgumentException ex) {
                            Log.w(TAG, "MediaPlayer IllegalArgumentException: "+ex);
                        } catch (IllegalStateException ex) {
                            Log.w(TAG, "MediaPlayer IllegalStateException: "+ex);
                        }
                    }
                }
            }
    
    

    其中值得注意的点其实就只有SoundPool的play方法,其中传入了音量大小和之前load返回的sampleId

    二、替换原生按键音

    摘要:替换原生按键音的主要思路是:在初始化的时候在相关数组中增加自己自定义的音频资源,为了达到这个目的需要在文件中增加一些代表自己文件资源的常量,具体在哪个文件中增加,其实完全可以在熟悉源码流程之后模仿源码来增加;之后在播放按键音的时候主动调用自己的按键音资源就可以了;最后当然别忘了把音频文件push到设备中去。

    需要修改的文件如下:

    /frameworks/base/media/java/android/media/AudioManager.java
    需要增加自己的音频种类,起名为:FX_KEYPRESS_CUSTOM,并把最大音频数量修改为11

        /**
         * Invalid keypress sound
         * @see #playSoundEffect(int)
         */
        public static final int FX_KEYPRESS_INVALID = 9;
         /**
         * @hide Custom sound
         * @see #playSoundEffect(int)
         */
        public static final int FX_KEYPRESS_CUSTOM = 10;
        /**
         * @hide Number of sound effects
         */
        public static final int NUM_SOUND_EFFECTS = 11;
    

    需要注意的是自己增加的常量最好全部hide标记,这样可以免去执行make update-api指令,同时并不会影响使用,之后的修改都会遵循这一原则

    /frameworks/base/core/java/android/view/SoundEffectConstants.java
    同样需要增加一个常量:

        public static final int CLICK = 0;
    
        public static final int NAVIGATION_LEFT = 1;
        public static final int NAVIGATION_UP = 2;
        public static final int NAVIGATION_RIGHT = 3;
        public static final int NAVIGATION_DOWN = 4;
         /**
         * @hide Custom click sound
         */
        public static final int CLICK_CUSTOM = 5;
    

    /frameworks/base/core/res/res/xml/audio_assets.xml
    在XML文件中增加一个自己的音频文件,注意id和之前在AudioManager.java中增加的常量一致,file和push到设备中的文件名保持一致

    <audio_assets version="1.0">
        <group name="touch_sounds">
            <asset id="FX_KEY_CLICK" file="Effect_Tick.ogg"/>
            <asset id="FX_FOCUS_NAVIGATION_UP" file="Effect_Tick.ogg"/>
            <asset id="FX_FOCUS_NAVIGATION_DOWN" file="Effect_Tick.ogg"/>
            <asset id="FX_FOCUS_NAVIGATION_LEFT" file="Effect_Tick.ogg"/>
            <asset id="FX_FOCUS_NAVIGATION_RIGHT" file="Effect_Tick.ogg"/>
            <asset id="FX_KEYPRESS_STANDARD" file="KeypressStandard.ogg"/>
            <asset id="FX_KEYPRESS_SPACEBAR" file="KeypressSpacebar.ogg"/>
            <asset id="FX_KEYPRESS_DELETE" file="KeypressDelete.ogg"/>
            <asset id="FX_KEYPRESS_RETURN" file="KeypressReturn.ogg"/>
            <asset id="FX_KEYPRESS_INVALID" file="KeypressInvalid.ogg"/>
            <asset id="FX_KEYPRESS_CUSTOM" file="boom.ogg"/>
        </group>
    </audio_assets>
    

    准备工作已经完成了,现在来修改一下调用流程,主动调用自己的按键音

    /frameworks/base/core/java/android/view/View.java
    调用playSoundEffect时传入之前增加的常量:

        public boolean performClick() {
            // We still need to call this method to handle the cases where performClick() was called
            // externally, instead of through performClickInternal()
            notifyAutofillManagerOnClick();
    
            final boolean result;
            final ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnClickListener != null) {
                playSoundEffect(SoundEffectConstants.CLICK_CUSTOM);//修改这里
                li.mOnClickListener.onClick(this);
                result = true;
            } else {
                result = false;
            }
    
            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    
            notifyEnterOrExitForAutoFillIfNeeded(true);
    
            return result;
        }
    

    /frameworks/base/core/java/android/view/ViewRootImpl.java
    View.java之后会调用到ViewRootImpl.java中,在switch/case中加入我们自己的情况:

        @Override
        public void playSoundEffect(int effectId) {
            checkThread();
            Log.d(mTag, "playSoundEffect");
    
            try {
                final AudioManager audioManager = getAudioManager();
    
                switch (effectId) {
                    case SoundEffectConstants.CLICK:
                        audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
                        return;
                    case SoundEffectConstants.NAVIGATION_DOWN:
                        audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);
                        return;
                    case SoundEffectConstants.NAVIGATION_LEFT:
                        audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);
                        return;
                    case SoundEffectConstants.NAVIGATION_RIGHT:
                        audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);
                        return;
                    case SoundEffectConstants.NAVIGATION_UP:
                        audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);
                        return;
                    //增加的case语句
                    case SoundEffectConstants.CLICK_CUSTOM:
                        audioManager.playSoundEffect(AudioManager.FX_KEYPRESS_CUSTOM);
                        Log.d(mTag, "play my SoundEffect");
                        return;
                    default:
                        throw new IllegalArgumentException("unknown effect id " + effectId +
                                " not defined in " + SoundEffectConstants.class.getCanonicalName());
                }
            } catch (IllegalStateException e) {
                // Exception thrown by getAudioManager() when mView is null
                Log.e(mTag, "FATAL EXCEPTION when attempting to play sound effect: " + e);
                e.printStackTrace();
            }
        }
    

    以上就是修改的全部文件了,实际上只有5个文件,比预想的要简单的多,这全都要归功于Android源码出色的设计模式使其在代码上高度解耦

    别忘了把音频文件push到设备里面,否则会启动异常的哦!push的路径为:/system/media/audio/ui/

    可能有些小伙伴对于为什么要修改上面的文件有一些疑问,这里附上播放按键音的UML时序图,只要熟悉调用流程,就明白了
    在这里插入图片描述

    三、调节按键音音量方法

    其实在之前的讲解过程中已经说到了,在调用SoundPool.play的时候其实会传入左右声道的音量值,只要按图索骥找到之前是在哪里传入的音量就可以啦!其实是在playSoundEffectVolume方法传入的音量值,那么只要在这个方法的参数中传入你想要的值就行了。

    展开全文
  • android中,上层可使用的键值默认情况下是92个,从0-91;一般情况下,这些键值是够用的,但是如果想扩充的话,还是需要添加新的键值的,那么如何将一个新的键值从驱动的设置映射到上层,使应用可以对我们自定义的...
  • 在本篇文章里小编给大家整理的是关于Android系统添加自定义鼠标样式通过按键切换实例详解内容,有需要的朋友们可以学习下。
  • NULL 博文链接:https://chennaigong.iteye.com/blog/1143049
  • 本帖最后由 Xinxin_2011 于 2014-11-12 08:39 编辑网上存在一些关于Android系统添加自定义按键的文章,但大多针对Android2.3和4.0系统的,许多文件都已经变动位置了,这两天我总结了一些,写出来欢迎大家交流与指正...
  • 自定义按键

    2013-07-01 17:42:12
    类似钢琴音的12个基准音,频率从440HZ到它的2倍,每次乘以2的开12次方,即可得到该基准音。
  • | android:verticalCorrection | Amount to offset the touch Y coordinate by, for bias correction. | 我们需要建一个xml文件,来布局我们的视图, 一般是在res文件夹中建一个名为xml的文件夹...一些可以自定义设置
  • android 自定义按钮 您好,今天我们将看到如何在android自定义按钮。 我们将看到如何制作带有背景色的圆角按钮,以及如何渐变到按钮。 有关两个示例,请参见下面的gif。 首先,让我们看看 如何在Android中...
  • 主要为大家详细介绍了Android自定义控件之电话拨打小键盘,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • Android绑定EditText自动弹出自定义软键盘不同按键设置不同背景的实现(自动向上顶适应布局),适配带虚拟键盘的特殊机型,
  • Android P 添加自定义按键

    千次阅读 2020-01-08 13:55:16
    1.kernel-4.4/arch/arm64/boot/dts/mediatek/tb8788p1_64_...其中633与634为自定义按键 &keypad { + mediatek,kpd-hw-init-map = <114 633 0 0 0 0 0 0 0 580 634 0 0 0 0 0 0 0 102 158 0 0 0 0 0 0 0 0 0...
  • 1,linux内核定义一个按键扫描码 在按键的配置文件中设置扫描码,比如dts中 rotate-key { linux,code = <760>; label = "rotate"; rockchip,adc_value = <355>; }; 其中“linux,code”就是按键扫描码...
  • Android 自定义功能按键实现

    千次阅读 2017-01-19 14:22:19
    我们在做Android 系统开发时,可能由于硬件需要,需要加入一些定义功能按键,但是怎么去实现这些按键的功能呢? 下面就是博主的基于rk3288 板子的一些经验1、实体按键比如有一些实体按键可能要定义这个按键的功能。...
  • Android自定义带箭头的Progressbar

    热门讨论 2014-09-12 12:50:31
    本例实现带箭头的自定义横向进度条,通过这个demo还可以改造成动画带着进度条跑动等效果。
  • 很多时候android常用的控件不能满足我们的需求,那么我们就需要自定义一个控件了。今天做了一个自定义控件的实例,来分享下。首先定义一个layout实现按钮内部布局:android:layout_width="fill_parent"android...
  • 今天给大家介绍一款安卓系统上的辅助快捷APP,不但有“AssistiveTouch”的全部功能,更可以自定义快捷键。这款APP名叫《Floating Toucher》使用起来非常简单,安装好以后直接启动就会进入设置界面。默认功能有Home键...
  • 安卓TV开发之自定义键盘,对应博客地址,https://blog.csdn.net/zhangxiangliang2/article/details/82751681
  • 主要介绍了android自定义形状的按键实例代码,本文分步骤给大家介绍的非常详细,需要的朋友可以参考下
  • /frameworks/base/core/java/android/view/KeyEvent.java public static final int KEYCODE_PROFILE_SWITCH = 288; // 定义添加物理按键 public static final int KEYCODE_PTT = 289; public static final int ...
  • 主要告诉大家Android按键添加声音的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java 在PhoneWindowManager.java文件中添加长按和抬起功能 volatile boolean mRecordOkKeyHandled; private void ...
  • 在项目中,产品对于输入方式会有特殊的要求,需要对输入方式增加特定的限制,这就需要采用自定义键盘。本文主要讲述数字键盘和字母键盘的自定义实现。键盘效果:自定义键盘的实现步骤如下:自定义CustomKeyboard, ...
  • public class CustomListener { private static long mLastClickTime; private long timeInterval = 1000L; public CustomListener() { long nowTime = System.currentTimeMillis(); ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 20,079
精华内容 8,031
关键字:

安卓自定义按键