Let’s take a look at StatusBarManagerService (1)

Write in front

The work needs to involve this part of the code, but I know very little about it; I will summarize while learning and understand this part of the logic and the knowledge points involved. I’m not sure how many chapters there will be in this series, I’ll just leave it at that.
This article mainly introduces the relationship between StatusBarManagerService and systemui.

Learn about StatusBarManagerService

1. What is StatusBarManagerService used for?

In the Android system, most of the third-party apps/system applications/underlying modules that want to interact with systemui must use StatusBarManagerService. The name of this service is status bar service, but most components of systemui can be managed through this service, such as status bar, navigation bar, recent tasks, communication control center, etc.
Because in the systemui source code, Google did not split this part, and the calling code was placed in StatusBar.java for transfer, resulting in the StatusBar class being very bloated. Starting from Android U, Google has carried out MVVM transformation on the systemui status bar part. StatusBar has been renamed CentralSurfaces and the interface has been split. I won’t go into details here. If you are interested, you can take a look at the source code.

2. How does StatusBarManagerService take effect?
(1) Creation of StatusBarManagerService

Like other ManagerServices, the StatusBarManagerService service is also created in SystemServer

private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
    ...
    StatusBarManagerService statusBar = null;
    ...
    t.traceBegin("StartStatusBarManagerService");
    try {
       statusBar = new StatusBarManagerService(context);
     if (!isWatch) {
          statusBar.publishGlobalActionsProvider();
     }
     ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar, false,
            DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
    } catch (Throwable e) {
       reportWtf("starting StatusBarManagerService", e);
    }
    t.traceEnd();
    ...
}

StatusBarManagerService constructor

StatusBarManagerService inherits from IStatusBarService.Stub, the method declared in the IStatusBarService.aidl file (part of it is cut off):

IStatusBarService

The previous method is a common method for calling SBM. The disable method can achieve different operations by setting different flags. For details, see:
Related flags in StatusBarManager

(2) StatusBarManagerService is associated with systemui

There is a class in systemui, CommandQueue.java, which inherits IStatusBar.Stub; the method declared in IStatusBar is consistent with the previous IStatusBarService transferred to systemui.

IStatusBar

Each module in systemui that wants to receive SBM callbacks registers Callback with it through CommandQueue; the method declaration in callbak is consistent with IStatusBar, but the methods are all default and empty, and you can choose to implement them as needed.

CommandQueue.Callback

systemui registered commandqueue to StatusBarManagerService in CentralSurfaces:

protected IStatusBarService mBarService;
...
@Override
public void start() {
    ...
    mBarService = IStatusBarService.Stub.asInterface(ServiceManager.getService(Context.STATUS_BAR_SERVICE));
    ...
    RegisterStatusBarResult result = null;
    try {
        result = mBarService.registerStatusBar(mCommandQueue);
    } catch (RemoteException ex) {
        ex.rethrowFromSystemServer();
    }
    ...
}

registerStatusBar

Through the registerStatusBar method, mBar in SBMService saves IStatusBar, which is CommandQueue.

StatusBarShellCommand and CommandRegistry

We know that we can dump the status of systemui through adb, for example:

adb shell dumpsys statusbar
adb shell dumpsys activity service SystemUIService

Because many classes in systemui implement dumpable, key information is printed out in the dump for debugging.
To call systemui through SBMService, you need to write a test app yourself. This operation is very troublesome. Can you test some content through adb?

CommandQueue constructor

CommandQueue has a member variable CommandRegistry:

CommandRegistry structure

The registerCommand method of CommandRegistry allows the control in systemui to register the adb cmd command for execution:

 commandRegistry.registerCommand("tile-service-add") { TileServiceRequestCommand() }
        commandQueue.addCallback(commandQueueCallback)
    
    
        inner class TileServiceRequestCommand : Command {
        override fun execute(pw: PrintWriter, args: List<String>) {
            val componentName: ComponentName = ComponentName.unflattenFromString(args[0])
                    ?: run {
                        Log.w(TAG, "Malformed componentName ${args[0]}")
                        return
                    }
            requestTileAdd(componentName, args[1], args[2], null) {
                Log.d(TAG, "Response: $it")
            }
        }

        override fun help(pw: PrintWriter) {
            pw.println("Usage: adb shell cmd statusbar tile-service-add " +
                    "<componentName> <appName> <label>")
        }
    }

