Android 11 SystemUI startup process

What does SystemUI contain?

On the surface, the status bar, notification bar, drop-down menu, navigation bar, lock screen, recent tasks, low battery prompt and other system pages we see are all SystemUI. SystemUI is located in the source code directory: framework/base/packages directory. It can be seen that SystemUI and framework are related. SystemUI relies on many internal APIs and system resources. SystemUI compilation depends on the system source code.

SystemUI is also an application, but what is special about this application is that it has no startup icon and no entry Activity . His entry program is a service: SystemUIService. This service will be pulled up by the system service. When this service is started, the SystemUI application process will be created . The specific startup process will be analyzed later. In addition to SystemUIService, SystemUI also has many services, such as: KeyguardService responsible for lock screen, RecentsSystemUserService responsible for recent tasks, ImageWallpaper responsible for wallpaper, TakeScreenshotService responsible for screenshots, etc.

The following is the view tree diagram of PhoneStatusBarView:

Panel Holder

PanelHolder is the view obtained after the user pulls down the status bar. It mainly contains two parts: QuickSettings and Notification panel. PanelHolder is a custom view inherited from FrameLayout, and its content is filled by include status_bar_expanded.xml. The layout of PanelHolder is relatively complex. In order to improve the reusability of the view, a large number of include tags are used. The following is the view tree diagram of PanelHolder, only the main views are given:

architectural relationship

Among the system services, there is a service that specifically serves the status bar of SystemUI. This service is StatusbarManagerService (abbreviation: SMS). The service closely related to this service is WindowManagerService (abbreviation: WMS). SMS mainly controls the status. Bar, navigation bar, for example: we can set full screen, immersive status bar are SMS in action.

Services component startup configuration list: (R.array.config_systemUIServiceComponents)

All SystemUIServices inherit from SystemUI.class, which is an abstract class

<item>com.android.systemui.util.NotificationChannels</item> Notification information 
<item>com.android.systemui.keyguard.KeyguardViewMediator</item> Lock screen 
<item>com.android.systemui.recents.Recents</item> item> Recent list 
After Android 10, the display of the recent list has been moved to the Launcher. In a class of Launcher3 TouchInteractionService.java IBinder mMyBinder = new IOverviewProxy.Stub() communicates with systemUI through the AIDL method 
———————————————— 
<item>com.android.systemui. volume.VolumeUI</item> Sound UI display 
<item>com.android.systemui.statusbar.phone.StatusBar</item> Status bar and drop-down panel 
<item>com.android.systemui.usb.StorageNotification</item> usb Notification management 
<item>com.android.systemui.power.PowerUI</item> Power UI display management 
<item>com.android.systemui.media.
<item>com.android.systemui.keyboard.KeyboardUI</item>Keyboard UI 
<item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>Shortcut   
<item>@string/config_systemUIVendorServiceComponent</item>Manufacturer related customization 
<item>com.android.systemui.util.leak.GarbageMonitor$Service</item>Garbage Monitor   
<item>com.android.systemui.LatencyTester</item> Latency Tester 
<item>com.android.systemui.globalactions .GlobalActionsComponent</item> Display of shutdown interface, global control 
<item>com.android.systemui.ScreenDecorations</item>Screen decorations   
<item>com.android.systemui.biometrics.AuthController</item>Biometrics   
<item> com.android.systemui.SliceBroadcastRelayHandler</item> Slice Broadcast 
<item>com.android.systemui.statusbar.  notification.InstantAppNotifier</item>   
<item>com. android.systemui.theme.ThemeOverlayController</item>   
<item>com.android.systemui.  accessibility.WindowMagnification</item>  
<item>com.android.systemui.accessibility.SystemActions</item>  
<item>com.android.systemui.toast.ToastUI</item>  Toast
<item>com.android.systemui.wmshell.WMShell</item> 

1. SystemUI startup process

1、SystemServer

After SystemServer starts, ActivityManagerService will be started in Main Thread. When ActivityManagerService systemReady, SystemUIService will be started.

/frameworks/base/services/java/com/android/server/SystemServer.java

①main

    /**
     * The main entry point from zygote.
     */
    public static void main(String[] args) {
        new SystemServer().run();
    }

②run

    private void run() {
        t.traceBegin("InitBeforeStartServices");
        ....
       // Create the system service manager.
            mSystemServiceManager = new SystemServiceManager(mSystemContext);
            mSystemServiceManager.setStartInfo(mRuntimeRestart,
                    mRuntimeStartElapsedTime, mRuntimeStartUptime);
            LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
        ....
       }

③mActivityManagerService.systemReady

mActivityManagerService.systemReady(() -> { 
    //Ready services 
    Slog.i(TAG, "Making services ready"); 
    .... 
            //Trace the opening system interface 
            t.traceBegin("StartSystemUI"); 
            try { 
                // Start the system interface 
                startSystemUi(context, windowManagerF); 
            } catch (Throwable e) { 
                reportWtf("starting System UI", e); 
            } 
            t.traceEnd(); 
    .... 
}

④startSystemUi

    private static void startSystemUi(Context context, WindowManagerService windowManager) {
        PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
        Intent intent = new Intent();
        intent.setComponent(pm.getSystemUiServiceComponent());
        intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
        //Slog.d(TAG, "Starting service: " + intent);
        //通过startServiceAsUser,SystemUIService就启动了,即SystemUI进程开机启动
        context.startServiceAsUser(intent, UserHandle.SYSTEM);
        windowManager.onSystemUiStarted();
    }

2、systemUIService

In the onCreate method of SystemUIService, the startServicesIfNeeded method of SystemUIApplication will be called. This method will call the startServicesIfNeeded (SERVICES) method to start a series of services.

    @Override
    public void onCreate() {
        super.onCreate();

        // Start all of SystemUI
        ((SystemUIApplication) getApplication()).startServicesIfNeeded();

3、SystemUIApplication

/frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java

①startServicesIfNeeded()

public void startServicesIfNeeded() { 
    //Get the paths of all services. All SERVICES inherit the SystemUI class: 
    String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponents(getResources()); 
    startServicesIfNeeded(/* metricsPrefix= */ " StartServices", names); 
}

②startServicesIfNeeded(String metricsPrefix, String[] services)

In the overloaded method, each name is obtained through reflection to obtain the instance object, and then the start method of each SystemUI subclass is called in turn to start each module.

private void startServicesIfNeeded(String metricsPrefix, String[] services) { 
    if (mServicesStarted) { 
        return; 
    } 
    mServices = new SystemUI[services.length]; 

    //Check, maybe it was completed long before we started 
    if (! mBootCompleteCache.isBootComplete()) { 
        // check to see if maybe it was already completed long before we began 
        // see ActivityManagerService.finishBooting() 
        if ("1".equals(SystemProperties.get("sys.boot_completed"))) { 
            mBootCompleteCache.setBootComplete(); 
            if (DEBUG) { 
                Log.v(TAG, "BOOT_COMPLETED was already sent"); 
            } 
        } 
    }

    final DumpManager dumpManager = mRootComponent.createDumpManager();

    Log.v(TAG, "Starting SystemUI services for user " +
            Process.myUserHandle().getIdentifier() + ".");
    TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",
            Trace.TRACE_TAG_APP);
    //开始追踪
    log.traceBegin(metricsPrefix);
    final int N = services.length;
    //遍历services这个数组
    for (int i = 0; i < N; i++) {
        String clsName = services[i];
        if (DEBUG) Log.d(TAG, "loading: " + clsName);
        log.traceBegin(metricsPrefix + clsName);
        long ti = System.currentTimeMillis();
        try {
            SystemUI obj = mComponentHelper.resolveSystemUI(clsName);
            if (obj == null) {
                Constructor constructor = Class.forName(clsName).getConstructor(Context.class);
                obj = (SystemUI) constructor.newInstance(this);
            }
            mServices[i] = obj;
        } catch (ClassNotFoundException
                | NoSuchMethodException
                | IllegalAccessException
                | InstantiationException
                | InvocationTargetException ex) {
            throw new RuntimeException(ex);
        }

        if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
        //Calling the start method of service in sequence to start the service 
    mServicesStarted = true;
        mServices[i].start();
        log.traceEnd(); 

        // Warn if initialization of component takes too long 
        //If the component initialization takes too long, issue a warning 
        ti = System.currentTimeMillis() - ti; 
        if (ti > 1000) { 
            Log.w(TAG , "Initialization of " + clsName + " took " + ti + " ms"); 
        } 
        if (mBootCompleteCache.isBootComplete()) { 
            mServices[i].onBootCompleted(); 
        } 

        dumpManager.registerDumpable(mServices[i].getClass( ).getName(), mServices[i]); 
    } 
    mRootComponent.getInitController().executePostInitTasks(); 
    //End tracing 
    log.traceEnd(); 

}

4、SystemUI

/**
 * @see SystemUIApplication#startServicesIfNeeded()
 *系统界面应用  如果需要,启动服务
 */
public abstract class SystemUI implements Dumpable {
    protected final Context mContext;

    public SystemUI(Context context) {
        mContext = context;
    }

    public abstract void start();

    protected void onConfigurationChanged(Configuration newConfig) {
    }

    @Override
    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
    }

    protected void onBootCompleted() {
    }

    public static void overrideNotificationAppName(Context context, Notification.Builder n,
            boolean system) {
        final Bundle extras = new Bundle();
        String appName = system
                ? context.getString(com.android.internal.R.string.notification_app_name_system)
                : context.getString(com.android.internal.R.string.notification_app_name_settings);
        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, appName);

        n.addExtras(extras);
    }
}

2. Status bar

1、SystemBars

SystemBars loads basically all SystemUI interface displays. As can be seen from the above, the start method called is actually a method in every subclass inherited from SytemUI.

①start

②createStatusBarFromConfig

Read the class name from the string resource file, instantiate the object through Java's reflection mechanism, and then call the start() method to start. The value of the class name is as follows:

    private void createStatusBarFromConfig() {
        ......
        String clsName = mContext.getString(R.string.config_statusBarComponent);
        ......
        try {
            cls = mContext.getClassLoader().loadClass(clsName);
        } catch (Throwable t) {
            throw andLog("Error loading status bar component: " + clsName, t);
        }
        try {
            mStatusBar = (BaseStatusBar) cls.newInstance();
        } catch (Throwable t) {
            throw andLog("Error creating status bar component: " + clsName, t);
        }
        ......
        mStatusBar.start();
        ......
    }

③String

    <!-- Component to be used as the status bar service.  Must implement the IStatusBar
     interface.  This name is in the ComponentName flattened format (package/class)  -->
    <string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.phone.StatusBar</string>

2、Status Bar

①createAndAddWindows

    public void createAndAddWindows(@Nullable RegisterStatusBarResult result) { 
        //Create the status bar 
        makeStatusBarView(result); 
        mNotificationShadeWindowController.attach(); 
        //Create the status bar window 
        mStatusBarWindowController.attach(); 
    }

②makeStatusBarView

protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) { 
    final Context context = mContext; 
    ..... 
    FragmentHostManager.get(mPhoneStatusBarWindow) 
            .addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> { 
                //CollapsedStatusBarFragment replaces status_bar_container(status bar Notification display area) 
                CollapsedStatusBarFragment statusBarFragment = 
                        (CollapsedStatusBarFragment) fragment; 

                PhoneStatusBarView oldStatusBarView = mStatusBarView; 
                mStatusBarView = (PhoneStatusBarView) statusBarFragment.getView(); 
                //Pass statusBar to handle the drop-down event 
                mStatusBarView.setBar(this);
                //传递 NotificationPanelView 显示下拉UI控制
                mStatusBarView.setPanel(mNotificationPanelViewController);
                mStatusBarView.setScrimController(mScrimController);
                //初始化通知栏区域
                statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
                ......
                    
            }).getFragmentManager()
            .beginTransaction()
            .replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),
                    CollapsedStatusBarFragment.TAG)
            .commit();
    .....

2、CollapsedStatusBarFragment

①onCreateView

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

②initNotificationIconArea

public void initNotificationIconArea(NotificationIconAreaController 
        notificationIconAreaController) { 
    //notification_icon_area is the layout in status_bar.xml, it belongs to notification Notification     
    //Get notification_icon_area, FrameLayout is converted to ViewGroup, 
    ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area); 
    / /Call notificationIconAreaController to get the view to display the notification (LinearLayout) 
    //Follow up in 4 
    mNotificationIconAreaInner = 
            notificationIconAreaController.getNotificationInnerAreaView(); 
    //If there is already a displayed view, remove itself through the view parent layout, and then addView again. 
    //Finally display mNotificationIconAreaInner (set transparency to 1, visibility to VISIBLE) 
    if (mNotificationIconAreaInner.getParent() != null) {
        ((ViewGroup) mNotificationIconAreaInner.getParent()) 
                .removeView(mNotificationIconAreaInner); 
    } 
    notificationIconArea.addView(mNotificationIconAreaInner); 

    //Same as above 
    ViewGroup statusBarCenteredIconArea = mStatusBar.findViewById(R.id.centered_icon_area); 
    mCenteredIconArea = notificationIconAreaCon troller.getCenteredNotificationAreaView() ; 
    if (mCenteredIconArea.getParent() != null) { 
        ((ViewGroup) mCenteredIconArea.getParent()) 
                .removeView(mCenteredIconArea); 
    } 
    statusBarCenteredIconArea.addView(mCenteredIconArea); 

    //Default to display until we know otherwise 
    // Default to showing until we know otherwise.
    showNotificationIconArea(false);
}

