Android 中关于navigationbar相关的模式主要分为三种:手势导航,“双按钮”导航,“三按钮”导致,下面主要总结下对应的启动流程以及遇到的问题。

(1)NavigationbarView 绘制

NavigationbarView在创建statusbar的时候统一被创建,具体代码流程如下:

../framework/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java

StatusBar启动后就会执行start方法调用的时候会执行createAndAddWindow()

public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
    makeStatusBarView(result);
..
}

然后执行makeStatusBarView()

protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
..
createNavigationBar(result);//这个方法就是创建navigationbar的入口
..
}
protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
    mNavigationBarController.createNavigationBars(true /* includeDefaultDisplay */, result);
}

../framework/base/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java

createNavigationBar()

public void createNavigationBars(final boolean includeDefaultDisplay,
        RegisterStatusBarResult result) {
//获取当前的display的数据
Display[] displays = mDisplayManager.getDisplays();
//执行for循环添加navigationbarview
    for (Display display : displays) {
        if (includeDefaultDisplay || display.getDisplayId() != DEFAULT_DISPLAY) {
            void createNavigationBar(Display display, RegisterStatusBarResult result) {
        if (display == null) {
            return;
        }

        final int displayId = display.getDisplayId();
        final boolean isOnDefaultDisplay = displayId == DEFAULT_DISPLAY;
        final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();

        try {
//framework通过属性”qemu.hw.mainkeys”来定义是否显示navigationbar
            if (!wms.hasNavigationBar(displayId)) {
                return;
            }
        } catch (RemoteException e) {
            // Cannot get wms, just return with warning message.
            Log.w(TAG, "Cannot get WindowManager.");
            return;
        }
        final Context context = isOnDefaultDisplay
                ? mContext
                : mContext.createDisplayContext(display);
        //navigationbarview其实是fragement,添加到系统view中
        NavigationBarFragment.create(context, (tag, fragment) -> {
            NavigationBarFragment navBar = (NavigationBarFragment) fragment;

            // Unfortunately, we still need it because status bar needs LightBarController
            // before notifications creation. We cannot directly use getLightBarController()
            // from NavigationBarFragment directly.
            LightBarController lightBarController = isOnDefaultDisplay
                    ? Dependency.get(LightBarController.class)
                    : new LightBarController(context,
                            Dependency.get(DarkIconDispatcher.class),
                            Dependency.get(BatteryController.class));
            navBar.setLightBarController(lightBarController);

            // TODO(b/118592525): to support multi-display, we start to add something which is
            //                    per-display, while others may be global. I think it's time to add
            //                    a new class maybe named DisplayDependency to solve per-display
            //                    Dependency problem.
            AutoHideController autoHideController = isOnDefaultDisplay
                    ? Dependency.get(AutoHideController.class)
                    : new AutoHideController(context, mHandler);
            navBar.setAutoHideController(autoHideController);
            navBar.restoreSystemUiVisibilityState();
            mNavigationBars.append(displayId, navBar);

            if (result != null) {
                navBar.setImeWindowStatus(display.getDisplayId(), result.mImeToken,
                        result.mImeWindowVis, result.mImeBackDisposition,
                        result.mShowImeSwitcher);
            }
        });
    }
(display, result);//创建navigationbar
        }
    }
}
../frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/PHONE/NavigationBarFragment.java

Create()(之前执行NavigationBarFragment/onCreate/onCreateView/onViewCreate的顺序),最终add了fragment的onCreateView加载的布局

public static View create(Context context, FragmentListener listener) {
//设置view添加的属性
        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
                WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                        | WindowManager.LayoutParams.FLAG_SLIPPERY,
                PixelFormat.TRANSLUCENT);
        lp.token = new Binder();
        lp.setTitle("NavigationBar" + context.getDisplayId());
        lp.accessibilityTitle = context.getString(R.string.nav_bar);
        lp.windowAnimations = 0;
        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;

        View navigationBarView = LayoutInflater.from(context).inflate(
                R.layout.navigation_bar_window, null);//加载navigation_bar_window布局

        if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView);
        if (navigationBarView == null) return null;

        final NavigationBarFragment fragment = FragmentHostManager.get(navigationBarView)
                .create(NavigationBarFragment.class);
        navigationBarView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
            @Override
            public void onViewAttachedToWindow(View v) {
                final FragmentHostManager fragmentHost = FragmentHostManager.get(v);
                //使用fragment切换.navigation_bar_frame,这个id定义在navigation_bar_window.xml中
                fragmentHost.getFragmentManager().beginTransaction()
                        .replace(.navigation_bar_frame, fragment, TAG)
                        .commit();
                fragmentHost.addTagListener(TAG, listener);
            }

            @Override
            public void onViewDetachedFromWindow(View v) {
                FragmentHostManager.removeAndDestroy(v);
            }
        });
        context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
        return navigationBarView;
    }

来看WindowManager加载的这个view的布局:navigation_bar_window.xml,发现根布局是自定义的view类NavigationBarFrame

<com.android.systemui.statusbar.phone.NavigationBarFrame
    xmlns:android="http:///apk/res/android"
    xmlns:systemui="http:///apk/res-auto"
    android:id="@+id/navigation_bar_frame"
    android:theme="@style/Theme.SystemUI"
    android:layout_height="match_parent"
    android:layout_width="match_parent">

</com.android.systemui.statusbar.phone.NavigationBarFrame>

我们进入NavigationBarFrame类。发现类里并不是我们的预期,就是一个FrameLayout,对DeadZone功能下的touch事件做了手脚,不管了。

NavigationBarFragment的生命周期呢。onCreateView()里,导航栏的真正的rootView。

public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
            Bundle savedInstanceState) {
        return inflater.inflate(R.layout.navigation_bar, container, false);
    }

进入导航栏的真正根布局:navigation_bar.xml,NavigationBarView 和 NavigationBarInflaterView 都要仔细研读

<com.android.systemui.statusbar.phone.NavigationBarView
    xmlns:android="http:///apk/res/android"
    xmlns:systemui="http:///apk/res-auto"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:background="@drawable/system_bar_background">

    <com.android.systemui.statusbar.phone.NavigationBarInflaterView
        android:id="@+id/navigation_inflater"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</com.android.systemui.statusbar.phone.NavigationBarView>

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

先看构造方法,因为加载xml布局首先走的是初始化(是先执行NavigationBarView再加载NavigationBarInflaterView)

//监听模式变换
public class NavigationBarInflaterView extends FrameLayout implements NavigationModeController.ModeChangedListener {
public NavigationBarInflaterView(Context context, AttributeSet attrs) {
        super(context, attrs);
        createInflaters();//根据屏幕旋转角度创建子view(单个back home or recent)的父布局
        mOverviewProxyService = Dependency.get(OverviewProxyService.class);
        mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
}
void createInflaters() {
        mLayoutInflater = LayoutInflater.from(mContext);
        Configuration landscape = new Configuration();
        landscape.setTo(mContext.getResources().getConfiguration());
        landscape.orientation = Configuration.ORIENTATION_LANDSCAPE;
        mLandscapeInflater = LayoutInflater.from(mContext.createConfigurationContext(landscape));
    }

再看onFinishInflate()方法,这是view的生命周期,每个view被inflate之后都会回调

protected void onFinishInflate() {
        super.onFinishInflate();
        inflateChildren();//加载navigation_layout/navigation_layout_verital布局,此布局复写用于定义控件点击范围
        clearViews();
        inflateLayout(getDefaultLayout());//关键方法:加载了 back.home.recent三个按钮的layout
}
protected String getDefaultLayout() {
        final int defaultResource = QuickStepContract.isGesturalMode(mNavBarMode)
                ? R.string.config_navBarLayoutHandle//手势下显示的字符结构
                : mOverviewProxyService.shouldShowSwipeUpUI()//是否显示上滑动的显示字符结构
                        ? R.string.config_navBarLayoutQuickstep
                        : R.string.config_navBarLayout;
        return getContext().getString(defaultResource);
    }

看inflateLayout():里面的newLayout参数很重要!!!根据上一个方法看到getDefaultLayout(),他return了一个在xml写死的字符串。字符样式显示如下:

<string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string>

再看inflateLayout方法,他解析分割了xml里配置的字符串,并传给了inflateButtons方法

protected void inflateLayout(String newLayout) {
        mCurrentLayout = newLayout;
        if (newLayout == null) {
            newLayout = getDefaultLayout();
        }
        String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);//根据“;”号分割成长度为3的数组
        if (sets.length != 3) {
            Log.d(TAG, "Invalid layout.");
            newLayout = getDefaultLayout();
            sets = newLayout.split(GRAVITY_SEPARATOR, 3);
        }
        String[] start = sets[0].split(BUTTON_SEPARATOR);//根据“,”号分割,包含 left[.5W]和back[1WC]
        String[] center = sets[1].split(BUTTON_SEPARATOR);//包含home
        String[] end = sets[2].split(BUTTON_SEPARATOR);//包含recent[1WC]和right[.5W]
        // Inflate these in start to end order or accessibility traversal will be messed up.
        inflateButtons(start, mHorizontal.findViewById(.ends_group),
                false /* landscape */, true /* start */);
        inflateButtons(start, mVertical.findViewById(.ends_group),
                true /* landscape */, true /* start */);

        inflateButtons(center, mHorizontal.findViewById(.center_group),
                false /* landscape */, false /* start */);
        inflateButtons(center, mVertical.findViewById(.center_group),
                true /* landscape */, false /* start */);

        addGravitySpacer(mHorizontal.findViewById(.ends_group));//插入空格符
        addGravitySpacer(mVertical.findViewById(.ends_group));//插入空格符

        inflateButtons(end, mHorizontal.findViewById(.ends_group),
                false /* landscape */, false /* start */);
        inflateButtons(end, mVertical.findViewById(.ends_group),
                true /* landscape */, false /* start */);

        updateButtonDispatchersCurrentView();
    }

再看inflateButtons()方法,遍历加载inflateButton:

private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape,
            boolean start) {
        for (int i = 0; i < buttons.length; i++) {
            inflateButton(buttons[i], parent, landscape, start);
        }
}

protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
            boolean start) {
        LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
        View v = createView(buttonSpec, parent, inflater);//这个是根据上面传来的字符加载不同布局
        if (v == null) return null;

        v = applySize(v, buttonSpec, landscape, start);//根据返回字符的解析,来确认图标显示的大小
        parent.addView(v);//addView到父布局
        addToDispatchers(v);
        View lastView = landscape ? mLastLandscape : mLastPortrait;
        View accessibilityView = v;
        if (v instanceof ReverseRelativeLayout) {
            accessibilityView = ((ReverseRelativeLayout) v).getChildAt(0);
        }
        if (lastView != null) {
            accessibilityView.setAccessibilityTraversalAfter(lastView.getId());
        }
        if (landscape) {
            mLastLandscape = accessibilityView;
        } else {
            mLastPortrait = accessibilityView;
        }
        return v;
    }

我们来看createView()方法:以home按键为例,加载了home的button,其实是加载了 R.layout.home 的layout布局

private View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
        View v = null;
        String button = extractButton(buttonSpec);
        if (LEFT.equals(button)) {
            button = extractButton(NAVSPACE);
        } else if (RIGHT.equals(button)) {
            button = extractButton(MENU_IME_ROTATE);
        }
        if (HOME.equals(button)) {
            v = inflater.inflate(R.layout.home, parent, false);
        } else if (BACK.equals(button)) {
            v = inflater.inflate(R.layout.back, parent, false);
        } 
        .....
        else if (button.startsWith(KEY)) {
            String uri = extractImage(button);
            int code = extractKeycode(button);
            v = inflater.inflate(R.layout.custom_key, parent, false);
            ((KeyButtonView) v).setCode(code);
            if (uri != null) {
                if (uri.contains(":")) {
                    ((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri));
                } else if (uri.contains("/")) {
                    int index = uri.indexOf('/');
                    String pkg = uri.substring(0, index);
                    int id = Integer.parseInt(uri.substring(index + 1));
                    ((KeyButtonView) v).loadAsync(Icon.createWithResource(pkg, id));
                }
            }
        }
        return v;
}

