From ba29cdb63e1dd600df814b8e76e6601d255781c9 Mon Sep 17 00:00:00 2001 From: Charles Chen Date: Wed, 19 Jan 2022 14:41:44 +0800 Subject: [PATCH] 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 --- core/java/android/app/Activity.java | 39 +++++- core/java/android/content/Context.java | 17 +++ core/java/android/content/ContextWrapper.java | 16 +-- .../RegisterComponentCallbacksTest.java | 124 ++++++++++++++++++ .../android/content/ContextWrapperTest.java | 4 +- .../content/TestComponentCallbacks2.java | 8 +- .../test/filters/FrameworksTestsFilter.java | 1 + 7 files changed, 188 insertions(+), 21 deletions(-) create mode 100644 core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index a31aa28208dd..7c90b717c495 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -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); + } } /** diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 207412511198..62dd627320a6 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -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, diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 98ced6d7ed5a..9adf17367039 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -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."); diff --git a/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java b/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java new file mode 100644 index 000000000000..c1b6666a2d17 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java @@ -0,0 +1,124 @@ +/* + * 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); + }); + } +} diff --git a/core/tests/coretests/src/android/content/ContextWrapperTest.java b/core/tests/coretests/src/android/content/ContextWrapperTest.java index ecaf1f42fbca..495770245b49 100644 --- a/core/tests/coretests/src/android/content/ContextWrapperTest.java +++ b/core/tests/coretests/src/android/content/ContextWrapperTest.java @@ -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 */); diff --git a/core/tests/coretests/src/android/content/TestComponentCallbacks2.java b/core/tests/coretests/src/android/content/TestComponentCallbacks2.java index 6ae7fc44c4ce..5c8787aeda9c 100644 --- a/core/tests/coretests/src/android/content/TestComponentCallbacks2.java +++ b/core/tests/coretests/src/android/content/TestComponentCallbacks2.java @@ -20,10 +20,10 @@ import android.content.res.Configuration; import androidx.annotation.NonNull; -class TestComponentCallbacks2 implements ComponentCallbacks2 { - android.content.res.Configuration mConfiguration; - boolean mLowMemoryCalled; - int mLevel; +public class TestComponentCallbacks2 implements ComponentCallbacks2 { + public Configuration mConfiguration = null; + public boolean mLowMemoryCalled = false; + public int mLevel = 0; @Override public void onConfigurationChanged(@NonNull Configuration newConfig) { diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java index 824f91e1e826..15a6afc5ff7c 100644 --- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java +++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java @@ -62,6 +62,7 @@ public final class FrameworksTestsFilter extends SelectTest { "android.view.PendingInsetsControllerTest", "android.window.", // all tests under the package. "android.app.activity.ActivityThreadTest", + "android.app.activity.RegisterComponentCallbacksTest" }; public FrameworksTestsFilter(Bundle testArgs) {