preface

Android volume control (a) volume key processing flow

Audioservice.java () : audioservice.java () : audioservice.java () : AudioService.java () : AudioService.java () : AudioService.java () : AudioService.java () : AudioService.java () : AudioService.java So I decided to analyze the SystemUI startup process first, and then go to the first chapter to analyze the volume bar processing process.

In the whole system App, the SystemUI App is very complicated. On Android11, Google also uses the Dagger to deal with the ugly dependency in it. At that time, I also spent a long time to check the PIECE of DI and wrote it together as a learning record.

If have the place that writes existence problem, still hope everybody can give directions, grateful endless.

So this blog is divided into three parts:

  • Analyze the SystemUI startup process
  • Dagger related code analysis in SystemUI
  • VolumeUI process analysis

If you already know the content of chapter 1, you can enjoy this article directly. This article is quite extensive, I hope you can read it carefully.

For those of you who know Dagger or have used Dagger you can take a look at section 2, if you have never dealt with Dagger you can skip section 2 without affecting the flow.

1. Analyze the SystemUI startup process

SystemUI is in frameworks/base/packages/SystemUI/path, in the form of apk preset in the system.

SystemUI is originally started in systemServer. Java.

1.1 start SystemUIService

// frameworks/base/services/java/com/android/server/SystemServer.java
public final class SystemServer {
 
    /** * The main entry point from zygote. */
    public static void main(String[] args) {
        new SystemServer().run();
    }
  
    private void run(a) {... startBootstrapServices(); startCoreServices();// SystemUIService starts in thisstartOtherServices(); SystemServerInitThreadPool.shutdown(); . }private void startOtherServices(a) {... traceBeginAndSlog("StartSystemUI");
        try {
            startSystemUi(context, windowManager);
        } catch (Throwable e) {
            reportWtf("starting System UI", e); } traceEnd(); . }static final 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);context.startServiceAsUser(intent, UserHandle.SYSTEM); windowManager.onSystemUiStarted(); }}Copy the code

Here’s how the SystemUI Component is retrieved. Let’s look at the PackageManagerInternal implementation

// frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public class PackageManagerService extends IPackageManager.Stub
        implements PackageSender {

    public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest) {...// Expose private service for system components to use.
        mPmInternal = newPackageManagerInternalImpl(); LocalServices.addService(PackageManagerInternal.class, mPmInternal); . }private class PackageManagerInternalImpl extends PackageManagerInternal {...@Override
        public ComponentName getSystemUiServiceComponent(a) {
            returnComponentName.unflattenFromString(mContext.getResources().getString( com.android.internal.R.string.config_systemUIServiceComponent)); }... }}Copy the code

Can see PackageManagerInternalImpl is PackageManagerService inner classes, at PackageManagerService constructor to instantiate the PackageManagerInternalImpl, And added to LocalServices.

Inside the PackageManagerInternalImpl is to obtain the string resources, file contents are as follows

<! -- frameworks/base/core/res/res/values/config.xml -->
<! -- SystemUi service component -->
<string name="config_systemUIServiceComponent" translatable="false"
        >com.android.systemui/com.android.systemui.SystemUIService</string>
Copy the code

So let’s see where the PackageManagerService is instantiated, and I’m going to look over here, and there’s a method in the PackageManagerService. Okay

// frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public class PackageManagerService extends IPackageManager.Stub
        implements PackageSender {
  
    public static PackageManagerService main(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {...// The PackageManagerService is instantiated
        PackageManagerService m = newPackageManagerService(injector, onlyCore, factoryTest); .returnm; }}Copy the code

You can see that there is a static method of Main inside the PackageManagerService, which instantiates the PackageManagerService.

PackageManagerService main (); PackageManagerService main ();

// frameworks/base/services/java/com/android/server/SystemServer.java
public final class SystemServer {

    private void run(a) {...// The PackageManagerService starts in this directory
        startBootstrapServices();
        startCoreServices();
        // SystemUIService starts in thisstartOtherServices(); SystemServerInitThreadPool.shutdown(); . }private void startBootstrapServices(@NonNull TimingsTraceAndSlog t) {...try {
            Watchdog.getInstance().pauseWatchingCurrentThread("packagemanagermain");
            / / start PackageManagerServicemPackageManagerService = PackageManagerService.main(mSystemContext, installer, mFactoryTestMode ! = FactoryTest.FACTORY_TEST_OFF, mOnlyCore); }finally {
            Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain"); }... }}Copy the code

You can see that startBootstrapServices started before startOtherServices, so I’m done here.

Can know the start is com. Android. Systemui. SystemUIService this service

As an aside, when starting a service, which one should run first, service or Application? Let’s analyze it from the code

1.2 Starting the Service Process

Activitythread. Java handleCreateService (); activityThread. Java handleCreateService ();

// frameworks/base/core/java/android/app/ActivityThread.java
@UnsupportedAppUsage
private void handleCreateService(CreateServiceData data) {
    // If we are getting ready to gc after going to the background, well
    // we are back active so skip it.
    unscheduleGcIdler();

    LoadedApk packageInfo = getPackageInfoNoCheck(
            data.info.applicationInfo, data.compatInfo);
    Service service = null;
    try {
        if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
        / / create the Context
        ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
        / / create the Application
        Application app = packageInfo.makeApplication(false, mInstrumentation);
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        // Create an instance of service
        service = packageInfo.getAppFactory()
                .instantiateService(cl, data.info.name, data.intent);
        // Service resources must be initialized with the same loaders as the application
        // context.
        context.getResources().addLoaders(
                    app.getResources().getLoaders().toArray(new ResourcesLoader[0]));

        context.setOuterContext(service);
        service.attach(context, this, data.info.name, data.token, app,
                ActivityManager.getService());
        // Call the onCreate() method of the service
        service.onCreate();
        mServices.put(data.token, service);
        try {
            ActivityManager.getService().serviceDoneExecuting(
                    data.token, SERVICE_DONE_EXECUTING_ANON, 0.0);
        } catch (RemoteException e) {
            throwe.rethrowFromSystemServer(); }}catch (Exception e) {
        if(! mInstrumentation.onException(service, e)) {throw new RuntimeException(
                "Unable to create service " + data.info.name
                + ":"+ e.toString(), e); }}}Copy the code

As you can see from the comment on line 15, an instance of Application is created, then an instance of Service is created, and finally the onCreate() method of Service is called

Let’s look at what happens in the makeApplication method when we create the Application:

// frameworks/base/core/java/android/app/LoadedApk.java
@UnsupportedAppUsage
public Application makeApplication(boolean forceDefaultAppClass,
        Instrumentation instrumentation) {...if(instrumentation ! =null) {
        try {
            instrumentation.callApplicationOnCreate(app);
        } catch(Exception e) { ... }}... }// frameworks/base/core/java/android/app/Instrumentation.java
public void callApplicationOnCreate(Application app) {
	app.onCreate();
}
Copy the code

Here’s the actual order again:

  1. Create an Application instance;
  2. Call Application’s onCreate() method;
  3. Create Service instance;
  4. Call the Service’s onCreate() method;

One such process occurs when SystemUIService is started.

Let’s take a look at SystemUI’s Application onCreate() method.

2. Analysis of Dagger related codes in SystemUI

As mentioned in the beginning of the article, if you do not know Dagger, you may not understand this section, you can just skip it without affecting the overall flow.

2.1 appComponentFactory label

In this article, we will look at how VolumeUI is initialized and started. In this article, we will look at how VolumeUI is initialized and started.

In section 1.2, we learned that the Application is instantiated first and then the onCreate method is called, but in our Androidmanifest.xml, the Application tag defines an attribute:

<application
    android:name=".SystemUIApplication"
    .
    tools:replace="android:appComponentFactory"
    android:appComponentFactory=".SystemUIAppComponentFactory">
</application>
Copy the code

AppComponentFactory, if we specify a class, then our implementation class will get a callback when we create an Application instance, and we can do some initialization, This can be done before or after the Application instance is created. The specific process here is not to go step by step analysis, you can go with the source code process, relatively simple.

2.2 Denpendency Dependency instance injection

SystemUIAppComponentFactory inherited from AppComponentFactory, rewrite the instantiateApplicationCompat method, when the system is to create an Application instance objects will callback this method.

// frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
public class SystemUIAppComponentFactory extends AppComponentFactory {

    private static final String TAG = "AppComponentFactory";
    @Inject
    public ContextComponentHelper mComponentHelper;

    public SystemUIAppComponentFactory(a) {
        super(a); }@NonNull
    @Override
    public Application instantiateApplicationCompat(
            @NonNull ClassLoader cl, @NonNull String className)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        Application app = super.instantiateApplicationCompat(cl, className);
        if (app instanceof ContextInitializer) {
            ((ContextInitializer) app).setContextAvailableCallback(
                    context -> {
                        SystemUIFactory.createFromConfig(context);
                        SystemUIFactory.getInstance().getRootComponent().inject(
                                SystemUIAppComponentFactory.this); }); }returnapp; }...public interface ContextAvailableCallback {
        void onContextAvailable(Context context);
    }

    public interface ContextInitializer {
        void setContextAvailableCallback(ContextAvailableCallback callback); }}Copy the code

ContextInitializer (ContextInitializer); ContextInitializer (ContextInitializer); ContextInitializer (ContextInitializer); Received after listening to create SystemUIFactory instance, and then initializes the SystemUI Dagger components, and then injected into SystemUIAppComponentFactory dependence, main dependence is ContextComponentHelper here.

// frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
public class SystemUIApplication extends Application implements
        SystemUIAppComponentFactory.ContextInitializer {...@Override
    public void setContextAvailableCallback( SystemUIAppComponentFactory.ContextAvailableCallback callback) {
        mContextAvailableCallback = callback;
    }

    @Override
    public void onCreate(a) {... mContextAvailableCallback.onContextAvailable(this); mRootComponent = SystemUIFactory.getInstance().getRootComponent(); mComponentHelper = mRootComponent.getContextComponentHelper(); . }}Copy the code

So you can see that the onContextAvailable method is called inside onCreate.

Next, look at SystemUIFactory, a class that provides custom SystemUI Components,

// frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
public class SystemUIFactory {
    private static final String TAG = "SystemUIFactory";

    static SystemUIFactory mFactory;
    private SystemUIRootComponent mRootComponent;

    public static <T extends SystemUIFactory> T getInstance(a) {
        return (T) mFactory;
    }

    public static void createFromConfig(Context context) {
        if(mFactory ! =null) {
            return;
        }

        final String clsName = context.getString(R.string.config_systemUIFactoryComponent);
        if (clsName == null || clsName.length() == 0) {
            throw new RuntimeException("No SystemUIFactory component configured");
        }

        try{ Class<? > cls =null;
            cls = context.getClassLoader().loadClass(clsName);
            mFactory = (SystemUIFactory) cls.newInstance();
            mFactory.init(context);
        } catch (Throwable t) {
            Log.w(TAG, "Error creating SystemUIFactory component: " + clsName, t);
            throw newRuntimeException(t); }}...public SystemUIFactory(a) {}

    private void init(Context context) {
        mRootComponent = buildSystemUIRootComponent(context);

        // Every other part of our codebase currently relies on Dependency, so we
        // really need to ensure the Dependency gets initialized early on.
        Dependency dependency = newDependency(); mRootComponent.createDependency().createSystemUI(dependency); dependency.start(); }}Copy the code

SystemUIFactory has two subclasses: CarSystemUIFactory and TvSystemUIFactory

Let’s talk about what SystemUIFactory’s createFromConfig does

1. Get the class name from config_systemUIFactoryComponent and then instantiate it.

2. Then call buildSystemUIRootComponent to obtain SystemUI component instance

3. Create a Dependency instance and bind it to the DependencyInjector. This step has completed the injection of all Dependency objects.

4. Then start the Dependency to do some initialization.

These steps are complicated, so take a look at the Dependency code.

2.3 Dependency code analysis

Dependency manages the majority of SystemUI dependencies and uses Lazy to delay instance initialization. Although the initialization is delayed, it is expected that all objects will be created during sysui startup.

// frameworks/base/packages/SystemUI/src/com/android/systemui/Dependency.java
/** * Class to handle ugly dependencies throughout sysui until we determine the * long-term dependency injection solution. */
public class Dependency {
    // Use Class as key and instantiate object as value as a cache
    private final ArrayMap<Object, Object> mDependencies = new ArrayMap<>();
    // The LazyDependencyCreator interface is used as the value to delay the initialization of the corresponding instance
    private final ArrayMap<Object, LazyDependencyCreator> mProviders = newArrayMap<>(); .// The instantiation of the corresponding object is delayed by Lazy
    @Inject Lazy<ActivityStarter> mActivityStarter;
    @InjectLazy<BroadcastDispatcher> mBroadcastDispatcher; .protected void start(a) {...// When the start method is called, the Class and deferred instantiation interface objects are put into mapmProviders.put(ActivityStarter.class, mActivityStarter::get); mProviders.put(BroadcastDispatcher.class, mBroadcastDispatcher::get); . }// This method is called whenever the instance object is needed
    // e.g. 
    // ActivityStarter starter = Dependency.get(ActivityStarter.class);
    @Deprecated
    public static <T> T get(Class<T> cls) {
        return sDependency.getDependency(cls);
    }

    protected final <T> T getDependency(Class<T> cls) {
        return getDependencyInner(cls);
    }
  
    // Use mDependencies to add an instance to the cache
    // Store the collected instances in mDependencies as a cache
    private synchronized <T> T getDependencyInner(Object key) {
        @SuppressWarnings("unchecked")
        T obj = (T) mDependencies.get(key);
        if (obj == null) {
            obj = createDependency(key);
            mDependencies.put(key, obj);

            // TODO: Get dependencies to register themselves instead
            if (autoRegisterModulesForDump() && obj instanceofDumpable) { mDumpManager.registerDumpable(obj.getClass().getName(), (Dumpable) obj); }}return obj;
    }

    // Get the injected instance through lazy load callback
    @VisibleForTesting
    protected <T> T createDependency(Object cls) {
        Preconditions.checkArgument(cls instanceofDependencyKey<? > || clsinstanceofClass<? >);@SuppressWarnings("unchecked")
        LazyDependencyCreator<T> provider = mProviders.get(cls);
        if (provider == null) {
            throw new IllegalArgumentException("Unsupported dependency " + cls
                    + "." + mProviders.size() + " providers known.");
        }
        returnprovider.createDependency(); }... }Copy the code

This class caches a large part of SystemUI’s dependencies. Google also explains that Dependency is only a way to solve dependencies. There is no good way to handle dependencies.

2.4 to start the Service

// frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIService.java
public class SystemUIService extends Service {
    @Override
    public void onCreate(a) {
        super.onCreate();

        // Start all of SystemUI((SystemUIApplication) getApplication()).startServicesIfNeeded(); }}Copy the code

ContextComponentHelper resolves the default Service class name to get the instance and starts it.

// frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
public class SystemUIApplication {
    
    public void startServicesIfNeeded(a) {
        String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponents(getResources());
        startServicesIfNeeded(/* metricsPrefix= */ "StartServices", names);
    }
    
    private void startServicesIfNeeded(String metricsPrefix, String[] services) {... mServices =new SystemUI[services.length];
        final int N = services.length;
        for (int i = 0; i < N; i++) {
            String clsName = services[i];
            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; }... mServices[i].start();if (mBootCompleteCache.isBootComplete()) {
                mServices[i].onBootCompleted();
            }
        }
        mServicesStarted = true; }}Copy the code

As explained in Section 2.2, the mComponentHelper singleton object is provided via the Dagger.

// frameworks/base/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
@Singleton
@Component(modules = {... })
public interface SystemUIRootComponent {...@Singleton
    ContextComponentHelper getContextComponentHelper(a); . }Copy the code

ContextComponentHelper is provided in SystemUIModule

// frameworks/base/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
public abstract class SystemUIModule {

    @Binds
    public abstract ContextComponentHelper bindComponentHelper( ContextComponentResolver componentHelper); . }Copy the code

It actually provides an instance of ContextComponentResolver.

// frameworks/base/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java
@Singleton
public class ContextComponentResolver implements ContextComponentHelper {
    private finalMap<Class<? >, Provider<Activity>> mActivityCreators;private finalMap<Class<? >, Provider<Service>> mServiceCreators;private finalMap<Class<? >, Provider<SystemUI>> mSystemUICreators;private finalMap<Class<? >, Provider<RecentsImplementation>> mRecentsCreators;private finalMap<Class<? >, Provider<BroadcastReceiver>> mBroadcastReceiverCreators;@InjectContextComponentResolver(Map<Class<? >, Provider<Activity>> activityCreators, Map<Class<? >, Provider<Service>> serviceCreators, Map<Class<? >, Provider<SystemUI>> systemUICreators, Map<Class<? >, Provider<RecentsImplementation>> recentsCreators, Map<Class<? >, Provider<BroadcastReceiver>> broadcastReceiverCreators) { mActivityCreators = activityCreators; mServiceCreators = serviceCreators; mSystemUICreators = systemUICreators; mRecentsCreators = recentsCreators; mBroadcastReceiverCreators = broadcastReceiverCreators; }...private <T> T resolve(String className, Map
       
        , Provider
        
         > creators)
        
       > {
        try{ Class<? > clazz = Class.forName(className); Provider<T> provider = creators.get(clazz);return provider == null ? null : provider.get();
        } catch (ClassNotFoundException e) {
            return null; }}}Copy the code

ContextComponentResolver: ContextComponentResolver: ContextComponentResolver: ContextComponentResolver: ContextComponentResolver: ContextComponentResolver: ContextComponentResolver

Let’s look at where the VolumeUI instance is provided. It’s provided in SystemUIBinder

// frameworks/base/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@Module(includes = {... })
public abstract class SystemUIBinder {

    @Binds
    @IntoMap
    @ClassKey(VolumeUI.class)
    public abstract SystemUI bindVolumeUI(VolumeUI sysui); . }Copy the code
// frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@Singleton
public class VolumeUI extends SystemUI {

    @Inject
    public VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent) {
        super(context); mVolumeComponent = volumeDialogComponent; }... }Copy the code

So this is where we provide our VolumeUI, and we end up with the general flow of our Dagger here.

2.5 Dagger summary

So we’re done with the Dagger part of the dependency handling. There are some annotations involved, all of which require us to understand the actual effect.

1. The Application tag has appComponentFactory attribute, you can specify the corresponding subclass, when instantiating Application, Service, Activity will call the corresponding method, we can do some initialization operations.

2. The SystemUI Dagger root component is the SystemUIRootComponent class, which references many modules. Most of the dependencies are injected by the SystemUIRootComponent class.

3. The Dependency among SystemUIRootComponent class. DependencyInjector createDependency (); This method, the Dependency DependencyInjector is inside the Dependency, annotated as @ Subcomponent, for SystemUIRootComponent child components, the role of the annotation is, DependencyInjector has obtained all instance objects provided in The SystemUIRootComponent even if the SystemUIRootComponent has not exposed instance objects via methods. Finally, inject all dependencies into Denpendency.

4. Dependency uses Lazy, which delays the initialization of dependent objects. It does not initialize them all at the beginning, but only when they are used and then cached by map.

5. ContextComponentResolver this class cache the Activity, Service, provided by the Dagger SystemUI instantiation objects and so on, the need to use the corresponding instance, will first from this class to get the cache object, A new instantiation object is created if it is empty.

3. Volume initialization

3.1 start the SystemUI

Without further ado, get right to the code

public class SystemUIService extends Service {...@Override
    public void onCreate(a) {
        super.onCreate(); ((SystemUIApplication) getApplication()).startServicesIfNeeded(); }... }Copy the code

Application to start all SystemUI, mComponentHelper class to get the corresponding instance object, if empty to class instantiation.

public class SystemUIApplication {
    
    public void startServicesIfNeeded(a) {
        String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponents(getResources());
        startServicesIfNeeded(/* metricsPrefix= */ "StartServices", names);
    }
    
    private void startServicesIfNeeded(String metricsPrefix, String[] services) {... mServices =new SystemUI[services.length];
        final int N = services.length;
        for (int i = 0; i < N; i++) {
            String clsName = services[i];
            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; }... mServices[i].start();if (mBootCompleteCache.isBootComplete()) {
                mServices[i].onBootCompleted();
            }
        }
        mServicesStarted = true; }}Copy the code

All servers that need to be started are written in the configuration file

    <! -- SystemUI Services: The classes of the stuff to start. -->
    <string-array name="config_systemUIServiceComponents" translatable="false">
        <item>com.android.systemui.util.NotificationChannels</item>
        <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
        <item>com.android.systemui.recents.Recents</item>
        <item>com.android.systemui.volume.VolumeUI</item>.</string-array>
Copy the code

It is important to note that the template pattern is used.

SystemUI is a base class that defines four abstract or empty methods that serve as templates specifying the behavior pattern of subclasses. Many of the subclasses defined in the resource file are subclasses of SystemUI. Since they all inherit from the SystemUI class, these subclasses have some common behavior patterns and how they should behave at certain stages varies from subclass to subclass. This is similar to AsyncTask, which we often use.

3.2 start VolumeUI

VolumeUI doesn’t have a lot of operations inside it. It holds a reference to the VolumeDialogComponent and does some initialization at start.

// frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@Singleton
public class VolumeUI extends SystemUI {

    private VolumeDialogComponent mVolumeComponent;

    @Inject
    public VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent) {
        super(context);
        mVolumeComponent = volumeDialogComponent;
    }

    @Override
    public void start(a) {
        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; mVolumeComponent.setEnableDialogs(enableVolumeUi, enableSafetyWarning); setDefaultVolumeController(); }...private void setDefaultVolumeController(a) {
        DndTile.setVisible(mContext, true);
        if (LOGD) Log.d(TAG, "Registering default volume controller"); mVolumeComponent.register(); }}Copy the code

Two things:

1. The VolumeDialogComponent will create an instance of our volume bar UI, called the VolumeDialogImpl

2. SetDefaultVolumeController method sets the AudioService callback interface

3.3 create VolumeDialogImpl

Take a look at the operations inside the VolumeDialogComponent

// frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@Singleton
public class VolumeDialogComponent implements VolumeComponent.TunerService.Tunable.VolumeDialogControllerImpl.UserActivityListener{...@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)
                // Call createDefault
                .withDefault(this::createDefault)
                // Callback processing
                .withCallback(dialog -> {
                    if(mDialog ! =null) {
                        mDialog.destroy();
                    }
                    mDialog = dialog;
                    mDialog.init(LayoutParams.TYPE_VOLUME_OVERLAY, mVolumeDialogCallback);
                }).build();
    }

    protected VolumeDialog createDefault(a) {
        VolumeDialogImpl impl = new VolumeDialogImpl(mContext);
        impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
        impl.setAutomute(true);
        impl.setSilentMode(false);
        returnimpl; }... }Copy the code