③showNotificationIconArea

When the status bar is pulled down, the icon in the status bar will slowly become transparent and invisible, through hideSystemIconArea(true), hideNotificationIconArea(true)

//When the status bar is pulled down, set the icon in the status bar to slowly become transparent and invisible     
public void hideNotificationIconArea(boolean animate) { 
        animateHide(mNotificationIconAreaInner, animate); 
        animateHide(mCenteredIconArea, animate); 
    } 
// Set the status bar icon transparency to 1 and visibility to VISIBLE 
    public void showNotificationIconArea(boolean animate) { 
        animateShow(mNotificationIconAreaInner, animate); 
        animateShow(mCenteredIconArea, animate); 
    } 

    public void hideOperatorName(boolean animate) { 
        if (mOperatorNameFrame != null) { 
            animateHide(mOperatorNameFrame, animate); 
        } 
    } 

    public void showOperatorName(boolean animate) {
        if (mOperatorNameFrame != null) {
            animateShow(mOperatorNameFrame, animate);
        }

④animateShow

    private void animateShow(View v, boolean animate) { 
        v.animate().cancel(); 
        //(Set transparency to 1, visibility to VISIBLE) 
        v.setVisibility(View.VISIBLE); 
        if (!animate) { 
            v. setAlpha(1f); 
            return; 
        } 
        ..... 
    }

⑤animateHiddenState

//将视图动画化为 INVISIBLE 或 GONE    
private void animateHiddenState(final View v, int state, boolean animate) {
        v.animate().cancel();
        if (!animate) {
            v.setAlpha(0f);
            v.setVisibility(state);
            return;
        }

        v.animate()
                .alpha(0f)
                .setDuration(160)
                .setStartDelay(0)
                .setInterpolator(Interpolators.ALPHA_OUT)
                .withEndAction(() -> v.setVisibility(state));
    }

3、status_bar.xml

<?xml version="1.0" encoding="utf-8"?>
<!--
**
** Copyright 2006, 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.
*/
-->

<!--    android:background="@drawable/status_bar_closed_default_background" -->
<com.android.systemui.statusbar.phone.PhoneStatusBarView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
    android:layout_width="match_parent"
    android:layout_height="@dimen/status_bar_height"
    android:id="@+id/status_bar"
    android:orientation="vertical"
    android:focusable="false"
    android:descendantFocusability="afterDescendants"
    android:accessibilityPaneTitle="@string/status_bar"
    >

    <!-- add for KGDAANWIKFRA-135 -->
    <View
        android:layout_width="match_parent"
        android:layout_height="@dimen/status_bar_height"
        android:id="@+id/status_bar_dark_view"
        android:background="#ff000000"
        android:visibility="gone" />
    //<!--通知灯,默认gone-->
    <ImageView
        android:id="@+id/notification_lights_out"
        android:layout_width="@dimen/status_bar_icon_size"
        android:layout_height="match_parent"
        android:paddingStart="@dimen/status_bar_padding_start"
        android:paddingBottom="2dip"
        android:src="@drawable/ic_sysbar_lights_out_dot_small"
        android:scaleType="center"
        android:visibility="gone"
        />
    //<!--状态栏内容-->
    <LinearLayout android:id="@+id/status_bar_contents"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingStart="@dimen/status_bar_padding_start"
        android:paddingEnd="@dimen/status_bar_padding_end"
        android:paddingTop="@dimen/status_bar_padding_top"
        android:orientation="horizontal"
        >
        <FrameLayout
            android:layout_height="match_parent"
            android:layout_width="0dp"
            android:layout_weight="1">

            <include layout="@layout/heads_up_status_bar_layout" />

            <!-- The alpha of the left side is controlled by PhoneStatusBarTransitions, and the
             individual views are controlled by StatusBarManager disable flags DISABLE_CLOCK and
             DISABLE_NOTIFICATION_ICONS, respectively -->
            <LinearLayout
                android:id="@+id/status_bar_left_side"
                android:layout_height="match_parent"
                android:layout_width="match_parent"
                android:clipChildren="false"
            >
                <ViewStub
                    android:id="@+id/operator_name"
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout="@layout/operator_name" />

                <com.android.systemui.statusbar.policy.Clock
                    android:id="@+id/clock"
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:textAppearance="@style/TextAppearance.StatusBar.Clock"
                    android:singleLine="true"
                    android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
                    android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
                    android:gravity="center_vertical|start"
                />

                //<!--通知图标区域-->
                <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
                    android:id="@+id/notification_icon_area"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:orientation="horizontal"
                    android:clipChildren="false"/>

            </LinearLayout>
        </FrameLayout>

        <!-- Space should cover the notch (if it exists) and let other views lay out around it -->
        <android.widget.Space
            android:id="@+id/cutout_space_view"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:gravity="center_horizontal|center_vertical"
        />

        //居中的图标区域        
        <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
            android:id="@+id/centered_icon_area"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="horizontal"
            android:clipChildren="false"
            android:gravity="center_horizontal|center_vertical"/>

        <com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:orientation="horizontal"
            android:gravity="center_vertical|end"
            >
            //<!--系统图标-->
            <include layout="@layout/system_icons" />
        </com.android.keyguard.AlphaOptimizedLinearLayout>
    </LinearLayout>
    //<!--紧急密码管理员文本-->
    <ViewStub
        android:id="@+id/emergency_cryptkeeper_text"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout="@layout/emergency_cryptkeeper_text"
    />

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

4、NotificationIconAreaController

①getNotificationInnerAreaView

    /** 
     * Returns the view that represents the notification area.+ 
     * Returns the view that represents the notification area.+ * Returns the view that represents the notification area. 
     */ 
    public View getNotificationInnerAreaView() { 
        return mNotificationIconArea; 
    }

②initializeNotificationAreaViews

/** 
 * Initializes the views that will represent the notification area. 
 * Initializes the views that will represent the notification area. 
 */ 
protected void initializeNotificationAreaViews(Context context) { 
    reloadDimens(context); 
    
    LayoutInflater layoutInflater = LayoutInflater.from(context); 
    //Notification icon area layout 
    mNotificationIconArea = inflateIconArea(layoutInflater); 
    //Notification icon 
    mNotificationIcons = mNotificationIconArea.findViewById(R. id.notificationIcons); 
    //Get notification scroll layout 
    mNotificationScrollLayout = mStatusBar.getNotificationScrollLayout(); 
    //Centered icon area layout 
    mCenteredIconArea = layoutInflater.inflate(R.layout.center_icon_area, null); 
    //Centered icon
    mCenteredIcon = mCenteredIconArea.findViewById(R.id.centeredIcon);

    initAodIcons();
}

③inflateIconArea

    protected View inflateIconArea(LayoutInflater inflater) {
        return inflater.inflate(R.layout.notification_icon_area, null);
    }

3. Status icon loading process

1、 status_bar.xml

        <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
            android:id="@+id/centered_icon_area"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="horizontal"
            android:clipChildren="false"
            android:gravity="center_horizontal|center_vertical"/>

        <com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:orientation="horizontal"
            android:gravity="center_vertical|end"
            > 
            <!--System icons--> 
            <include layout="@layout/system_icons" /> 
        </com.android.keyguard.AlphaOptimizedLinearLayout>

2、system_icons.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:systemui="http://schemas.android.com/apk/res-auto"
    android:id="@+id/system_icons"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_vertical">
    //StatusIconContainer继承AlphaOptimizedLinearLayout
    <com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        android:paddingEnd="@dimen/signal_cluster_battery_padding"
        android:gravity="center_vertical"
        android:orientation="horizontal"/>

    <com.android.systemui.BatteryMeterView android:id="@+id/battery"
        android:layout_height="match_parent"
        android:layout_width="wrap_content"
        android:clipToPadding="false"
        android:clipChildren="false"
        systemui:textAppearance="@style/TextAppearance.StatusBar.Clock" />
</LinearLayout>

3、AlphaOptimizedLinearLayout

    //This method is used to mark whether the current view is overdrawn. It returns true if it exists, and false if it does not exist. 
    //The default return in the API is true, and the status icon does not have overdrawing. 
    @Override 
    public boolean hasOverlappingRendering() { 
        return false; 
    }

4、CollapsedStatusBarFragment

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mStatusBar = (PhoneStatusBarView) view;
        if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_PANEL_STATE)) {
            mStatusBar.restoreHierarchyState(
                    savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));
        }
        // TINNO BEGIN, add for KGDAANWIKFRA-135
        if (Utils.TINNO_NOTCH_SCREEN) {
            mStatusBarDarkView = mStatusBar.findViewById(R.id.status_bar_dark_view);
            toggleStatusBarDarkView();
            mLastOrientation = getContext().getResources().getConfiguration().orientation;
        }
        // TINNO END
        //DarkIconManager初始化
        mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons),
                Dependency.get(CommandQueue.class));
        
        mDarkIconManager.setShouldLog(true);
        Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
        mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
        mClockView = mStatusBar.findViewById(R.id.clock);
        showSystemIconArea(false);
        showClock(false);
        initEmergencyCryptkeeperText();
        initOperatorName(); 
    }

4、StatusBarIconController

①DarkIconManager

    /** 
     * Version of ViewGroup that observes state from the DarkIconDispatcher. 
     */ 
    public static class DarkIconManager extends IconManager { 
        private final DarkIconDispatcher mDarkIconDispatcher; 
        private int mIconHPadding; 

        public DarkIconManager(LinearLayout linearLayout, CommandQueue commandQueue) { 
            super(linearLayout, commandQueue); 
            mIconHPadding = mContext.getResources().getDimensionPixelSize( 
                    R.dimen.status_bar_icon_padding); 
            mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class); 
        } 
        //Each icon should correspond to the index representing the order and the slot with the data type String 
        @Override
        protected void onIconAdded(int index, String slot, boolean blocked,
                StatusBarIconHolder holder) {
            StatusIconDisplayable view = addHolder(index, slot, blocked, holder);
            mDarkIconDispatcher.addDarkReceiver((DarkReceiver) view);
        }
        .....

        //onSetIcon可能就是刷新icon状态的
        @Override
        public void onSetIcon(int viewIndex, StatusBarIcon icon) {
            super.onSetIcon(viewIndex, icon);
            mDarkIconDispatcher.applyDark((DarkReceiver) mGroup.getChildAt(viewIndex));
        }
        .....

    }

②IconManager

public static class IconManager implements DemoMode {
    .....
    protected void onIconAdded(int index, String slot, boolean blocked,
        StatusBarIconHolder holder) {
        
        addHolder(index, slot, blocked, holder);
    }    
    
    protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked,
        StatusBarIconHolder holder) {
        switch (holder.getType()) {
            case TYPE_ICON:
                return addIcon(index, slot, blocked, holder.getIcon());
    
            case TYPE_WIFI:
                return addSignalIcon(index, slot, holder.getWifiState());
    
            case TYPE_MOBILE:
                return addMobileIcon(index, slot, holder.getMobileState());
        }
        return null;
    }
    
    @VisibleForTesting
    protected StatusBarIconView addIcon(int index, String slot, boolean blocked,
            StatusBarIcon icon) {
        
            StatusBarIconView view = onCreateStatusBarIconView(slot, blocked);
            view.set(icon);
            mGroup.addView(view, index, onCreateLayoutParams());
            return view;
    }
        .....
}

5、StatusBarIconControllerImpl

//继承StatusBarIconList
public class StatusBarIconControllerImpl extends StatusBarIconList implements Tunable,
        ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController {   
    ......        
    @Inject
    public StatusBarIconControllerImpl(Context context, CommandQueue commandQueue) {
        //config_statusBarIcons
        super(context.getResources().getStringArray(
                com.android.internal.R.array.config_statusBarIcons));
        Dependency.get(ConfigurationController.class).addCallback(this);
     .....
    }
}

6、StatusBarIconList

All slots have been defined during initialization and then loaded from the framework. The index is the order in the string-array.

public class StatusBarIconList {
    private ArrayList<Slot> mSlots = new ArrayList<>();

    public StatusBarIconList(String[] slots) {
        final int N = slots.length;
        for (int i=0; i < N; i++) {
            mSlots.add(new Slot(slots[i], null));
        }
    }

7、config_statusBarIcons

<string-array name="config_statusBarIcons">
        <item><xliff:g id="id">@string/status_bar_rotate</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_headset</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_data_saver</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_managed_profile</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_ime</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_sync_failing</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_sync_active</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_location</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_bluetooth</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_nfc</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_tty</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_speakerphone</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_zen</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_mute</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_volume</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_vpn</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_ethernet</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_wifi</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_mobile</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_airplane</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_cdma_eri</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_data_connection</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_phone_evdo_signal</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_phone_signal</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_battery</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_alarm_clock</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_secure</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_clock</xliff:g></item>
    </string-array>
 
