Merge "Enable to add ComponentCallbacks to Activity"

This commit is contained in:
Charles Chen
2022-02-10 13:14:15 +00:00
committed by Android (Google) Code Review
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) {