Can see VolumeDialogComponent hold VolumeDialogControllerImpl references

At initialization, our VolumeDialogImpl is created and its init method is called

// frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
public class VolumeDialogImpl implements VolumeDialog.ConfigurationController.ConfigurationListener {
          
    private final H mHandler = newH(); .public void init(int windowType, Callback callback) {
        initDialog();

        mAccessibility.init();

        // Add a callback to the VolumeDialogController
        mController.addCallback(mControllerCallbackH, mHandler);
        mController.getState();

        Dependency.get(ConfigurationController.class).addCallback(this);
    }
          
    private final VolumeDialogController.Callbacks mControllerCallbackH
            = new VolumeDialogController.Callbacks() {

        @Override
        public void onStateChanged(State state) { onStateChangedH(state); }... };private final class H extends Handler {...private static final int STATE_CHANGED = 7;

        public H(a) {
            super(Looper.getMainLooper());
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                ...
                case STATE_CHANGED: onStateChangedH(mState); break; }}}... }Copy the code

This code does a few things:

1. Initialize the dialog, set the layout of the dialog, etc.

2. Add a Callback for the VolumeDialogController. When the VolumeDialogController receives the Callback from the AudioService, the Callback notifies the Dialog of the event to respond to the Callback. The two parameters are the interface that calls back each state and the Handler that is initialized on the main thread.

// frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@Singleton
public class VolumeDialogControllerImpl implements VolumeDialogController.Dumpable {
    
    protected C mCallbacks = newC(); .// Add callback listening
    public void addCallback(Callbacks callback, Handler handler) {
        mCallbacks.add(callback, handler);
        callback.onAccessibilityModeChanged(mShowA11yStream);
    }
  
    class C implements Callbacks {
        // Callbacks as key, Handler as value
        private final HashMap<Callbacks, Handler> mCallbackMap = new HashMap<>();

        public void add(Callbacks callback, Handler handler) {
            if (callback == null || handler == null) throw new IllegalArgumentException();
            mCallbackMap.put(callback, handler);
        }

        @Override
        public void onStateChanged(final State state) {
            final long time = System.currentTimeMillis();
            final State copy = state.copy();
            for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
                entry.getValue().post(new Runnable() {
                    @Override
                    public void run(a) { entry.getKey().onStateChanged(copy); }}); } Events.writeState(time, copy); }}... }Copy the code

C is the Callbacks implementation class and has an internal Map to store the Callbacks and handlers

After VolumeDialogControllerImpl received from AudioService method, is called mCallbacks method, because the call is in the worker thread, so here by the Handler to the UI thread to call, You can change the UI directly in the corresponding implementation place.