    <string translatable="false" name="status_bar_rotate">rotate</string>
    <string translatable="false" name="status_bar_headset">headset</string>
    <string translatable="false" name="status_bar_data_saver">data_saver</string>
    <string translatable="false" name="status_bar_managed_profile">managed_profile</string>
    <string translatable="false" name="status_bar_ime">ime</string>
    <string translatable="false" name="status_bar_sync_failing">sync_failing</string>
    <string translatable="false" name="status_bar_sync_active">sync_active</string>
    <string translatable="false" name="status_bar_cast">cast</string>
    <string translatable="false" name="status_bar_hotspot">hotspot</string>
    <string translatable="false" name="status_bar_location">location</string>
    <string translatable="false" name="status_bar_bluetooth">bluetooth</string>
    <string translatable="false" name="status_bar_nfc">nfc</string>
    <string translatable="false" name="status_bar_tty">tty</string>
    <string translatable="false" name="status_bar_speakerphone">speakerphone</string>
    <string translatable="false" name="status_bar_zen">zen</string>
    <string translatable="false" name="status_bar_mute">mute</string>
    <string translatable="false" name="status_bar_volume">volume</string>
    <string translatable="false" name="status_bar_wifi">wifi</string>
    <string translatable="false" name="status_bar_cdma_eri">cdma_eri</string>
    <string translatable="false" name="status_bar_data_connection">data_connection</string>
    <string translatable="false" name="status_bar_phone_evdo_signal">phone_evdo_signal</string>
    <string translatable="false" name="status_bar_phone_signal">phone_signal</string>
    <string translatable="false" name="status_bar_battery">battery</string>
    <string translatable="false" name="status_bar_alarm_clock">alarm_clock</string>
    <string translatable="false" name="status_bar_secure">secure</string>
    <string translatable="false" name="status_bar_clock">clock</string>
    <string translatable="false" name="status_bar_mobile">mobile</string>
    <string translatable="false" name="status_bar_vpn">vpn</string>
    <string translatable="false" name="status_bar_ethernet">ethernet</string>
    <string translatable="false" name="status_bar_airplane">airplane</string>

Okay, here we have finished the first part of the initialization process.

4. Status display process

From the above initialization process, we can know that each icon corresponds to a slot, and the number of slots is relatively large. Let's choose a common Headset to talk about. The other processes are roughly the same.

1、PhoneStatusBarPolicy

A large number of listeners are registered during initialization

①init

//  初始化headset的slot
mSlotHeadset = resources.getString(com.android.internal.R.string.status_bar_headset);
/** Initialize the object after construction. */
public void init() {
    // listen for broadcasts
    IntentFilter filter = new IntentFilter();
    //  注册headset状态变化的action
    filter.addAction(AudioManager.ACTION_HEADSET_PLUG);
    
    filter.addAction(Intent.ACTION_SIM_STATE_CHANGED);
    filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED);
    filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
    filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
    filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
    mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler);
    Observer<Integer> observer = ringer -> mHandler.post(this::updateVolumeZen);

    mRingerModeTracker.getRingerMode().observeForever(observer);
    mRingerModeTracker.getRingerModeInternal().observeForever(observer);
    ....
    }

②BroadcastReceiver

    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            switch (action) {
                case Intent.ACTION_SIM_STATE_CHANGED:
                    // Avoid rebroadcast because SysUI is direct boot aware.
                    if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
                        break;
                    }
                    break;
                case TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED:
                    updateTTY(intent.getIntExtra(TelecomManager.EXTRA_CURRENT_TTY_MODE,
                            TelecomManager.TTY_MODE_OFF));
                    break;
                case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
                case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
                case Intent.ACTION_MANAGED_PROFILE_REMOVED:
                    updateManagedProfile();
                    break;
                //监听ACTION_HEADSET_PLUG
                case AudioManager.ACTION_HEADSET_PLUG:
                    updateHeadsetPlug(context, intent);
                    break;
            }
        }
    };

③updateHeadsetPlug

Complete icon addition and status monitoring, and then update the headset icon when receiving the corresponding action change.

    private void updateHeadsetPlug(Context context, Intent intent) {
        boolean connected = intent.getIntExtra("state", 0) != 0;
        boolean hasMic = intent.getIntExtra("microphone", 0) != 0;
        if (connected) {
            String contentDescription = mResources.getString(hasMic
                    ? R.string.accessibility_status_bar_headset
                    : R.string.accessibility_status_bar_headphones);
            //setIcon负责设置icon
            mIconController.setIcon(mSlotHeadset, hasMic ? R.drawable.stat_sys_headset_mic
                    : R.drawable.stat_sys_headset, contentDescription);
            //setIconVisibility则根据connected状态设置icon的可见性
            mIconController.setIconVisibility(mSlotHeadset, true); 
        } else { 
            /*UNISOC: Add for bug 1130932 {@ */ 
            AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 
            if (!audioManager.isWiredHeadsetOn()) { 
                / /setIconVisibility sets the visibility of the icon according to the connected state 
                mIconController.setIconVisibility(mSlotHeadset, false); 
            } 
            /* @} */ 
        } 
    }

2、StatusBarIconControllerImpl

①setIcon

@Override 
public void setIcon(String slot, int resourceId, CharSequence contentDescription) { 
    //Find the corresponding index according to the slot 
    int index = getSlotIndex(slot); 
    //Use index to obtain the corresponding icon 
    StatusBarIconHolder holder = getIcon(index, 0); 
    if (holder == null) { 
        //The first time, icon == null, so create a 
        StatusBarIcon through new StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(), 
                Icon.createWithResource( 
                        mContext, resourceId ), 0, 0, contentDescription); 
        holder = StatusBarIconHolder.fromIcon(icon); 
        //Then call this method 
        setIcon(index, holder); 
    } else {
        holder.getIcon().icon = Icon.createWithResource(mContext, resourceId);
        holder.getIcon().contentDescription = contentDescription;
        handleSet(index, holder);
    }
}


@Override
public void setIcon(int index, @NonNull StatusBarIconHolder holder) {
    boolean isNew = getIcon(index, holder.getTag()) == null;
    super.setIcon(index, holder);

    if (isNew) {
        addSystemIcon(index, holder);
    } else {
        handleSet(index, holder);
    }
}

private void addSystemIcon(int index, StatusBarIconHolder holder) {
    String slot = getSlotName(index);
    int viewIndex = getViewIndex(index, holder.getTag()); 
    boolean blocked = mIconBlacklist.contains(slot); 
    //onIconAdded-》In the initialization process, add the icon entrance to StatusBarIconController 
    //At this point, setIcon is added 
    mIconGroups.forEach (l -> l.onIconAdded(viewIndex, slot, blocked, holder)); 
}

②setIconVisibility

public void setIconVisibility(String slot, boolean visibility) { 
    int index = getSlotIndex(slot); 
    StatusBarIconHolder holder = getIcon(index, 0); 
    if (holder == null || holder.isVisible() == visibility) { 
        return; 
    } 
    holder.setVisible(visibility); 
    //The icon has been created successfully, the icon is not empty, and the first time is icon.visible != visibility 
    //It will smoothly go to handleSet(index, icon) 
    handleSet(index, holder ); 
} 

private void handleSet(int index, StatusBarIconHolder holder) { 
    int viewIndex = getViewIndex(index, holder.getTag()); 
    //The place where setIcon is set in StatusBarIconController in the initialization process 
    //At this point, setIconVisibility is also set
    mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
}

5. Create a status bar window

1、StatusBarWindowController

mStatusBarWindowController.attach()

    /**
     * Adds the status bar view to the window manager.
     */
    public void attach() {
        // Now that the status bar window encompasses the sliding panel and its
        // translucent backdrop, the entire thing is made TRANSLUCENT and is
        // hardware-accelerated.
        mLp = new WindowManager.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                mBarHeight,
                WindowManager.LayoutParams.TYPE_STATUS_BAR,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                        | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
                PixelFormat.TRANSLUCENT);
        mLp.privateFlags |= PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
        mLp.token = new Binder();
        mLp.gravity = Gravity.TOP;
        mLp.setFitInsetsTypes(0 /* types */);
        mLp.setTitle("StatusBar");
        mLp.packageName = mContext.getPackageName();
        mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;

        //WindowManager中添加view
        //mStatusBarView = mSuperStatusBarViewFactory.getStatusBarWindowView();
        //private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
        mWindowManager.addView(mStatusBarView, mLp);
        mLpChanged.copyFrom(mLp);
    }

2、SuperStatusBarViewFactory

    /**
     * Gets the inflated {@link StatusBarWindowView} from {@link R.layout#super_status_bar}.
     * Returns a cached instance, if it has already been inflated.
     */
    public StatusBarWindowView getStatusBarWindowView() {
        if (mStatusBarWindowView != null) {
            return mStatusBarWindowView;
        }

        //由其可知加载的布局来自于super_status_bar
        mStatusBarWindowView =
                (StatusBarWindowView) mInjectionInflationController.injectable(
                LayoutInflater.from(mContext)).inflate(R.layout.super_status_bar,
                /* root= */ null);
        if (mStatusBarWindowView == null) {
            throw new IllegalStateException(
                    "R.layout.super_status_bar could not be properly inflated");
        }
        return mStatusBarWindowView;
    }


3、super_status_bar.xml

As can be seen from the front, status_bar_container (status bar notification display area) will be replaced with CollapsedStatusBarFragment to complete the display of the icon.

<!-- This is the status bar window. -->
<com.android.systemui.statusbar.phone.StatusBarWindowView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:sysui="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
    >
        <FrameLayout
            android:id="@+id/status_bar_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:visibility="gone"
        />

        <FrameLayout
            android:id="@+id/car_top_navigation_bar_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>

</com.android.systemui.statusbar.phone.StatusBarWindowView

6. Battery icon refresh process

1、BatteryMeterView

①Construction method BatteryMeterView()

    public BatteryMeterView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        BroadcastDispatcher broadcastDispatcher = Dependency.get(BroadcastDispatcher.class);

        setOrientation(LinearLayout.HORIZONTAL);
        setGravity(Gravity.CENTER_VERTICAL | Gravity.START);

        TypedArray atts = context.obtainStyledAttributes(attrs, R.styleable.BatteryMeterView,
                defStyle, 0);
        final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
                context.getColor(R.color.meter_background_color));
        mPercentageStyleId = atts.getResourceId(R.styleable.BatteryMeterView_textAppearance, 0);
        /*Bug 1296708 add charge animation of batteryView*/ 
        //Add charging animation of battery view true 
        mBatteryAnimation = mContext.getResources().getBoolean( 
                R.bool.config_battery_animation); 
        
        //Add battery level to parent layout 
        if (mBatteryAnimation ) { 
            //mBatteryAnimation is true 
            mUnisocDrawable = new BatteryMeterDrawable(context, new Handler(), frameColor, false); 
        }else{ 
            mDrawable = new ThemedBatteryDrawable(context, frameColor); 
        } 
        /*@}*/ 

        atts.recycle(); 

        mSettingObserver = new SettingObserver(new Handler(context.getMainLooper()));
        mShowPercentAvailable = context.getResources().getBoolean(
                com.android.internal.R.bool.config_battery_percentage_setting_available);


        addOnAttachStateChangeListener(
                new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS,
                        Dependency.get(CommandQueue.class)));

        setupLayoutTransition();

        mSlotBattery = context.getString(
                com.android.internal.R.string.status_bar_battery);
        mBatteryIconView = new ImageView(context);
        /*Bug 1296708 add charge animation of batteryView*/
        //添加电池视图的充电动画
        if (mBatteryAnimation) {
            mBatteryIconView.setImageDrawable(mUnisocDrawable);
        }else{
            mBatteryIconView.setImageDrawable(mDrawable);
        }

        final MarginLayoutParams mlp = new MarginLayoutParams(
                getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_width),
                getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_height));
        mlp.setMargins(0, 0, 0,
                getResources().getDimensionPixelOffset(R.dimen.battery_margin_bottom));
        addView(mBatteryIconView, mlp);

        updateShowPercent();
        mDualToneHandler = new DualToneHandler(context);
        // Init to not dark at all.
        //Set the default theme color of the battery layout. When the status bar theme changes, the battery layout will be changed accordingly (switching between light and dark colors) //A DarkReceiver listener is added to the PhoneStatusBarView, and finally the onDarkChanged 
        () of the BatteryMeterView is called. Method 
        onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT); 

        //Set Settings.System.SHOW_BATTERY_PERCENT to monitor 
        mUserTracker = new CurrentUserTracker(broadcastDispatcher) { 
            @Override 
            public void onUserSwitched(int newUserId) { 
                mUser = newUserId; 
                getContext(). getContentResolver().unregisterContentObserver(mSettingObserver); 
                getContext().getContentResolver().registerContentObserver(
                        Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mSettingObserver, 
                        newUserId); 
                //When the user clicks the switch to display the battery percentage, the updateShowPercent() method is called to add the battery percentage before the battery level 
                updateShowPercent(); 
            } 
        }; 

        setClipChildren(false ); 
        setClipToPadding(false); 
        Dependency.get(ConfigurationController.class).observe(viewAttachLifecycle(this), this); 
    }