//home.xml
<com.android.systemui.statusbar.policy.KeyButtonView
    xmlns:android="http:///apk/res/android"
    xmlns:systemui="http:///apk/res-auto"
    android:id="@+id/home"
    android:layout_width="@dimen/navigation_key_width"
    android:layout_height="match_parent"
    android:layout_weight="0"
    systemui:keyCode="3"//systemui自定义的属性,模拟的keycode码
    android:scaleType="center"
    android:contentDescription="@string/accessibility_home"
    android:paddingStart="@dimen/navigation_key_padding"
    android:paddingEnd="@dimen/navigation_key_padding"
    />
SystemUI\src\com\android\systemui\statusbar\policy\KeyButtonView.java

先来看KeyButtonView的构造方法,我们之前xml的systemui:keyCode=”3”方法在这里获取。再来看Touch事件,通过sendEvent()方法可以看出,back等view的点击touch事件不是自己处理的,而是交由系统以实体按键(keycode)的形式处理的.

当然KeyButtonView类还处理了支持长按的button,按键的响声等,这里忽略。

至此,导航栏按键事件我们梳理完毕。

public KeyButtonView(Context context, AttributeSet attrs, int defStyle, InputManager manager,
            UiEventLogger uiEventLogger) {
        super(context, attrs);
        mUiEventLogger = uiEventLogger;

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.KeyButtonView,
                defStyle, 0);

        mCode = a.getInteger(R.styleable.KeyButtonView_keyCode, KEYCODE_UNKNOWN);

        mPlaySounds = a.getBoolean(R.styleable.KeyButtonView_playSound, true);

        TypedValue value = new TypedValue();
        if (a.getValue(R.styleable.KeyButtonView_android_contentDescription, value)) {
            mContentDescriptionRes = value.resourceId;
        }

        a.recycle();

        setClickable(true);
        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);

        mRipple = new KeyButtonRipple(context, this);
        mOverviewProxyService = Dependency.get(OverviewProxyService.class);
        mInputManager = manager;
        setBackground(mRipple);
        setWillNotDraw(false);
        forceHasOverlappingRendering(false);
    }
    
    //touch事件处理
    public boolean onTouchEvent(MotionEvent ev) {
        final boolean showSwipeUI = mOverviewProxyService.shouldShowSwipeUpUI();
        final int action = ev.getAction();
        int x, y;
        if (action == MotionEvent.ACTION_DOWN) {
            mGestureAborted = false;
        }
        if (mGestureAborted) {
            setPressed(false);
            return false;
        }
        ...
        
      }
      
      private void sendEvent(int action, int flags, long when) {
        mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_NAV_BUTTON_EVENT)
                .setType(MetricsEvent.TYPE_ACTION)
                .setSubtype(mCode)
                .addTaggedData(MetricsEvent.FIELD_NAV_ACTION, action)
                .addTaggedData(MetricsEvent.FIELD_FLAGS, flags));
        logSomePresses(action, flags);
        //back键单独处理
        if (mCode == KeyEvent.KEYCODE_BACK && flags != KeyEvent.FLAG_LONG_PRESS) {
            Log.i(TAG, "Back button event: " + KeyEvent.actionToString(action));
            if (action == MotionEvent.ACTION_UP) {
                mOverviewProxyService.notifyBackAction((flags & KeyEvent.FLAG_CANCELED) == 0,
                        -1, -1, true /* isButton */, false /* gestureSwipeLeft */);
            }
        }
        final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
//这里根据mCode new了一个KeyEvent事件,通过injectInputEvent使事件生效。
        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);

        int displayId = INVALID_DISPLAY;

        // Make KeyEvent work on multi-display environment
        if (getDisplay() != null) {
            displayId = getDisplay().getDisplayId();
        }
        // Bubble controller will give us a valid display id if it should get the back event
        BubbleController bubbleController = Dependency.get(BubbleController.class);
        int bubbleDisplayId = bubbleController.getExpandedDisplayId(mContext);
        if (mCode == KeyEvent.KEYCODE_BACK && bubbleDisplayId != INVALID_DISPLAY) {
            displayId = bubbleDisplayId;
        }
        if (displayId != INVALID_DISPLAY) {
            ev.setDisplayId(displayId);
        }
        //keycode通过系统处理
        mInputManager.injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
    }

设置图片的icon到底在哪?我们之前一直阅读的是NavigationBarInflaterView,根据布局我们还有一个类没有看,NavigationBarView

