Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2983d174 authored by Dave Mankoff's avatar Dave Mankoff
Browse files

Change SysemUIFactory to SystemUIInitializer.

This change allows variants of SystemUI to remove the reflection
traditionally used to startup systemui and instead directly stand up
the code that they need.

Specifically, it introduces two primary pieces:

- SystemUIInitializer
  A rename of the SystemUIFactory along with some modest refactoring.
  It is now an abstract class that new SystemUI build targets should
  implement themselves. Implementations use this class to specify a
  root Dagger component used to stand up the rest of SystemUI.

  A default SystemUIInitializerImpl is provided that starts up AOSP.

- SystemUIAppComponentFactoryBase
  An abstract implementation of AppComponentFactory that is used to
  decide what SystemUIInitializer should be loaded.

  Implementers should return a SystemUIInitializer instance of their
  choice in #createSystemUIInitializer.

  The implementation of SystemUIAppComponentFactoryBase should in turn
  be specified in AndroidManifest.xml's
  Application.android:appComponentFactory attribute.

  The default SystemUIAppComponentFactory loads the
  SystemUIInitializer implementation reflectively. This should be
  considered deprecated behavior and may be removed in a future
  release. Please define your own SystemUIAppComponentFactoryBase
  implementation and return a specific SystemUIInitializer
  implementation from it.

Currently, AOSP, TV, and Pixel specific builds still rely on the
reflective loader. Other internal builds have been moved off of the
reflective loader.

Bug: 235624311
Test: manual
Change-Id: I7029fb2960f54fb29e08b96ee49170f150a1d0b6
parent 9475638a
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -9,8 +9,8 @@
-keep class com.android.systemui.statusbar.phone.CentralSurfaces
-keep class com.android.systemui.statusbar.tv.TvStatusBar
-keep class com.android.systemui.car.CarSystemUIFactory
-keep class com.android.systemui.SystemUIFactory
-keep class com.android.systemui.tv.TvSystemUIFactory
-keep class com.android.systemui.SystemUIInitializer
-keep class com.android.systemui.tv.TvSystemUIInitializer
-keep class * extends com.android.systemui.CoreStartable
-keep class * implements com.android.systemui.CoreStartable$Injector

+1 −1
Original line number Diff line number Diff line
@@ -22,7 +22,7 @@
<resources>
    <!-- SystemUIFactory component -->
    <string name="config_systemUIFactoryComponent" translatable="false">
        com.android.systemui.tv.TvSystemUIFactory
        com.android.systemui.tv.TvSystemUIInitializer
    </string>

    <!-- Svelte specific logic, see RecentsConfiguration.SVELTE_* constants. -->
+1 −1
Original line number Diff line number Diff line
@@ -286,7 +286,7 @@
    <bool name="config_enableFullscreenUserSwitcher">false</bool>

    <!-- SystemUIFactory component -->
    <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIFactory</string>
    <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIInitializerImpl</string>

    <!-- QS tile shape store width. negative implies fill configuration instead of stroke-->
    <dimen name="config_qsTileStrokeWidthActive">-1dp</dimen>
+10 −148
Original line number Diff line number Diff line
@@ -16,160 +16,22 @@

package com.android.systemui;

import android.app.Activity;
import android.app.Application;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.AppComponentFactory;

import com.android.systemui.dagger.ContextComponentHelper;
import com.android.systemui.dagger.SysUIComponent;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javax.inject.Inject;

/**
 * Implementation of AppComponentFactory that injects into constructors.
 *
 * This class sets up dependency injection when creating our application.
 * Starts up SystemUI using the AOSP {@link SystemUIInitializerImpl}.
 *
 * Services support dependency injection into their constructors.
 * This initializer relies on reflection to start everything up and should be considered deprecated.
 * Instead, create your own {@link SystemUIAppComponentFactoryBase}, specify it in your
 * AndroidManifest.xml and construct your own {@link SystemUIInitializer} directly.
 *
 * ContentProviders support injection into member variables - _not_ constructors.
 * @deprecated Define your own SystemUIAppComponentFactoryBase implementation and use that. This
 *             implementation may be changed or removed in future releases.
 */
public class SystemUIAppComponentFactory extends AppComponentFactory {

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

    public SystemUIAppComponentFactory() {
        super();
    }

