diff options
author | 2023-09-15 21:27:06 +0000 | |
---|---|---|
committer | 2023-09-22 19:59:56 +0000 | |
commit | 852dd9d8d6eb4ce11a514fe3fdc94fecbf3c6a9e (patch) | |
tree | 1618d278618de3daba4f4edc8ac252726afe2607 | |
parent | a803fdee60d9ea3f41f9ab450d342213bda9f767 (diff) |
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.
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:1a7c2f292e7cc1fddfc9b0c64d1e5e4264f60144)
Merged-In: Ia079d06a403a59bb0f1eafdaad6ce238749a2af2
Change-Id: Ia079d06a403a59bb0f1eafdaad6ce238749a2af2
3 files changed, 174 insertions, 4 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 34662c89cb8a..bee5011da657 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -255,6 +255,7 @@ import java.util.Objects; 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 @@ -369,6 +370,11 @@ public final class ActivityThread extends ClientTransactionHandler @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; @@ -3536,6 +3542,21 @@ public final class ActivityThread extends ClientTransactionHandler 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 = @@ -6094,6 +6115,8 @@ public final class ActivityThread extends ClientTransactionHandler " 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 000000000000..c644d57fb9a7 --- /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 69c1d294eab0..a1fe7f75a826 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -24,6 +24,7 @@ import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation; 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; @@ -33,6 +34,7 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; import android.util.ArrayMap; +import android.util.Log; import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; @@ -59,7 +61,7 @@ import java.util.Set; * 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(); @@ -81,6 +83,9 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { 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()) @@ -109,6 +114,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { final Consumer<WindowLayoutInfo> extConsumer = consumer::accept; synchronized (mLock) { mJavaToExtConsumers.put(consumer, extConsumer); + updateListenerRegistrations(); } addWindowLayoutInfoListener(activity, extConsumer); } @@ -162,6 +168,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { final Consumer<WindowLayoutInfo> extConsumer; synchronized (mLock) { extConsumer = mJavaToExtConsumers.remove(consumer); + updateListenerRegistrations(); } if (extConsumer != null) { removeWindowLayoutInfoListener(extConsumer); @@ -192,6 +199,17 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } @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(); @@ -336,25 +354,28 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { 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)); } @@ -407,6 +428,16 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } } + 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; |