public NavigationBarView(Context context, AttributeSet attrs) {
        super(context, attrs);

        ...
        // Set up the context group of buttons
        mContextualButtonGroup = new ContextualButtonGroup(.menu_container);
        final ContextualButton imeSwitcherButton = new ContextualButton(.ime_switcher,
                R.drawable.ic_ime_switcher_default);
        final RotationContextButton rotateSuggestionButton = new RotationContextButton(
                .rotate_suggestion, R.drawable.ic_sysbar_rotate_button);
        final ContextualButton accessibilityButton =
                new ContextualButton(.accessibility_button,
                        R.drawable.ic_sysbar_accessibility_button);
        mContextualButtonGroup.addButton(imeSwitcherButton);
        if (!isGesturalMode) {
            mContextualButtonGroup.addButton(rotateSuggestionButton);
        }
        mContextualButtonGroup.addButton(accessibilityButton);

        mOverviewProxyService = Dependency.get(OverviewProxyService.class);
        //此类主要功能是手势模式下上滑后出现的提示
        mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
        mFloatingRotationButton = new FloatingRotationButton(context);
        mRotationButtonController = new RotationButtonController(context,
                R.style.RotateButtonCCWStart90,
                isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton,
                mRotationButtonListener);

        mConfiguration = new Configuration();
        mTmpLastConfiguration = new Configuration();
        mConfiguration.updateFrom(context.getResources().getConfiguration());
	  //pin页面的提示类
        mScreenPinningNotify = new ScreenPinningNotify(mContext);
        //navigationbar的颜色控制类
        mBarTransitions = new NavigationBarTransitions(this, Dependency.get(CommandQueue.class));
//mButtonDispatchers是维护这些home/back/recent图标view的管理类,会传递到他的		 				child,NavigationBarInflaterView类中
        mButtonDispatchers.put(.back, new ButtonDispatcher(.back));
        mButtonDispatchers.put(.home, new ButtonDispatcher(.home));
        mButtonDispatchers.put(.home_handle, new ButtonDispatcher(.home_handle));
        mButtonDispatchers.put(.recent_apps, new ButtonDispatcher(.recent_apps));
        mButtonDispatchers.put(.ime_switcher, imeSwitcherButton);
        mButtonDispatchers.put(.accessibility_button, accessibilityButton);
        mButtonDispatchers.put(.rotate_suggestion, rotateSuggestionButton);
        mButtonDispatchers.put(.menu_container, mContextualButtonGroup);
        mDeadZone = new DeadZone(this);

        mNavColorSampleMargin = getResources()
                        .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
        //手势模式下屏幕左右滑动的EdgeBack类
        mEdgeBackGestureHandler = new EdgeBackGestureHandler(context, mOverviewProxyService,
                mSysUiFlagContainer, mPluginManager, this::updateStates);
        //获取范围类的RegionSampling来变化显示是light/dark
        mRegionSamplingHelper = new RegionSamplingHelper(this,
                new RegionSamplingHelper.SamplingCallback() {
                    @Override
                    public void onRegionDarknessChanged(boolean isRegionDark) {
                        getLightTransitionsController().setIconsDark(!isRegionDark ,
                                true /* animate */);
                    }
                    @Override
                    public Rect getSampledRegion(View sampledView) {
                        if (mOrientedHandleSamplingRegion != null) {
                            return mOrientedHandleSamplingRegion;
                        }
                        updateSamplingRect();
                        return mSamplingBounds;
                    }
                    @Override
                    public boolean isSamplingEnabled() {
                        return isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode);
                    }
                });
    }

下面说明下navigationbarview图标如此加载,当加载完成后会执行onAttachedToWindow,这个方法中对图标进行添加

protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        requestApplyInsets();
  //设置图标的入口方法
        reorient();
        //模式变化时各个类对于onNavigationModeChanged状态的处理
        onNavigationModeChanged(mNavBarMode);
        setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
        if (mRotationButtonController != null) {
            mRotationButtonController.registerListeners();
        }

        mEdgeBackGestureHandler.onNavBarAttached();
        getViewTreeObserver().addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
}

public void reorient() {
		//view gone对应的布局
        updateCurrentView();

        ((NavigationBarFrame) getRootView()).setDeadZone(mDeadZone);
        mDeadZone.onConfigurationChanged(mCurrentRotation);

        // force the low profile & disabled states into compliance
        mBarTransitions.init();

        // Resolve layout direction if not resolved since components changing layout direction such
        // as changing languages will recreate this view and the direction will be resolved later
        if (!isLayoutDirectionResolved()) {
            resolveLayoutDirection();
        }
        //更新navButtonIcon图标
        updateNavButtonIcons();

        getHomeButton().setVertical(mIsVertical);
}

public void updateNavButtonIcons() {
        // We have to replace or restore the back and home button icons when exiting or entering
        // carmode, respectively. Recents are not available in CarMode in nav bar so change
        // to recent icon is not required.
        final boolean useAltBack =
                (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
        KeyButtonDrawable backIcon = mBackIcon;
        orientBackButton(backIcon);
        KeyButtonDrawable homeIcon = mHomeDefaultIcon;
        if (!mUseCarModeUi) {
            orientHomeButton(homeIcon);
        }
        //设置home/back的button image
        getHomeButton().setImageDrawable(homeIcon);
        getBackButton().setImageDrawable(backIcon);
        
		//根据是否分屏模式来显示recent对应的图标
        updateRecentsIcon();

        // Update IME button visibility, a11y and rotate button always overrides the appearance
        mContextualButtonGroup.setButtonVisibility(.ime_switcher,
                (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0);

        mBarTransitions.reapplyDarkIntensity();
		...
        // When screen pinning, don't hide back and home when connected service or back and
        // recents buttons when disconnected from launcher service in screen pinning mode,
        // as they are used for exiting.
       //判断当前是否为pinning状态
        final boolean pinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive();
        if (mOverviewProxyService.isEnabled()) {
            // Force disable recents when not in legacy mode
            disableRecent |= !QuickStepContract.isLegacyMode(mNavBarMode);
            if (pinningActive && !QuickStepContract.isGesturalMode(mNavBarMode)) {
                disableBack = disableHome = false;
            }
        } else if (pinningActive) {//pinning状态禁用back/home
            disableBack = disableRecent = false;
        }

        ViewGroup navButtons = getCurrentView().findViewById(.nav_buttons);
        if (navButtons != null) {
            LayoutTransition lt = navButtons.getLayoutTransition();
            if (lt != null) {
                if (!lt.getTransitionListeners().contains(mTransitionListener)) {
                    lt.addTransitionListener(mTransitionListener);
                }
            }
        }
		//根据是否禁用来显示对应的button
        getBackButton().setVisibility(disableBack       ? View.INVISIBLE : View.VISIBLE);
        getHomeButton().setVisibility(disableHome       ? View.INVISIBLE : View.VISIBLE);
        getRecentsButton().setVisibility(disableRecent  ? View.INVISIBLE : View.VISIBLE);
        getHomeHandle().setVisibility(disableHomeHandle ? View.INVISIBLE : View.VISIBLE);
        notifyActiveTouchRegions();
    }

上述图标切换在横竖屏切换,分辨率变化中都会对图标进行切换,具体实现在navigationbarview/navigationbarInflateview中

../framework/base/package/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java

最后一个问题navigationbar button的click事件在何处添加的,当布局加载完成后会回调onViewCreated

public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mNavigationBarView = (NavigationBarView) view;
        ...
		
	  //back/home/recent 设置click事件方法入口	
        prepareNavigationBarView();
        checkNavBarModes();
		
	   //亮/灭屏/用户切换 navigatinbarview变化
        IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
        filter.addAction(Intent.ACTION_SCREEN_ON);
        filter.addAction(Intent.ACTION_USER_SWITCHED);
        mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter,
                Handler.getMain(), UserHandle.ALL);
        
        notifyNavigationBarScreenOn();

        mOverviewProxyService.addCallback(mOverviewProxyListener);
        updateSystemUiStateFlags(-1);
		
		...
}

