Android 8.1 Platform System UI Virtual Navigation Key Loading Process Analysis

Keywords: Mobile Android xml Fragment Java

demand

Based on MTK8163 8.1 platform, customize the navigation bar, dynamically click on icons to hide virtual keys, and the customization steps are shown in the next article.

thinking

Before you start to do the requirement, you must read the code flow of the System UI Navigation module!!! Don't copy other people's changed requirement code directly on the internet. If you change it blindly, it is easy to have problems, but there is no way to solve them. There are blogs on the old platform (8.0-) explaining the navigation bar module of System UI on the internet, and they can search by themselves. 8.0 has made a lot of detailed changes to the System UI, and there are many code changes, but the overall basic process has not changed.

Source code reading can follow a clue to the code, do not care too much about the details of the code! For example, if I customize this requirement, I can follow the navigation bar back, desktop, a function and code flow in recen t tasks, and generally know which method to load the view, where to load the key code, how to generate click events, and so on, regardless of the specific logic judgment.

Code flow:

1.SystemUI\src\com\android\systemui\statusbar\phone\StatusBar.java;

Start with the status bar entry.

 protected void makeStatusBarView() {
        final Context context = mContext;
        updateDisplaySize(); // populates mDisplayMetrics
        updateResources();
        updateTheme();

        ...
        ...

         try {
            boolean showNav = mWindowManagerService.hasNavigationBar();
            if (DEBUG) Log.v(TAG, "hasNavigationBar=" + showNav);
            if (showNav) {
                createNavigationBar();//Create navigation bar
            }
        } catch (RemoteException ex) {

        }
    }

2.SystemUI\src\com\android\systemui\statusbar\phone\StatusBar.java;

Enter the create Navigation Bar method and find that it is mainly managed by Navigation Bar Fragment.

  protected void createNavigationBar() {
        mNavigationBarView = NavigationBarFragment.create(mContext, (tag, fragment) -> {
            mNavigationBar = (NavigationBarFragment) fragment;
            if (mLightBarController != null) {
                mNavigationBar.setLightBarController(mLightBarController);
            }
            mNavigationBar.setCurrentSysuiVisibility(mSystemUiVisibility);
        });
    }

3.SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarFragment.java;

Looking at the creation method of Navigation Bar Fragment, I finally know that it is Windows Manager who adds the layout of the navigation bar and finally adds the layout of the onCreateView loading of the fragment. (Actually, all the modules of System UI are Windows Managers to load View.)

public static View create(Context context, FragmentListener listener) {
        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");
        lp.windowAnimations = 0;

        View navigationBarView = LayoutInflater.from(context).inflate(
                R.layout.navigation_bar_window, null);

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

        context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
        FragmentHostManager fragmentHost = FragmentHostManager.get(navigationBarView);
        NavigationBarFragment fragment = new NavigationBarFragment();
        fragmentHost.getFragmentManager().beginTransaction()
                .replace(R.id.navigation_bar_frame, fragment, TAG) //Be careful! The layout loaded by onCreateView in fragment s is add ed to the view of this Window s property.
                .commit();
        fragmentHost.addTagListener(TAG, listener);
        return navigationBarView;
    }
}

4.SystemUI\res\layout\navigation_bar_window.xml;

Looking at the layout of the view loaded by Windows Manager: navigation_bar_window.xml, we find that the root layout is a custom view class Navigation BarFrame. (Actually, System UI and other system applications such as Launcher are all this way to customize the view, and a lot of logic processing is also in the customized view, which can not be ignored.)

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

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

5.SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarFrame.java;

We enter the NavigationBarFrame class. Discovering that the class is not what we expected, but a FrameLayout that manipulates touch events under DeadZone functionality, regardless.

6. Look back at the life cycle of Navigation Bar Fragment. In onCreateView(), the real rootView of the navigation bar.

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

7.SystemUI\res\layout\navigation_bar.xml;