    @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().getSysUIComponent().inject(
                                SystemUIAppComponentFactory.this);
                    }
            );
        }

        return app;
    }

    @NonNull
    @Override
    public ContentProvider instantiateProviderCompat(
            @NonNull ClassLoader cl, @NonNull String className)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {

        ContentProvider contentProvider = super.instantiateProviderCompat(cl, className);
        if (contentProvider instanceof ContextInitializer) {
            ((ContextInitializer) contentProvider).setContextAvailableCallback(
                    context -> {
                        SystemUIFactory.createFromConfig(context);
                        SysUIComponent rootComponent =
                                SystemUIFactory.getInstance().getSysUIComponent();
                        try {
                            Method injectMethod = rootComponent.getClass()
                                    .getMethod("inject", contentProvider.getClass());
                            injectMethod.invoke(rootComponent, contentProvider);
                        } catch (NoSuchMethodException
                                | IllegalAccessException
                                | InvocationTargetException e) {
                            Log.w(TAG, "No injector for class: " + contentProvider.getClass(), e);
                        }
                    }
            );
        }

        return contentProvider;
    }

    @NonNull
    @Override
    public Activity instantiateActivityCompat(@NonNull ClassLoader cl, @NonNull String className,
            @Nullable Intent intent)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        if (mComponentHelper == null) {
            // This shouldn't happen, but is seen on occasion.
            // Bug filed against framework to take a look: http://b/141008541
            SystemUIFactory.getInstance().getSysUIComponent().inject(
                    SystemUIAppComponentFactory.this);
        }
        Activity activity = mComponentHelper.resolveActivity(className);
        if (activity != null) {
            return activity;
        }
        return super.instantiateActivityCompat(cl, className, intent);
    }

    @NonNull
    @Override
    public Service instantiateServiceCompat(
            @NonNull ClassLoader cl, @NonNull String className, Intent intent)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        if (mComponentHelper == null) {
            // This shouldn't happen, but does when a device is freshly formatted.
            // Bug filed against framework to take a look: http://b/141008541
            SystemUIFactory.getInstance().getSysUIComponent().inject(
                    SystemUIAppComponentFactory.this);
        }
        Service service = mComponentHelper.resolveService(className);
        if (service != null) {
            return service;
        }
        return super.instantiateServiceCompat(cl, className, intent);
    }

    @NonNull
@Deprecated
public class SystemUIAppComponentFactory extends SystemUIAppComponentFactoryBase {
    @Override
    public BroadcastReceiver instantiateReceiverCompat(@NonNull ClassLoader cl,
            @NonNull String className, @Nullable Intent intent)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        if (mComponentHelper == null) {
            // This shouldn't happen, but does when a device is freshly formatted.
            // Bug filed against framework to take a look: http://b/141008541
            SystemUIFactory.getInstance().getSysUIComponent().inject(
                    SystemUIAppComponentFactory.this);
        }
        BroadcastReceiver receiver = mComponentHelper.resolveBroadcastReceiver(className);
        if (receiver != null) {
            return receiver;
        }

        return super.instantiateReceiverCompat(cl, className, intent);
    }

    /**
     * A callback that receives a Context when one is ready.
     */
    public interface ContextAvailableCallback {
        void onContextAvailable(Context context);
    }

    /**
     * Implemented in classes that get started by the system before a context is available.
     */
    public interface ContextInitializer {
        void setContextAvailableCallback(ContextAvailableCallback callback);
    protected SystemUIInitializer createSystemUIInitializer(Context context) {
        return SystemUIInitializerFactory.createWithContext(context);
    }
}
+183 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.systemui

import android.app.Activity
import android.app.Application
import android.app.Service
import android.content.BroadcastReceiver
import android.content.ContentProvider
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.core.app.AppComponentFactory
import com.android.systemui.dagger.ContextComponentHelper
import java.lang.reflect.InvocationTargetException
import java.util.concurrent.ExecutionException
import javax.inject.Inject

/**
 * Implementation of AppComponentFactory that injects into constructors.
 *
 * This class sets up dependency injection when creating our application.
 *
 * Activities, Services, and BroadcastReceivers support dependency injection into
 * their constructors.
 *
 * ContentProviders support injection into member variables - _not_ constructors.
 */
abstract class SystemUIAppComponentFactoryBase : AppComponentFactory() {
    companion object {
        private const val TAG = "AppComponentFactory"
        // Must be static due to http://b/141008541.
        var systemUIInitializer: SystemUIInitializer? = null
    }

    @set:Inject
    lateinit var componentHelper: ContextComponentHelper