private void prepareNavigationBarView() {
            //设置navigationbar相关图标背景
        mNavigationBarView.reorient();
      //设置recentsButton的setOnClickListener/setOnTouchListener/setOnLongClickListener监听
        ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
        recentsButton.setOnClickListener(this::onRecentsClick);
        recentsButton.setOnTouchListener(this::onRecentsTouch);
        recentsButton.setLongClickable(true);
        recentsButton.setOnLongClickListener(this::onLongPressBackRecents);
       //设置backButton 的setLongClickable属性,backbutton具体操作在keyButtonView中实现
        ButtonDispatcher backButton = mNavigationBarView.getBackButton();
        backButton.setLongClickable(true);
//设置homeButton 的setOnTouchListener/setOnLongClickListener监听
        ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
        homeButton.setOnTouchListener(this::onHomeTouch);
        homeButton.setOnLongClickListener(this::onHomeLongClick);
            //盲人模式下accessibilityButton的setOnClickListener/setOnLongClickListener监听
        ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
        accessibilityButton.setOnClickListener(this::onAccessibilityClick);
        accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
        updateAccessibilityServicesState(mAccessibilityManager);

        updateScreenPinningGestures();
    }

至此,SystemUI的虚拟导航栏模块代码流程结束。

总结:创建一个window属性的父view,通过读取解析xml里config的配置,addView需要的icon,src图片资源通过代码设置,touch事件以keycode方式交由系统处理。

Settings中切换系统导航的流程分析

手机/平板等设备系统导航默认为“三按钮”导航,那么切换流程是如何实现的的,下面主要分析下“三按钮”导航切换到“双按钮”导航,“三按钮”导航切换到手势导航的流程。

  1. “三按钮”导航切换到“双按钮”导航分析

原生系统导航Preference定义在accessibility_settings.xml中,具体路径如下:

../packages/apps/Settings/res/xml/accessibility_settings.xml

<Preference
            android:fragment="com.android.settings.gestures.SystemNavigationGestureSettings"//系统导航的fragment
            android:key="gesture_system_navigation_input_summary_accessibility"
            android:persistent="false"
            android:title="@string/system_navigation_title"
            settings:searchable="false"
            settings:controller="com.android.settings.gestures.SystemNavigationPreferenceController"/>//对应的控制类

由于SystemNavigationGestureSettings.java继承之RadioButtonPickerFragment,radioClick最后调用的是

setDefaultKey方法