There are many prefabricated commands in the native StatusBarShellCommand. Let’s end this article with the complete code on Android U:

/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package com.android.server.statusbar;

import static android.app.StatusBarManager.DEFAULT_SETUP_DISABLE2_FLAGS;
import static android.app.StatusBarManager.DEFAULT_SETUP_DISABLE_FLAGS;
import static android.app.StatusBarManager.DISABLE2_NONE;
import static android.app.StatusBarManager.DISABLE_NONE;

import android.app.StatusBarManager.DisableInfo;
import android.content.ComponentName;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ShellCommand;
import android.service.quicksettings.TileService;
import android.util.Pair;

import java.io.PrintWriter;

public class StatusBarShellCommand extends ShellCommand {

    private static final IBinder sToken = new StatusBarShellCommandToken();

    private final StatusBarManagerService mInterface;
    private final Context mContext;

    public StatusBarShellCommand(StatusBarManagerService service, Context context) {
        mInterface = service;
        mContext = context;
    }

    @Override
    public int onCommand(String cmd) {
        if (cmd == null) {
            onHelp();
            return 1;
        }
        try {
            switch (cmd) {
                case "expand-notifications":
                    return runExpandNotifications();
                case "expand-settings":
                    return runExpandSettings();
                case "collapse":
                    return runCollapse();
                case "add-tile":
                    return runAddTile();
                case "remove-tile":
                    return runRemoveTile();
                case "click-tile":
                    return runClickTile();
                case "check-support":
                    final PrintWriter pw = getOutPrintWriter();
                    pw.println(String.valueOf(TileService.isQuickSettingsSupported()));
                    return 0;
                case "get-status-icons":
                    return runGetStatusIcons();
                case "disable-for-setup":
                    return runDisableForSetup();
                case "send-disable-flag":
                    return runSendDisableFlag();
                case "tracing":
                    return runTracing();
                case "run-gc":
                    return runGc();
                // Handle everything that would be handled in `handleDefaultCommand()` explicitly,
                // so the default can be to pass all args to StatusBar
                case "-h":
                case "help":
                    onHelp();
                    return 0;
                case "dump":
                    return super.handleDefaultCommands(cmd);
                default:
                    return runPassArgsToStatusBar();
            }
        } catch (RemoteException e) {
            final PrintWriter pw = getOutPrintWriter();
            pw.println("Remote exception: " + e);
        }
        return -1;
    }

    private int runAddTile() throws RemoteException {
        mInterface.addTile(ComponentName.unflattenFromString(getNextArgRequired()));
        return 0;
    }

    private int runRemoveTile() throws RemoteException {
        mInterface.remTile(ComponentName.unflattenFromString(getNextArgRequired()));
        return 0;
    }

    private int runClickTile() throws RemoteException {
        mInterface.clickTile(ComponentName.unflattenFromString(getNextArgRequired()));
        return 0;
    }

    private int runCollapse() throws RemoteException {
        mInterface.collapsePanels();
        return 0;
    }

    private int runExpandSettings() throws RemoteException {
        mInterface.expandSettingsPanel(null);
        return 0;
    }

    private int runExpandNotifications() throws RemoteException {
        mInterface.expandNotificationsPanel();
        return 0;
    }

    private int runGetStatusIcons() {
        final PrintWriter pw = getOutPrintWriter();
        for (String icon : mInterface.getStatusBarIcons()) {
            pw.println(icon);
        }
        return 0;
    }

    private int runDisableForSetup() {
        String arg = getNextArgRequired();
        String pkg = mContext.getPackageName();
        boolean disable = Boolean.parseBoolean(arg);

        if (disable) {
            mInterface.disable(DEFAULT_SETUP_DISABLE_FLAGS, sToken, pkg);
            mInterface.disable2(DEFAULT_SETUP_DISABLE2_FLAGS, sToken, pkg);
        } else {
            mInterface.disable(DISABLE_NONE, sToken, pkg);
            mInterface.disable2(DISABLE2_NONE, sToken, pkg);
        }

        return 0;
    }

