Android 4.0 平板全屏实现(二)

    在上一篇 Android 4.0 平板全屏实现(一) 中,实现了android 4.0 上平板的导航栏的“不可见”,即所有的导航键都不显示,但导航栏所占的区域大小还在,只是显示成一个黑条,这显然不是我们想要的效果。本着打破沙锅问到底的精神,笔者继续翻源码,查阅相关资料,发现4.0 以后的导航栏都是被 SystemUI.apk 的应用控制的,它的源码所在的位置:
    frameworks/base/packages/SystemUI
    在
SystemUI 启动流程 一文中对 SystemUI 有详细的介绍,在这里就不再赘述,各位看官可移步了解之。废话不多说,开始回到正题。我们最终想要得到的效果,就是完全隐藏平板上的导航栏,没有占用任何显示空间。

    SystemUI 的启动过程(平板部分)
    在Android 4.0 ICS SystemUI浅析——SystemUI启动流程这篇文章中做了详细的介绍,挑几个重要的步骤来讲:
    1.
SystemServer 在开机之后启动SystemUIService

    [mw_shl_code=java,true]static final void startSystemUi(Context context) {  
    Intent intent = new Intent();  
    intent.setComponent(new ComponentName("com.android.systemui",  
                "com.android.systemui.SystemUIService"));  
    Slog.d(TAG, "Starting service: " + intent);  
    context.startService(intent);  
} [/mw_shl_code]
   2. SystemUIService 启动两个继承自 SystemUI 的 "service", 其中 导航栏的类是根据屏幕分辨率来判断的
    [mw_shl_code=java,true] // Pick status bar or system bar.
        IWindowManager wm = IWindowManager.Stub.asInterface(
                ServiceManager.getService(Context.WINDOW_SERVICE));
        try {
                Log.d(TAG, "*** canStatusBarHide = " + wm.canStatusBarHide() + " ***");
            SERVICES[0] = wm.canStatusBarHide()
                    ? R.string.config_statusBarComponent
                    : R.string.config_systemBarComponent;
        } catch (RemoteException e) {
            Slog.w(TAG, "Failing checking whether status bar can hide", e);
        }

        final int N = SERVICES.length;
        mServices = new SystemUI[N];
        for (int i=0; i<N; i++) {
            Class cl = chooseClass(SERVICES );
            Slog.d(TAG, "loading: " + cl);
            try {
                mServices = (SystemUI)cl.newInstance();
            } catch (IllegalAccessException ex) {
                throw new RuntimeException(ex);
            } catch (InstantiationException ex) {
                throw new RuntimeException(ex);
            }
            mServices.mContext = this;
            Slog.d(TAG, "running: " + mServices);
            mServices.start();
        }
[/mw_shl_code]
    如果是平板(wm.canStatusBarHide() = false)则启用 com.android.systemui.statusbar.tablet.TabletStatusBar,这也是我们接下来研究的重点。因为手机已经可以做到完全全屏。
    继续跟踪,发现 PhoneStatusBar 和 TabletStatusBar 都继承自 StatusBar, 在 StatusBar 中有一个非常重要的抽象函数 makeStatusBarView(),没错,看名字就可以知道,就是它初始化了SystemUI的主要部分(手机的是 NavigationBar 和 StatusBar, 平板的是 Combination bar). 不管是手机还是平板,都是通过这个函数来生成自己的导航栏,被 StatusBar 调用,具体代码如下:
    [mw_shl_code=java,true]public void start() {
        // First set up our views and stuff.
        View sb = makeStatusBarView();

        // Connect in to the status bar manager service
        StatusBarIconList iconList = new StatusBarIconList();
        ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();
        ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>();
        mCommandQueue = new CommandQueue(this, iconList);
        mBarService = IStatusBarService.Stub.asInterface(
                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
        int[] switches = new int[7];
        ArrayList<IBinder> binders = new ArrayList<IBinder>();
        try {
            mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
                    switches, binders);
        } catch (RemoteException ex) {
            // If the system process isn't there we're doomed anyway.
        }

        disable(switches[0]);
        setSystemUiVisibility(switches[1]);
        topAppWindowChanged(switches[2] != 0);
        // StatusBarManagerService has a back up of IME token and it's restored here.
        setImeWindowStatus(binders.get(0), switches[3], switches[4]);
        setHardKeyboardStatus(switches[5] != 0, switches[6] != 0);

        // Set up the initial icon state
        int N = iconList.size();
        int viewIndex = 0;
        for (int i=0; i<N; i++) {
            StatusBarIcon icon = iconList.getIcon(i);
            if (icon != null) {
                addIcon(iconList.getSlot(i), i, viewIndex, icon);
                viewIndex++;
            }
        }

        // Set up the initial notification state
        N = notificationKeys.size();
        if (N == notifications.size()) {
            for (int i=0; i<N; i++) {
                addNotification(notificationKeys.get(i), notifications.get(i));
            }
        } else {
            Log.wtf(TAG, "Notification list length mismatch: keys=" + N
                    + " notifications=" + notifications.size());
        }

        // Put up the view
        final int height = getStatusBarHeight();
        mStatusBarHeight = height;

        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                height,
                WindowManager.LayoutParams.TYPE_STATUS_BAR,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
                PixelFormat.OPAQUE);

        // the status bar should be in an overlay if possible
        final Display defaultDisplay
            = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
                .getDefaultDisplay();
        if (ActivityManager.isHighEndGfx(defaultDisplay)) {
            lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
        }

        lp.gravity = getStatusBarGravity();
        lp.setTitle("StatusBar");
        lp.packageName = mContext.getPackageName();
        lp.windowAnimations = R.style.Animation_StatusBar;
        mStatusBarLayoutParams = lp;
        mStatusBarView = sb;
        WindowManagerImpl.getDefault().addView(sb, lp);

        if (SPEW) {
            Slog.d(TAG, "Added status bar view: gravity=0x" + Integer.toHexString(lp.gravity)
                   + " icons=" + iconList.size()
                   + " disabled=0x" + Integer.toHexString(switches[0])
                   + " lights=" + switches[1]
                   + " menu=" + switches[2]
                   + " imeButton=" + switches[3]
                   );
        }

        mDoNotDisturb = new DoNotDisturb(mContext);
    }