protected boolean setDefaultKey(String key) {
  //通过overlaymanager设置对应不同的模式
        setCurrentSystemNavigationMode(mOverlayManager, key);
  //根据不同的navigationmode设置不同的video资源
        setIllustrationVideo(mVideoPreference, key);
        if (TextUtils.equals(KEY_SYSTEM_NAV_GESTURAL, key) && (
                isAnyServiceSupportAccessibilityButton() || isNavBarMagnificationEnabled())) {
            Intent intent = new Intent(getActivity(), SettingsTutorialDialogWrapperActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(intent);
        }
        return true;
}

static void setCurrentSystemNavigationMode(IOverlayManager overlayManager, String key) {
        String overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY;
        switch (key) {
            case KEY_SYSTEM_NAV_GESTURAL:
                overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY;
                break;
            case KEY_SYSTEM_NAV_2BUTTONS:
                overlayPackage = NAV_BAR_MODE_2BUTTON_OVERLAY;
                break;
            case KEY_SYSTEM_NAV_3BUTTONS:
                overlayPackage = NAV_BAR_MODE_3BUTTON_OVERLAY;
                break;
        }

        try {
           //3键切为2键后 执行系统setEnabledExclusiveInCategory方法
overlayManager.setEnabledExclusiveInCategory(overlayPackage, USER_CURRENT);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

../frameworks/base/core/java/android/content/om/OverlayManager.java

public void setEnabledExclusiveInCategory(@NonNull final String packageName,
            @NonNull UserHandle user) throws SecurityException, IllegalStateException {
        try {
            if (!mService.setEnabledExclusiveInCategory(packageName, user.getIdentifier())) {
                throw new IllegalStateException("setEnabledExclusiveInCategory failed");
            }
        } catch (SecurityException e) {
            rethrowSecurityException(e);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

../frameworks/base/services/core/java/com/android/server/om/OverlayManagerService.java

public boolean setEnabledExclusiveInCategory(@Nullable String packageName,final int userIdArg) {
				...
                try {
                    synchronized (mLock) {
                        try {
                            mImpl.setEnabledExclusive(packageName,
                                    true /* withinCategory */, realUserId)
                                .ifPresent(mPropagateOverlayChange);
                            return true;
                        } catch (OperationFailedException e) {
                            return false;
                        }
                    }
                    ...
        }

private final Consumer<PackageAndUser> mPropagateOverlayChange = (pair) -> {
        //更新setting文件
        persistSettings();
        FgThread.getHandler().post(() -> {
            List<String> affectedTargets = updatePackageManager(pair.packageName, pair.userId);
            updateActivityManager(affectedTargets, pair.userId);
            //发送overlaychange广播,systemui会接收到此广播
broadcastActionOverlayChanged(pair.packageName, pair.userId);
        });
};

private void broadcastActionOverlayChanged(@NonNull final String targetPackageName,
            final int userId) {
  //发送ACTION_OVERLAY_CHANGED的广播
        final Intent intent = new Intent(ACTION_OVERLAY_CHANGED,
                Uri.fromParts("package", targetPackageName, null));
            //设置此flag限制只有动态注册才能接收次action
        intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
        try {
            ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, null, null,
                    null, .AppOpsManager.OP_NONE, null, false, false, userId);
        } catch (RemoteException e) {
            // Intentionally left empty.
        }
    }

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

public NavigationModeController(Context context,DeviceProvisionedController deviceProvisionedController,
ConfigurationController configurationController,@UiBackground Executor uiBgExecutor) {
...
	   //初始化中添加ACTION_OVERLAY_CHANGED广播监听
        IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
        overlayFilter.addDataScheme("package");
        overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
        mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, overlayFilter, null, null);
        ...
        }

private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            updateCurrentInteractionMode(true /* notify */);
        }
};

public void updateCurrentInteractionMode(boolean notify) {
        mCurrentUserContext = getCurrentUserContext();
        //获取当前导航栏的模式
        int mode = getCurrentInteractionMode(mCurrentUserContext);
        //如果是手势的话执行switchToDefaultGestureNavOverlayIfNecessary
        if (mode == NAV_BAR_MODE_GESTURAL) {
            switchToDefaultGestureNavOverlayIfNecessary();
        }
        mUiBgExecutor.execute(() ->
            Settings.Secure.putString(mCurrentUserContext.getContentResolver(),
                    Secure.NAVIGATION_MODE, String.valueOf(mode)));
		//回调通知onNavigationModeChanged navigationbar改变
        if (notify) {
            for (int i = 0; i < mListeners.size(); i++) {
                //这个回调很多类注册了,我们只要关注navigationbarFragment/navigationbarview的回调
                mListeners.get(i).onNavigationModeChanged(mode);            
}
        }
    }

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

public void onNavigationModeChanged(int mode) {
        mNavBarMode = mode;
        updateScreenPinningGestures();
        ...
        // Workaround for b/132825155, for secondary users, we currently don't receive configuration
        // changes on overlay package change since SystemUI runs for the system user. In this case,
        // trigger a new configuration change to ensure that the nav bar is updated in the same way.
        int userId = ActivityManagerWrapper.getInstance().getCurrentUserId();
        if (userId != UserHandle.USER_SYSTEM) {
            mHandler.post(() -> {
                FragmentHostManager fragmentHost = FragmentHostManager.get(mNavigationBarView);
          //监听到navigatinbarmodechange重新加载fragment,重新执行布局
                fragmentHost.reloadFragments();
            });
        }
    }

至此在setting中切换导航栏模式的流程就如上所述。

  1. “双按钮”导航/“三按钮”导航切换到手势导航

“双按钮”导航/“三按钮”导航切换到手势导航,流程与(2)上述基本相同,切换后navigationbar的高度会发生变化,具体的代码在NavigationBarView.java

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int w = MeasureSpec.getSize(widthMeasureSpec);
        int h = MeasureSpec.getSize(heightMeasureSpec);
        if (DEBUG) Log.d(TAG, String.format(
                "onMeasure: (%dx%d) old: (%dx%d)", w, h, getMeasuredWidth(), getMeasuredHeight()));

        final boolean newVertical = w > 0 && h > w
                && !isGesturalMode(mNavBarMode);
      //方向变化后重新加载图标 
 if (newVertical != mIsVertical) {
            mIsVertical = newVertical;
            if (DEBUG) {
                Log.d(TAG, String.format("onMeasure: h=%d, w=%d, vert=%s", h, w,
                        mIsVertical ? "y" : "n"));
            }
            reorient();
            notifyVerticalChangedListener(newVertical);
        }
              //如果是2.,3键模式,navigationbar heigh的高度是不变的,当切换到手势模式下时,navigationbar height		    会变小,一般为16dp
        if (isGesturalMode(mNavBarMode)) {
            // Update the nav bar background to match the height of the visible nav bar
            int height = mIsVertical
                    ? getResources().getDimensionPixelSize(
                            com.android.internal.R.dimen.navigation_bar_height_landscape)
                    : getResources().getDimensionPixelSize(
                            com.android.internal.R.dimen.navigation_bar_height);
            int frameHeight = getResources().getDimensionPixelSize(
                    com.android.internal.R.dimen.navigation_bar_frame_height);
            mBarTransitions.setBackgroundFrame(new Rect(0, frameHeight - height, w, h));
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
  1. 手势导航上滑源码分析

Android O之前的虚拟按键,基本的控制方法都是在SystemUI中做处理的,在Android R上为了在手势导航操作时其动画更加流畅,与Launcher互动效果更好,google的设计师就把手势导航相关的操作放到了Launcher3中,而且为了与SystemUI进行信息同步,利用两个aidl的文件利用binder做Launcher3与Systemui之前的进程通信。

在分析之前我们来看下AndroidManifest的一个开机启动属性android:directBootAware="true"这个属性对于手势导航的启动是有决定性的作用,有了这属性,其无论是应用还是服务都能第一时间启动,它保证了手势导航功能在系统准备好之前做好初始化。

<application
        android:name=".SystemUIApplication"
        android:persistent="true"//该应用是可持久的,也即是常驻的应用
        android:allowClearUserData="false"//是否允许清楚用户数据
        android:backupAgent=".backup.BackupHelper"
        android:killAfterRestore="false"//kill之后是否restore
        android:hardwareAccelerated="true"
        android:label="@string/app_label"
        android:icon="@drawable/icon"
        android:process="com.android.systemui"
        android:supportsRtl="true"
        android:theme="@style/Theme.SystemUI"
        android:defaultToDeviceProtectedStorage="true"
        android:directBootAware="true"
>

而Launcher3是只对TouchInteractionService进行了设置,而且TouchInteractionService设置了一个action “android.intent.action.QUICKSTEP_SERVICE”,这为SystemUI绑定TouchInteractionService提供了向导。

下面先看下Launcher3与SystemUI之间做对接的服务,这两个服务为 SystemUI (OverviewProxyService)和Launcher3(TouchInteractionService) 。

OverviewProxyService:Class to send information from overview to launcher with a binder

是随着SystemUI应用启动而生成的,作为一个单例类存在于SystemUI这种系统应用中,而非一个正常的”Service“

OverviewProxyService的构造函数如下:

public OverviewProxyService(Context context, CommandQueue commandQueue,
            NavigationBarController navBarController, NavigationModeController navModeController,
            NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
            PipUI pipUI, Optional<Divider> dividerOptional,
            Optional<Lazy<StatusBar>> statusBarOptionalLazy,
            BroadcastDispatcher broadcastDispatcher) {
        super(broadcastDispatcher);
        //配置mRecentsComponentName的值com.android.launcher3/com.android.quickstep.RecentsActivity,默认值在framework res下
        mRecentsComponentName = ComponentName.unflattenFromString(context.getString(
                com.android.internal.R.string.config_recentsComponentName));
        //指向launcher3来接收ACTION_QUICKSTEP
        mQuickStepIntent = new Intent(ACTION_QUICKSTEP)
                .setPackage(mRecentsComponentName.getPackageName());
....

        // 添加navigationbar mode 切换的监听
        mNavBarMode = navModeController.addListener(this);

        // 添加 launcher package changes的监听
        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
        filter.addDataScheme("package");
        filter.addDataSchemeSpecificPart(mRecentsComponentName.getPackageName(),
                PatternMatcher.PATTERN_LITERAL);
        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        mContext.registerReceiver(mLauncherStateChangedReceiver, filter);

        // Listen for status bar state changes
        statusBarWinController.registerCallback(mStatusBarWindowCallback);
        mScreenshotHelper = new ScreenshotHelper(context);

        // Listen for tracing state changes
        commandQueue.addCallback(new CommandQueue.Callbacks() {
            @Override
            public void onTracingStateChanged(boolean enabled) {
                mSysUiState.setFlag(SYSUI_STATE_TRACING_ENABLED, enabled)
                        .commitUpdate(mContext.getDisplayId());
            }
        });

        // Listen for user setup
        startTracking();

        // Connect to the service
        updateEnabledState();
        startConnectionToCurrentUser();
    }

OverviewProxyService中实现了ISystemUiProxy.aidl,如下所示:

SystemUI/src/com/android/systemui/recents/OverviewProxyService.java

private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
        @Override
        public void startScreenPinning(int taskId) {//开始固定屏幕
            if (!verifyCaller("startScreenPinning")) {
                return;
            }
            long token = Binder.clearCallingIdentity();
            try {
                mHandler.post(() -> {
                    mStatusBarOptionalLazy.ifPresent(
                            statusBarLazy -> statusBarLazy.get().showScreenPinningRequest(taskId,
                                    false /* allowCancel */));
                });
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }
        @Override
        public void stopScreenPinning() {//停止固定屏幕
            if (!verifyCaller("stopScreenPinning")) {
                return;
            }
            long token = Binder.clearCallingIdentity();
            try {
                mHandler.post(() -> {
                    try {
                        ActivityTaskManager.getService().stopSystemLockTaskMode();
                    } catch (RemoteException e) {
                        Log.e(TAG_OPS, "Failed to stop screen pinning");
                    }
                });
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }
		...
    };
     /***还有很多其他功能,在ISystemUiProxy.aidl文件中都有功能相关的描述
        SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl***/

mSysUiProxy是作为binder服务端存在的,在SystemUI等待着类似屏幕固定,分屏,最近任务展示等方法的回调。而mSysUiProxy与Launcher3与进行通信的开端便是“ServiceConnection”服务的链接绑定,如下所示:

SystemUI/src/com/android/systemui/recents/OverviewProxyService.java

/*service对应的action,用于绑定服务*/
private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";
//OverviewProxyService构造函数中调用startConnectionToCurrentUser()来binder Service
    private void internalConnectToCurrentUser() {
        /*断开之间的所有链接*/
        disconnectFromLauncherService();
 
        // If user has not setup yet or already connected, do not try to connect
        if (!mDeviceProvisionedController.isCurrentUserSetup() || !isEnabled()) {
            Log.v(TAG_OPS, "Cannot attempt connection, is setup "
                + mDeviceProvisionedController.isCurrentUserSetup() + ", is enabled "
                + isEnabled());
            return;
        }
        mHandler.removeCallbacks(mConnectionRunnable);
        /*Intent填入指定的action*/
        Intent launcherServiceIntent = new Intent(ACTION_QUICKSTEP)
                .setPackage(mRecentsComponentName.getPackageName());
        try {
            /*绑定服务*/
            mBound = mContext.bindServiceAsUser(launcherServiceIntent,
                    mOverviewServiceConnection,
                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
                    UserHandle.of(mDeviceProvisionedController.getCurrentUser()));
        } catch (SecurityException e) {
            Log.e(TAG_OPS, "Unable to bind because of security error", e);
        }
        if (mBound) {
            // Ensure that connection has been established even if it thinks it is bound
            mHandler.postDelayed(mDeferredConnectionCallback, DEFERRED_CALLBACK_MILLIS);
        } else {
            // Retry after exponential backoff timeout
            retryConnectionWithBackoff();
        }
    }
 
    private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mConnectionBackoffAttempts = 0;
            mHandler.removeCallbacks(mDeferredConnectionCallback);
            try {
                service.linkToDeath(mOverviewServiceDeathRcpt, 0);
            } catch (RemoteException e) {
                // Failed to link to death (process may have died between binding and connecting),
                // just unbind the service for now and retry again
                Log.e(TAG_OPS, "Lost connection to launcher service", e);
                disconnectFromLauncherService();
                retryConnectionWithBackoff();
                return;
            }
 
            mCurrentBoundedUserId = mDeviceProvisionedController.getCurrentUser();
             /*获取IOverviewProxy的代理*/
            mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
 
            Bundle params = new Bundle();
            params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
            params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
            params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
            try {
                /*把ISystemUiProxy推荐给Launcher3*/
                mOverviewProxy.onInitialize(params);
            } catch (RemoteException e) {
                mCurrentBoundedUserId = -1;
                Log.e(TAG_OPS, "Failed to call onInitialize()", e);
            }
            dispatchNavButtonBounds();
 
            // Update the systemui state flags
            updateSystemUiStateFlags();
 
            notifyConnectionChanged();
        }
 
        @Override
        public void onNullBinding(ComponentName name) {
            Log.w(TAG_OPS, "Null binding of '" + name + "', try reconnecting");
            mCurrentBoundedUserId = -1;
            retryConnectionWithBackoff();
        }
 
        @Override
        public void onBindingDied(ComponentName name) {
            Log.w(TAG_OPS, "Binding died of '" + name + "', try reconnecting");
            mCurrentBoundedUserId = -1;
            retryConnectionWithBackoff();
        }
 
        @Override
        public void onServiceDisconnected(ComponentName name) {
            // Do nothing
            mCurrentBoundedUserId = -1;
        }
    }

在mOverviewServiceConnection的onServiceConnected方法中,又看到了另外一个aidl的实现"IOverviewProxy"。这个对象是在服务绑定后通过binder传递过来的,那么"IOverviewProxy"的服务端就应该在Launcher3中了。当"IOverviewProxy"出现在SystemUI中的第一时间,mSysUiProxy就迫不及待的把自己打包好放到了Bundle(mSysUiProxy这个服务者在很尽责的推销自己嘛),然后又由mOverviewProxy反向传递给了Launcher3。

TouchInteractionService:Service connected by system-UI for handling touch interaction.

TouchInteractionService 是Launcher3中的Service,注册于AndroidManifest中,服务有directBootAware属性,为开机即启动的服务。

<service
            android:name="com.android.quickstep.TouchInteractionService"
            android:permission="android.permission.STATUS_BAR_SERVICE"
            android:directBootAware="true" >
            <intent-filter>
                <action android:name="android.intent.action.QUICKSTEP_SERVICE" />
            </intent-filter>
        </service>

下面就要看下IOverviewProxy在TouchInteractionService中的实现了:

private final IBinder mMyBinder = new IOverviewProxy.Stub() {
        public void onActiveNavBarRegionChanges(Region region) {
            mActiveNavBarRegion = region;
        }
        /*从SystemUI而来,携带着在SystemUI中实现的ISystemUiProxy*/
        public void onInitialize(Bundle bundle) {
            mISystemUiProxy = ISystemUiProxy.Stub
                    .asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
            MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor);
            MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::onSystemUiProxySet);
        }
 
        @Override
        public void onOverviewToggle() {
            mOverviewCommandHelper.onOverviewToggle();
        }
 
        @Override
        public void onOverviewShown(boolean triggeredFromAltTab) {
            mOverviewCommandHelper.onOverviewShown(triggeredFromAltTab);
        }
 
        @Override
        public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
            if (triggeredFromAltTab && !triggeredFromHomeKey) {
                // onOverviewShownFromAltTab hides the overview and ends at the target app
                mOverviewCommandHelper.onOverviewHidden();
            }
        }
 
        /***一些最近任务界面,Back按键等事件的互通,具体
        SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl***/
    };
 
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "Touch service connected");
        /*当绑定成功后,反馈给SystemUI的IOverviewProxy*/
        return mMyBinder;
    }

