Android 手机按键客制化详解
在Android 中会有以下5个按键(Back、Home、Menu、Power、Volume)与用户进行交互,Framework 层中实现按键功能,因此,从手机系统定制的角度,可以满足客户的客制化要求。本文主要从Framework层浅析这些客制化需求的实现。
Back、Home、Menu、Power、Volume 按键图
- Android 按键修改相关的类
- PhoneWindowManager 简介
- 如何打开 或者 关闭 Navigation Bar
- 如何长按Home 键启动Google Now
- 如何长按实体Menu键进入多窗口模式
- 如何点击 Menu键进入调出最近任务列表
- 如何让App拿到Power key 值
- 如何修Activity启动是的窗口(app启动白屏,黑屏问题)
- WindowManagerPolicy 简介
欢迎关注微信公众号:程序员Android
公众号ID:ProgramAndroid
获取更多信息
微信公众号:ProgramAndroid
我们不是牛逼的程序员,我们只是程序开发中的垫脚石。
我们不发送红包,我们只是红包的搬运工。
1. Android 按键修改相关的类
以MTK 平台为例,按键客制化的代码主要存放在以下类中
-
- PhoneWindowManager
PhoneWindowManager 代码路径如下:
\alps\frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
-
- 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
虚拟导航栏
解决方法:
- 修改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
- 修改 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 "文件中的字符串"
- 修改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
- 预制 Google Now APK
请自行安装APK
- 修改 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);
}
}
- 在按键事件分发之前处理
在按键分发处理之前调用自定义长按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 分屏多任务模式。
解决方案如下:
- 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);
}
};
- 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();
}
}
...
}
- 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值,但通过以下方法可以实现。
- 修改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处理
}
}
...
}
- 如果只想让某个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)是浅色(例如白色)的情况下比较明显。
此所谓的闪"黑屏",其实是应用程序的启动窗口。
启动窗口出现的条件如下:
-
仅在要启动的Activity在新的Task或者新的Process时,才可能显示启动窗口
-
启动窗口先于Activity窗口显示,当Activity窗口的内容准备好之后,启动窗口就会被移除掉,show出真正的activity 窗口
-
启动窗口和普通的Activity window类似,只是没有画任何内容,默认是一个黑色背景的窗口
正是由于启动窗口默认是黑色背景的,所以在当前的手机主题为浅色调的时候,就比较容易因为颜色的深浅对比而产生一种视觉上的闪动感。
解决方法如下:
1.去掉启动窗口
在 ActivityStack.java中将SHOW_APP_STARTING_PREVIEW 设置为false 既可
- 修改启动窗口样式
在 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.微信红包,微信扫一扫即可领取红包
微信扫一扫,每天领取微信红包
小礼物走一走,来简书关注我