Parsing the Loading Process of SystemUI Navigation Bar on Android 8.1 Platform

  • 2021-11-13 18:07:16
  • OfStack

Demand

Customize the navigation bar based on MTK8163 8.1 platform, increase the volume minus on the left and increase the volume plus on the right

Train of thought

Before the requirements begin to be done, 1 must study the code flow of SystemUI Navigation module! ! ! Don't go directly to the online copy to change the demand code of others. If you change it blindly, it is easy to have problems, but there is no way to solve them. Online old platform (8.0-) explain System UI navigation bar module blog, self-search. 8.0 to System UI or made a lot of details of changes, code changes reflect more, but the overall basic process has not changed.

Source code reading can follow a clue to the code, don't care too much about the details of the code! For example, when I customize this requirement, I can follow a function and code flow in the return of navigation bar (back), desktop (home) and recent task (recent), and generally know which method is adjusted and which method is finally loaded, where is the key code loaded, and how to generate click events, regardless of the specific logical judgment inside.

Code flow

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

Start at the status bar entrance.


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 a navigation bar 
    }
  } catch (RemoteException ex) {
  }
}

2. Enter the createNavigationBar method and find that it is mainly managed by NavigationBarFragment.


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

3. Look at the create method of NavigationBarFragment, and finally know that WindowManager went to addView to make the layout of navigation bar, and finally add made the layout of onCreateView loading of fragment. (In fact, all modules of SystemUI are WindowManager 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) // Attention! fragment Li onCreateView The loaded layout is add To this Window Property of view In. 
      .commit();
  fragmentHost.addTagListener(TAG, listener);
  return navigationBarView;
 }
}

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

Looking at the layout of view loaded by WindowManager: navigation_bar_window. xml, it is found that the root layout is a custom view class NavigationBarFrame. (In fact, SystemUI and other system applications such as Launcher are all this way of customizing view, and many logical processes are also in custom view, which cannot 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 go into the NavigationBarFrame class. It is found that the class is not what we expected, that is, an FrameLayout, which has tampered with the touch event under the DeadZone function, regardless.

6. Come back and look at the life cycle of NavigationBarFragment. In onCreateView (), the navigation bar is the real rootView.


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

Go to the real root layout of the navigation bar: navigation_bar. xml, well, custom view, NavigationBarView, and NavigationBarInflaterView.


<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>

7. SystemUI\ src\ com\ android\ systemui\ statusbar\ phone\ NavigationBarInflaterView. java; Inherited from FrameLayout

Look at the constructor first, because the first step to load the xml layout is to initialize


public NavigationBarInflaterView(Context context, AttributeSet attrs) {
  super(context, attrs);
  createInflaters();// Create a child based on the screen rotation angle view (Single back home or recent ) Parent layout of 
  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 again at the onFinishInflate () method, which is the life cycle of view, and every view is called back after inflate.


@Override
protected void onFinishInflate() {
  super.onFinishInflate();
  inflateChildren();// It doesn't matter to go in and see   Ignore 
  clearViews();// It doesn't matter to go in and see   Ignore 
  inflateLayout(getDefaultLayout());// Key method: Load the  back.home.recent3 Of a button layout
}

Look at inflateLayout (): The newLayout parameter inside is very important! ! ! According to the previous method, I saw getDefaultLayout (), and he return wrote a string dead in xml. Looking at the inflateLayout method, he parsed and divided the string configured in xml and passed it to the inflateButtons method


protected void inflateLayout(String newLayout) {
  mCurrentLayout = newLayout;
  if (newLayout == null) {
    newLayout = getDefaultLayout();
  }
  String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);// According to ";" The length of number division is 3 Array of 
  String[] start = sets[0].split(BUTTON_SEPARATOR);// Divided according to the "," sign, including  left[.5W] And back[1WC]
  String[] center = sets[1].split(BUTTON_SEPARATOR);// Include home
  String[] end = sets[2].split(BUTTON_SEPARATOR);// Include 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 at the inflateButtons () method again and iterate over the load of inflateButton:


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

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


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

8.SystemUI\src\com\android\systemui\statusbar\policy\KeyButtonView.java

First, let's look at the construction method of KeyButtonView: systemui: keyCode= "3" method of xml is obtained here. Let's look at Touch events. Through sendEvent () method, it can be seen that touch events clicked by view such as back are not handled by themselves, but are handled by the system in the form of physical keys (keycode).

Of course, the KeyButtonView class also handles button, which supports long press, and the sound of keys, which are ignored here.

At this point, we have sorted out the navigation bar key events.


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

9. One question remains: Where is the icon for setting the picture? We read NavigationBarInflaterView in the previous 1. According to the layout, we still have one class to read, NavigationBarView. java

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

Go into the NavigationBarView class and find the constructor.


public NavigationBarView(Context context, AttributeSet attrs) {
  super(context, attrs);
  mDisplay = ((WindowManager) context.getSystemService(
      Context.WINDOW_SERVICE)).getDefaultDisplay();
  ...
  ...
  updateIcons(context, Configuration.EMPTY, mConfiguration);// Key methods 
  mBarTransitions = new NavigationBarTransitions(this);
  //mButtonDispatchers  Is to maintain these home back recent Icons view The management class of will be passed to his child,NavigationBarInflaterView 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 coloured icon Resources 
    iconLight = mNavBarPlugin.getRecentImage(
                  ctx.getDrawable(R.drawable.ic_sysbar_recent));
    // Dark color 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);
    ...
    ...

}

10. As can be seen from No.10, taking recent as an example, the resources of mRecentIcon are obtained during initialization, and then we can know who called mRecentIcon, that is, we can look back at the calling process.


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

updateRecentsIcon This method sets the resources of the recent picture, and then see who called the updateRecentsIcon method: onConfigurationChanged screen rotation will reset the resource picture


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

reorient () also calls the setNavigationIconHints () method:


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

Push it up again, and finally trace it back to onConfigurationChanged () method of NavigationBarFragment and onAttachedToWindow () and onSizeChanged () methods of NavigationBarView. That is to say, when the layout of NavigationBarView navigation bar is loaded, the picture resources will be set, and the length change and screen rotation may cause reset

At this point, the code flow of virtual navigation bar module of SystemUI is finished.

Summarize

Create a parent view of an window property By reading and parsing the configuration of config in xml, icon required by addView, or exchange sequence src picture resources set bright and dark colors through code touch events are handled by the system in keycode mode

Related articles: