Android 手机按键客制化详解

2023-05-16

在Android 中会有以下5个按键(Back、Home、Menu、Power、Volume)与用户进行交互,Framework 层中实现按键功能,因此,从手机系统定制的角度,可以满足客户的客制化要求。本文主要从Framework层浅析这些客制化需求的实现。

Back、Home、Menu、Power、Volume 按键图

  1. Android 按键修改相关的类
  2. PhoneWindowManager 简介
  3. 如何打开 或者 关闭 Navigation Bar
  4. 如何长按Home 键启动Google Now
  5. 如何长按实体Menu键进入多窗口模式
  6. 如何点击 Menu键进入调出最近任务列表
  7. 如何让App拿到Power key 值
  8. 如何修Activity启动是的窗口(app启动白屏,黑屏问题)
  9. WindowManagerPolicy 简介

欢迎关注微信公众号:程序员Android
公众号ID:ProgramAndroid
获取更多信息

微信公众号:ProgramAndroid

我们不是牛逼的程序员,我们只是程序开发中的垫脚石。
我们不发送红包,我们只是红包的搬运工。

1. Android 按键修改相关的类

以MTK 平台为例,按键客制化的代码主要存放在以下类中

    1. PhoneWindowManager

PhoneWindowManager 代码路径如下:

\alps\frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
    1. WindowManagerPolicy

PhoneWindowManager 实现 的接口类WindowManagerPolicy 代码路径如下:

alps\frameworks\base\core\java\android\view\WindowManagerPolicy.java

2. PhoneWindowManager 简介

PhoneWindowManager 类实现接口如下:

java.lang.Object
    ↳  android.view.WindowManagerPolicy.java
         ↳ com.android.server.policy.PhoneWindowManager.java

PhoneWindowManager 类实现关系

PhoneWindowManager主要用于实现各种实体或虚拟按键处理,如需特殊处理按键,请修改源码。

3. 如何打开 或者 关闭 Navigation Bar

虚拟导航栏

解决方法:

  1. 修改config.xml 文件中

搜索关键字 config_showNavigationBar , 查看 config_showNavigationBar 值
true 表示显示,false 表示不显示

  <!-- Whether a software navigation bar should be shown. NOTE: in the future this may be
         autodetected from the Configuration. -->
    <bool name="config_showNavigationBar">true</bool>

参考路径如下:
alps\frameworks\base\core\res\res\values\config.xml

  1. 修改 system.prop 文件

查询关键字 qemu.hw.mainkeys ,并查看值,0 表示关闭 1.表示开启 。

# temporary enables NAV bar (soft keys)
qemu.hw.mainkeys=1

不同项目文件存放地址不一样,可以使用以下命令查找
终端下查找文件方法

find 路径 -name "文件名.java"

或者直接查找文件中的字符串

 find 路径 -type f -name "文件名" | xargs grep "文件中的字符串"

  1. 修改PhoneWindowManager代码

如果上面两个修改都不生效(搜索关键字config_showNavigationBar、qemu.hw.mainkeys),请在PhoneWindowManager 查看setInitialDisplaySize方法中mHasNavigationBar 的值是否被写死,true表示会显示、false 表示不显示导航栏。

 @Override
    public void setInitialDisplaySize(Display display, int width, int height, int density) {
       ...
     // mHasNavigationBar  值控制是否显示虚拟导航栏
        mHasNavigationBar = res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
     
        ...
      }

4. 如何长按Home 键启动Google Now

  1. 预制 Google Now APK

请自行安装APK

  1. 修改 PhoneWindowManager 代码