The real root layout of the navigation bar: navigation_bar.xml, well, custom view. Navigation BarView and Navigation BarInflaterView should be read carefully.

<com.android.systemui.statusbar.phone.NavigationBarView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/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>

8. SystemUI src com android systemui statusbar phone Navigation BarInflaterView. java; inherited from FrameLayout

Look at the construction method first, because the first step in loading the xml layout is initialization

  public NavigationBarInflaterView(Context context, AttributeSet attrs) {
        super(context, attrs);
        createInflaters();//Create the parent layout of the child view (single back home or recent) based on the screen rotation angle
        Display display = ((WindowManager)
                context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
        Mode displayMode = display.getMode();
        isRot0Landscape = displayMode.getPhysicalWidth() > displayMode.getPhysicalHeight();
    }
 private void inflateChildren() {
        removeAllViews();
        mRot0 = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout, this, false);
        mRot0.setId(R.id.rot0);
        addView(mRot0);
        mRot90 = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout_rot90, this,
                false);
        mRot90.setId(R.id.rot90);
        addView(mRot90);
        updateAlternativeOrder();
    }

Look at the onFinishInflate() method, which is the lifecycle of view, and each view will be called back after it is inflate d.

 @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        inflateChildren();//It doesn't matter to go in and ignore it.
        clearViews();//It doesn't matter to go in and ignore it.
        inflateLayout(getDefaultLayout());//Key Method: Load the layout of back. home. receive three buttons
    }

Look at inflateLayout(): The new Layout parameter is very important!!! Based on the previous method, you see getDefaultLayout(), which return s a string that has been written to death in xml. Looking at the inflateLayout method, he parses and splits the string configured in XML and passes it to the inflateButtons method

protected void inflateLayout(String newLayout) {
        mCurrentLayout = newLayout;
        if (newLayout == null) {
            newLayout = getDefaultLayout();
        }
        String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);//Divide an array with a growth of 3 according to';'
        String[] start = sets[0].split(BUTTON_SEPARATOR);//Separated by ",", including left[.5W] and back [.1WC]
        String[] center = sets[1].split(BUTTON_SEPARATOR);//Include home
        String[] end = sets[2].split(BUTTON_SEPARATOR);//Contains recent[1WC] and right[.5W]
        // Inflate these in start to end order or accessibility traversal will be messed up.
        inflateButtons(start, mRot0.findViewById(R.id.ends_group), isRot0Landscape, true);
        inflateButtons(start, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, true);

        inflateButtons(center, mRot0.findViewById(R.id.center_group), isRot0Landscape, false);
        inflateButtons(center, mRot90.findViewById(R.id.center_group), !isRot0Landscape, false);

        addGravitySpacer(mRot0.findViewById(R.id.ends_group));
        addGravitySpacer(mRot90.findViewById(R.id.ends_group));

        inflateButtons(end, mRot0.findViewById(R.id.ends_group), isRot0Landscape, false);
        inflateButtons(end, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, false);
    }

        protected String getDefaultLayout() {
        return mContext.getString(R.string.config_navBarLayout);
    }
    //SystemUI\res\values\config.xml
     <!-- Nav bar button default ordering/layout -->
    <string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string>

Look again at the inflateButtons() method, which iterates through loading 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);
        }
    }

    @Nullable
    protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
            boolean start) {
        LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
        View v = createView(buttonSpec, parent, inflater);//Create view
        if (v == null) return null;

        v = applySize(v, buttonSpec, landscape, start);
        parent.addView(v);//addView to parent layout
        addToDispatchers(v);
        View lastView = landscape ? mLastLandscape : mLastPortrait;
        View accessibilityView = v;
        if (v instanceof ReverseFrameLayout) {
            accessibilityView = ((ReverseFrameLayout) v).getChildAt(0);
        }
        if (lastView != null) {
            accessibilityView.setAccessibilityTraversalAfter(lastView.getId());
        }
        if (landscape) {
            mLastLandscape = accessibilityView;
        } else {
            mLastPortrait = accessibilityView;
        }
        return v;
    }