    private int runSendDisableFlag() {
        String pkg = mContext.getPackageName();
        int disable1 = DISABLE_NONE;
        int disable2 = DISABLE2_NONE;

        DisableInfo info = new DisableInfo();

        String arg = getNextArg();
        while (arg != null) {
            switch (arg) {
                case "search":
                    info.setSearchDisabled(true);
                    break;
                case "home":
                    info.setNagivationHomeDisabled(true);
                    break;
                case "recents":
                    info.setRecentsDisabled(true);
                    break;
                case "notification-alerts":
                    info.setNotificationPeekingDisabled(true);
                    break;
                case "statusbar-expansion":
                    info.setStatusBarExpansionDisabled(true);
                    break;
                case "system-icons":
                    info.setSystemIconsDisabled(true);
                    break;
                case "clock":
                    info.setClockDisabled(true);
                    break;
                case "notification-icons":
                    info.setNotificationIconsDisabled(true);
                    break;
                default:
                    break;
            }

            arg = getNextArg();
        }

        Pair<Integer, Integer> flagPair = info.toFlags();

        mInterface.disable(flagPair.first, sToken, pkg);
        mInterface.disable2(flagPair.second, sToken, pkg);

        return 0;
    }

    private int runPassArgsToStatusBar() {
        mInterface.passThroughShellCommand(getAllArgs(), getOutFileDescriptor());
        return 0;
    }

    private int runTracing() {
        switch (getNextArg()) {
            case "start":
                mInterface.startTracing();
                break;
            case "stop":
                mInterface.stopTracing();
                break;
        }
        return 0;
    }

    private int runGc() {
        mInterface.runGcForTest();
        return 0;
    }

    @Override
    public void onHelp() {
        final PrintWriter pw = getOutPrintWriter();
        pw.println("Status bar commands:");
        pw.println("help");
        pw.println("Print this help text.");
        pw.println("");
        pw.println("expand-notifications");
        pw.println("Open the notifications panel.");
        pw.println("");
        pw.println("expand-settings");
        pw.println("Open the notifications panel and expand quick settings if present.");
        pw.println("");
        pw.println("collapse");
        pw.println("Collapse the notifications and settings panel.");
        pw.println("");
        pw.println(" add-tile COMPONENT");
        pw.println("Add a TileService of the specified component");
        pw.println("");
        pw.println("remove-tile COMPONENT");
        pw.println("Remove a TileService of the specified component");
        pw.println("");
        pw.println(" click-tile COMPONENT");
        pw.println("Click on a TileService of the specified component");
        pw.println("");
        pw.println("check-support");
        pw.println(" Check if this device supports QS + APIs");
        pw.println("");
        pw.println("get-status-icons");
        pw.println("Print the list of status bar icons and the order they appear in");
        pw.println("");
        pw.println(" disable-for-setup DISABLE");
        pw.println("If true, disable status bar components unsuitable for device setup");
        pw.println("");
        pw.println(" send-disable-flag FLAG...");
        pw.println("Send zero or more disable flags (parsed individually) to StatusBarManager");
        pw.println("Valid options:");
        pw.println(" <blank> - equivalent to "none"");
        pw.println(" none - re-enables all components");
        pw.println(" search - disable search");
        pw.println(" home - disable navigation home");
        pw.println(" recents - disable recents/overview");
        pw.println(" notification-peek - disable notification peeking");
        pw.println(" statusbar-expansion - disable status bar expansion");
        pw.println(" system-icons - disable system icons appearing in status bar");
        pw.println("clock - disable clock appearing in status bar");
        pw.println(" notification-icons - disable notification icons from status bar");
        pw.println("");
        pw.println(" tracing (start | stop)");
        pw.println("Start or stop SystemUI tracing");
        pw.println("");
        pw.println(" NOTE: any command not listed here will be passed through to IStatusBar");
        pw.println("");
        pw.println("Commands implemented in SystemUI:");
        pw.flush();
        // Sending null args to systemui will print help
        mInterface.passThroughShellCommand(new String[] {}, getOutFileDescriptor());
    }

    /**
     * Token to send to StatusBarManagerService for disable* commands
     */
    private static final class StatusBarShellCommandToken extends Binder {
    }
}

Write at the back

If there are any mistakes in the article, I hope you guys will criticize and correct me~

If you like this article, it is written by Johnny Deng.
If not, I don’t know who wrote it.