长按Home键启动Google Now ,实现方法参考launchAssistLongPressAction 功能实现。

 private void launchAssistAction(String hint, int deviceId) {
        sendCloseSystemWindows(SYSTEM_DIALOG_REASON_ASSIST);
        if (!isUserSetupComplete()) {
            // Disable opening assist window during setup
            return;
        }
        Bundle args = null;
        if (deviceId > Integer.MIN_VALUE) {
            args = new Bundle();
            args.putInt(Intent.EXTRA_ASSIST_INPUT_DEVICE_ID, deviceId);
        }
        if ((mContext.getResources().getConfiguration().uiMode
                & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION) {
            // On TV, use legacy handling until assistants are implemented in the proper way.
            ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
                    .launchLegacyAssist(hint, UserHandle.myUserId(), args);
        } else {
            if (hint != null) {
                if (args == null) {
                    args = new Bundle();
                }
                args.putBoolean(hint, true);
            }
            StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
            if (statusbar != null) {
                statusbar.startAssist(args);
            }
        }
    }

自己实现常按Home 键吊起Google Now 方法,供在按键分发处理事件时候调用。

 private void launchAssistLongPressAction() {
        performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
        sendCloseSystemWindows(SYSTEM_DIALOG_REASON_ASSIST);

        // launch the search activity
        Intent intent = new Intent(Intent.ACTION_SEARCH_LONG_PRESS);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        try {
            // TODO: This only stops the factory-installed search manager.
            // Need to formalize an API to handle others
            SearchManager searchManager = getSearchManager();
            if (searchManager != null) {
                searchManager.stopSearch();
            }
            startActivityAsUser(intent, UserHandle.CURRENT);
        } catch (ActivityNotFoundException e) {
            Slog.w(TAG, "No activity to handle assist long press action.", e);
        }
    }


  private SearchManager getSearchManager() {
        if (mSearchManager == null) {
            mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
        }
        return mSearchManager;
    }


  private void startActivityAsUser(Intent intent, UserHandle handle) {
        if (isUserSetupComplete()) {
            mContext.startActivityAsUser(intent, handle);
        } else {
            Slog.i(TAG, "Not starting activity because user setup is in progress: " + intent);
        }
    }

  1. 在按键事件分发之前处理

在按键分发处理之前调用自定义长按Home 键的方法

   @Override
    public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
          ...
 } else if (keyCode == KeyEvent.KEYCODE_ASSIST) {
            if (down) {
                if (repeatCount == 0) {
                    mAssistKeyLongPressed = false;
                } else if (repeatCount == 1) {
                    mAssistKeyLongPressed = true;
                    if (!keyguardOn) {
                         launchAssistLongPressAction();
                    }
                }
            ...
}

注意 双击Home 键调出最近任务列表请用以下方法

双击Home 键调出最近任务列表

在 phoneWindowManager.java 的 interceptKeyBeforeQueueing 方法中修改
修改方法如下:

    int result = 0; // 原为 int result, 请加入初始值.
        // 请在类中补充 boolean homeDownDoubleClick = false; 的定义
        // 请在类中补充 long lastHomeDownTime=0; 的定义
        // 请在类中补充 long lastHomeUpTime=0; 的定义
        // 检测原理: 检测上一次按下的 home key 与本次按下的 home key 时间间隔是否 < 500ms
        // if yes, 则认为是双击 home key

        if (keyCode == KeyEvent.KEYCODE_HOME) {
            if (down) {

                // this is home down
                if (((event.getEventTime() - lastHomeDownTime) < 500)) {
                    homeDownDoubleClick = true;
                } else {
                    homeDownDoubleClick = false;
                }
                lastHomeDownTime = event.getEventTime();
            } else {
                // then home up comes
                Log.d(TAG, "homeDownDoubleClick=" + homeDownDoubleClick
                        + ",lastHomeDownTime=" + lastHomeDownTime
                        + ",lastHomeUpTime=" + lastHomeUpTime
                        + ",this home up=" + event.getEventTime());

                if (homeDownDoubleClick
                        && ((event.getEventTime() - lastHomeUpTime) < 500)) {

                    Log.d(TAG, "double click on home detected");
                    try {
                        IStatusBarService statusbar = getStatusBarService();
                        if (statusbar != null) {
                            // 调出最近任务列表
                            statusbar.preloadRecentApps();
                            statusbar.toggleRecentApps();
                        }
                    } catch (RemoteException e) {
                        Slog.e(TAG,
                                "RemoteException when preloading recent apps",
                                e);
                        mStatusBarService = null;
                    }

                    result |= ACTION_WAKE_UP;
                    return result;
                }
                lastHomeUpTime = event.getEventTime();
            }
        }

 

5. 如何长按实体Menu键进入多窗口模式

Android N上支持Multi-Window,通过recent key 进入多窗口,对于没有打开虚拟导航栏,只有实体menu按键的手机,可以考虑向SystemUI发送广播的形式,进入Android 分屏多任务模式。
解决方案如下:

  1. PhoneStatusBar 里注册广播

PhoneStatusBar 是SystemUI模块的代码,参考路径如下:

alps/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java

自定义广播实现可以参考系统mDemoReceiver 的实现方法
动态注册广播方法如下:

   context.registerReceiverAsUser(mDemoReceiver, UserHandle.ALL, demoFilter,
                android.Manifest.permission.DUMP, null);
                
        context.registerReceiverAsUser(mAppLongSwitchReceiver, UserHandle.ALL, new IntentFilter("广播的Action"),
                android.Manifest.permission.DUMP, null);

自定义接收广播后,onReceive处理事件实现分屏方法如下:

private BroadcastReceiver mAppLongSwitchReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            if (DEBUG) Log.v(TAG, "onReceive: " + intent);
            toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS,
                    MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS);
        }
    };
  1. PhoneWindowManager 中发送广播