Let's look at the createView() method: Take the home button as an example, loading the button of home, which is actually loading the layout of R.layout.home.

 private View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
        View v = null;

        ...
        ...

        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 (RECENT.equals(button)) {
            v = inflater.inflate(R.layout.recent_apps, parent, false);
        } else if (MENU_IME.equals(button)) {
            v = inflater.inflate(R.layout.menu_ime, parent, false);
        } else if (NAVSPACE.equals(button)) {
            v = inflater.inflate(R.layout.nav_key_space, parent, false);
        } else if (CLIPBOARD.equals(button)) {
            v = inflater.inflate(R.layout.clipboard, parent, false);
        } 

        ...
        ...

        return v;
    }
    //SystemUI\res\layout\home.xml 
    //There is no src icon for home in the layout. It must have been set in the code.
    //Here is also a custom view:KeyButtonView
    <com.android.systemui.statusbar.policy.KeyButtonView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res-auto"
    android:id="@+id/home"
    android:layout_width="@dimen/navigation_key_width"//Reference to navigation_key_width in dimens.xml
    android:layout_height="match_parent"
    android:layout_weight="0"
    systemui:keyCode="3"//System Mui custom properties
    android:scaleType="fitCenter"
    android:contentDescription="@string/accessibility_home"
    android:paddingTop="@dimen/home_padding"
    android:paddingBottom="@dimen/home_padding"
    android:paddingStart="@dimen/navigation_key_padding"
    android:paddingEnd="@dimen/navigation_key_padding"
    />

9.SystemUI\src\com\android\systemui\statusbar\policy\KeyButtonView.java 
Let's start with the KeyButtonView construction method: our previous xml system mui: keyCode="3" method is obtained here. Looking at Touch events, we can see from sendEvent() method that the click-touch events of back view and other views are not handled by themselves, but handled by the system in the form of keycode.

Of course, the KeyButtonView class also handles buttons that support long press, button sounds, etc., which are ignored here.

So far, we have sorted out the key events of the navigation bar.

 public KeyButtonView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs);

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

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

        mSupportsLongpress = a.getBoolean(R.styleable.KeyButtonView_keyRepeat, true);
        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);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);

        mRipple = new KeyButtonRipple(context, this);
        setBackground(mRipple);
    }

    ...
    ...

 public boolean onTouchEvent(MotionEvent ev) {

       ...

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mDownTime = SystemClock.uptimeMillis();
                mLongClicked = false;
                setPressed(true);
                if (mCode != 0) {
                    sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);//Key method
                } else {
                    // Provide the same haptic feedback that the system offers for virtual keys.
                    performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
                }
                playSoundEffect(SoundEffectConstants.CLICK);
                removeCallbacks(mCheckLongPress);
                postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
                break;

            ...
            ...

        }

        return true;
    }

 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));
        final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
        //Here, according to mCode new, a KeyEvent event is made effective by 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);
        InputManager.getInstance().injectInputEvent(ev,
                InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
    }

10. Another question remains: Where is the icon for the image? We've been reading Navigation Bar InflaterView before. According to the layout, we still have a class that we haven't seen. Navigation BarView. Java

SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarView.java;

