summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/Application.java49
-rw-r--r--core/java/android/app/WindowContext.java21
-rw-r--r--core/java/android/app/WindowTokenClient.java4
-rw-r--r--core/java/android/content/ComponentCallbacksController.java127
-rw-r--r--core/java/android/content/Context.java21
-rw-r--r--core/java/android/content/TEST_MAPPING5
-rw-r--r--core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java140
7 files changed, 324 insertions, 43 deletions
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index 146d648fe65a..618eda8c84e8 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentCallbacks;
import android.content.ComponentCallbacks2;
+import android.content.ComponentCallbacksController;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
@@ -53,14 +54,14 @@ import java.util.ArrayList;
public class Application extends ContextWrapper implements ComponentCallbacks2 {
private static final String TAG = "Application";
@UnsupportedAppUsage
- private ArrayList<ComponentCallbacks> mComponentCallbacks =
- new ArrayList<ComponentCallbacks>();
- @UnsupportedAppUsage
private ArrayList<ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
new ArrayList<ActivityLifecycleCallbacks>();
@UnsupportedAppUsage
private ArrayList<OnProvideAssistDataListener> mAssistCallbacks = null;
+ private final ComponentCallbacksController mCallbacksController =
+ new ComponentCallbacksController();
+
/** @hide */
@UnsupportedAppUsage
public LoadedApk mLoadedApk;
@@ -260,47 +261,25 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
@CallSuper
public void onConfigurationChanged(@NonNull Configuration newConfig) {
- Object[] callbacks = collectComponentCallbacks();
- if (callbacks != null) {
- for (int i=0; i<callbacks.length; i++) {
- ((ComponentCallbacks)callbacks[i]).onConfigurationChanged(newConfig);
- }
- }
+ mCallbacksController.dispatchConfigurationChanged(newConfig);
}
@CallSuper
public void onLowMemory() {
- Object[] callbacks = collectComponentCallbacks();
- if (callbacks != null) {
- for (int i=0; i<callbacks.length; i++) {
- ((ComponentCallbacks)callbacks[i]).onLowMemory();
- }
- }
+ mCallbacksController.dispatchLowMemory();
}
@CallSuper
public void onTrimMemory(int level) {
- Object[] callbacks = collectComponentCallbacks();
- if (callbacks != null) {
- for (int i=0; i<callbacks.length; i++) {
- Object c = callbacks[i];
- if (c instanceof ComponentCallbacks2) {
- ((ComponentCallbacks2)c).onTrimMemory(level);
- }
- }
- }
+ mCallbacksController.dispatchTrimMemory(level);
}
public void registerComponentCallbacks(ComponentCallbacks callback) {
- synchronized (mComponentCallbacks) {
- mComponentCallbacks.add(callback);
- }
+ mCallbacksController.registerCallbacks(callback);
}
public void unregisterComponentCallbacks(ComponentCallbacks callback) {
- synchronized (mComponentCallbacks) {
- mComponentCallbacks.remove(callback);
- }
+ mCallbacksController.unregisterCallbacks(callback);
}
public void registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {
@@ -575,16 +554,6 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
}
}
- private Object[] collectComponentCallbacks() {
- Object[] callbacks = null;
- synchronized (mComponentCallbacks) {
- if (mComponentCallbacks.size() > 0) {
- callbacks = mComponentCallbacks.toArray();
- }
- }
- return callbacks;
- }
-
@UnsupportedAppUsage
private Object[] collectActivityLifecycleCallbacks() {
Object[] callbacks = null;
diff --git a/core/java/android/app/WindowContext.java b/core/java/android/app/WindowContext.java
index cbe2995f2467..d44918cf0379 100644
--- a/core/java/android/app/WindowContext.java
+++ b/core/java/android/app/WindowContext.java
@@ -20,8 +20,11 @@ import static android.view.WindowManagerImpl.createWindowContextWindowManager;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiContext;
+import android.content.ComponentCallbacks;
+import android.content.ComponentCallbacksController;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -49,6 +52,8 @@ public class WindowContext extends ContextWrapper {
private final IWindowManager mWms;
private final WindowTokenClient mToken;
private boolean mListenerRegistered;
+ private final ComponentCallbacksController mCallbacksController =
+ new ComponentCallbacksController();
/**
* Default constructor. Will generate a {@link WindowTokenClient} and attach this context to
@@ -131,8 +136,24 @@ public class WindowContext extends ContextWrapper {
}
void destroy() {
+ mCallbacksController.clearCallbacks();
final ContextImpl impl = (ContextImpl) getBaseContext();
impl.scheduleFinalCleanup(getClass().getName(), "WindowContext");
Reference.reachabilityFence(this);
}
+
+ @Override
+ public void registerComponentCallbacks(@NonNull ComponentCallbacks callback) {
+ mCallbacksController.registerCallbacks(callback);
+ }
+
+ @Override
+ public void unregisterComponentCallbacks(@NonNull ComponentCallbacks callback) {
+ mCallbacksController.unregisterCallbacks(callback);
+ }
+
+ /** Dispatch {@link Configuration} to each {@link ComponentCallbacks}. */
+ void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
+ mCallbacksController.dispatchConfigurationChanged(newConfig);
+ }
}
diff --git a/core/java/android/app/WindowTokenClient.java b/core/java/android/app/WindowTokenClient.java
index 2298e84d755e..82cef072ad0f 100644
--- a/core/java/android/app/WindowTokenClient.java
+++ b/core/java/android/app/WindowTokenClient.java
@@ -61,7 +61,7 @@ public class WindowTokenClient extends IWindowToken.Stub {
@Override
public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
- final Context context = mContextRef.get();
+ final WindowContext context = mContextRef.get();
if (context == null) {
return;
}
@@ -72,6 +72,8 @@ public class WindowTokenClient extends IWindowToken.Stub {
if (displayChanged || configChanged) {
// TODO(ag/9789103): update resource manager logic to track non-activity tokens
mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId);
+ ActivityThread.currentActivityThread().getHandler().post(
+ () -> context.dispatchConfigurationChanged(newConfig));
}
if (displayChanged) {
context.updateDisplay(newDisplayId);
diff --git a/core/java/android/content/ComponentCallbacksController.java b/core/java/android/content/ComponentCallbacksController.java
new file mode 100644
index 000000000000..a81aaf78bdb9
--- /dev/null
+++ b/core/java/android/content/ComponentCallbacksController.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2021 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.content;
+
+import android.annotation.NonNull;
+import android.content.res.Configuration;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A helper class to manage {@link ComponentCallbacks} and {@link ComponentCallbacks2}, such as
+ * registering ,unregistering {@link ComponentCallbacks} and sending callbacks to all registered
+ * {@link ComponentCallbacks}.
+ *
+ * @see Context#registerComponentCallbacks(ComponentCallbacks)
+ * @see Context#unregisterComponentCallbacks(ComponentCallbacks)
+ * @see ComponentCallbacks
+ * @see ComponentCallbacks2
+ *
+ * @hide
+ */
+public class ComponentCallbacksController {
+ @GuardedBy("mLock")
+ private List<ComponentCallbacks> mComponentCallbacks;
+
+ private final Object mLock = new Object();
+
+ /**
+ * Register the {@link ComponentCallbacks}.
+ *
+ * @see Context#registerComponentCallbacks(ComponentCallbacks)
+ */
+ public void registerCallbacks(@NonNull ComponentCallbacks callbacks) {
+ synchronized (mLock) {
+ if (mComponentCallbacks == null) {
+ mComponentCallbacks = new ArrayList<>();
+ }
+ mComponentCallbacks.add(callbacks);
+ }
+ }
+
+ /**
+ * Unregister the {@link ComponentCallbacks}.
+ *
+ * @see Context#unregisterComponentCallbacks(ComponentCallbacks)
+ */
+ public void unregisterCallbacks(@NonNull ComponentCallbacks callbacks) {
+ synchronized (mLock) {
+ if (mComponentCallbacks == null || mComponentCallbacks.isEmpty()) {
+ return;
+ }
+ mComponentCallbacks.remove(callbacks);
+ }
+ }
+
+ /**
+ * Clear all registered {@link ComponentCallbacks}.
+ * It is useful when the associated {@link Context} is going to be released.
+ */
+ public void clearCallbacks() {
+ synchronized (mLock) {
+ if (mComponentCallbacks != null) {
+ mComponentCallbacks.clear();
+ }
+ }
+ }
+
+ /**
+ * Sending {@link ComponentCallbacks#onConfigurationChanged(Configuration)} to all registered
+ * {@link ComponentCallbacks}.
+ */
+ public void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
+ forAllComponentCallbacks(callbacks -> callbacks.onConfigurationChanged(newConfig));
+ }
+
+ /**
+ * Sending {@link ComponentCallbacks#onLowMemory()} to all registered
+ * {@link ComponentCallbacks}.
+ */
+ public void dispatchLowMemory() {
+ forAllComponentCallbacks(ComponentCallbacks::onLowMemory);
+ }
+
+ /**
+ * Sending {@link ComponentCallbacks2#onTrimMemory(int)} to all registered
+ * {@link ComponentCallbacks2}.
+ */
+ public void dispatchTrimMemory(int level) {
+ forAllComponentCallbacks(callbacks -> {
+ if (callbacks instanceof ComponentCallbacks2) {
+ ((ComponentCallbacks2) callbacks).onTrimMemory(level);
+ }
+ });
+ }
+
+ private void forAllComponentCallbacks(Consumer<ComponentCallbacks> callbacksConsumer) {
+ final ComponentCallbacks[] callbacksArray;
+ synchronized (mLock) {
+ if (mComponentCallbacks == null || mComponentCallbacks.isEmpty()) {
+ return;
+ }
+ callbacksArray = new ComponentCallbacks[mComponentCallbacks.size()];
+ mComponentCallbacks.toArray(callbacksArray);
+ }
+ for (ComponentCallbacks callbacks : callbacksArray) {
+ callbacksConsumer.accept(callbacks);
+ }
+ }
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 64ca92fa8132..92ff640e33b0 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -655,12 +655,21 @@ public abstract class Context {
/**
* Add a new {@link ComponentCallbacks} to the base application of the
* Context, which will be called at the same times as the ComponentCallbacks
- * methods of activities and other components are called. Note that you
+ * methods of activities and other components are called. Note that you
* <em>must</em> be sure to use {@link #unregisterComponentCallbacks} when
* appropriate in the future; this will not be removed for you.
+ * <p>
+ * After {@link Build.VERSION_CODES#S}, Registering the ComponentCallbacks to Context created
+ * via {@link #createWindowContext(int, Bundle)} or
+ * {@link #createWindowContext(Display, int, Bundle)} will receive
+ * {@link ComponentCallbacks#onConfigurationChanged(Configuration)} from Window Context rather
+ * than its base application. It is helpful if you want to handle UI components that
+ * associated with the Window Context when the Window Context has configuration changes.</p>
*
* @param callback The interface to call. This can be either a
* {@link ComponentCallbacks} or {@link ComponentCallbacks2} interface.
+ *
+ * @see Context#createWindowContext(int, Bundle)
*/
public void registerComponentCallbacks(ComponentCallbacks callback) {
getApplicationContext().registerComponentCallbacks(callback);
@@ -6358,6 +6367,16 @@ public abstract class Context {
* windowContext.getSystemService(WindowManager.class).addView(overlayView, mParams);
* </pre>
* <p>
+ * After {@link Build.VERSION_CODES#S}, window context provides the capability to listen to its
+ * {@link Configuration} changes by calling
+ * {@link #registerComponentCallbacks(ComponentCallbacks)}, while other kinds of {@link Context}
+ * will register the {@link ComponentCallbacks} to {@link #getApplicationContext() its
+ * Application context}. Note that window context only propagate
+ * {@link ComponentCallbacks#onConfigurationChanged(Configuration)} callback.
+ * {@link ComponentCallbacks#onLowMemory()} or other callbacks in {@link ComponentCallbacks2}
+ * won't be invoked.
+ * </p>
+ * <p>
* Note that using {@link android.app.Application} or {@link android.app.Service} context for
* UI-related queries may result in layout or continuity issues on devices with variable screen
* sizes (e.g. foldables) or in multi-window modes, since these non-UI contexts may not reflect
diff --git a/core/java/android/content/TEST_MAPPING b/core/java/android/content/TEST_MAPPING
index a2880dfdfd17..614143e7c04d 100644
--- a/core/java/android/content/TEST_MAPPING
+++ b/core/java/android/content/TEST_MAPPING
@@ -44,9 +44,12 @@
},
{
"include-filter": "android.content.ContextTest"
+ },
+ {
+ "include-filter": "android.content.ComponentCallbacksControllerTest"
}
],
- "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java"]
+ "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java", "(/|^)ComponentCallbacksController.java"]
}
]
} \ No newline at end of file
diff --git a/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java b/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java
new file mode 100644
index 000000000000..09985a8bee4b
--- /dev/null
+++ b/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2021 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.content;
+
+import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.WindowConfiguration;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:ComponentCallbacksControllerTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ComponentCallbacksControllerTest {
+ private ComponentCallbacksController mController;
+
+ @Before
+ public void setUp() {
+ mController = new ComponentCallbacksController();
+ }
+
+ @Test
+ public void testUnregisterCallbackWithoutRegistrationNoCrash() {
+ mController.unregisterCallbacks(new FakeComponentCallbacks());
+ }
+
+ @Test
+ public void testDispatchWithEmptyCallbacksNoCrash() {
+ mController.dispatchConfigurationChanged(new Configuration());
+ mController.dispatchLowMemory();
+ mController.dispatchTrimMemory(TRIM_MEMORY_BACKGROUND);
+ }
+
+ @Test
+ public void testClearCallbacksNoCrash() {
+ mController.clearCallbacks();
+ }
+
+ @Test
+ public void testDispatchTrimMemoryWithoutComponentCallbacks2NoCrash() {
+ // Register a ComponentCallbacks instead of ComponentCallbacks2
+ mController.registerCallbacks(new FakeComponentCallbacks());
+
+ mController.dispatchTrimMemory(TRIM_MEMORY_BACKGROUND);
+ }
+
+ @Test
+ public void testDispatchConfigurationChanged() throws Exception {
+ final TestComponentCallbacks2 callback = new TestComponentCallbacks2();
+ mController.registerCallbacks(callback);
+
+ final Configuration config = new Configuration();
+ config.windowConfiguration.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
+ config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
+
+ mController.dispatchConfigurationChanged(config);
+
+ assertThat(callback.mConfiguration).isEqualTo(config);
+
+ mController.dispatchConfigurationChanged(Configuration.EMPTY);
+
+ assertThat(callback.mConfiguration).isEqualTo(Configuration.EMPTY);
+ }
+
+ @Test
+ public void testDispatchLowMemory() {
+ final TestComponentCallbacks2 callback = new TestComponentCallbacks2();
+ mController.registerCallbacks(callback);
+
+ mController.dispatchLowMemory();
+
+ assertThat(callback.mLowMemoryCalled).isTrue();
+ }
+
+ @Test
+ public void testDispatchTrimMemory() {
+ final TestComponentCallbacks2 callback = new TestComponentCallbacks2();
+ mController.registerCallbacks(callback);
+
+ mController.dispatchTrimMemory(TRIM_MEMORY_BACKGROUND);
+
+ assertThat(callback.mLevel).isEqualTo(TRIM_MEMORY_BACKGROUND);
+ }
+
+ private static class FakeComponentCallbacks implements ComponentCallbacks {
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {}
+
+ @Override
+ public void onLowMemory() {}
+ }
+
+ private static class TestComponentCallbacks2 implements ComponentCallbacks2 {
+ private Configuration mConfiguration;
+ private boolean mLowMemoryCalled;
+ private int mLevel;
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ mConfiguration = newConfig;
+ }
+
+ @Override
+ public void onLowMemory() {
+ mLowMemoryCalled = true;
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ mLevel = level;
+ }
+ }
+}