Merge "Enable to listen to WindowContext's config changes" into sc-dev
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
127
core/java/android/content/ComponentCallbacksController.java
Normal file
127
core/java/android/content/ComponentCallbacksController.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user