②onDarkChanged

    //Modify the percentage font color and battery level brush color and background color 
    @Override 
    public void onDarkChanged(Rect area, float darkIntensity, int tint) { 
        float intensity = DarkIconDispatcher.isInArea(area, this) ? darkIntensity : 0; 
        mNonAdaptedSingleToneColor = mDualToneHandler.getSingleColor(intensity); 
        mNonAdaptedForegroundColor = mDualToneHandler.getFillColor(intensity); 
        mNonAdaptedBackgroundColor = mDualToneHandler.getBackgroundColor(intensity); 

        if (!mUseWallpaperTextColors) { 
            //Add battery charging animation 
            updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackground Color, 
                    mNonAdaptedSingleToneColor); 
        } 
    }

③updateColors

    //Add the battery percentage before the battery level 
    private void updateColors(int foregroundColor, int backgroundColor, int singleToneColor) { 
        /*Bug 1296708 add charge animation of batteryView*/ 
        //Add battery charging animation 
        if (mDrawable != null) { 
          mDrawable. setColors(foregroundColor, backgroundColor, singleToneColor); 
        } 
        if (mUnisocDrawable != null) { 
          mUnisocDrawable.setColors(foregroundColor, backgroundColor); 
        } 
        mTextColor = singleToneColor; 
        if (mBatteryPercentView != null) { 
            mBatteryPercentView.setTextColor(singleToneColor); 
        } 
    }

2、PhoneStatusBarView

    private DarkReceiver mBattery; 

    @Override 
    public void onFinishInflate() { 
        // 
        mBattery = findViewById(R.id.battery); 
        mCutoutSpace = findViewById(R.id.cutout_space_view); 
        mCenterIconSpace = findViewById(R.id.centered_icon_area); 
        updateResources() ; 
    } 

    @Override 
    protected void onAttachedToWindow() { 
        super.onAttachedToWindow(); 
        // Always have Battery meters in the status bar observe the dark/light modes. 
        // Always have Battery meters in the status bar observe the dark/light modes. 
        Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mBattery); 
        if (updateOrientationAndCutout()) {
            updateLayoutForCutout();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mBattery);
        mDisplayCutout = null;
    }

7. Battery status change process

1、BatteryControllerImpl

①init

    @Override
    public void init() {
        //注册广播
        registerReceiver();
        if (!mHasReceivedBattery) {
            // Get initial state. Relying on Sticky behavior until API for getting info.
            Intent intent = mContext.registerReceiver(
                    null,
                    new IntentFilter(Intent.ACTION_BATTERY_CHANGED)
            );
            if (intent != null && !mHasReceivedBattery) {
                onReceive(mContext, intent);
            }
        }
        updatePowerSave();
        updateEstimate();
    }

②registerReceiver

    private void registerReceiver() {
        IntentFilter filter = new IntentFilter();
        //添加广播的方式接收
        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
        
        filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
        /* UNISOC: Bug 1363779 battery icon shows '+' after switching from power saving mode to super power saving @{ */
        filter.addAction(UnisocPowerManagerUtil.ACTION_POWEREX_SAVE_MODE_CHANGED);
        /* @} */
        filter.addAction(ACTION_LEVEL_TEST);
        mBroadcastDispatcher.registerReceiver(this, filter);
    }

③onReceive

    @Override
    public void onReceive(final Context context, Intent intent) {
        final String action = intent.getAction();
        //监听到ACTION_BATTERY_CHANGED
        if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
            if (mTestmode && !intent.getBooleanExtra("testmode", false)) return;
            mHasReceivedBattery = true;
            mLevel = (int)(100f
                    * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
                    / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
            mPluggedIn = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;

            final int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
                    BatteryManager.BATTERY_STATUS_UNKNOWN); 
            mCharged = status == BatteryManager.BATTERY_STATUS_FULL; 
            mCharging = mCharged || status == BatteryManager.BATTERY_STATUS_CHARGING; 
            //Traverse callback monitoring and send status parameters 
            fireBatteryLevelChanged(); 
        } 
        .... 
    }

④fireBatteryLevelChanged

    protected final ArrayList<BatteryController.BatteryStateChangeCallback> 
            mChangeCallbacks = new ArrayList<>(); 

    protected void fireBatteryLevelChanged() { 
        synchronized (mChangeCallbacks) { 
            final int N = mChangeCallbacks.size(); 
            //Traverse the callback listening and send the status parameters 
            for ( int i = 0; i < N; i++) { 
                mChangeCallbacks.get(i).onBatteryLevelChanged(mLevel, mPluggedIn, mCharging); 
            } 
        } 
    }

2、BatteryStateChangeCallback

    interface BatteryStateChangeCallback {

        default void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
        }

        default void onPowerSaveChanged(boolean isPowerSave) {
        }

        default void onReverseChanged(boolean isReverse, int level, String name) {
        }
    }

3、BatteryMeterView

BatteryMeterView implements BatteryStateChangeCallback and receives change monitoring onBatteryLevelChanged()

    //Implemented BatteryStateChangeCallback 
    public class BatteryMeterView extends LinearLayout implements 
        BatteryStateChangeCallback, Tunable, DarkReceiver, ConfigurationListener { 
      
    ..... 
    @Override 
    public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { 
        /*Bug 1296708 add charge animation of batteryView* / 
        if (mDrawable != null) { 
            //Whether to draw the charging lightning-shaped icon 
            mDrawable.setCharging(pluggedIn); 
            //Calculate the percentage drawing path based on the current level/100f 
            mDrawable.setBatteryLevel(level); 
        } 
        mCharging = pluggedIn; 
        mLevel = level; 
        updatePercentText();
    }
        ....
    }

8. NavigationBar navigation bar module

ⅠCreate navigation bar file

1、Status Bar

①makeStatusBarView

    // ================================================================================
    // Constructing the view
    // ================================================================================
    protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
        ....
            //创建导航栏
            createNavigationBar(result);
        .....

②createNavigationBar

    private final NavigationBarController mNavigationBarController;

    // TODO(b/117478341): This was left such that CarStatusBar can override this method.
    // Try to remove this.
    protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
        //
        mNavigationBarController.createNavigationBars(true /* includeDefaultDisplay */, result);

        ...
    }

2、NavigationBarController

①createNavigationBars

    public void createNavigationBars(final boolean includeDefaultDisplay,
            RegisterStatusBarResult result) {
        Display[] displays = mDisplayManager.getDisplays();
        for (Display display : displays) {
            if (includeDefaultDisplay || display.getDisplayId() != DEFAULT_DISPLAY) {
                //
                createNavigationBar(display, result);
            }
        }
    }

②createNavigationBar

   /**
     * Adds a navigation bar on default display or an external display if the display supports
     * system decorations.
     *
     * @param display the display to add navigation bar on.
     */
    @VisibleForTesting
    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 {
            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); 
        //Finally, it is created through the create method of NavigationBarFragment. 
        NavigationBarFragment.create(context, (tag, fragment) -> { 
            NavigationBarFragment navBar = (NavigationBarFragment) fragment;

3、NavigationBarFragment

①create

The code does two things:

1. Create navigationBarView and add navigationBarView to windowManager.

2. Create NavigationBarFragment and replace the layout file of navigation_bar_window with navigation_bar.

    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" + 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);

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

        //创建NavigationBarFragment 替换navigation_bar_window的布局文件,改成navigation_bar
        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);
                //navigation_bar_frame是navigation_bar_window中NavigationBarFrame的ID
                fragmentHost.getFragmentManager().beginTransaction()
                        .replace(R.id.navigation_bar_frame, fragment, TAG)
                        .commit();
                fragmentHost.addTagListener(TAG, listener);
            }

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

③onCreateView

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

4、navigation_bar_window.xml

<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:theme="@style/Theme.SystemUI"
    android:layout_height="match_parent"
    android:layout_width="match_parent">

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

5、navigation_bar.xml

<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.CornerHandleView
        android:id="@+id/assist_hint_left"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="left|bottom"
        android:rotation="270"
        android:visibility="gone"/>
    <com.android.systemui.CornerHandleView
        android:id="@+id/assist_hint_right"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="right|bottom"
        android:rotation="180"
        android:visibility="gone"/>

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

ⅡLoad layout file

6、NavigationBarInflaterView

①NavigationBarInflaterView(Context context, AttributeSet attrs)

    public NavigationBarInflaterView(Context context, AttributeSet attrs) {
        super(context, attrs);
        createInflaters();
        mOverviewProxyService = Dependency.get(OverviewProxyService.class);
        mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
        /* UNISOC: add for bug 1071183,1134237  @{ */
        mSupportDynamicBar = NavigationBarView.isSupportDynamicNavBar(context, mNavBarMode);
        /* }@ */
    }

②createInflaters

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

    @Override 
    protected void onFinishInflate() { 
        super.onFinishInflate(); 
        //Add a horizontal horizontal screen layout 
        inflateChildren(); 
        //Clear the layout 
        clearViews(); 
        inflateLayout(getDefaultLayout()); 
    }

④inflateChildren

    private void inflateChildren() {
        removeAllViews();
        //水平布局
        mHorizontal = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout,
                this /* root */, false /* attachToRoot */);
        addView(mHorizontal);
        //垂直布局
        mVertical = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout_vertical,
                this /* root */, false /* attachToRoot */);
        addView(mVertical);
        updatealternativeorder();
    }

⑤clearViews

    private void clearViews() {
        if (mButtonDispatchers != null) {
            for (int i = 0; i < mButtonDispatchers.size(); i++) {
                mButtonDispatchers.valueAt(i).clear();
            }
        }
        clearAllChildren(mHorizontal.findViewById(R.id.nav_buttons));
        clearAllChildren(mVertical.findViewById(R.id.nav_buttons));
    }

    private void clearAllChildren(ViewGroup group) {
        for (int i = 0; i < group.getChildCount(); i++) {
            ((ViewGroup) group.getChildAt(i)).removeAllViews();
        }
    }

⑥inflateLayout

    protected void inflateLayout(String newLayout) {
        mCurrentLayout = newLayout;
        if (newLayout == null) {
            newLayout = getDefaultLayout();
        }
        String[] sets = newLayout.split(GRAVITY_SEPARATOR, 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);
        String[] center = sets[1].split(BUTTON_SEPARATOR);
        String[] end = sets[2].split(BUTTON_SEPARATOR);
        // Inflate these in start to end order or accessibility traversal will be messed up.
        inflateButtons(start, mHorizontal.findViewById(R.id.ends_group),
                false /* landscape */, true /* start */);
        inflateButtons(start, mVertical.findViewById(R.id.ends_group),
                true /* landscape */, true /* start */);

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

        addGravitySpacer(mHorizontal.findViewById(R.id.ends_group));
        addGravitySpacer(mVertical.findViewById(R.id.ends_group));

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

        updateButtonDispatchersCurrentView();
    }

⑦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);
        if (v == null) return null;

        v = applySize(v, buttonSpec, landscape, start);
        parent.addView(v);
        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;
    }

⑧getDefaultLayout

    //Which controls are displayed in the navigation bar are determined by getDefaultLayout 
    protected String getDefaultLayout() { 
        /* UNISOC: Bug 1072090 new feature of dynamic navigationbar @{ */ 
        if (mSupportDynamicBar) { 
            return readLNavigationLayoutSettings(); 
        } 
        /* @} */ 

        final int defaultResource = QuickStepContract.isGesturalMode(mNavBarMode) 
                ? R.string.config_navBarLayoutHandle 
                : mOverviewProxyService.shouldShowSwipeUpUI() 
                ? R.string.config_navBarLayoutQuickstep 
                : R.string.config_navBarLayout; 
        return getContext().getString(defaultResource); 
    }

⑨createView

    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 (RECENT.equals(button)) {
            v = inflater.inflate(R.layout.recent_apps, parent, false);
        } else if (MENU_IME_ROTATE.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);
        } else if (CONTEXTUAL.equals(button)) {
            v = inflater.inflate(R.layout.contextual, parent, false);
        } else if (HOME_HANDLE.equals(button)) {
            v = inflater.inflate(R.layout.home_handle, parent, false);
        } else if (IME_SWITCHER.equals(button)) {
            v = inflater.inflate(R.layout.ime_switcher, 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));
                }
            }
        }
        /* UNISOC: Bug 1072090 new feature of dynamic navigationbar @{ */
        else if (HIDE.equals(button)) {
            v = inflater.inflate(R.layout.hide, parent, false);
        } else if (PULL.equals(button)) {
            v = inflater.inflate(R.layout.pull, parent, false);
            /*UNISOC: Add for bug 902309 1146896 @{ */
        } else if (SPACE_PLACE.equals(button)) {
            v = inflater.inflate(R.layout.space, parent, false);
            /* }@ */
        } else {
            return null;
        }
        /* @} */
        return v;
    }

7、home.xml、back.xml、recent_apps.xml

<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"
    android:layout_height="match_parent"
    android:layout_weight="0"
    systemui:keyCode="3"
    android:scaleType="center"
    android:contentDescription="@string/accessibility_home"
    android:paddingStart="@dimen/navigation_key_padding"
    android:paddingEnd="@dimen/navigation_key_padding"
    />
        

<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/back"
    android:layout_width="@dimen/navigation_key_width"
    android:layout_height="match_parent"
    android:layout_weight="0"
    systemui:keyCode="4"
    android:scaleType="center"
    android:contentDescription="@string/accessibility_back"
    android:paddingStart="@dimen/navigation_key_padding"
    android:paddingEnd="@dimen/navigation_key_padding"
    />

        
        
