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
This commit is contained in:
Charles Chen 2022-01-19 14:41:44 +08:00
parent 9950dab656
commit ba29cdb63e
7 changed files with 188 additions and 21 deletions

View File

@ -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);
}
}
/**

View File

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

View File

@ -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.");

View File

@ -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);
});
}
}

View File

@ -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 */);

View File

@ -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) {

View File

@ -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) {