    /**
     * Returns a new [SystemUIInitializer].
     *
     * The returned implementation should be specific to your build.
     */
    protected abstract fun createSystemUIInitializer(context: Context): SystemUIInitializer

    private fun createSystemUIInitializerInternal(context: Context): SystemUIInitializer {
        return systemUIInitializer ?: run {
            val initializer = createSystemUIInitializer(context.applicationContext)
            try {
                initializer.init(false)
            } catch (exception: ExecutionException) {
                throw RuntimeException("Failed to initialize SysUI", exception)
            } catch (exception: InterruptedException) {
                throw RuntimeException("Failed to initialize SysUI", exception)
            }
            initializer.sysUIComponent.inject(
                this@SystemUIAppComponentFactoryBase
            )

            systemUIInitializer = initializer
            return initializer
        }
    }

    override fun instantiateApplicationCompat(cl: ClassLoader, className: String): Application {
        val app = super.instantiateApplicationCompat(cl, className)
        if (app !is ContextInitializer) {
            throw RuntimeException("App must implement ContextInitializer")
        } else {
            app.setContextAvailableCallback { context ->
                createSystemUIInitializerInternal(context)
            }
        }

        return app
    }

    override fun instantiateProviderCompat(cl: ClassLoader, className: String): ContentProvider {
        val contentProvider = super.instantiateProviderCompat(cl, className)
        if (contentProvider is ContextInitializer) {
            contentProvider.setContextAvailableCallback { context ->
                val initializer = createSystemUIInitializerInternal(context)
                val rootComponent = initializer.sysUIComponent
                try {
                    val injectMethod = rootComponent.javaClass
                        .getMethod("inject", contentProvider.javaClass)
                    injectMethod.invoke(rootComponent, contentProvider)
                } catch (e: NoSuchMethodException) {
                    Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e)
                } catch (e: IllegalAccessException) {
                    Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e)
                } catch (e: InvocationTargetException) {
                    Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e)
                }
                initializer
            }
        }
        return contentProvider
    }

    override fun instantiateActivityCompat(
        cl: ClassLoader,
        className: String,
        intent: Intent?
    ): Activity {
        if (!this::componentHelper.isInitialized) {
            // This shouldn't happen, but is seen on occasion.
            // Bug filed against framework to take a look: http://b/141008541
            systemUIInitializer?.sysUIComponent?.inject(this@SystemUIAppComponentFactoryBase)
        }
        return componentHelper.resolveActivity(className)
            ?: super.instantiateActivityCompat(cl, className, intent)
    }

    override fun instantiateServiceCompat(
        cl: ClassLoader,
        className: String,
        intent: Intent?
    ): Service {
        if (!this::componentHelper.isInitialized) {
            // This shouldn't happen, but does when a device is freshly formatted.
            // Bug filed against framework to take a look: http://b/141008541
            systemUIInitializer?.sysUIComponent?.inject(this@SystemUIAppComponentFactoryBase)
        }
        return componentHelper.resolveService(className)
            ?: super.instantiateServiceCompat(cl, className, intent)
    }

    override fun instantiateReceiverCompat(
        cl: ClassLoader,
        className: String,
        intent: Intent?
    ): BroadcastReceiver {
        if (!this::componentHelper.isInitialized) {
            // This shouldn't happen, but does when a device is freshly formatted.
            // Bug filed against framework to take a look: http://b/141008541
            systemUIInitializer?.sysUIComponent?.inject(this@SystemUIAppComponentFactoryBase)
        }
        return componentHelper.resolveBroadcastReceiver(className)
            ?: super.instantiateReceiverCompat(cl, className, intent)
    }

    /**
     * An Interface for classes that can be notified when an Application Context becomes available.
     *
     * An instance of this will be passed to implementers of [ContextInitializer].
     */
    fun interface ContextAvailableCallback {
        /** Notifies when the Application Context is available.  */
        fun onContextAvailable(context: Context): SystemUIInitializer
    }

    /**
     * Interface for classes that can be constructed by the system before a context is available.
     *
     * This is intended for [Application] and [ContentProvider] implementations that
     * either may not have a Context until some point after construction or are themselves
     * a [Context].
     *
     * Implementers will be passed a [ContextAvailableCallback] that they should call as soon
     * as an Application Context is ready.
     */
    interface ContextInitializer {
        /**
         * Called to supply the [ContextAvailableCallback] that should be called when an
         * Application [Context] is available.
         */
        fun setContextAvailableCallback(callback: ContextAvailableCallback)
    }
}
Loading