diff options
3 files changed, 174 insertions, 4 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 1571fddb012f..f4caef0f2553 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 @@ -370,6 +371,11 @@ public final class ActivityThread extends ClientTransactionHandler @GuardedBy("mAppThread") private int mLastProcessState = PROCESS_STATE_UNKNOWN; 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; @@ -3540,6 +3546,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 = @@ -6098,6 +6119,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 6130fa456476..becd0020a157 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.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; @@ -34,6 +35,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; @@ -60,7 +62,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(); @@ -82,6 +84,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()) @@ -110,6 +115,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { final Consumer<WindowLayoutInfo> extConsumer = consumer::accept; synchronized (mLock) { mJavaToExtConsumers.put(consumer, extConsumer); + updateListenerRegistrations(); } addWindowLayoutInfoListener(activity, extConsumer); } @@ -163,6 +169,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { final Consumer<WindowLayoutInfo> extConsumer; synchronized (mLock) { extConsumer = mJavaToExtConsumers.remove(consumer); + updateListenerRegistrations(); } if (extConsumer != null) { removeWindowLayoutInfoListener(extConsumer); @@ -193,6 +200,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(); @@ -337,25 +355,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)); } @@ -408,6 +429,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; |