<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/recent_apps"
    android:layout_width="@dimen/navigation_key_width"
    android:layout_height="match_parent"
    android:layout_weight="0"
    android:scaleType="center"
    android:contentDescription="@string/accessibility_recent"
    android:paddingStart="@dimen/navigation_key_padding"
    android:paddingEnd="@dimen/navigation_key_padding"
    />

8、config

    <!-- Nav bar button default ordering/layout -->
    <string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string>
    <string name="config_navBarLayoutQuickstep" translatable="false">back[1.7WC];home;contextual[1.7WC]</string>
    <string name="config_navBarLayoutHandle" translatable="false">back[40AC];home_handle;ime_switcher[40AC]</string>

First, which controls are displayed in the navigation bar are determined by getDefaultLayout.

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

Normally we have three keys: home, recent, and back. If you need to add others, just put them in this configuration folder. At the same time, add the corresponding layout file in createView.

Second, the createView method creates the corresponding layout file and adds it to the navigation bar.

So now we have added the layout files, but you will find that there is no code added to the resource file in NavigationBarInflaterView or control click touch event processing logic. So where are these two parts of code?

the answer is:

1.NavigationBarView completes adding resource files.

2.NavigationBarFragment adds processing logic for click events and touch events.

ⅢAdd resource files

1、NavigationBarView

①NavigationBarView(Context context, AttributeSet attrs)

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

        mIsVertical = false;
        mLongClickableAccessibilityButton = false;
        mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
        //UNISOC: Add for bug 1242615
        mOldNavBarMode = mNavBarMode;
        /* UNISCO: Bug 1072090,1116092 new feature of dynamic navigationbar @{*/
        mSupportDynamicBar = isSupportDynamicNavBar(mContext, mNavBarMode);
        mKeyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
        /* }@ */
        /* UNISOC: Modify for bug963304 {@ */
        mStatusBarManager = (StatusBarManager) mContext.getSystemService(android.app.Service.STATUS_BAR_SERVICE);
        /* @} */
        boolean isGesturalMode = isGesturalMode(mNavBarMode);

        mSysUiFlagContainer = Dependency.get(SysUiState.class);
        mPluginManager = Dependency.get(PluginManager.class);
        // Set up the context group of buttons
        mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container);
        final ContextualButton imeSwitcherButton = new ContextualButton(R.id.ime_switcher,
                R.drawable.ic_ime_switcher_default);
        final RotationContextButton rotateSuggestionButton = new RotationContextButton(
                R.id.rotate_suggestion, R.drawable.ic_sysbar_rotate_button);
        final ContextualButton accessibilityButton =
                new ContextualButton(R.id.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);

        mConfiguration = new Configuration();
        mTmpLastConfiguration = new Configuration();
        mConfiguration.updateFrom(context.getResources().getConfiguration());

        mScreenPinningNotify = new ScreenPinningNotify(mContext);
        mBarTransitions = new NavigationBarTransitions(this, Dependency.get(CommandQueue.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.home_handle, new ButtonDispatcher(R.id.home_handle));
        mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
        mButtonDispatchers.put(R.id.ime_switcher, imeSwitcherButton);
        mButtonDispatchers.put(R.id.accessibility_button, accessibilityButton);
        mButtonDispatchers.put(R.id.rotate_suggestion, rotateSuggestionButton);
        mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup);
        mDeadZone = new DeadZone(this);

        /* UNISOC: Bug 1072090 new feature of dynamic navigationbar @{ */
        if(mSupportDynamicBar){
            mStatusBar = Dependency.get(StatusBar.class);
            mButtonDispatchers.put(R.id.hide, new ButtonDispatcher(R.id.hide));
            mButtonDispatchers.put(R.id.pull, new ButtonDispatcher(R.id.pull));
        }

        mNavColorSampleMargin = getResources()
                        .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
        //updateStates更新状态
        mEdgeBackGestureHandler = new EdgeBackGestureHandler(context, mOverviewProxyService,
                mSysUiFlagContainer, mPluginManager, this::updateStates);
        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);
                    }
                });
    }

②updateStates

    public void updateStates() {
        final boolean showSwipeUpUI = mOverviewProxyService.shouldShowSwipeUpUI();

        if (mNavigationInflaterView != null) {
            // Reinflate the navbar if needed, no-op unless the swipe up state changes
            mNavigationInflaterView.onLikelyDefaultLayoutChange();
        }

        updateSlippery();
        //初始化加载资源,主要是图片
        reloadNavIcons();
        updateNavButtonIcons();
        setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
        WindowManagerWrapper.getInstance().setNavBarVirtualKeyHapticFeedbackEnabled(!showSwipeUpUI);
        getHomeButton().setAccessibilityDelegate(
                showSwipeUpUI ? mQuickStepAccessibilityDelegate : null);
    }

③reloadNavIcons

    //Initialize loading resources, mainly pictures 
    private void reloadNavIcons() { 
        updateIcons(Configuration.EMPTY); 
    }

ⅣAdd processing logic for click events and touch events.

1、NavigationBarFragment

①onViewCreated

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mNavigationBarView = (NavigationBarView) view;
        final Display display = view.getDisplay();
        // It may not have display when running unit test.
        if (display != null) {
            mDisplayId = display.getDisplayId();
            mIsOnDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY;
        }

        mNavigationBarView.setComponents(mStatusBarLazy.get().getPanelController());
        mNavigationBarView.setDisabledFlags(mDisabledFlags1);
        mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged);
        mNavigationBarView.setOnTouchListener(this::onNavigationTouch);
        if (savedInstanceState != null) {
            mNavigationBarView.getLightTransitionsController().restoreState(savedInstanceState);
        }
        mNavigationBarView.setNavigationIconHints(mNavigationIconHints);
        mNavigationBarView.setWindowVisible(isNavBarWindowVisible());
     
        添加home,recent触摸事件回调
        prepareNavigationBarView();
        checkNavBarModes();

        IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
        filter.addAction(Intent.ACTION_SCREEN_ON);
        filter.addAction(Intent.ACTION_USER_SWITCHED);
        //UNISOC: Add for bug 1274603
        filter.addAction(Intent.ACTION_USER_PRESENT);
        filter.addAction(PowerManagerEx.ACTION_POWEREX_SAVE_MODE_CHANGED);
        mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter,
                Handler.getMain(), UserHandle.ALL);
        notifyNavigationBarScreenOn();

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

②prepareNavigationBarView

setOnClickListener, setOnTouchListener, setLongClickable, setOnLongClickListener is to add control code to the corresponding control

    private void prepareNavigationBarView() {
        mNavigationBarView.reorient();

        ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
        recentsButton.setOnClickListener(this::onRecentsClick);
        recentsButton.setOnTouchListener(this::onRecentsTouch);
        recentsButton.setLongClickable(true);
        recentsButton.setOnLongClickListener(this::onLongPressBackRecents);

        ButtonDispatcher backButton = mNavigationBarView.getBackButton();
        backButton.setLongClickable(true);

        ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
        homeButton.setOnTouchListener(this::onHomeTouch);
        homeButton.setOnLongClickListener(this::onHomeLongClick);

        ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
        accessibilityButton.setOnClickListener(this::onAccessibilityClick);
        accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
        updateAccessibilityServicesState(mAccessibilityManager);

        updateScreenPinningGestures();
    }

③onHomeTouch

    private boolean onHomeTouch(View v, MotionEvent event) {
        if (mHomeBlockedThisTouch && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
            return true;
        }
        // If an incoming call is ringing, HOME is totally disabled.
        // (The user is already on the InCallUI at this point,
        // and his ONLY options are to answer or reject the call.)
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mHomeBlockedThisTouch = false;
                TelecomManager telecomManager =
                        getContext().getSystemService(TelecomManager.class);
                if (telecomManager != null && telecomManager.isRinging()) {
                    if (mStatusBarLazy.get().isKeyguardShowing()) {
                        Log.i(TAG, "Ignoring HOME; there's a ringing incoming call. " +
                                "No heads up");
                        mHomeBlockedThisTouch = true;
                        return true;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mStatusBarLazy.get().awakenDreams();
                break;
        }
        return false;
    }

④onHomeLongClick

    @VisibleForTesting
    boolean onHomeLongClick(View v) {
        if (!mNavigationBarView.isRecentsButtonVisible()
                && ActivityManagerWrapper.getInstance().isScreenPinningActive()) {
            return onLongPressBackHome(v);
        }
        if (shouldDisableNavbarGestures()) {
            return false;
        }
        mMetricsLogger.action(MetricsEvent.ACTION_ASSIST_LONG_PRESS);

        /* UNISOC: Bug 1074234, 970184, Super power feature @{ */
        if (UnisocPowerManagerUtil.isSuperPower()) {
            Log.d(TAG, "onHomeLongClick SUPPORT_SUPER_POWER_SAVE ignore!");
            return false;
        }
        /* @} */

        mUiEventLogger.log(NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS);
        Bundle args  = new Bundle();
        args.putInt(
                AssistManager.INVOCATION_TYPE_KEY, AssistManager.INVOCATION_HOME_BUTTON_LONG_PRESS);
        mAssistManager.startAssist(args);
        mStatusBarLazy.get().awakenDreams();

        if (mNavigationBarView != null) {
            mNavigationBarView.abortCurrentGesture();
        }
        return true;
    }

9. Recents module

packages/apps/SystemUI/src/com/android/systemui/recents/

1、Recent

public class Recents extends SystemUI implements CommandQueue.Callbacks { 
    private final RecentsImplementation mImpl; 
    private final CommandQueue mCommandQueue; 

    public Recents(Context context, RecentsImplementation impl, CommandQueue commandQueue) { 
        super(context); 
        mImpl = impl; 
        mCommandQueue = commandQueue; 
    } 

    //By As can be seen from the previous startup of SystemUI, all calls are to the start method of the subclass 
    // A callback is added to the start method and the onStart method of RecentsImplementation is called. Follow up below with RecentsImplementation 
    @Override 
    public void start() { 
        mCommandQueue.addCallback(this); 
        mImpl.onStart(mContext); 
    } 
    ... 
}

2、RecentsImplementation

public interface RecentsImplementation { 
    //You can see that the methods in this interface are all default-modified methods, but no function body is written. The specific implementation is implemented by subclasses, so we follow the OverviewProxyRecentsImpl class 
    default void onStart(Context context) {} 
    default void onBootCompleted() {} 
    default void onAppTransitionFinished() {} 
    default void onConfigurationChanged(Configuration newConfig) {} 

    default void preloadRecentApps() {} 
    default void cancelPreloadRecentApps() {} 
    default void showRecentApps(boolean triggeredFromAltTab) {} 
    default void hideRecentApps(boolean triggeredFromAltTab , boolean triggeredFromHomeKey) {} 
    default void toggleRecentApps() {} 
    default void growRecents() {}
    default boolean splitPrimaryTask(int stackCreateMode, Rect initialBounds,
            int metricsDockAction) {
        return false;
    }

    default void dump(PrintWriter pw) {}
}

3、OverviewProxyRecentsImpl

/**
 * An implementation of the Recents interface which proxies to the OverviewProxyService.
 */
@Singleton
public class OverviewProxyRecentsImpl implements RecentsImplementation {

    private final static String TAG = "OverviewProxyRecentsImpl";
    @Nullable
    private final Lazy<StatusBar> mStatusBarLazy;
    private final Optional<Divider> mDividerOptional;

    private Context mContext;
    private Handler mHandler;
    private TrustManager mTrustManager;
    private OverviewProxyService mOverviewProxyService;

    @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
    @Inject
    public OverviewProxyRecentsImpl(Optional<Lazy<StatusBar>> statusBarLazy, 
            Optional<Divider> dividerOptional) { 
        mStatusBarLazy = statusBarLazy.orElse(null); 
        mDividerOptional = dividerOptional; 
    } 
    
    //It can be seen that the onStart() method called before is specifically the subclass called. Override method 
    @Override 
    public void onStart(Context context) { 
        mContext = context; 
        mHandler = new Handler(); 
        mTrustManager = (TrustManager) context.getSystemService(Context.TRUST_SERVICE); 
        mOverviewProxyService = Dependency.get(OverviewProxyService.class); 
    } 
    
    
     @Override 
    public void toggleRecentApps() {
        // If connected to launcher service, let it handle the toggle logic 
        IOverviewProxy overviewProxy = mOverviewProxyService.getProxy(); 
        if (overviewProxy != null) { 
            final Runnable toggleRecents = () -> { 
                try { 
                    if (mOverviewProxyService.getProxy() ! = null) { 
         //You can see that the method of displaying the recent app is to get the proxy of OverviewProxyService and then operate it. 
         //Then follow the OverviewProxyService class to see the origin of overviewProxy 
                        mOverviewProxyService.getProxy().onOverviewToggle(); 
                        mOverviewProxyService .notifyToggleRecentApps(); 
                    } 
                } catch (RemoteException e) {
                    Log.e(TAG, "Cannot send toggle recents through proxy service.", e);
                }
            };
            // Preload only if device for current user is unlocked
            if (mStatusBarLazy != null && mStatusBarLazy.get().isKeyguardShowing()) {
                mStatusBarLazy.get().executeRunnableDismissingKeyguard(() -> {
                        // Flush trustmanager before checking device locked per user
                        mTrustManager.reportKeyguardShowingChanged();
                        mHandler.post(toggleRecents);
                    }, null,  true /* dismissShade */, false /* afterKeyguardGone */,
                    true /* deferred */);
            } else {
                toggleRecents.run();
            }
            return;
        } else {
            // Do nothing
        }
    }   
}

4、OverviewProxyService

①getProxy

    private IOverviewProxy mOverviewProxy; 

    //You can see that the getProxy() method returns a mOverviewProxy:IOverviewProxy object reference. Next, check which object it points to. 
    public IOverviewProxy getProxy() { 
        return mOverviewProxy; 
    }

②ServiceConnection

    //You should be able to find it through mOverviewServiceConnection, it should be a parameter in bindService. 
    private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() { 
        @Override 
        public void onServiceConnected(ComponentName name, IBinder service) { 
            if (SysUiState.DEBUG) { 
                Log.d(TAG_OPS, "Overview proxy service connected"); 
            } 
            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 = getCurrentUserId();
            //mOverviewProxy指向了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 {
                mOverviewProxy.onInitialize(params);
            } catch (RemoteException e) {
                mCurrentBoundedUserId = -1;
                Log.e(TAG_OPS, "Failed to call onInitialize()", e);
            }
            dispatchNavButtonBounds();
            ......
        }

③internalConnectToCurrentUser

    private void internalConnectToCurrentUser() {
        disconnectFromLauncherService();

        // If user has not setup yet or already connected, do not try to connect
        if (!isEnabled()) {
            Log.v(TAG_OPS, "Cannot attempt connection, is enabled " + isEnabled());
            return;
        }
        mHandler.removeCallbacks(mConnectionRunnable);
        //ACTION_QUICKSTEP,这个action就是Launcher中的
        Intent launcherServiceIntent = new Intent(ACTION_QUICKSTEP);
        if (mRecentsComponentName != null) {
            launcherServiceIntent.setPackage(mRecentsComponentName.getPackageName());
        }
        try {
            //The incoming intent is launcherServiceIntent, and its parameter is ACTION_QUICKSTEP. View the definition of this action as 
            mBound = mContext.bindServiceAsUser(launcherServiceIntent, 
                    mOverviewServiceConnection, 
                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, 
                    UserHandle.of(getCurrentUserId())); 
        } catch (SecurityException e) { 
            Log.e(TAG_OPS, "Unable to bind because of security error", e); 
        } catch (IllegalArgumentException e) { 
            Log.e(TAG_OPS, "Unable to bind because of illegal argument 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();
        }
    }

5、AndroidManifest.xml

The code is located in packages/apps/Launcher3/quickstep/AndroidManifest.xml

        <service
            //TouchInteractionService
            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>

6、TouchInteractionService

    private OverviewCommandHelper mOverviewCommandHelper;
   
private final IBinder mMyBinder = new IOverviewProxy.Stub() {

        @BinderThread
        public void onInitialize(Bundle bundle) {
            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;
        }

        @BinderThread
        @Override 
        //In other words, the mOverviewProxyService.getProxy().onOverviewToggle() // called by OverviewProxyRecentsImpl above // 
        actually calls the implementation of mmyBinder in TouchInteractionService. mmyBinder is a remote proxy of IOverviewProxy. 
        public void onOverviewToggle() { 
            TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle"); 
            mOverviewCommandHelper.onOverviewToggle(); 
        } 
        .... 
        }

7、OverviewCommandHelper

①onOverviewToggle

    //That is to say, mOverviewProxyService.getProxy().onOverviewToggle() called by OverviewProxyRecentsImpl above 
    @BinderThread 
    public void onOverviewToggle() { 
        // If currently screen pinning, do not enter overview 
        if (mDeviceState.isScreenPinningActive()) { 
            return; 
        } 

        ActivityManagerWrapper.getInstance() 
                .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); 
        //You can see that the RecentsActivityCommand thread is mainly started here 
        MAIN_EXECUTOR.execute(new RecentsActivityCommand<>()); 
    } 

    public RecentsActivityCommand() { 
        mActivityInterface = mOverviewComponentObserver.getActivityInterface();
        mCreateTime = SystemClock.elapsedRealtime();
        mAnimationProvider = new AppToOverviewAnimationProvider<>(mActivityInterface,
                RecentsModel.getRunningTaskId(), mDeviceState);

        // Preload the plan
        mRecentsModel.getTasks(null);
    }

②run()

Finally, a process from clicking the switch to launching the RecentsActivity of the Launcher was realized.

        
        @Override
        public void run() {
            long elapsedTime = mCreateTime - mLastToggleTime;
            mLastToggleTime = mCreateTime;

            if (handleCommand(elapsedTime)) {
                // Command already handled.
                return;
            }
    
            if (mActivityInterface.switchToRecentsIfVisible(this::onTransitionComplete)) {
                // If successfully switched, then return
                return;
            }

            // Otherwise, start overview.
            mListener = mActivityInterface.createActivityInitListener(this::onActivityReady);
            mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),
                    new RemoteAnimationProvider() {
                        @Override
                        public AnimatorSet createWindowAnimation(
                                RemoteAnimationTargetCompat[] appTargets,
                                RemoteAnimationTargetCompat[] wallpaperTargets) {
                            return RecentsActivityCommand.this.createWindowAnimation(appTargets,
                                    wallpaperTargets);
                        }
                    }, mContext, MAIN_EXECUTOR.getHandler(),
                    mAnimationProvider.getRecentsLaunchDuration());
        }