当上面的IOverviewProxy函数onInitialize执行完成之后,SystemUI也就和Launcher3胜利会师了,从而进入了你中有我,我中有你的状态。整个流程如下图所示:

android root禁用导航栏_ide

当Launcher3成功与SystemUI能够正常通信之后Launcher3就开始注册屏幕Touch事件的监听了。

Launcher3/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java

private final IBinder mMyBinder = new IOverviewProxy.Stub() {
        @BinderThread
        public void onInitialize(Bundle bundle) {
      //获取binder代理
            ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(
                    bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
            MAIN_EXECUTOR.execute(() -> {
                SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy);
                //监听屏幕触摸事件
TouchInteractionService.this.initInputMonitor();
                preloadOverview(true /* fromInit */);
            });
            sIsInitialized = true;
        }

private void initInputMonitor() {
        disposeEventHandlers();
        if (mDeviceState.isButtonNavMode() || !SystemUiProxy.INSTANCE.get(this).isActive()) {
            return;
        }
        Bundle bundle = SystemUiProxy.INSTANCE.get(this).monitorGestureInput("swipe-up",
                mDeviceState.getDisplayId());
//这个地方就是注册监听触摸事件的地方了
        mInputMonitorCompat = InputMonitorCompat.fromBundle(bundle, KEY_EXTRA_INPUT_MONITOR);
        mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
                mMainChoreographer, this::onInputEvent);
        mRotationTouchHelper.updateGestureTouchRegions();
    }

从上面看到,Launcher3处理触摸事件的方法是onInputEvent,其会根据系统处于不同的状态而生成不同的事件处理器,以下是具体的代码实现:

../packages/apps/Launcher3/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java

private void onInputEvent(InputEvent ev) {
        if (!(ev instanceof MotionEvent)) {
            Log.e(TAG, "Unknown event " + ev);
            return;
        }
        MotionEvent event = (MotionEvent) ev;

        TestLogging.recordMotionEvent(
                TestProtocol.SEQUENCE_TIS, "TouchInteractionService.onInputEvent", event);

        if (!mDeviceState.isUserUnlocked()) {
            return;
        }

        Object traceToken = TraceHelper.INSTANCE.beginFlagsOverride(
                TraceHelper.FLAG_ALLOW_BINDER_TRACKING);

        final int action = event.getAction();
        if (action == ACTION_DOWN) {
            if (TestProtocol.sDebugTracing) {
                Log.d(TestProtocol.NO_SWIPE_TO_HOME, "TouchInteractionService.onInputEvent:DOWN");
            }
....
}

Laucher3中处理手势相关操作,具体后续会继续研究下对应的处理流程。