From 4ec0db91392892caf49effc9d8d90eb04221fa2f Mon Sep 17 00:00:00 2001 From: Kevin Chyn Date: Fri, 9 Dec 2022 02:22:04 +0000 Subject: Identify and fix potential thread safety issues in SplitController 1) Went through SplitController call paths 2) Some methods require locks and were not annotated. Checked the call paths for these methods, ensured callers had acquired the lock somewhere up the chain, and added GuardedBy to these methods 3) Fixed unsafe area(s): A) onStartActivityResult accesses #getContainer, which iterates through mTaskContainers, which requires a lock. Updated the onStartActivityResult block to use a lock Bug: 242647030 Test: make androidx.window.extensions RUN_ERROR_PRONE=true check warnings Test: Smoke test with Jetpack WindowManager Demos apk Change-Id: I1099cf76f88e49c706b65e394afbb82dd4a1bb99 --- .../extensions/embedding/SplitController.java | 45 ++++++++++++++++------ .../extensions/embedding/SplitPresenter.java | 18 ++++----- .../embedding/TaskFragmentContainer.java | 3 ++ 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 405ee14c2346..1af1313f0bbb 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -108,6 +108,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Currently applied split configuration. @GuardedBy("mLock") private final List mSplitRules = new ArrayList<>(); + /** * A developer-defined {@link SplitAttributes} calculator to compute the current * {@link SplitAttributes} with the current device and window states. @@ -125,6 +126,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") @Nullable private SplitAttributesCalculator mSplitAttributesCalculator; + /** * Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info * below it. @@ -230,6 +232,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @NonNull + @GuardedBy("mLock") + @VisibleForTesting List getSplitRules() { return mSplitRules; } @@ -246,7 +250,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } /** - * Clears the listener set in {@link SplitController#setSplitInfoListener}. + * Clears the listener set in {@link SplitController#setSplitInfoCallback(Consumer)}. */ @Override public void clearSplitInfoCallback() { @@ -466,6 +470,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen updateContainersInTask(wct, taskContainer); } + @GuardedBy("mLock") void updateContainersInTaskIfVisible(@NonNull WindowContainerTransaction wct, int taskId) { final TaskContainer taskContainer = getTaskContainer(taskId); if (taskContainer != null && taskContainer.isVisible()) { @@ -473,6 +478,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } + @GuardedBy("mLock") private void updateContainersInTask(@NonNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer) { // Update all TaskFragments in the Task. Make a copy of the list since some may be @@ -756,6 +762,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Places the given activity to the top most TaskFragment in the task if there is any. */ + @GuardedBy("mLock") @VisibleForTesting void placeActivityInTopContainer(@NonNull WindowContainerTransaction wct, @NonNull Activity activity) { @@ -879,6 +886,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** Finds the activity below the given activity. */ @VisibleForTesting @Nullable + @GuardedBy("mLock") Activity findActivityBelow(@NonNull Activity activity) { Activity activityBelow = null; final TaskFragmentContainer container = getContainerWithActivity(activity); @@ -1213,6 +1221,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return null; } + @GuardedBy("mLock") TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity, int taskId) { return newContainer(pendingAppearedActivity, pendingAppearedActivity, taskId); } @@ -1350,7 +1359,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Removes a secondary container for the given primary container if an existing split is * already registered. */ - void removeExistingSecondaryContainers(@NonNull WindowContainerTransaction wct, + // Suppress GuardedBy warning because lint asks to mark this method as + // @GuardedBy(existingSplitContainer.getSecondaryContainer().mController.mLock), which is mLock + // itself + @SuppressWarnings("GuardedBy") + @GuardedBy("mLock") + private void removeExistingSecondaryContainers(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer primaryContainer) { // If the primary container was already in a split - remove the secondary container that // is now covered by the new one that replaced it. @@ -1368,6 +1382,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Returns the topmost not finished container in Task of given task id. */ + @GuardedBy("mLock") @Nullable TaskFragmentContainer getTopActiveContainer(int taskId) { final TaskContainer taskContainer = mTaskContainers.get(taskId); @@ -1737,6 +1752,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @Nullable + @GuardedBy("mLock") TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) { for (int i = mTaskContainers.size() - 1; i >= 0; i--) { final List containers = mTaskContainers.valueAt(i).mContainers; @@ -1750,6 +1766,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @Nullable + @GuardedBy("mLock") TaskContainer getTaskContainer(int taskId) { return mTaskContainers.get(taskId); } @@ -1758,6 +1775,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return mHandler; } + @GuardedBy("mLock") int getTaskId(@NonNull Activity activity) { // Prefer to get the taskId from TaskFragmentContainer because Activity.getTaskId() is an // IPC call. @@ -1850,6 +1868,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * @see #shouldRetainAssociatedContainer(TaskFragmentContainer, TaskFragmentContainer) */ + @GuardedBy("mLock") boolean shouldRetainAssociatedActivity(@NonNull TaskFragmentContainer finishingContainer, @NonNull Activity associatedActivity) { final TaskFragmentContainer associatedContainer = getContainerWithActivity( @@ -1970,6 +1989,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @VisibleForTesting class ActivityStartMonitor extends Instrumentation.ActivityMonitor { @VisibleForTesting + @GuardedBy("mLock") Intent mCurrentIntent; @Override @@ -2034,18 +2054,21 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Override public void onStartActivityResult(int result, @NonNull Bundle bOptions) { super.onStartActivityResult(result, bOptions); - if (mCurrentIntent != null && result != START_SUCCESS) { - // Clear the pending appeared intent if the activity was not started successfully. - final IBinder token = bOptions.getBinder( - ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN); - if (token != null) { - final TaskFragmentContainer container = getContainer(token); - if (container != null) { - container.clearPendingAppearedIntentIfNeeded(mCurrentIntent); + synchronized (mLock) { + if (mCurrentIntent != null && result != START_SUCCESS) { + // Clear the pending appeared intent if the activity was not started + // successfully. + final IBinder token = bOptions.getBinder( + ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN); + if (token != null) { + final TaskFragmentContainer container = getContainer(token); + if (container != null) { + container.clearPendingAppearedIntentIfNeeded(mCurrentIntent); + } } } + mCurrentIntent = null; } - mCurrentIntent = null; } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index a432e2b10c14..9db9f8788190 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -39,7 +39,6 @@ import android.view.WindowMetrics; import android.window.TaskFragmentCreationParams; import android.window.WindowContainerTransaction; -import androidx.annotation.GuardedBy; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -63,7 +62,10 @@ import java.util.concurrent.Executor; /** * Controls the visual presentation of the splits according to the containers formed by * {@link SplitController}. + * + * Note that all calls into this class must hold the {@link SplitController} internal lock. */ +@SuppressWarnings("GuardedBy") class SplitPresenter extends JetpackTaskFragmentOrganizer { @VisibleForTesting static final int POSITION_START = 0; @@ -163,7 +165,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * @return The newly created secondary container. */ @NonNull - @GuardedBy("mController.mLock") TaskFragmentContainer createNewSplitWithEmptySideContainer( @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Intent secondaryIntent, @NonNull SplitPairRule rule) { @@ -210,7 +211,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * created and the activity will be re-parented to it. * @param rule The split rule to be applied to the container. */ - @GuardedBy("mController.mLock") void createNewSplitContainer(@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity, @NonNull SplitPairRule rule) { @@ -285,7 +285,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * @param rule The split rule to be applied to the container. * @param isPlaceholder Whether the launch is a placeholder. */ - @GuardedBy("mController.mLock") void startActivityToSide(@NonNull WindowContainerTransaction wct, @NonNull Activity launchingActivity, @NonNull Intent activityIntent, @Nullable Bundle activityOptions, @NonNull SplitRule rule, @@ -328,7 +327,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * @param updatedContainer The task fragment that was updated and caused this split update. * @param wct WindowContainerTransaction that this update should be performed with. */ - @GuardedBy("mController.mLock") void updateSplitContainer(@NonNull SplitContainer splitContainer, @NonNull TaskFragmentContainer updatedContainer, @NonNull WindowContainerTransaction wct) { @@ -369,7 +367,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode); } - @GuardedBy("mController.mLock") private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer primaryContainer, @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule, @@ -393,7 +390,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * creation has not been reported from the server yet. */ // TODO(b/190433398): Handle resize if the fragment hasn't appeared yet. - void resizeTaskFragmentIfRegistered(@NonNull WindowContainerTransaction wct, + private void resizeTaskFragmentIfRegistered(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container, @Nullable Rect bounds) { if (container.getInfo() == null) { @@ -520,7 +517,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { return !(splitAttributes.getSplitType() instanceof ExpandContainersSplitType); } - @GuardedBy("mController.mLock") @NonNull SplitAttributes computeSplitAttributes(@NonNull TaskProperties taskProperties, @NonNull SplitRule rule, @Nullable Pair minDimensionsPair) { @@ -572,8 +568,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } @NonNull - static Pair getActivitiesMinDimensionsPair(@NonNull Activity primaryActivity, - @NonNull Activity secondaryActivity) { + private static Pair getActivitiesMinDimensionsPair( + @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) { return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryActivity)); } @@ -619,7 +615,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { return new Size(windowLayout.minWidth, windowLayout.minHeight); } - static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds, + private static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds, @Nullable Size minDimensions) { if (minDimensions == null) { return false; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index 8240874ca53d..6bfdfe7593b8 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -253,6 +253,7 @@ class TaskFragmentContainer { mPendingAppearedActivities.remove(activityToken); } + @GuardedBy("mController.mLock") void clearPendingAppearedActivities() { final List cleanupActivities = new ArrayList<>(mPendingAppearedActivities); // Clear mPendingAppearedActivities so that #getContainerWithActivity won't return the @@ -452,6 +453,7 @@ class TaskFragmentContainer { * Removes all activities that belong to this process and finishes other containers/activities * configured to finish together. */ + @GuardedBy("mController.mLock") void finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, @NonNull WindowContainerTransaction wct, @NonNull SplitController controller) { if (!mIsFinished) { @@ -476,6 +478,7 @@ class TaskFragmentContainer { mInfo = null; } + @GuardedBy("mController.mLock") private void finishActivities(boolean shouldFinishDependent, @NonNull SplitPresenter presenter, @NonNull WindowContainerTransaction wct, @NonNull SplitController controller) { // Finish own activities -- cgit v1.2.3-59-g8ed1b From 6883c9aab4c7f0a9e381246da5734171f8dabbed Mon Sep 17 00:00:00 2001 From: Kevin Chyn Date: Tue, 13 Dec 2022 00:00:19 +0000 Subject: Address thread safety issues in WindowLayoutComponentImpl Check usages of its class variables (and functions). Added a lock object, and use it to protect access to class variables in cases where the calling thread is undefined. Bug: 242647030 Test: make androidx.window.extensions RUN_ERROR_PRONE=true check warnings Test: Smoke test with Jetpack WindowManager Demos apk Change-Id: I5050e67a30e4b839f7954d56857f61d13c712140 --- .../DeviceStateManagerFoldingFeatureProducer.java | 1 + .../layout/WindowLayoutComponentImpl.java | 126 +++++++++++++-------- 2 files changed, 77 insertions(+), 50 deletions(-) diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java index cc2bb63ca8e1..0bdf98c8680f 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java @@ -155,6 +155,7 @@ public final class DeviceStateManagerFoldingFeatureProducer * Adds the data to the storeFeaturesConsumer when the data is ready. * @param storeFeaturesConsumer a consumer to collect the data when it is first available. */ + @Override public void getData(Consumer> storeFeaturesConsumer) { mRawFoldSupplier.getData((String displayFeaturesString) -> { if (TextUtils.isEmpty(displayFeaturesString)) { 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 9b16877e39e7..84b2bfc38559 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -36,6 +36,7 @@ import android.os.Bundle; import android.os.IBinder; import android.util.ArrayMap; +import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiContext; @@ -63,13 +64,19 @@ import java.util.function.Consumer; public class WindowLayoutComponentImpl implements WindowLayoutComponent { private static final String TAG = "SampleExtension"; + private final Object mLock = new Object(); + + @GuardedBy("mLock") private final Map> mWindowLayoutChangeListeners = new ArrayMap<>(); + @GuardedBy("mLock") private final DataProducer> mFoldingFeatureProducer; + @GuardedBy("mLock") private final List mLastReportedFoldingFeatures = new ArrayList<>(); + @GuardedBy("mLock") private final Map mConfigurationChangeListeners = new ArrayMap<>(); @@ -84,7 +91,9 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { /** Registers to listen to {@link CommonFoldingFeature} changes */ public void addFoldingStateChangedCallback(Consumer> consumer) { - mFoldingFeatureProducer.addDataChangedCallback(consumer); + synchronized (mLock) { + mFoldingFeatureProducer.addDataChangedCallback(consumer); + } } /** @@ -113,29 +122,32 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { @Override public void addWindowLayoutInfoListener(@NonNull @UiContext Context context, @NonNull Consumer consumer) { - if (mWindowLayoutChangeListeners.containsKey(context) - // In theory this method can be called on the same consumer with different context. - || mWindowLayoutChangeListeners.containsValue(consumer)) { - return; - } - if (!context.isUiContext()) { - throw new IllegalArgumentException("Context must be a UI Context, which should be" - + " an Activity, WindowContext or InputMethodService"); - } - mFoldingFeatureProducer.getData((features) -> { - WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, features); - consumer.accept(newWindowLayout); - }); - mWindowLayoutChangeListeners.put(context, consumer); - - final IBinder windowContextToken = context.getWindowContextToken(); - if (windowContextToken != null) { - // We register component callbacks for window contexts. For activity contexts, they will - // receive callbacks from NotifyOnConfigurationChanged instead. - final ConfigurationChangeListener listener = - new ConfigurationChangeListener(windowContextToken); - context.registerComponentCallbacks(listener); - mConfigurationChangeListeners.put(windowContextToken, listener); + synchronized (mLock) { + if (mWindowLayoutChangeListeners.containsKey(context) + // In theory this method can be called on the same consumer with different + // context. + || mWindowLayoutChangeListeners.containsValue(consumer)) { + return; + } + if (!context.isUiContext()) { + throw new IllegalArgumentException("Context must be a UI Context, which should be" + + " an Activity, WindowContext or InputMethodService"); + } + mFoldingFeatureProducer.getData((features) -> { + WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, features); + consumer.accept(newWindowLayout); + }); + mWindowLayoutChangeListeners.put(context, consumer); + + final IBinder windowContextToken = context.getWindowContextToken(); + if (windowContextToken != null) { + // We register component callbacks for window contexts. For activity contexts, they + // will receive callbacks from NotifyOnConfigurationChanged instead. + final ConfigurationChangeListener listener = + new ConfigurationChangeListener(windowContextToken); + context.registerComponentCallbacks(listener); + mConfigurationChangeListeners.put(windowContextToken, listener); + } } } @@ -146,25 +158,29 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { */ @Override public void removeWindowLayoutInfoListener(@NonNull Consumer consumer) { - for (Context context : mWindowLayoutChangeListeners.keySet()) { - if (!mWindowLayoutChangeListeners.get(context).equals(consumer)) { - continue; + synchronized (mLock) { + for (Context context : mWindowLayoutChangeListeners.keySet()) { + if (!mWindowLayoutChangeListeners.get(context).equals(consumer)) { + continue; + } + final IBinder token = context.getWindowContextToken(); + if (token != null) { + context.unregisterComponentCallbacks(mConfigurationChangeListeners.get(token)); + mConfigurationChangeListeners.remove(token); + } + break; } - final IBinder token = context.getWindowContextToken(); - if (token != null) { - context.unregisterComponentCallbacks(mConfigurationChangeListeners.get(token)); - mConfigurationChangeListeners.remove(token); - } - break; + mWindowLayoutChangeListeners.values().remove(consumer); } - mWindowLayoutChangeListeners.values().remove(consumer); } + @GuardedBy("mLock") @NonNull - Set getContextsListeningForLayoutChanges() { + private Set getContextsListeningForLayoutChanges() { return mWindowLayoutChangeListeners.keySet(); } + @GuardedBy("mLock") private boolean isListeningForLayoutChanges(IBinder token) { for (Context context: getContextsListeningForLayoutChanges()) { if (token.equals(Context.getToken(context))) { @@ -174,10 +190,6 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { return false; } - protected boolean hasListeners() { - return !mWindowLayoutChangeListeners.isEmpty(); - } - /** * A convenience method to translate from the common feature state to the extensions feature * state. More specifically, translates from {@link CommonFoldingFeature.State} to @@ -201,13 +213,17 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } private void onDisplayFeaturesChanged(List storedFeatures) { - mLastReportedFoldingFeatures.clear(); - mLastReportedFoldingFeatures.addAll(storedFeatures); - for (Context context : getContextsListeningForLayoutChanges()) { - // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer. - Consumer layoutConsumer = mWindowLayoutChangeListeners.get(context); - WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, storedFeatures); - layoutConsumer.accept(newWindowLayout); + synchronized (mLock) { + mLastReportedFoldingFeatures.clear(); + mLastReportedFoldingFeatures.addAll(storedFeatures); + for (Context context : getContextsListeningForLayoutChanges()) { + // Get the WindowLayoutInfo from the activity and pass the value to the + // layoutConsumer. + Consumer layoutConsumer = mWindowLayoutChangeListeners.get( + context); + WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, storedFeatures); + layoutConsumer.accept(newWindowLayout); + } } } @@ -232,7 +248,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { @NonNull public WindowLayoutInfo getCurrentWindowLayoutInfo(int displayId, @NonNull WindowConfiguration windowConfiguration) { - return getWindowLayoutInfo(displayId, windowConfiguration, mLastReportedFoldingFeatures); + synchronized (mLock) { + return getWindowLayoutInfo(displayId, windowConfiguration, + mLastReportedFoldingFeatures); + } } /** @see #getWindowLayoutInfo(Context, List) */ @@ -330,6 +349,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { return !WindowConfiguration.inMultiWindowMode(windowingMode); } + @GuardedBy("mLock") private void onDisplayFeaturesChangedIfListening(@NonNull IBinder token) { if (isListeningForLayoutChanges(token)) { mFoldingFeatureProducer.getData( @@ -341,13 +361,17 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { super.onActivityCreated(activity, savedInstanceState); - onDisplayFeaturesChangedIfListening(activity.getActivityToken()); + synchronized (mLock) { + onDisplayFeaturesChangedIfListening(activity.getActivityToken()); + } } @Override public void onActivityConfigurationChanged(Activity activity) { super.onActivityConfigurationChanged(activity); - onDisplayFeaturesChangedIfListening(activity.getActivityToken()); + synchronized (mLock) { + onDisplayFeaturesChangedIfListening(activity.getActivityToken()); + } } } @@ -360,7 +384,9 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { @Override public void onConfigurationChanged(@NonNull Configuration newConfig) { - onDisplayFeaturesChangedIfListening(mToken); + synchronized (mLock) { + onDisplayFeaturesChangedIfListening(mToken); + } } @Override -- cgit v1.2.3-59-g8ed1b