Add raw configuration change listener updates.
Add raw configuration change listener updates. Letterboxed Activities do
not receive a configuration update when they are repositioned. Listening
to all configuration changes will correctly update folding features.
Change exceptions from hard exceptions to Log.wtf so that we do not
crash on production apps.
Bug: 295785410
Test: Open Samples and open the slim (letterbox) Activities.
Change-Id: Ia079d06a403a59bb0f1eafdaad6ce238749a2af2
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 0191201..c51dab7 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -36,6 +36,7 @@
import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
import static android.window.ConfigurationHelper.isDifferentDisplay;
import static android.window.ConfigurationHelper.shouldUpdateResources;
+
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
@@ -265,6 +266,7 @@
import java.util.TimeZone;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
/**
* This manages the execution of the main thread in an
@@ -382,6 +384,11 @@
@GuardedBy("mAppThread")
private int mLastProcessState = PROCESS_STATE_UNKNOWN;
final ArrayList<WeakReference<AssistStructure>> mLastAssistStructures = new ArrayList<>();
+
+ @NonNull
+ private final ConfigurationChangedListenerController mConfigurationChangedListenerController =
+ new ConfigurationChangedListenerController();
+
private int mLastSessionId;
// Holds the value of the last reported device ID value from the server for the top activity.
int mLastReportedDeviceId;
@@ -3608,6 +3615,21 @@
return mConfigurationController.getConfiguration();
}
+ /**
+ * @hide
+ */
+ public void addConfigurationChangedListener(Executor executor,
+ Consumer<IBinder> consumer) {
+ mConfigurationChangedListenerController.addListener(executor, consumer);
+ }
+
+ /**
+ * @hide
+ */
+ public void removeConfigurationChangedListener(Consumer<IBinder> consumer) {
+ mConfigurationChangedListenerController.removeListener(consumer);
+ }
+
@Override
public void updatePendingConfiguration(Configuration config) {
final Configuration updatedConfig =
@@ -6252,6 +6274,8 @@
" did not call through to super.onConfigurationChanged()");
}
}
+ mConfigurationChangedListenerController
+ .dispatchOnConfigurationChanged(activity.getActivityToken());
return configToReport;
}
diff --git a/core/java/android/app/ConfigurationChangedListenerController.java b/core/java/android/app/ConfigurationChangedListenerController.java
new file mode 100644
index 0000000..c644d57
--- /dev/null
+++ b/core/java/android/app/ConfigurationChangedListenerController.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2023 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;
+
+import android.annotation.NonNull;
+import android.os.IBinder;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Manages listeners for unfiltered configuration changes.
+ * @hide
+ */
+class ConfigurationChangedListenerController {
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final List<ListenerContainer> mListenerContainers = new ArrayList<>();
+
+ /**
+ * Adds a listener to receive updates when they are dispatched. This only dispatches updates and
+ * does not relay the last emitted value. If called with the same listener then this method does
+ * not have any effect.
+ * @param executor an executor that is used to dispatch the updates.
+ * @param consumer a listener interested in receiving updates.
+ */
+ void addListener(@NonNull Executor executor,
+ @NonNull Consumer<IBinder> consumer) {
+ synchronized (mLock) {
+ if (indexOf(consumer) > -1) {
+ return;
+ }
+ mListenerContainers.add(new ListenerContainer(executor, consumer));
+ }
+ }
+
+ /**
+ * Removes the listener that was previously registered. If the listener was not registered this
+ * method does not have any effect.
+ */
+ void removeListener(@NonNull Consumer<IBinder> consumer) {
+ synchronized (mLock) {
+ final int index = indexOf(consumer);
+ if (index > -1) {
+ mListenerContainers.remove(index);
+ }
+ }
+ }
+
+ /**
+ * Dispatches the update to all registered listeners
+ * @param activityToken a token for the {@link Activity} that received a configuration update.
+ */
+ void dispatchOnConfigurationChanged(@NonNull IBinder activityToken) {
+ final List<ListenerContainer> consumers;
+ synchronized (mLock) {
+ consumers = new ArrayList<>(mListenerContainers);
+ }
+ for (int i = 0; i < consumers.size(); i++) {
+ consumers.get(i).accept(activityToken);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private int indexOf(Consumer<IBinder> consumer) {
+ for (int i = 0; i < mListenerContainers.size(); i++) {
+ if (mListenerContainers.get(i).isMatch(consumer)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private static final class ListenerContainer {
+
+ @NonNull
+ private final Executor mExecutor;
+ @NonNull
+ private final Consumer<IBinder> mConsumer;
+
+ ListenerContainer(@NonNull Executor executor,
+ @NonNull Consumer<IBinder> consumer) {
+ mExecutor = executor;
+ mConsumer = consumer;
+ }
+
+ public boolean isMatch(@NonNull Consumer<IBinder> consumer) {
+ return mConsumer.equals(consumer);
+ }
+
+ public void accept(@NonNull IBinder activityToken) {
+ mExecutor.execute(() -> mConsumer.accept(activityToken));
+ }
+
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index e0942b74..9b84a48 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -25,6 +25,7 @@
import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
import android.app.Activity;
+import android.app.ActivityThread;
import android.app.Application;
import android.app.WindowConfiguration;
import android.content.ComponentCallbacks;
@@ -34,6 +35,7 @@
import android.os.Bundle;
import android.os.IBinder;
import android.util.ArrayMap;
+import android.util.Log;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
@@ -60,7 +62,7 @@
* Please refer to {@link androidx.window.sidecar.SampleSidecarImpl} instead.
*/
public class WindowLayoutComponentImpl implements WindowLayoutComponent {
- private static final String TAG = "SampleExtension";
+ private static final String TAG = WindowLayoutComponentImpl.class.getSimpleName();
private final Object mLock = new Object();
@@ -82,6 +84,9 @@
private final Map<java.util.function.Consumer<WindowLayoutInfo>, Consumer<WindowLayoutInfo>>
mJavaToExtConsumers = new ArrayMap<>();
+ private final RawConfigurationChangedListener mRawConfigurationChangedListener =
+ new RawConfigurationChangedListener();
+
public WindowLayoutComponentImpl(@NonNull Context context,
@NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer) {
((Application) context.getApplicationContext())
@@ -102,6 +107,7 @@
final Consumer<WindowLayoutInfo> extConsumer = consumer::accept;
synchronized (mLock) {
mJavaToExtConsumers.put(consumer, extConsumer);
+ updateListenerRegistrations();
}
addWindowLayoutInfoListener(activity, extConsumer);
}
@@ -155,6 +161,7 @@
final Consumer<WindowLayoutInfo> extConsumer;
synchronized (mLock) {
extConsumer = mJavaToExtConsumers.remove(consumer);
+ updateListenerRegistrations();
}
if (extConsumer != null) {
removeWindowLayoutInfoListener(extConsumer);
@@ -185,6 +192,17 @@
}
@GuardedBy("mLock")
+ private void updateListenerRegistrations() {
+ ActivityThread currentThread = ActivityThread.currentActivityThread();
+ if (mJavaToExtConsumers.isEmpty()) {
+ currentThread.removeConfigurationChangedListener(mRawConfigurationChangedListener);
+ } else {
+ currentThread.addConfigurationChangedListener(Runnable::run,
+ mRawConfigurationChangedListener);
+ }
+ }
+
+ @GuardedBy("mLock")
@NonNull
private Set<Context> getContextsListeningForLayoutChanges() {
return mWindowLayoutChangeListeners.keySet();
@@ -329,25 +347,28 @@
continue;
}
if (featureRect.left != 0 && featureRect.top != 0) {
- throw new IllegalArgumentException("Bounding rectangle must start at the top or "
+ Log.wtf(TAG, "Bounding rectangle must start at the top or "
+ "left of the window. BaseFeatureRect: " + baseFeature.getRect()
+ ", FeatureRect: " + featureRect
+ ", WindowConfiguration: " + windowConfiguration);
+ continue;
}
if (featureRect.left == 0
&& featureRect.width() != windowConfiguration.getBounds().width()) {
- throw new IllegalArgumentException("Horizontal FoldingFeature must have full width."
+ Log.wtf(TAG, "Horizontal FoldingFeature must have full width."
+ " BaseFeatureRect: " + baseFeature.getRect()
+ ", FeatureRect: " + featureRect
+ ", WindowConfiguration: " + windowConfiguration);
+ continue;
}
if (featureRect.top == 0
&& featureRect.height() != windowConfiguration.getBounds().height()) {
- throw new IllegalArgumentException("Vertical FoldingFeature must have full height."
+ Log.wtf(TAG, "Vertical FoldingFeature must have full height."
+ " BaseFeatureRect: " + baseFeature.getRect()
+ ", FeatureRect: " + featureRect
+ ", WindowConfiguration: " + windowConfiguration);
+ continue;
}
features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
}
@@ -400,6 +421,16 @@
}
}
+ private final class RawConfigurationChangedListener implements
+ java.util.function.Consumer<IBinder> {
+ @Override
+ public void accept(IBinder activityToken) {
+ synchronized (mLock) {
+ onDisplayFeaturesChangedIfListening(activityToken);
+ }
+ }
+ }
+
private final class ConfigurationChangeListener implements ComponentCallbacks {
final IBinder mToken;