Go into the NavigationBarView class and find the construction method.

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

        mDisplay = ((WindowManager) context.getSystemService(
                Context.WINDOW_SERVICE)).getDefaultDisplay();


        ...
        ...

        updateIcons(context, Configuration.EMPTY, mConfiguration);//Key method

        mBarTransitions = new NavigationBarTransitions(this);

        //mButtonDispatchers is the management class that maintains these home back receive icon view s, which are passed to his child, Navigation BarInflaterView class.
        mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back));
        mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
        mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
        mButtonDispatchers.put(R.id.menu, new ButtonDispatcher(R.id.menu));
        mButtonDispatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher));
        mButtonDispatchers.put(R.id.accessibility_button,
                new ButtonDispatcher(R.id.accessibility_button));

    }

     private void updateIcons(Context ctx, Configuration oldConfig, Configuration newConfig) {

           ...

            iconLight = mNavBarPlugin.getHomeImage(
                                        ctx.getDrawable(R.drawable.ic_sysbar_home));
            iconDark = mNavBarPlugin.getHomeImage(
                                        ctx.getDrawable(R.drawable.ic_sysbar_home_dark));
            //mHomeDefaultIcon = getDrawable(ctx,
            //        R.drawable.ic_sysbar_home, R.drawable.ic_sysbar_home_dark);
            mHomeDefaultIcon = getDrawable(iconLight,iconDark);

            //Bright icon resources
            iconLight = mNavBarPlugin.getRecentImage(
                                        ctx.getDrawable(R.drawable.ic_sysbar_recent));
            //Dark icon resources
            iconDark = mNavBarPlugin.getRecentImage(
                                        ctx.getDrawable(R.drawable.ic_sysbar_recent_dark));
            //mRecentIcon = getDrawable(ctx,
            //        R.drawable.ic_sysbar_recent, R.drawable.ic_sysbar_recent_dark);
            mRecentIcon = getDrawable(iconLight,iconDark);


            mMenuIcon = getDrawable(ctx, R.drawable.ic_sysbar_menu,
                                        R.drawable.ic_sysbar_menu_dark);

           ...
           ...

    }

11. As you can see from the tenth, take the receiver as an example, we get the mRecentIcon resource at the time of initialization, and then we can see who calls mRecentIcon, that is to say, we can look back at the invocation process.

private void updateRecentsIcon() {
        getRecentsButton().setImageDrawable(mDockedStackExists ? mDockedIcon : mRecentIcon);
        mBarTransitions.reapplyDarkIntensity();
    }

UpdateRecentsIcon This method sets the resources of the receive image, and then see who calls the updateRecentsIcon method: onConfiguration Changed screen rotation will reset the resource image.

@Override
    protected void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        boolean uiCarModeChanged = updateCarMode(newConfig);
        updateTaskSwitchHelper();
        updateIcons(getContext(), mConfiguration, newConfig);
        updateRecentsIcon();
        if (uiCarModeChanged || mConfiguration.densityDpi != newConfig.densityDpi
                || mConfiguration.getLayoutDirection() != newConfig.getLayoutDirection()) {
            // If car mode or density changes, we need to reset the icons.
            setNavigationIconHints(mNavigationIconHints, true);
        }
        mConfiguration.updateFrom(newConfig);
    }
public void setNavigationIconHints(int hints, boolean force) {

        ...
        ...

        mNavigationIconHints = hints;

        // 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.
        KeyButtonDrawable backIcon = (backAlt)
                ? getBackIconWithAlt(mUseCarModeUi, mVertical)
                : getBackIcon(mUseCarModeUi, mVertical);

        getBackButton().setImageDrawable(backIcon);

        updateRecentsIcon();

        ...
        ...

    }

reorient() also calls the setNavigationIconHints() method:

public void reorient() {
        updateCurrentView();

        ...

        setNavigationIconHints(mNavigationIconHints, true);

        getHomeButton().setVertical(mVertical);
    }

Push up again, and eventually go back to the onConfiguration Changed () method of Navigation BarFragment and onAttachedToWindow() and onSizeChanged() method of Navigation BarView. That is to say, when the layout of Navigation BarView Navigation Bar is loaded, the image resources will be set, and the length will be changed. Screen rotation may cause reset.

At this point, the virtual navigation bar module code flow of the System UI ends.

summary

Create a parent view of the window property
By reading the configuration of config in parsing xml, addView needs icon, or swap order
src image resources are coded to set bright and dark colors
touch events are handled by the system in keycode mode

 

 


Original: https://blog.csdn.net/danxinzhicheng/article/details/80019902?utm_source=copy

Posted by Jim02 on Wed, 30 Jan 2019 16:27:15 -0800