在 PhoneWindowManager 的interceptKeyBeforeDispatching方法中发送广播

@Override
    public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
       ...

          if (!keyguardOn) {
            
                if (down && repeatCount == 1){
                    Intent intent = new Intent("com.app_long_switch");
                    mContext.sendBroadcast(intent);
                    return -1;
                }
                
                
                if (down && repeatCount == 0) {
                    preloadRecentApps();
                } else if (!down) {
                    toggleRecentApps();
                }
            }
       ...
     
      }

  1. destory 方法注销广播

再destory 方法中记得一定要注销广播

 mContext.unregisterReceiver(mDemoReceiver);
 mContext.unregisterReceiver(mAppLongSwitchReceiver);

6. 如何点击 Menu键进入调出最近任务列表

如果想调出最近任务列表,需要拦截menu的事件,在PhoneWindowManager的interceptKeyBeforeDispatching 中处理即可

else if (keyCode == KeyEvent.KEYCODE_MENU) {
            // Hijack modified menu keys for debugging features
            final int chordBug = KeyEvent.META_SHIFT_ON;
             this.toggleRecentApps();
             return -1;
            

如果想长按Menu 调出可以使用以下方法

else if (keyCode == KeyEvent.KEYCODE_MENU) {
            // Hijack modified menu keys for debugging features
            final int chordBug = KeyEvent.META_SHIFT_ON;
            if(KeyEvent.ACTION_UP == event.getAction()
              &&event.getEventTime()-event.getDownTime()>500){//long press
             this.toggleRecentApps();
             return -1;
            }

7. 如何让 App 拿到Power key 值

一般情况下App 是拿不到Power的Key值,但通过以下方法可以实现。

  1. 修改PhoneWindowManager 文件实现

在PhoneWindowManager 中修改interceptKeyBeforeQueueing 方法实现让特定的APP拿到Power key 值

 /** {@inheritDoc} */
    @Override
    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
       ...
         case KeyEvent.KEYCODE_POWER: {
            //com.example.adc为要处理power key的包名
              if(win != null && win.getAttrs() !=null&&win.getOwningPackage().equals("com.example.adc")){
                  return 1;// return 1事件就传给app处理
       }
 
        }
       ... 

}
  1. 如果只想让某个app的某个Activity 处理
 /** {@inheritDoc} */
    @Override
    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
       ...
         case KeyEvent.KEYCODE_POWER: {
            // 如果只想让power键让某个Activity处理,将以上的if条件改为:
            if(win != null && win.getAttrs() != null&&win.getAttrs().getTitle().equals("xxx.xxx.xxx.xxxActivity")){
                return 1;// return 1 就会传给 xxx.xxx.xxx.xxxActivity处理
}
        }
       ... 

}

8. 如何修Activity启动是的窗口(app启动白屏,黑屏问题)

当用户从主菜单进入其他应用程序例如时钟、联系人、文件管理等时,可能会出现屏幕闪一下黑屏、白屏等问题,这种现象在当前手机主题(Theme)是浅色(例如白色)的情况下比较明显。

此所谓的闪"黑屏",其实是应用程序的启动窗口。
启动窗口出现的条件如下:

  1. 仅在要启动的Activity在新的Task或者新的Process时,才可能显示启动窗口

  2. 启动窗口先于Activity窗口显示,当Activity窗口的内容准备好之后,启动窗口就会被移除掉,show出真正的activity 窗口

  3. 启动窗口和普通的Activity window类似,只是没有画任何内容,默认是一个黑色背景的窗口