10. VolumeUI module

This module is designed using MVP architecture

It can be seen from the creation of SystemUI’s StatusBar that the entrance to VolumeUI is VolumeUI#start()

Ⅰ MVP architecture binding process

1、VolumeUI

①start()

    @Override 
    public void start() { 
        boolean enableVolumeUi = mContext.getResources().getBoolean(R.bool.enable_volume_ui); 
        boolean enableSafetyWarning = 
            mContext.getResources().getBoolean(R.bool.enable_safety_warning); 
        mEnabled = enableVolumeUi || enableSafetyWarning ; 
        if (!mEnabled) return; 
        //As you can see from the name, mVolumeComponent represents the VolumeUI component through which the entire MVP can be created. 
        mVolumeComponent.setEnableDialogs(enableVolumeUi, enableSafetyWarning); 
        //register the function of starting VolumeUI 
        setDefaultVolumeController(); 
    }

②VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent)

    //VolumeUI will create a VolumeDialogComponent object when it starts 
    @Inject 
    public VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent) { 
        super(context); 
        mVolumeComponent = volumeDialogComponent; 
    }

③setDefaultVolumeController

    private void setDefaultVolumeController() { 
        DndTile.setVisible(mContext, true); 
        if (D.BUG) Log.d(TAG, "Registering default volume controller"); 
        //After the VolumeDialogComponent object is created, its register( ) method starts the VolumeUI function. 
        //register starts the function of VolumeUI 
        //It actually associates the Presenter layer and the Model layer. 
        mVolumeComponent.register(); 
    }

④VolumeDialogComponent’s register method

    private final VolumeDialogControllerImpl mController;

    @Override
    public void register() {
        mController.register();
        DndTile.setCombinedIcon(mContext, true);
    }

⑤VolumeDialogControllerImpl’s register method

    public void register() {
        setVolumeController();
        setVolumePolicy(mVolumePolicy);
        showDndTile(mShowDndTile);
        try {
            mMediaSessions.init();
        } catch (SecurityException e) {
            Log.w(TAG, "No access to media sessions", e);
        }
    }

2、VolumeDialogComponent

①Constructor of VolumeDialogComponent

    //接口,实现类为VolumeDialogImpl
    private VolumeDialog mDialog;

    @Inject
    public VolumeDialogComponent(Context context, KeyguardViewMediator keyguardViewMediator,
            VolumeDialogControllerImpl volumeDialogController) {
        mContext = context;
        mKeyguardViewMediator = keyguardViewMediator;
        mController = volumeDialogController;
        mController.setUserActivityListener(this);
        // Allow plugins to reference the VolumeDialogController.
        Dependency.get(PluginDependencyProvider.class)
                .allowPluginDependency(VolumeDialogController.class);
        Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class)
                .withPlugin(VolumeDialog.class) 
            //VolumeDialogComponent creates a VolumeDialogImpl object through createDefault(), which represents the View layer.withDefault 
                (this::createDefault) 
                .withCallback(dialog -> { 
                    if (mDialog != null) { 
                        mDialog.destroy() ; 
                    } 
                    mDialog = dialog; 
                    //Then initialized through init(). 
                    mDialog.init(LayoutParams.TYPE_VOLUME_OVERLAY, mVolumeDialogCallback); 
                }).build(); 
        applyConfiguration(); 
        Dependency.get(TunerService.class).addTunable( this, VOLUME_DOWN_SILENT, VOLUME_UP_SILENT,
                VOLUME_SILENT_DO_NOT_DISTURB);
    }

②createDefault()

    protected VolumeDialog createDefault() {
        VolumeDialogImpl impl = new VolumeDialogImpl(mContext);
        impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
        impl.setAutomute(true);
        impl.setSilentMode(false);
        return impl;
    }

2、VolumeDialogImpl---View层

①init(int windowType, Callback callback)

    public void init(int windowType, Callback callback) { 
        initDialog(); 

        mAccessibility.init(); 

        //Register a callback to VolumeDialogControllerImpl (Presenter layer) 
        //That is, the View layer is associated with the Presenter layer, so that it can be controlled through the Presenter layer View layer. 
        mController.addCallback(mControllerCallbackH, mHandler); 
        mController.getState(); 

        Dependency.get(ConfigurationController.class).addCallback(this); 
    }

3、VolumeDialogControllerImpl---P层

①register

    
    
    //Associate the AudioManager, that is, the association between the presenter layer and the model layer 
    public void register() { 
        //Associate the AudioManager 
        setVolumeController(); 
        
        setVolumePolicy(mVolumePolicy); 
        showDndTile(mShowDndTile); 
        try { 
            mMediaSessions.init(); 
        } catch (SecurityException e) { 
            Log.w(TAG, "No access to media sessions", e); 
        } 
    }

②setVolumeController()

    private AudioManager mAudio;
    protected final VC mVolumeController = new VC();

    protected void setVolumeController() {
        try {
            mAudio.setVolumeController(mVolumeController);
        } catch (SecurityException e) {
            Log.w(TAG, "Unable to set the volume controller", e);
            return;
        }
    }

Ⅱ How does VolumeUI display the UI after pressing the Power key?

Since VolumeDialogControllerImpl has registered a callback with AudioManager, when the volume key is pressed to adjust the volume, VolumeDialogControllerImpl will receive a callback.

1. VC (VolumeDialogControllerImpl internal class)

    private final W mWorker;     

    private final class VC extends IVolumeController.Stub { 
        private final String TAG = VolumeDialogControllerImpl.TAG + ".VC"; 
        
        ...... 
        @Override 
        public void volumeChanged(int streamType, int flags) throws RemoteException { 
            if (D.BUG) Log.d(TAG, "volumeChanged " + AudioSystem.streamToString(streamType) 
                    + " " + Util.audioManagerFlagsToString(flags)); 
            if (mDestroyed) return; 
            //mWorker is an internal final class inherited from Handler , 
            mWorker.obtainMessage(W.VOLUME_CHANGED, streamType, flags).sendToTarget(); 
        } 
        ........... 
    } are processed differently according to the received message.

2. W (VolumeDialogControllerImpl internal class)

①handleMessage

    private final class W extends Handler {
        private static final int VOLUME_CHANGED = 1;
        private static final int DISMISS_REQUESTED = 2;
        .....

        W(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                //当消息为VOLUME_CHANGED时,调用onVolumeChangedW方法
                case VOLUME_CHANGED: onVolumeChangedW(msg.arg1, msg.arg2); break;
                case DISMISS_REQUESTED: onDismissRequestedW(msg.arg1); break;
                case GET_STATE: onGetStateW(); break;
                case SET_RINGER_MODE: onSetRingerModeW(msg.arg1, msg.arg2 != 0); break;
                case SET_ZEN_MODE: onSetZenModeW(msg.arg1); break;
                case SET_EXIT_CONDITION: onSetExitConditionW((Condition) msg.obj); break;
                case SET_STREAM_MUTE: onSetStreamMuteW(msg.arg1, msg.arg2 != 0); break;
                case LAYOUT_DIRECTION_CHANGED: mCallbacks.onLayoutDirectionChanged(msg.arg1); break;
                case CONFIGURATION_CHANGED: mCallbacks.onConfigurationChanged(); break;
                case SET_STREAM_VOLUME: onSetStreamVolumeW(msg.arg1, msg.arg2); break;
                case SET_ACTIVE_STREAM: onSetActiveStreamW(msg.arg1); break;
                case NOTIFY_VISIBLE: onNotifyVisibleW(msg.arg1 != 0); break;
                case USER_ACTIVITY: onUserActivityW(); break;
                case SHOW_SAFETY_WARNING: onShowSafetyWarningW(msg.arg1); break;
                case GET_CAPTIONS_COMPONENT_STATE:
                    onGetCaptionsComponentStateW((Boolean) msg.obj); break;
                case ACCESSIBILITY_MODE_CHANGED: onAccessibilityModeChanged((Boolean) msg.obj);
            }
        }
    }