[/mw_shl_code]
    通过上面这段代码,可以看到,在调用makeStatusBarView生成View sb 之后,通过 WindowManagerImpl.getDefault().addView(sb, lp); 将其显示到屏幕之上。追踪到这里,我想大多数同学应该知道怎么去做导航栏的隐藏了。。
    没错,很简单,调用 WindowManagerImpl.getDefault().updateView(sb, lp) 就可以搞定。如果是要导航栏始终不可见,将其高度设置为0就可以,但是我们还要将其还原。还得update回来。。
      接下来的问题,就是要找个合适的位子,添加上我们的这段代码,在什么地方调用updateView才是最合适的呢?
      
      全屏实现(4.0平板)
      
      由于 StatusBar 是被 SystemUIService 调用的,而且仅仅执行 start() 函数,而 StatusBar 是个普通的Java类,没有Context上下文环境,不具备接收事件的能力(这里可能描述得不够清楚,主要就是说应用层发送的广播啊,startService之类的方法都不被StatusBar接收到。因为我们需要在应用层调用相关cmd来控制导航栏的显示/隐藏)
      我们会发现,SystemUIService 是个普通的 service 能够使用到组件之间的通信之中,并且StatusBar也是在这里被调起来的。开始动工吧,能想到的最简单的调用顺序就是 :
      1. 扩展 SystemUI, 添加多一个抽象函数 onReceive(String action)
      2. 在 SystemUIService 中注册一个 BroadcastReceiver 监听我们定制的两个 action (隐藏/显示)
      3. 在 BroadcastReceive 函数中,调用 SystemUI 的 onReceive函数
      4. 在 StatusBar 的 onReceive 函数中处理导航栏的显示/隐藏。

     在 SystemUI 中添加抽象函数(frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUI.java)
     [mw_shl_code=java,true]public abstract class SystemUI {
    public Context mContext;

    public abstract void start();

    public abstract void onReceive(String action);

    protected void onConfigurationChanged(Configuration newConfig) {
    }

    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    }
}[/mw_shl_code]
   在 SystemUIService 中注册 BroadcastReceiver (frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIService.java)
    [mw_shl_code=java,true]@Override
    public void onCreate() {
        // Pick status bar or system bar.
        IWindowManager wm = IWindowManager.Stub.asInterface(
                ServiceManager.getService(Context.WINDOW_SERVICE));
        try {
                Log.d(TAG, "*** canStatusBarHide = " + wm.canStatusBarHide() + " ***");
            SERVICES[0] = wm.canStatusBarHide()
                    ? R.string.config_statusBarComponent
                    : R.string.config_systemBarComponent;
        } catch (RemoteException e) {
            Slog.w(TAG, "Failing checking whether status bar can hide", e);
        }

        final int N = SERVICES.length;
        mServices = new SystemUI[N];
        for (int i=0; i<N; i++) {
            Class cl = chooseClass(SERVICES);
            Slog.d(TAG, "loading: " + cl);
            try {
                mServices = (SystemUI)cl.newInstance();
            } catch (IllegalAccessException ex) {
                throw new RuntimeException(ex);
            } catch (InstantiationException ex) {
                throw new RuntimeException(ex);
            }
            mServices.mContext = this;
            Slog.d(TAG, "running: " + mServices);
            mServices.start();
        }

        // register broadcast receiver
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(ACTION_DISPLAY_STATUS_BAR);
        intentFilter.addAction(ACTION_HIDE_STATUS_BAR);
        registerReceiver(mStatusBarReceiver, intentFilter);

    }[/mw_shl_code]
   在 onReceive 中分发事件frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIService.java
   [mw_shl_code=java,true]BroadcastReceiver mStatusBarReceiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                        if (intent != null && !TextUtils.isEmpty(intent.getAction())) {
                                String action = intent.getAction();

                                final int N = SERVICES.length;
                                for (int i = 0; i < N; i++) {
                                        Slog.d(TAG, "invoke: " + mServices + "'s onReceive()");
                                        mServices.onReceive(action);
                                }
                        }
                }
        };[/mw_shl_code]
    在StatusBar中处理消息(frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java)
     [mw_shl_code=java,true]public abstract class StatusBar extends SystemUI implements CommandQueue.Callbacks {
    static final String TAG = "StatusBar";
    private static final boolean SPEW = false;

    protected CommandQueue mCommandQueue;
    protected IStatusBarService mBarService;

    // Up-call methods
    protected abstract View makeStatusBarView();
    protected abstract int getStatusBarGravity();
    public abstract int getStatusBarHeight();
    public abstract void animateCollapse();

    private DoNotDisturb mDoNotDisturb;

    private View mStatusBarView;
    private int mStatusBarHeight;
    private WindowManager.LayoutParams mStatusBarLayoutParams;


    public void start() {
        // First set up our views and stuff.
        View sb = makeStatusBarView();

        // Connect in to the status bar manager service
        StatusBarIconList iconList = new StatusBarIconList();
        ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();
        ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>();
        mCommandQueue = new CommandQueue(this, iconList);
        mBarService = IStatusBarService.Stub.asInterface(
                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
        int[] switches = new int[7];
        ArrayList<IBinder> binders = new ArrayList<IBinder>();
        try {
            mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
                    switches, binders);
        } catch (RemoteException ex) {
            // If the system process isn't there we're doomed anyway.
        }

        disable(switches[0]);
        setSystemUiVisibility(switches[1]);
        topAppWindowChanged(switches[2] != 0);
        // StatusBarManagerService has a back up of IME token and it's restored here.
        setImeWindowStatus(binders.get(0), switches[3], switches[4]);
        setHardKeyboardStatus(switches[5] != 0, switches[6] != 0);

        // Set up the initial icon state
        int N = iconList.size();
        int viewIndex = 0;
        for (int i=0; i<N; i++) {
            StatusBarIcon icon = iconList.getIcon(i);
            if (icon != null) {
                addIcon(iconList.getSlot(i), i, viewIndex, icon);
                viewIndex++;
            }
        }

        // Set up the initial notification state
        N = notificationKeys.size();
        if (N == notifications.size()) {
            for (int i=0; i<N; i++) {
                addNotification(notificationKeys.get(i), notifications.get(i));
            }
        } else {
            Log.wtf(TAG, "Notification list length mismatch: keys=" + N
                    + " notifications=" + notifications.size());
        }

        // Put up the view
        final int height = getStatusBarHeight();
        mStatusBarHeight = height;

        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                height,
                WindowManager.LayoutParams.TYPE_STATUS_BAR,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
                PixelFormat.OPAQUE);

        // the status bar should be in an overlay if possible
        final Display defaultDisplay
            = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
                .getDefaultDisplay();
        if (ActivityManager.isHighEndGfx(defaultDisplay)) {
            lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
        }

        lp.gravity = getStatusBarGravity();
        lp.setTitle("StatusBar");
        lp.packageName = mContext.getPackageName();
        lp.windowAnimations = R.style.Animation_StatusBar;
        mStatusBarLayoutParams = lp;
        mStatusBarView = sb;

        WindowManagerImpl.getDefault().addView(sb, lp);

        if (SPEW) {
            Slog.d(TAG, "Added status bar view: gravity=0x" + Integer.toHexString(lp.gravity)
                   + " icons=" + iconList.size()
                   + " disabled=0x" + Integer.toHexString(switches[0])
                   + " lights=" + switches[1]
                   + " menu=" + switches[2]
                   + " imeButton=" + switches[3]
                   );
        }

        mDoNotDisturb = new DoNotDisturb(mContext);
    }

    protected View updateNotificationVetoButton(View row, StatusBarNotification n) {
        View vetoButton = row.findViewById(R.id.veto);
        if (n.isClearable()) {
            final String _pkg = n.pkg;
            final String _tag = n.tag;
            final int _id = n.id;
            vetoButton.setOnClickListener(new View.OnClickListener() {
                    public void onClick(View v) {
                        try {
                            mBarService.onNotificationClear(_pkg, _tag, _id);
                        } catch (RemoteException ex) {
                            // system process is dead if we're here.
                        }
                    }
                });
            vetoButton.setVisibility(View.VISIBLE);
        } else {
            vetoButton.setVisibility(View.GONE);
        }
        return vetoButton;
    }

        @Override
        public void onReceive(String action) {
                Log.d(TAG, "*** onReceive(), action = " + action + " ***");
                if (SystemUIService.ACTION_DISPLAY_STATUS_BAR.equals(action)) {
                        mStatusBarLayoutParams.height = mStatusBarHeight;
                        WindowManagerImpl.getDefault().updateViewLayout(mStatusBarView,
                                        mStatusBarLayoutParams);
                } else if (SystemUIService.ACTION_HIDE_STATUS_BAR.equals(action)) {
                        mStatusBarLayoutParams.height = 0;
                        WindowManagerImpl.getDefault().updateViewLayout(mStatusBarView,
                                        mStatusBarLayoutParams);
                }

        }

}[/mw_shl_code]
     至此,在SystemUI.apk 中的改动全部完成。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值