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

Commit ba29cdb6 authored by Charles Chen's avatar Charles Chen
Browse files

Enable to add ComponentCallbacks to Activity

This CL is mainly from the requirements to receive WindowMetrics
updates in Jetpack Library. It also benefits the developers if
they want to listen to Activity's configuration changes outside
Activity

Test: atest RegisterComponentCallbacksTest
Bug: 199442549
Change-Id: Iff5c0a4d9c61d30b84ff9aee465f1b6b06f1a48d
parent 9950dab6
Loading
Loading
Loading
Loading
+38 −1
Original line number Diff line number Diff line
@@ -47,7 +47,9 @@ import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ActivityNotFoundException;
import android.content.ComponentCallbacks;
import android.content.ComponentCallbacks2;
import android.content.ComponentCallbacksController;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -982,6 +984,8 @@ public class Activity extends ContextThemeWrapper
    @Nullable
    private DumpableContainerImpl mDumpableContainer;

    private ComponentCallbacksController mCallbacksController;

    private final WindowControllerCallback mWindowControllerCallback =
            new WindowControllerCallback() {
        /**
@@ -1323,6 +1327,28 @@ public class Activity extends ContextThemeWrapper
        }
    }

    @Override
    public void registerComponentCallbacks(ComponentCallbacks callback) {
        if (CompatChanges.isChangeEnabled(OVERRIDABLE_COMPONENT_CALLBACKS)
                && mCallbacksController == null) {
            mCallbacksController = new ComponentCallbacksController();
        }
        if (mCallbacksController != null) {
            mCallbacksController.registerCallbacks(callback);
        } else {
            super.registerComponentCallbacks(callback);
        }
    }

    @Override
    public void unregisterComponentCallbacks(ComponentCallbacks callback) {
        if (mCallbacksController != null) {
            mCallbacksController.unregisterCallbacks(callback);
        } else {
            super.unregisterComponentCallbacks(callback);
        }
    }

    private void dispatchActivityPreCreated(@Nullable Bundle savedInstanceState) {
        getApplication().dispatchActivityPreCreated(this, savedInstanceState);
        Object[] callbacks = collectActivityLifecycleCallbacks();
@@ -2668,10 +2694,12 @@ public class Activity extends ContextThemeWrapper
        if (mUiTranslationController != null) {
            mUiTranslationController.onActivityDestroyed();
        }

        if (mDefaultBackCallback != null) {
            getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mDefaultBackCallback);
        }
        if (mCallbacksController != null) {
            mCallbacksController.clearCallbacks();
        }
    }

    /**
@@ -2991,6 +3019,9 @@ public class Activity extends ContextThemeWrapper
        }

        dispatchActivityConfigurationChanged();
        if (mCallbacksController != null) {
            mCallbacksController.dispatchConfigurationChanged(newConfig);
        }
    }

    /**
@@ -3162,12 +3193,18 @@ public class Activity extends ContextThemeWrapper
        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onLowMemory " + this);
        mCalled = true;
        mFragments.dispatchLowMemory();
        if (mCallbacksController != null) {
            mCallbacksController.dispatchLowMemory();
        }
    }

    public void onTrimMemory(int level) {
        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onTrimMemory " + this + ": " + level);
        mCalled = true;
        mFragments.dispatchTrimMemory(level);
        if (mCallbacksController != null) {
            mCallbacksController.dispatchTrimMemory(level);
        }
    }

    /**
+17 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UiContext;
import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.BroadcastOptions;
import android.app.GameManager;
@@ -45,6 +46,8 @@ import android.app.VrManager;
import android.app.ambientcontext.AmbientContextManager;
import android.app.people.PeopleManager;
import android.app.time.TimeManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -85,6 +88,7 @@ import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient;
import android.view.textclassifier.TextClassificationManager;
import android.window.WindowContext;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.compat.IPlatformCompatNative;

@@ -111,6 +115,19 @@ import java.util.function.Consumer;
 * broadcasting and receiving intents, etc.
 */
public abstract class Context {
    /**
     * After {@link Build.VERSION_CODES#TIRAMISU},
     * {@link #registerComponentCallbacks(ComponentCallbacks)} will add a {@link ComponentCallbacks}
     * to {@link Activity} or {@link ContextWrapper#getBaseContext()} instead of always adding to
     * {@link #getApplicationContext()}.
     *
     * @hide
     */
    @ChangeId
    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
    @VisibleForTesting
    public static final long OVERRIDABLE_COMPONENT_CALLBACKS = 193247900L;

    /** @hide */
    @IntDef(flag = true, prefix = { "MODE_" }, value = {
            MODE_PRIVATE,
+2 −14
Original line number Diff line number Diff line
@@ -25,8 +25,6 @@ import android.annotation.UiContext;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -73,16 +71,6 @@ public class ContextWrapper extends Context {
    @UnsupportedAppUsage
    Context mBase;

    /**
     * After {@link Build.VERSION_CODES#TIRAMISU},
     * {@link #registerComponentCallbacks(ComponentCallbacks)} will delegate to
     * {@link #getBaseContext()} instead of {@link #getApplicationContext()}.
     */
    @ChangeId
    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
    @VisibleForTesting
    static final long COMPONENT_CALLBACK_ON_WRAPPER = 193247900L;

    /**
     * A list to store {@link ComponentCallbacks} which
     * passes to {@link #registerComponentCallbacks(ComponentCallbacks)} before
@@ -1355,7 +1343,7 @@ public class ContextWrapper extends Context {
    public void registerComponentCallbacks(ComponentCallbacks callback) {
        if (mBase != null) {
            mBase.registerComponentCallbacks(callback);
        } else if (!CompatChanges.isChangeEnabled(COMPONENT_CALLBACK_ON_WRAPPER)) {
        } else if (!CompatChanges.isChangeEnabled(OVERRIDABLE_COMPONENT_CALLBACKS)) {
            super.registerComponentCallbacks(callback);
            synchronized (mLock) {
                // Also register ComponentCallbacks to ContextWrapper, so we can find the correct
@@ -1397,7 +1385,7 @@ public class ContextWrapper extends Context {
                mCallbacksRegisteredToSuper.remove(callback);
            } else if (mBase != null) {
                mBase.unregisterComponentCallbacks(callback);
            } else if (CompatChanges.isChangeEnabled(COMPONENT_CALLBACK_ON_WRAPPER)) {
            } else if (CompatChanges.isChangeEnabled(OVERRIDABLE_COMPONENT_CALLBACKS)) {
                // Throw exception for Application that is targeting S-v2+
                throw new IllegalStateException("ComponentCallbacks must be unregistered after "
                        + "this ContextWrapper is attached to a base Context.");
+124 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 android.app.activity;

import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
import static android.content.Context.OVERRIDABLE_COMPONENT_CALLBACKS;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;

import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;

import android.app.Activity;
import android.app.WindowConfiguration;
import android.app.activity.ActivityThreadTest.TestActivity;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.ComponentCallbacks;
import android.content.TestComponentCallbacks2;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;

import androidx.test.core.app.ActivityScenario;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;

/**
 * Test for verifying {@link Activity#registerComponentCallbacks(ComponentCallbacks)} behavior.
 * Build/Install/Run:
 *  atest FrameworksCoreTests:android.app.activity.RegisterComponentCallbacksTest
 */
@RunWith(AndroidJUnit4.class)
@SmallTest
@Presubmit
public class RegisterComponentCallbacksTest {
    @Rule
    public ActivityScenarioRule rule = new ActivityScenarioRule<>(TestActivity.class);
    @Rule
    public TestRule compatChangeRule = new PlatformCompatChangeRule();

    @Test
    public void testRegisterComponentCallbacks() {
        final ActivityScenario scenario = rule.getScenario();
        final TestComponentCallbacks2 callbacks = new TestComponentCallbacks2();
        final Configuration config = new Configuration();
        config.fontScale = 1.2f;
        config.windowConfiguration.setWindowingMode(
                WindowConfiguration.WINDOWING_MODE_FREEFORM);
        config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
        final int trimMemoryLevel = TRIM_MEMORY_RUNNING_LOW;

        scenario.onActivity(activity -> {
            // It should be no-op to unregister a ComponentCallbacks without registration.
            activity.unregisterComponentCallbacks(callbacks);

            activity.registerComponentCallbacks(callbacks);
            // Verify #onConfigurationChanged
            activity.onConfigurationChanged(config);
            assertThat(callbacks.mConfiguration).isEqualTo(config);
            // Verify #onTrimMemory
            activity.onTrimMemory(trimMemoryLevel);
            assertThat(callbacks.mLevel).isEqualTo(trimMemoryLevel);
            // verify #onLowMemory
            activity.onLowMemory();
            assertThat(callbacks.mLowMemoryCalled).isTrue();

            activity.unregisterComponentCallbacks(callbacks);
        });
    }

    @DisableCompatChanges(OVERRIDABLE_COMPONENT_CALLBACKS)
    @Test
    public void testRegisterComponentCallbacksBeforeT() {
        final ActivityScenario scenario = rule.getScenario();
        final TestComponentCallbacks2 callbacks = new TestComponentCallbacks2();
        final Configuration config = new Configuration();
        config.fontScale = 1.2f;
        config.windowConfiguration.setWindowingMode(
                WindowConfiguration.WINDOWING_MODE_FREEFORM);
        config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
        final int trimMemoryLevel = TRIM_MEMORY_RUNNING_LOW;

        scenario.onActivity(activity -> {
            // It should be no-op to unregister a ComponentCallbacks without registration.
            activity.unregisterComponentCallbacks(callbacks);

            activity.registerComponentCallbacks(callbacks);
            // Verify #onConfigurationChanged
            activity.onConfigurationChanged(config);
            assertWithMessage("The ComponentCallbacks must be added to #getApplicationContext "
                    + "before T.").that(callbacks.mConfiguration).isNull();
            // Verify #onTrimMemory
            activity.onTrimMemory(trimMemoryLevel);
            assertWithMessage("The ComponentCallbacks must be added to #getApplicationContext "
                    + "before T.").that(callbacks.mLevel).isEqualTo(0);
            // verify #onLowMemory
            activity.onLowMemory();
            assertWithMessage("The ComponentCallbacks must be added to #getApplicationContext "
                    + "before T.").that(callbacks.mLowMemoryCalled).isFalse();

            activity.unregisterComponentCallbacks(callbacks);
        });
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@

package android.content;

import static android.content.ContextWrapper.COMPONENT_CALLBACK_ON_WRAPPER;
import static android.content.Context.OVERRIDABLE_COMPONENT_CALLBACKS;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;

@@ -61,7 +61,7 @@ public class ContextWrapperTest {
     * register {@link ComponentCallbacks} to {@link ContextWrapper#getApplicationContext} before
     * {@link ContextWrapper#attachBaseContext(Context)}.
     */
    @DisableCompatChanges(COMPONENT_CALLBACK_ON_WRAPPER)
    @DisableCompatChanges(OVERRIDABLE_COMPONENT_CALLBACKS)
    @Test
    public void testRegisterComponentCallbacksWithoutBaseContextBeforeT() {
        final ContextWrapper wrapper = new TestContextWrapper(null /* base */);
Loading