②onVolumeChangedW

    boolean onVolumeChangedW(int stream, int flags) { 
        //Determine which callback to execute based on flags. If you want to display the UI, onShowRequested() will be called back 
        final boolean showUI = shouldShowUI(flags); 
        final boolean fromKey = (flags & AudioManager.FLAG_FROM_KEY ) != 0; 
        final boolean showVibrateHint = (flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0; 
        final boolean showSilentHint = (flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0; 
        boolean changed = false; 
        if (showUI) { 
            changed |= updateActiveStreamW( stream); 
        } 
        int lastAudibleStreamVolume = getAudioManagerStreamVolume(stream); 
        changed |= updateStreamLevelW(stream, lastAudibleStreamVolume);
        changed |= checkRoutedToBluetoothW(showUI ? AudioManager.STREAM_MUSIC : stream); 
        if (changed) { 
            mCallbacks.onStateChanged(mState); 
        } 
        //This callback is of course implemented by the View layer, that is, 
        if (showUI) { is called in VolumeDialogImpl 
            mCallbacks.onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED); 
        } 
        if (showVibrateHint) { 
            mCallbacks.onShowVibrateHint(); 
        } 
        if (showSilentHint) { 
            mCallbacks.onShowSilentHint(); 
        } 
        if (changed && fromKey) { 
            Events.writeEvent(Events.EVENT_KEY, stream , lastAudibleStreamVolume); 
        }
        return changed;
    }

3、VolumeDialogImpl

①onShowRequested

    
    public void init(int windowType, Callback callback) {
        initDialog();

        mAccessibility.init();
        //初始化的时候添加回调addCallback
        mController.addCallback(mControllerCallbackH, mHandler);
        mController.getState();

        Dependency.get(ConfigurationController.class).addCallback(this);
    }
    //mControllerCallbackH
    private final VolumeDialogController.Callbacks mControllerCallbackH
            = new VolumeDialogController.Callbacks() {
        @Override
        public void onShowRequested(int reason) {
        //
            showH(reason);
        }
        }

②showH

    private void showH(int reason) {
        if (D.BUG) Log.d(TAG, "showH r=" + Events.SHOW_REASONS[reason]);
        mHandler.removeMessages(H.SHOW);
        mHandler.removeMessages(H.DISMISS);
        rescheduleTimeoutH();

        /* UNISOC: Modify for bug1347675,1384445 @{ */
        Configuration config = mContext.getResources().getConfiguration();
        boolean orientationPortrait = config.orientation == ORIENTATION_PORTRAIT;
        if ((mConfigChanged || (mOrientationPortrait != orientationPortrait)) && !mDialog.isShowing()) {
            initDialog(); // resets mShowing to false
            mConfigurableTexts.update();
            mConfigChanged = false;
            mOrientationPortrait = orientationPortrait;
        }
        /* @} */

        initSettingsH();
        mShowing = true;
        mIsAnimatingDismiss = false;
        mDialog.show();
        Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
        mController.notifyVisible(true);
        mController.getCaptionsComponentState(false);
        checkODICaptionsTooltip(false);
    }

ⅢAudioService’s volume key processing flow

1、PhoneWindowManager

①dispatchDirectAudioEvent

    // pre-condition: event.getKeyCode() is one of KeyEvent.KEYCODE_VOLUME_UP,
    //                                   KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_MUTE
    private void dispatchDirectAudioEvent(KeyEvent event) {
        // When System Audio Mode is off, volume keys received by AVR can be either consumed by AVR
        // or forwarded to the TV. It's up to Amplifier manufacturer’s implementation.
        HdmiControlManager hdmiControlManager = getHdmiControlManager();
        if (null != hdmiControlManager
                && !hdmiControlManager.getSystemAudioMode()
                && shouldCecAudioDeviceForwardVolumeKeysSystemAudioModeOff()) {
            HdmiAudioSystemClient audioSystemClient = hdmiControlManager.getAudioSystemClient();
            if (audioSystemClient != null) {
                audioSystemClient.sendKeyEvent(
                        event.getKeyCode(), event.getAction() == KeyEvent.ACTION_DOWN);
                return;
            }
        }
        try {
            //这里通过AIDL获取IAudioService的实例
            getAudioService().handleVolumeKey(event, mUseTvRouting,
                    mContext.getOpPackageName(), TAG);
        } catch (Exception e) {
            Log.e(TAG, "Error dispatching volume key in handleVolumeKey for event:"
                    + event, e);
        }
    }

②getAudioService()

    
    import android.media.IAudioService;

    static IAudioService getAudioService() {
        IAudioService audioService = IAudioService.Stub.asInterface(
                ServiceManager.checkService(Context.AUDIO_SERVICE));
        if (audioService == null) {
            Log.w(TAG, "Unable to find IAudioService interface.");
        }
        return audioService;
    }

Here, the operation of the audio key is directly performed. The instance of AudioService is obtained through Binder, and the handleVolumeKey method is called. The meaning of the parameters is as follows:

Button type

Audio Service operation type

meaning

KEYCODE_VOLUME_UP

AudioManager.ADJUST_RAISE

Volume up

KEYCODE_VOLUME_DOWN

AudioManager.ADJUST_LOWER

Volume down

KEYCODE_VOLUME_MUTE

AudioManager.ADJUST_TOGGLE_MUTE

Change mute status

2、AudioService

①handleVolumeKey

//AudioService继承了IAudioService
public class AudioService extends IAudioService.Stub
        implements AccessibilityManager.TouchExplorationStateChangeListener,
            AccessibilityManager.AccessibilityServicesStateChangeListener {
            
    // pre-condition: event.getKeyCode() is one of KeyEvent.KEYCODE_VOLUME_UP,
    //                                   KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_MUTE
    public void handleVolumeKey(@NonNull KeyEvent event, boolean isOnTv,
            @NonNull String callingPackage, @NonNull String caller) {
        int keyEventMode = VOL_ADJUST_NORMAL;
        if (isOnTv) {
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                keyEventMode = VOL_ADJUST_START;
            } else { // may catch more than ACTION_UP, but will end vol adjustement
                // the vol key is either released (ACTION_UP), or multiple keys are pressed
                // (ACTION_MULTIPLE) and we don't know what to do for volume control on CEC, end
                // the repeated volume adjustement
                keyEventMode = VOL_ADJUST_END;
            }
        } else if (event.getAction() != KeyEvent.ACTION_DOWN) {
            return;
        }

        int flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND
                | AudioManager.FLAG_FROM_KEY;

        switch (event.getKeyCode()) {
            case KeyEvent.KEYCODE_VOLUME_UP: 
                //During the key processing process, the corresponding code is not passed to AudioService. 
                //Instead, relevant definitions are used to convert operations such as KEYCODE_VOLUME_UP into ADJUST_RAISE, etc. 
                //The flag stores some volume requirements or information, this is also very important. 
                    adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE, 
                            AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller, 
                            Binder.getCallingUid(), true, keyEventMode); 
                break; 
            case KeyEvent.KEYCODE_VOLUME_DOWN: 
                    adjustSuggestedStreamVolume(AudioManager.ADJUST_LOWER, 
                            AudioManager.USE_DEFAULT _STREAM_TYPE, flags, callingPackage, caller,
                            Binder.getCallingUid(), true, keyEventMode);
                break;
            case KeyEvent.KEYCODE_VOLUME_MUTE:
                if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
                    adjustSuggestedStreamVolume(AudioManager.ADJUST_TOGGLE_MUTE,
                            AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller,
                            Binder.getCallingUid(), true, VOL_ADJUST_NORMAL);
                }
                break;
            default:
                Log.e(TAG, "Invalid key code " + event.getKeyCode() + " sent by " + callingPackage);
                return; // not needed but added if code gets added below this switch statement
        }
    }
      
}

②adjustSuggestedStreamVolume

    private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
            String callingPackage, String caller, int uid, boolean hasModifyAudioSettings,
            int keyEventMode) {
        if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream=" + suggestedStreamType
                + ", flags=" + flags + ", caller=" + caller
                + ", volControlStream=" + mVolumeControlStream
                + ", userSelect=" + mUserSelectedVolumeControlStream);
        if (direction != AudioManager.ADJUST_SAME) {
            sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType,
                    direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage)
                    .append("/").append(caller).append(" uid:").append(uid).toString()));
        }

        boolean hasExternalVolumeController = notifyExternalVolumeController(direction);

        new MediaMetrics.Item(mMetricsId + "adjustSuggestedStreamVolume")
                .setUid(Binder.getCallingUid())
                .set(MediaMetrics.Property.CALLING_PACKAGE, callingPackage)
                .set(MediaMetrics.Property.CLIENT_NAME, caller)
                .set(MediaMetrics.Property.DIRECTION, direction > 0
                        ? MediaMetrics.Value.UP : MediaMetrics.Value.DOWN)
                .set(MediaMetrics.Property.EXTERNAL, hasExternalVolumeController
                        ? MediaMetrics.Value.YES : MediaMetrics.Value.NO)
                .set(MediaMetrics.Property.FLAGS, flags)
                .record();

        if (hasExternalVolumeController) {
            return;
        }

        final int streamType;
        synchronized (mForceControlStreamLock) {
            // Request lock in case mVolumeControlStream is changed by other thread.
            if (mUserSelectedVolumeControlStream) { // implies mVolumeControlStream != -1
                streamType = mVolumeControlStream;
            } else {
                // Obtained here, it may be an active audio stream, but it is not sure and needs further confirmation. 
                final int maybeActiveStreamType = getActiveStreamType(suggestedStreamType); 
                final boolean activeForReal; 
                if (maybeActiveStreamType == AudioSystem.STREAM_RING 
                        || maybeActiveStreamType == AudioSystem. STREAM_NOTIFICATION) { 
                    activeForReal = wasStreamActiveRecently(maybeActiveStreamType, 0); 
                } else { 
                    activeForReal = AudioSystem.isStreamActive(maybeActiveStreamType, 0); 
                } 
                if (activeForReal || mVolumeControlStream == -1) { 
                    streamType = maybeActiveStreamType;
                } else { 
                // activeForReal is false and mVolumeControlStream is not -1 
                // Indicates that the user clicked the volume progress bar. At this time, the stream type to be modified is the stream type corresponding to mVolumeControlStream streamType 
                    = mVolumeControlStream; 
                } 
            } 
        } 

        final boolean isMute = isMuteAdjust (direction); 
        // Ensure that the stream type we obtain is valid 
        ensureValidStreamType(streamType); 
        // Perform stream mapping on the stream we obtain, and get the stream type that ultimately needs to be operated 
        final int resolvedStream = mStreamVolumeAlias[streamType] ; 

        // Play sounds on STREAM_RING only. 
        if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && 
                resolvedStream != AudioSystem.STREAM_RING) {
            flags &= ~AudioManager.FLAG_PLAY_SOUND; 
        } 

        // For notifications/ring, show the ui before making any adjustments 
        // Don't suppress mute/unmute requests 
        // Don't suppress adjustments for single volume device 
        // Notifications and ringing , display the UI before adjusting the volume. 
        if (mVolumeController.suppressAdjustment(resolvedStream, flags, isMute) 
                && !mIsSingleVolume) { 
            direction = 0; 
            flags &= ~AudioManager.FLAG_PLAY_SOUND; 
            flags &= ~AudioManager.FLAG_VIBRATE; 
            if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment"); 
        } 
        // Set the volume here
        adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid,
                hasModifyAudioSettings, keyEventMode);
    }

suppressAdjustment: Literally means suppressing adjustment. Why suppress adjustment? To put it bluntly, when we do not have a UI progress bar that displays the volume, no matter whether we are increasing the volume or decreasing the volume (note: except for muting and unmuting), this time it is always the first step. Display the volume bar without changing the volume level. So when this method returns true, direction = 0. The direction here is 0, which means our operation is ADJUST_SAME. You can check the comments of ADJUST_SAME in AudioManager to know that this operation means that the UI will only pop up but the volume will not be adjusted.