正是由于启动窗口默认是黑色背景的,所以在当前的手机主题为浅色调的时候,就比较容易因为颜色的深浅对比而产生一种视觉上的闪动感。

解决方法如下:

1.去掉启动窗口

在 ActivityStack.java中将SHOW_APP_STARTING_PREVIEW 设置为false 既可

  1. 修改启动窗口样式
    在 PhoneWindowManager中的addStartingWindow 方法中添加自定义样式或者背景等
    /** {@inheritDoc} */
    @Override
    public View addStartingWindow(IBinder appToken, String packageName, int theme,
            CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
            int icon, int logo, int windowFlags, Configuration overrideConfig) {

   
             ...
             //添加自定义背景
             View.setBackgroundColor(...);    
       
             ...
             
            }

9. WindowManagerPolicy 简介

PhoneWindowManager 实现 的接口类如下:

alps\frameworks\base\core\java\android\view\WindowManagerPolicy.java

WindowManagerPolicy 接口实现

WindowManagerPolicy 是一个接口类,主要对外提供一些接口。
常用接口如下:

 

WindowState 接口

WindowMangerFuncs接口

Screen On 接口

Keyguard 接口

至此,本篇已结束,如有不对的地方,欢迎您的建议与指正。同时期待您的关注,感谢您的阅读,谢谢!

如有侵权,请联系小编,小编对此深感抱歉,届时小编会删除文章,立即停止侵权行为,请您多多包涵。

既然都看到这里,领两个红包在走吧!
以下两个红包每天都可以领取

1.支付宝搜索 522398497,或扫码支付宝红包海报。

支付宝扫一扫,每天领取大红包

2.微信红包,微信扫一扫即可领取红包

 

微信扫一扫,每天领取微信红包

小礼物走一走,来简书关注我

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android 手机按键客制化详解 的相关文章