The Callbacks code is as follows:

// frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
@ProvidesInterface(version = VolumeDialogController.VERSION)
@DependsOn(target = StreamState.class)
@DependsOn(target = State.class)
@DependsOn(target = Callbacks.class)
public interface VolumeDialogController {

    @ProvidesInterface(version = Callbacks.VERSION)
    public interface Callbacks {
        int VERSION = 1;

        void onShowRequested(int reason);
        void onDismissRequested(int reason);
        void onStateChanged(State state);
        void onLayoutDirectionChanged(int layoutDirection);
        void onConfigurationChanged(a);
        void onShowVibrateHint(a);
        void onShowSilentHint(a);
        void onScreenOff(a);
        void onShowSafetyWarning(int flags);
        void onAccessibilityModeChanged(Boolean showA11yStream);
        void onCaptionComponentStateChanged(Boolean isComponentEnabled, Boolean fromTooltip); }}Copy the code

3.4 registered VolumeController

To see setDefaultVolumeController then, this is more important

// frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@Singleton
public class VolumeDialogComponent implements VolumeComponent.TunerService.Tunable.VolumeDialogControllerImpl.UserActivityListener{
    
    private final VolumeDialogControllerImpl mController;
    
    @Inject
    public VolumeDialogComponent(Context context, KeyguardViewMediator keyguardViewMediator, VolumeDialogControllerImpl volumeDialogController) { mController = volumeDialogController; . }...@Override
    public void register(a) { mController.register(); }... }Copy the code