③adjustStreamVolume

    protected void adjustStreamVolume(int streamType, int direction, int flags,
            String callingPackage, String caller, int uid, boolean hasModifyAudioSettings,
            int keyEventMode) {
        //mUseFixedVolume表示使用固定音量,我们无法修改音量
        if (mUseFixedVolume) {
            return;
        }
        if (DEBUG_VOL) Log.d(TAG, "adjustStreamVolume() stream=" + streamType + ", dir=" + direction
                + ", flags=" + flags + ", caller=" + caller);

        ensureValidDirection(direction);
        ensureValidStreamType(streamType);

        boolean isMuteAdjust = isMuteAdjust(direction);
        
        if (isMuteAdjust && !isStreamAffectedByMute(streamType)) {
            return;
        }

        // If adjust is mute and the stream is STREAM_VOICE_CALL or STREAM_BLUETOOTH_SCO, make sure
        // that the calling app have the MODIFY_PHONE_STATE permission.
        if (isMuteAdjust &&
            (streamType == AudioSystem.STREAM_VOICE_CALL ||
                streamType == AudioSystem.STREAM_BLUETOOTH_SCO) &&
            mContext.checkCallingOrSelfPermission(
                android.Manifest.permission.MODIFY_PHONE_STATE)
                    != PackageManager.PERMISSION_GRANTED) {
            Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: adjustStreamVolume from pid="
                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
            return;
        }

        // If the stream is STREAM_ASSISTANT,
        // make sure that the calling app have the MODIFY_AUDIO_ROUTING permission.
        if (streamType == AudioSystem.STREAM_ASSISTANT &&
            mContext.checkCallingOrSelfPermission(
                android.Manifest.permission.MODIFY_AUDIO_ROUTING)
                    != PackageManager.PERMISSION_GRANTED) {
            Log.w(TAG, "MODIFY_AUDIO_ROUTING Permission Denial: adjustStreamVolume from pid="
                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
            return;
        }

        // use stream type alias here so that streams with same alias have the same behavior, 
        // including with regard to silent mode control (eg the use of STREAM_RING below and in 
        // checkForRingerModeChange() in place of STREAM_RING or STREAM_NOTIFICATION) 
        // Perform mapping of the audio stream and get the mapped audio stream 
        int streamTypeAlias ​​= mStreamVolumeAlias[streamType]; 
        
        //mStreamStates is an array that stores the VolumeStreamState type, saving the status of each audio stream. 
        //VolumeStreamState is an internal class of AudioService, which stores all information about a single audio stream, such as stream type, volume, mute status, etc. 
        //And the same stream type has different sizes on different devices (such as headphones and speakers, the media volume is different) // 
        This is also maintained in VolumeStreamState. 

        VolumeStreamState streamState = mStreamStates[streamTypeAlias];

        final int device = getDeviceForStream(streamTypeAlias);

        int aliasIndex = streamState.getIndex(device);
        boolean adjustVolume = true;
        int step;

        // skip a2dp absolute volume control request when the device
        // is not an a2dp device
        if (!AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
                && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) {
            return;
        }

        // If we are being called by the system (e.g. hardware keys) check for current user
        // so we handle user restrictions correctly.
        if (uid == android.os.Process.SYSTEM_UID) {
            uid = UserHandle.getUid(getCurrentUserId(), UserHandle.getAppId(uid)); 
        } 
        if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage) 
                != AppOpsManager.MODE_ALLOWED) { 
            return; 
        } 

        // reset any pending volume command 
        // Clear any pending volume commands 
        synchronized (mSafeMediaVolumeStateLock) { 
            mPendingVolumeCommand = null; 
        } 
       // Indicates that the volume is not fixed 
        flags &= ~AudioManager.FLAG_FIXED_VOLUME; 
        // If it is a multimedia volume, and it is a device that uses a fixed volume 
        if (streamTypeAlias ​​== AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) { 
            // Add a flag indicating fixed volume
            flags |= AudioManager.FLAG_FIXED_VOLUME;

            // Always toggle between max safe volume and 0 for fixed volume devices where safe
            // volume is enforced, and max and 0 for the others.
            // This is simulated by stepping by the full allowed volume range
            if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE &&
                    mSafeMediaVolumeDevices.contains(device)) {
                step = safeMediaVolumeIndex(device);
            } else {
                step = streamState.getMaxIndex();
            }
            if (aliasIndex != 0) {
                aliasIndex = step;
            }
        } else {
            // convert one UI step (+/-1) into a number of internal units on the stream alias 
            // If it is not a multimedia volume, or it is a multimedia volume but not a fixed volume device 
            // Convert the volume value step amount from Convert the source stream type to the target stream type. Since different stream types have different volume adjustment ranges, this conversion is necessary 
            step = rescaleStep(10, streamType, streamTypeAlias); 
        } 
        
        // // Scenario mode processing 
        // If either the client forces allowing ringer modes for this adjustment, 
        // or the stream type is one that is affected by ringer modes 
        if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || 
                (streamTypeAlias ​​== getUiSoundsStreamType())) { 
            int ringerMode = getRingerModeInternal(); 
            // do not vibrate if already in vibrate mode
            // If it is already in vibration mode, no vibration will be performed 
            if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { 
                flags &= ~AudioManager.FLAG_VIBRATE; 
            } 
            // Check if the ringer mode handles this adjustment. If it does we don't 
            / / need to adjust the volume further. 
             // Check whether we need to switch the scene mode according to our operation 
            final int result = checkForRingerModeChange(aliasIndex, direction, step, 
                    streamState.mIsMuted, callingPackage, flags); 
            adjustVolume = (result & FLAG_ADJUST_VOLUME) ! = 0; 
            // If suppressing a volume adjustment in silent mode, display the UI hint
            if ((result & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) {
                flags |= AudioManager.FLAG_SHOW_SILENT_HINT;
            }
            // If suppressing a volume down adjustment in vibrate mode, display the UI hint
            if ((result & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0) {
                flags |= AudioManager.FLAG_SHOW_VIBRATE_HINT;
            }
        }

        // If the ringer mode or zen is muting the stream, do not change stream unless
        // it'll cause us to exit dnd
        // 勿扰模式
        if (!volumeAdjustmentAllowedByDnd(streamTypeAlias, flags)) {
            adjustVolume = false;
        }
        // old volume 
        int oldIndex = mStreamStates[streamType].getIndex(device); 

        if (adjustVolume 
                && (direction != AudioManager.ADJUST_SAME) && (keyEventMode != VOL_ADJUST_END)) { 
            mAudioHandler.removeMessages(MSG_UNMUTE_STREAM); 
            // 
                    } else 
            { state = 
                direction 
                == AudioManager.ADJUST_MUTE; 
                } 
                if 
                (streamTypeAlias ​​== 
                    AudioSystem.STREAM_MUSIC ) {
                    setSystemAudioMute(state); 
                } 
                for (int stream = 0; stream < mStreamStates.length; stream++) { 
                    if (streamTypeAlias ​​== mStreamVolumeAlias[stream]) { 
                        if (!(readCameraSoundForced() 
                                    && (mStreamStates[stream].getStreamType() 
                                        == AudioSystem.STREAM_SYSTEM_ENFORCED))) { 
                            // Get the VolumeStreamState instance corresponding to the current stream here, and then call the mute method 
                            // Eventually, it will also go to AudioSystem to call the native method 
                            mStreamStates[stream].mute(state); 
                        } 
                    }
                } 
            } else if ((direction == AudioManager.ADJUST_RAISE) && 
                    !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) { 
                // Safe volume prompt, it will only be detected when the volume increases 
                Log.e(TAG, "adjustStreamVolume( ) safe volume index = " + oldIndex); 
                mVolumeController.postDisplaySafeVolumeWarning(flags); 
            } else if (!isFullVolumeDevice(device) 
                    && (streamState.adjustIndex(direction * step, device, caller, 
                            hasModifyAudioSettings) 
                            || streamState.mIsMuted)) { 
                // Post message to set system volume (it in turn will post a
                // message to persist).
                if (streamState.mIsMuted) {
                    // Unmute the stream if it was previously muted
                    if (direction == AudioManager.ADJUST_RAISE) {
                        // unmute immediately for volume up
                        streamState.mute(false);
                    } else if (direction == AudioManager.ADJUST_LOWER) {
                        if (mIsSingleVolume) {
                            sendMsg(mAudioHandler, MSG_UNMUTE_STREAM, SENDMSG_QUEUE,
                                    streamTypeAlias, flags, null, UNMUTE_STREAM_DELAY);
                        }
                    }
                }
                // 设置音量到底层
                sendMsg(mAudioHandler,
                        MSG_SET_DEVICE_VOLUME,
                        SENDMSG_QUEUE,
                        device,
                        0,
                        streamState,
                        0);
            }

            int newIndex = mStreamStates[streamType].getIndex(device);

            // Check if volume update should be send to AVRCP
            if (streamTypeAlias == AudioSystem.STREAM_MUSIC
                    && AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
                    && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
                if (DEBUG_VOL) {
                    Log.d(TAG, "adjustSreamVolume: postSetAvrcpAbsoluteVolumeIndex index="
                            + newIndex + "stream=" + streamType);
                }
                mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(newIndex / 10);
            }

            // Check if volume update should be send to Hearing Aid
            if (device == AudioSystem.DEVICE_OUT_HEARING_AID) {
                // only modify the hearing aid attenuation when the stream to modify matches
                // the one expected by the hearing aid
                if (streamType == getHearingAidStreamType()) {
                    if (DEBUG_VOL) {
                        Log.d(TAG, "adjustSreamVolume postSetHearingAidVolumeIndex index="
                                + newIndex + " stream=" + streamType);
                    }
                    mDeviceBroker.postSetHearingAidVolumeIndex(newIndex, streamType);
                }
            }

            // Check if volume update should be sent to Hdmi system audio.
            if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
                setSystemAudioVolume(oldIndex, newIndex, getStreamMaxVolume(streamType), flags);
            }
        }

        final int newIndex = mStreamStates[streamType].getIndex(device);

        if (adjustVolume) {
            synchronized (mHdmiClientLock) {
                if (mHdmiManager != null) {
                    // mHdmiCecSink true => mHdmiPlaybackClient != null
                    if (mHdmiCecSink
                            && mHdmiCecVolumeControlEnabled
                            && streamTypeAlias == AudioSystem.STREAM_MUSIC
                            // vol change on a full volume device
                            && isFullVolumeDevice(device)) {
                        int keyCode = KeyEvent.KEYCODE_UNKNOWN;
                        switch (direction) {
                            case AudioManager.ADJUST_RAISE:
                                keyCode = KeyEvent.KEYCODE_VOLUME_UP;
                                break;
                            case AudioManager.ADJUST_LOWER:
                                keyCode = KeyEvent.KEYCODE_VOLUME_DOWN;
                                break;
                            case AudioManager.ADJUST_TOGGLE_MUTE:
                                keyCode = KeyEvent.KEYCODE_VOLUME_MUTE;
                                break;
                            default:
                                break;
                        }
                        if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
                            final long ident = Binder.clearCallingIdentity();
                            try {
                                final long time = java.lang.System.currentTimeMillis();
                                switch (keyEventMode) {
                                    case VOL_ADJUST_NORMAL:
                                        mHdmiPlaybackClient.sendVolumeKeyEvent(keyCode, true);
                                        mHdmiPlaybackClient.sendVolumeKeyEvent(keyCode, false);
                                        break;
                                    case VOL_ADJUST_START:
                                        mHdmiPlaybackClient.sendVolumeKeyEvent(keyCode, true);
                                        break;
                                    case VOL_ADJUST_END:
                                        mHdmiPlaybackClient.sendVolumeKeyEvent(keyCode, false);
                                        break;
                                    default:
                                        Log.e(TAG, "Invalid keyEventMode " + keyEventMode);
                                }
                            } finally {
                                Binder.restoreCallingIdentity(ident);
                            }
                        }
                    }

                    if (streamTypeAlias == AudioSystem.STREAM_MUSIC
                            && (oldIndex != newIndex || isMuteAdjust)) {
                        maybeSendSystemAudioStatusCommand(isMuteAdjust); 
                    } 
                } 
            } 
        } 
        // Notify the external volume of changes 
        sendVolumeUpdate(streamType, oldIndex, newIndex, flags, device); 
    }

④sendVolumeUpdate

    // UI update and Broadcast Intent
    protected void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags, int device)
    {
        streamType = mStreamVolumeAlias[streamType];

        if (streamType == AudioSystem.STREAM_MUSIC) {
            flags = updateFlagsForTvPlatform(flags);
            if (isFullVolumeDevice(device)) {
                flags &= ~AudioManager.FLAG_SHOW_UI;
            }
        }
        mVolumeController.postVolumeChanged(streamType, flags);
    }

3、AudioManager

    /**
     * Increase the ringer volume.
     *
     * @see #adjustVolume(int, int)
     * @see #adjustStreamVolume(int, int, int)
     */
    public static final int ADJUST_RAISE = 1;

    /**
     * Decrease the ringer volume.
     *
     * @see #adjustVolume(int, int)
     * @see #adjustStreamVolume(int, int, int)
     */
    public static final int ADJUST_LOWER = -1;

    /**
     * Maintain the previous ringer volume. This may be useful when needing to
     * show the volume toast without actually modifying the volume.
     *
     * @see #adjustVolume(int, int)
     * @see #adjustStreamVolume(int, int, int)
     */
    public static final int ADJUST_SAME = 0;

    /**
     * Mute the volume. Has no effect if the stream is already muted.
     *
     * @see #adjustVolume(int, int)
     * @see #adjustStreamVolume(int, int, int)
     */
    public static final int ADJUST_MUTE = -100;

    /**
     * Unmute the volume. Has no effect if the stream is not muted.
     *
     * @see #adjustVolume(int, int)
     * @see #adjustStreamVolume(int, int, int)
     */
    public static final int ADJUST_UNMUTE = 100;

    /**
     * Toggle the mute state. If muted the stream will be unmuted. If not muted
     * the stream will be muted.
     *
     * @see #adjustVolume(int, int)
     * @see #adjustStreamVolume(int, int, int)
     */
    public static final int ADJUST_TOGGLE_MUTE = 101;

Guess you like

Origin blog.csdn.net/weixin_47465999/article/details/131596152