Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6883c9aa authored by Kevin Chyn's avatar Kevin Chyn
Browse files

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
parent 4ec0db91
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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<List<CommonFoldingFeature>> storeFeaturesConsumer) {
        mRawFoldSupplier.getData((String displayFeaturesString) -> {
            if (TextUtils.isEmpty(displayFeaturesString)) {
+76 −50
Original line number Diff line number Diff line
@@ -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<Context, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners =
            new ArrayMap<>();

    @GuardedBy("mLock")
    private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;

    @GuardedBy("mLock")
    private final List<CommonFoldingFeature> mLastReportedFoldingFeatures = new ArrayList<>();

    @GuardedBy("mLock")
    private final Map<IBinder, ConfigurationChangeListener> mConfigurationChangeListeners =
            new ArrayMap<>();

@@ -84,8 +91,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {

    /** Registers to listen to {@link CommonFoldingFeature} changes */
    public void addFoldingStateChangedCallback(Consumer<List<CommonFoldingFeature>> consumer) {
        synchronized (mLock) {
            mFoldingFeatureProducer.addDataChangedCallback(consumer);
        }
    }

    /**
     * Adds a listener interested in receiving updates to {@link WindowLayoutInfo}
@@ -113,8 +122,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
    @Override
    public void addWindowLayoutInfoListener(@NonNull @UiContext Context context,
            @NonNull Consumer<WindowLayoutInfo> consumer) {
        synchronized (mLock) {
            if (mWindowLayoutChangeListeners.containsKey(context)
                // In theory this method can be called on the same consumer with different context.
                    // In theory this method can be called on the same consumer with different
                    // context.
                    || mWindowLayoutChangeListeners.containsValue(consumer)) {
                return;
            }
@@ -130,14 +141,15 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {

            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.
                // 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);
            }
        }
    }

    /**
     * Removes a listener no longer interested in receiving updates.
@@ -146,6 +158,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
     */
    @Override
    public void removeWindowLayoutInfoListener(@NonNull Consumer<WindowLayoutInfo> consumer) {
        synchronized (mLock) {
            for (Context context : mWindowLayoutChangeListeners.keySet()) {
                if (!mWindowLayoutChangeListeners.get(context).equals(consumer)) {
                    continue;
@@ -159,12 +172,15 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
            }
            mWindowLayoutChangeListeners.values().remove(consumer);
        }
    }

    @GuardedBy("mLock")
    @NonNull
    Set<Context> getContextsListeningForLayoutChanges() {
    private Set<Context> 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,15 +213,19 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
    }

    private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) {
        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<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(context);
                // Get the WindowLayoutInfo from the activity and pass the value to the
                // layoutConsumer.
                Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(
                        context);
                WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, storedFeatures);
                layoutConsumer.accept(newWindowLayout);
            }
        }
    }

    /**
     * Translates the {@link DisplayFeature} into a {@link WindowLayoutInfo} when a
@@ -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,15 +361,19 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
        @Override
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            super.onActivityCreated(activity, savedInstanceState);
            synchronized (mLock) {
                onDisplayFeaturesChangedIfListening(activity.getActivityToken());
            }
        }

        @Override
        public void onActivityConfigurationChanged(Activity activity) {
            super.onActivityConfigurationChanged(activity);
            synchronized (mLock) {
                onDisplayFeaturesChangedIfListening(activity.getActivityToken());
            }
        }
    }

    private final class ConfigurationChangeListener implements ComponentCallbacks {
        final IBinder mToken;
@@ -360,8 +384,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {

        @Override
        public void onConfigurationChanged(@NonNull Configuration newConfig) {
            synchronized (mLock) {
                onDisplayFeaturesChangedIfListening(mToken);
            }
        }

        @Override
        public void onLowMemory() {}