发现很多手机(比如三星)的导航栏向上滑动可以即时显示,如果没有操作过几秒将自动隐藏,刚好客户有一个类似的需求,决定来搞一下。
首先来考虑一下向上滑动这个动作,它是一个系统全局的动作,我们基本确定要修改的是PhoneWindowManager,之前有注意到这里面有两个可以帮助我们实现这个需求的东西:SystemGesturesPointerEventListener和IStatusBarService。前者用来监听手势动作,后者使用IPC的方式或许可以帮助我们动态改变SystemUI的相关视图,当然,相关的接口需要我们自己来添加实现。
由于我们之前的需求已经将虚拟导航栏去掉,所以先要将其恢复:
device/../prj/system.prop
qemu.hw.mainkeys=0
监听手势动作
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
// monitor for system gestures
mSystemGestures = new SystemGesturesPointerEventListener(context,
new SystemGesturesPointerEventListener.Callbacks() {
@Override
public void onSwipeFromTop() {
/// M: Disable gesture in immersive mode. {@
if (isGestureIsolated()) {
return;
}
/// @}
if (mStatusBar != null) {
requestTransientBars(mStatusBar);
}
}
@Override
public void onSwipeFromBottom() {
/// M: Disable gesture in immersive mode. {@
if (isGestureIsolated()) {
return;
}
/// @}
if (mNavigationBar != null && mNavigationBarOnBottom) {
requestTransientBars(mNavigationBar);
}
//zyl add
else{
IStatusBarService service = getStatusBarService();
if (service != null) {
try {
service.addNavigationBar();
} catch (RemoteException e) {
// do nothing.
}
}
}
}
@Override
public void onSwipeFromRight() {
/// M: Disable gesture in immersive mode. {@
if (isGestureIsolated()) {
return;
}
/// @}
if (mNavigationBar != null && !mNavigationBarOnBottom) {
requestTransientBars(mNavigationBar);
}
}
...
})
添加并实现我们的aidl方法
frameworks/base/core/java/android/internal/statusbar/IStatusBarService.aidl
void addNavigationBar(); //zyl add
它的具体实现在相对应的service中
frameworks/base/core/java/com/android/server/statusbar/StatusBarManagerService.java
//zyl add
@Override
public void addNavigationBar(){
if (mBar != null) {
try {
mBar.addNavigationBar();
} catch (RemoteException ex) {
}
}
}
参照系统里的其它方法第二次IPC
frameworks/base/core/java/android/internal/statusbar/IStatusBar.aidl
void addNavigationBar();//zyl add
找到它的实现类
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
这里在它的实现方法中使用Handler将信息传给callback,最终的实现将在Callbacks的实现类中。
//zyl add
@Override
public void addNavigationBar(){
synchronized (mLock) {
mHandler.removeMessages(MSG_ADD_NAVIGATIONBAR);
mHandler.obtainMessage(MSG_ADD_NAVIGATIONBAR).sendToTarget();
}
}
private static final int MSG_ADD_NAVIGATIONBAR = 33 << MSG_SHIFT; //zyl add
public interface Callbacks {
...
void addNavigationBarView(); //zyl add
}
private final class H extends Handler {
public void handleMessage(Message msg) {
...
//zyl add
case MSG_ADD_NAVIGATIONBAR:
mCallbacks.addNavigationBarView();
break;
}
}
找到CommandQueue.Callbacks的实现类
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
public abstract class BaseStatusBar extends SystemUI implements
CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener,
ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
ExpandableNotificationRow.OnExpandClickListener,
OnGutsClosedListener {
...
//zyl add
@Override
public void addNavigationBarView(){
}
}
BaseStatusBar是一个抽象类,所以我们更应该关注它的子类。
第一个实现类PhoneStatusBar
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
//zyl add
private static final String ACTION_DISPLAY_NAR_BAR="com.xxx.ACTION_DISPLAY_NAVBAR";
private static final String ACTION_HIDE_NAV_BAR="com.xxx.ACTION_HIDE_NAVBAR";
private static final String ACTION_REFRESH_NAV_BAR="com.xxx.ACTION_REFRESH_NAV_BAR";
private static final int DELAY_REMOVE_NAVBAR=5000;
private BroadcastReceiver mNavBarStatusReceiver=new BroadcastReceiver(){
public void onReceive(Context context, Intent intent) {
String action=intent.getAction();
if(action.equals(ACTION_REFRESH_NAV_BAR)){
if(mNavigationBarView!=null){
mHandler.removeCallbacks(delayRemoveNavBar);
mHandler.postDelayed(delayRemoveNavBar,DELAY_REMOVE_NAVBAR);
}
}
}
};
private Runnable delayRemoveNavBar=new Runnable(){
@Override
public void run() {
if(mNavigationBarView!=null){
mWindowManager.removeView(mNavigationBarView);
mNavigationBarView=null;
}
}
};
@Override
public void start() {
...
//zyl add
IntentFilter filter=new IntentFilter();
filter.addAction(ACTION_REFRESH_NAV_BAR);
mContext.registerReceiver(mNavBarStatusReceiver,filter);
mHandler.postDelayed(delayRemoveNavBar,DELAY_REMOVE_NAVBAR);
}
@Override
public void destroy() {
...
mContext.unregisterReceiver(mNavBarStatusReceiver);
}
//zyl add
@Override
public void addNavigationBarView(){
if (mNavigationBarView == null) {
mNavigationBarView = (NavigationBarView) View.inflate(mContext, R.layout.navigation_bar, null);
mNavigationBarView.setDisabledFlags(mDisabled1);
prepareNavigationBarView();
}
mWindowManager.addView(mNavigationBarView, getNavigationBarLayoutParams());
mHandler.postDelayed(delayRemoveNavBar,DELAY_REMOVE_NAVBAR);
}
这里我定义了多个广播,可以扩展一下问题,比如在setting快速设置面板增加是否常显NavBar,5秒后自动隐藏使用了Handler的postDelayed方法。当然,关于这个地方的处理肯定还有别的方法,我这里为了简单省事,使用的广播。
第二个实现类TvStatusBar
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
//zyl add
@Override
public void addNavigationBarView(){
}
因为我们做的是手机,所以这个类肯定用不到,只需要重写方法即可。
广播的发出
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
void sendEvent(int action, int flags, long when) {
final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
InputDevice.SOURCE_KEYBOARD);
InputManager.getInstance().injectInputEvent(ev,
InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
//zyl add
Intent intent=new Intent("com.xxx.ACTION_REFRESH_NAV_BAR");
mContext.sendBroadcast(intent,null);
}
这个类其实比较需要关注一下,特别是 InputManager.getInstance().injectInputEvent这个方法将普通的触摸事件转换成了按键事件。而我们选择在事件转换的同时发出广播,时机上相对合适。但作为一个自定义View,在内部做这种处理很方便,但不推荐。更合适的方法或许我们可以在内部定义一个接口,选择在PhoneStatusBar中实现此接口比较好。
NavigationBar背景色的问题
通过以上修改我们实现了想要的效果,但有一个问题就是很多界面上导航栏显示黑色背景,讲真的,很丑。我知道Google的意图是让上层应用根据自己的需求来修改这个背景色,但问题是系统级的应用大都没有这方面的考虑。最合适的方法当然是去修改每一个应用,但那不是我一个人的工作。所以我尝试从系统级别做一个统一的透明色修改。
首先来看布局文件
frameworks/base/packages/SystemUI/res/layout/navigation_bar.xml
这个父级布局的背景色看起来确实使用了黑色,我们将其改为透明看看
android:background="@color/system_bar_background_transparent"
编译刷机,完全没有效果!好吧,那只能是java代码里还有调控了,查找相应的类,找到一个父类的调用方法中有修改:
frameworks/base/packages/SystemUI/com/android/systemui/statusbar/phone/BarTransitions.java
public BarTransitions(View view, int gradientResourceId) {
mTag = "BarTransitions." + view.getClass().getSimpleName();
mView = view;
mBarBackground = new BarBackgroundDrawable(mView.getContext(), gradientResourceId);
/// M: Modify statusbar style for GMO
if (HIGH_END || FeatureOptions.LOW_RAM_SUPPORT) {
//zyl modify
if(view instanceof NavigationBarView){
}else{
mView.setBackground(mBarBackground);
}
}
}
我们的NavigationBarView调用到这里的时候不修改背景色,而如果是StatusBar的时候还是保持系统的设计。
编译刷机,有效果,但部分界面还是黑色的背景,并且在显示透明背景的时候有一个从黑色到透明色的变化。那说明在这个view添加到window的时候使用的是黑色,我们再来看一下PhoneWindow中有没有相关的颜色控制。
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
注意到有一个变量mNavigationBarColor,并且在setNavigationBarColor的时候更新的就是此变量,我们需要改变的是view在初始化的时候此变量值。
protected ViewGroup generateLayout(DecorView decor) {
...
if (!mForcedNavigationBarColor) {
//mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
//zyl modify
mNavigationBarColor = 0x00000000;
}
...
}
编译刷机,导航栏初始颜色变为透明,跟我们想的一样,但部分应用的界面依然显示为黑色背景。个人认为还是与window的创建添加过程有关,并且与Activity的主题样式有关(frameworks/base/core/res/res/values/themes.xml等)这些个人不建议修改,即使将导航栏成功修改为在所有界面都显示为透明背景色,也很容易影响应用层自定义的底部菜单甚至影响应用对NavigationBar的管理。当然,还有一个方法就是制作一张合适的背景图,这肯定是一个方向,尽管我没有成功。总之,这个问题最好还是交给上层应用自己来处理。以上关于此问题的修改是在不影响应用层的前提下做的有限的且有一定效果的修改。