Loading core/java/android/app/ActivityThread.java +23 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -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 = Loading Loading @@ -6094,6 +6115,8 @@ public final class ActivityThread extends ClientTransactionHandler " did not call through to super.onConfigurationChanged()"); } } mConfigurationChangedListenerController .dispatchOnConfigurationChanged(activity.getActivityToken()); return configToReport; } Loading core/java/android/app/ConfigurationChangedListenerController.java 0 → 100644 +116 −0 Original line number Diff line number Diff line /* * 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)); } } } libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +35 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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(); Loading @@ -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()) Loading Loading @@ -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); } Loading Loading @@ -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); Loading Loading @@ -191,6 +198,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() { Loading Loading @@ -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)); } Loading Loading @@ -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; Loading Loading
core/java/android/app/ActivityThread.java +23 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -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 = Loading Loading @@ -6094,6 +6115,8 @@ public final class ActivityThread extends ClientTransactionHandler " did not call through to super.onConfigurationChanged()"); } } mConfigurationChangedListenerController .dispatchOnConfigurationChanged(activity.getActivityToken()); return configToReport; } Loading
core/java/android/app/ConfigurationChangedListenerController.java 0 → 100644 +116 −0 Original line number Diff line number Diff line /* * 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)); } } }
libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +35 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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(); Loading @@ -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()) Loading Loading @@ -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); } Loading Loading @@ -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); Loading Loading @@ -191,6 +198,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() { Loading Loading @@ -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)); } Loading Loading @@ -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; Loading