随机推荐

  • 【测试沉思录】15. 性能测试中的系统资源分析之二:内存

    作者 xff1a 马海琴 编辑 xff1a 毕小烦 二 内存 内存又称主存 xff0c 是 CPU 能直接寻址的存储空间 xff08 由半导体器件制成 xff09 内存的特点是存取速率快 xff0c 断电一般不保存数据 xff08 非持久化
  • 【测试沉思录】16. 性能测试中的系统资源分析之三:磁盘

    作者 xff1a 马海琴 编辑 xff1a 毕小烦 三 磁盘 磁盘是可以持久化存储的设备 xff0c 根据存储介质的不同 xff0c 常见磁盘可以分为两类 xff1a 机械磁盘和固态磁盘 磁盘就像人的大脑皮层 xff0c 负责数据的储存 记
  • 【测试沉思录】17. 性能测试中的系统资源分析之四:网络

    作者 xff1a 马海琴 编辑 xff1a 毕小烦 计算机网络 xff0c 就是通过光缆 电缆 电话线或无线通讯将两台以上的计算机互连起来的集合 xff0c 包括广域网 城域网 局域网和无线网 计算机网络是传输信息的媒介 我们常说的千兆网
  • 【测试沉思录】18.如何测试微信小程序?

    作者 xff1a 雷远缘 编辑 xff1a 毕小烦 一 先知道小程序是什么 啥是小程序 xff1f 小程序是一种不需要下载安装即可使用的应用 xff0c 它实现了应用 触手可及 的梦想 xff0c 用户扫一扫或者搜一下即可打开应用 也体现了
  • 【测试沉思录】19. 如何设置 JMeter 线程组?

    作者 xff1a 宋赟 编辑 xff1a 毕小烦 最近有不少测试同学问我 JMeter 线程组如何设置并发的问题 xff0c 发现很多人对线程组里的参数不是很清楚 xff0c 今天就科普一下 JMeter 线程组的信息 xff0c 也简单介
  • 【测试沉思录】20. 如何做好测试需求分析?

    作者 xff1a 刘亚茹 编辑 xff1a 毕小烦 我们都知道测试用例是软件测试中保障质量的必要手段 xff0c 而测试需求作为用例编写的主要依据却往往被很多人忽视 到底什么是测试需求 xff1f 又如何做好测试需求分析呢 xff1f 本文
  • 【测试沉思录】21. 如何用 JMeter 编写性能测试脚本?

    作者 xff1a 宋赟 编辑 xff1a 毕小烦 Apache JMeter 应该是应用最广泛的性能测试工具 怎么用 JMeter 编写性能测试脚本 xff1f 1 编写 HTTP 性能测试脚本 STEP 1 添加 HTTP 请求 STEP
  • 【测试沉思录】21. 如何用 JMeter 编写性能测试脚本?

    作者 xff1a 宋赟 编辑 xff1a 毕小烦 Apache JMeter 应该是应用最广泛的性能测试工具 怎么用 JMeter 编写性能测试脚本 xff1f 1 编写 HTTP 性能测试脚本 STEP 1 添加 HTTP 请求 STEP
  • 【测试沉思录】22. 前端性能测试怎么做?

    作者 xff1a 张丹青 编辑 xff1a 毕小烦 普通用户如何评价一个网站的体验好不好呢 xff1f 除了满足他的功能需求以外 xff0c 用得爽不爽可能是最大的评估因素 这个爽不爽可以简单理解为快不快 xff0c 好不好看 xff0c
  • 【测试沉思录】23. 如何实现基于场景的接口自动化测试用例?

    作者 xff1a 陈爱娇 编辑 xff1a 毕小烦 自动化本身是为了提高工作效率 xff0c 不论选择何种框架 xff0c 何种开发语言 xff0c 我们最终想实现的效果 xff0c 就是让大家用最少的代码 xff0c 最小的投入 xff0
  • 搭建linux服务器详细教程

    Linux服务器的部署 xff0c 配置 xff0c 搭建步骤 xff1a 1 准备 xff1a 1 1 jdk1 8 xff1a jdk 8u11 linux x64 tar gz tomcat xff1a apache tomcat 8
  • 使用Word2013写论文的时候,波浪号(~)一直在一行的上面,无法上下居中 的解决方案

    这里提供四种方法给大家 xff0c 不用谢 xff01 1 搜狗输入法 xff0c 直接打 blh xff0c 即可获得波浪号 xff08 这个方法大多数地方都可以用 xff0c 比如打摄氏度符号 xff08 xff09 的时候 xff09
  • centos7.4安装图形界面并远程桌面连接

    1 系统版本 CentOS release 6 2 Final 以下安装需要用root权限操作 2 安装x windows yum groupinstall y X Window System 注意有引号 3 安装图形界面软件 GNOME
  • linux服务器更改网络配置

    文章目录 前言一 更改vmware的虚拟网络配置二 修改window的网络配置三 修改虚拟机内部的配置四 映射 选做 修改hostname修改hosts修改windows的配置验证 前言 linux服务器更改网络配置 xff0c 是为让它的
  • FastBoot 刷机教程

    本篇文章主要介绍 Android 开发中的 FastBoot 部分知识点 xff0c 通过阅读本篇文章 xff0c 您将收获以下内容 一 Fastboot 简介 欢迎关注微信公众号 程序员Android 微信公众号 xff1a Progra
  • Google GMS Crash 优化方案

    极力推荐文章 xff1a 欢迎收藏 Android 干货分享 阅读五分钟 xff0c 每日十点 xff0c 和您一起终身学习 xff0c 这里是程序员Android GMS GoogleMobile Service 包是出口国外手机中 Go
  • FastBoot 刷机使用方法

    和你一起终身学习 xff0c 这里是程序员Android 经典好文推荐 xff0c 通过阅读本文 xff0c 您将收获以下知识点 一 Fastboot 简介 二 Fastboot 刷机准备 三 Fastboot 刷机命令 四 其他刷机工具
  • Android 系统奔溃触发WatchDog分析

    和你一起终身学习 xff0c 这里是程序员Android 经典好文推荐 xff0c 通过阅读本文 xff0c 您将收获以下知识点 一 前言 二 场景介绍 三 分析trace文件 一 前言 作为一个Android开发者 xff0c 不管是Ap
  • Android 人脸解锁源码剖析

    和你一起终身学习 xff0c 这里是程序员Android 经典好文推荐 xff0c 通过阅读本文 xff0c 您将收获以下知识点 一 人脸识别身份验证HIDL 二 人脸模块流程分析 三 人脸录入 四 人脸匹配 五 人脸解锁屏幕 一 人脸识别
  • Android 手机按键客制化详解

    在Android 中会有以下5个按键 xff08 Back Home Menu Power Volume xff09 与用户进行交互 xff0c Framework 层中实现按键功能 xff0c 因此 xff0c 从手机系统定制的角度 xf