VolumeDialogComponent call VolumeDialogControllerImpl method

// frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@Singleton
public class VolumeDialogControllerImpl implements VolumeDialogController.Dumpable {...private AudioManager mAudio;
    protected final VC mVolumeController = newVC(); .public void register(a) {
        setVolumeController();
    }
  
    protected void setVolumeController(a) {
        try {
            mAudio.setVolumeController(mVolumeController);
        } catch (SecurityException e) {
            Log.w(TAG, "Unable to set the volume controller", e);
            return; }}private final class VC extends IVolumeController.Stub {
        @Override
        public void volumeChanged(int streamType, int flags) throws RemoteException {
            // Received method called by AudioServicemWorker.obtainMessage(W.VOLUME_CHANGED, streamType, flags).sendToTarget(); }}... }Copy the code

The setVolumeController method of AudioManager is called to set the callback interface for the volume control.

// frameworks/base/services/core/java/com/android/server/audio/AudioService.java
public class AudioService extends IAudioService.Stub
        implements AccessibilityManager.TouchExplorationStateChangeListener.AccessibilityManager.AccessibilityServicesStateChangeListener {

    private final VolumeController mVolumeController = new VolumeController();
              
    @Override
    public void setVolumeController(final IVolumeController controller) {... mVolumeController.setController(controller); }public static class VolumeController {
        private IVolumeController mController;

        public void setController(IVolumeController controller) {
            mController = controller;
            mVisible = false;
        }

        // This method is called when the volume changes
        public void postVolumeChanged(int streamType, int flags) {
            if (mController == null)
                return;
            try {
                mController.volumeChanged(streamType, flags);
            } catch (RemoteException e) {
                Log.w(TAG, "Error calling volumeChanged", e); }}... }}Copy the code

Inside the AudioService we define an internal class called VolumeController that holds a reference to IVolumeController. When the volume changes, the method of VolumeController will be called. Then the method of IVolumeController will be called. Eventually the callback to the SystemUI VolumeDialogControllerImpl VC in the class.

// frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@Singleton
public class VolumeDialogControllerImpl implements VolumeDialogController.Dumpable {...private final class VC extends IVolumeController.Stub {
        @Override
        public void volumeChanged(int streamType, int flags) throws RemoteException {
            // Received method called by AudioServicemWorker.obtainMessage(W.VOLUME_CHANGED, streamType, flags).sendToTarget(); }}private final class W extends Handler {
        private static final int VOLUME_CHANGED = 1;

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

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case VOLUME_CHANGED: onVolumeChangedW(msg.arg1, msg.arg2); break; . }}}boolean onVolumeChangedW(int stream, int flags) {
        final boolean showUI = shouldShowUI(flags);
        final booleanfromKey = (flags & AudioManager.FLAG_FROM_KEY) ! =0;
        final booleanshowVibrateHint = (flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) ! =0;
        final booleanshowSilentHint = (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) {
            // Call mCallbacks onStateChanged methodmCallbacks.onStateChanged(mState); }...returnchanged; }... }Copy the code

MWork is initialized by the child thread’s Looper, so onVolumeChangedW is executed on the child thread, so our mCallbacks are executed on the child thread.

3.5 VolumeUI summary

Here’s a look at the VolumeUI collation process

  1. VolumeUI holds a reference to the VolumeDialogComponent. When the VolumeUI start method is called, it checks whether the volume bar and safe volume prompts are enabled, and then registers AudioService listeners
  2. VolumeDialogComponent constructor to create instances – VolumeDialogImpl article volume, at the same time VolumeDialogImpl to perform some initialization operation, at the same time add VolumeDialogControllerImpl listening in the callback.
  3. Registered AudioService listener is registered in VolumeDialogControllerImpl, when AudioService adjusted volume after the operation, VolumeDialogControllerImpl will receive a notice, At the same time, the received message is called back to the VolumeDialogImpl and UI adjustments are made to complete the operation.

VolumeUI is a complex system. This is a one-sided description of the code flow of VolumeUI, without a detailed analysis of the code inside VolumeDialogImpl, which is mostly UI-related processing. This article focuses on the analysis of the code flow, if there is any improvement, please point out. Thank you very much.