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

Commit 5f483e0d authored by Diego Vela's avatar Diego Vela Committed by Android (Google) Code Review
Browse files

Merge changes from topic "b-295785410-report-folding-features-to-letterboxed-apps" into udc-qpr-dev

* changes:
  Add raw configuration change listener updates.
  Report folding features to letterboxed apps.
parents cf7e08e7 1a7c2f29
Loading
Loading
Loading
Loading
+23 −0
Original line number Diff line number Diff line
@@ -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;
    }
+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));
        }

    }
}
+1 −8
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ package androidx.window.extensions;
import android.app.ActivityThread;
import android.app.Application;
import android.content.Context;
import android.window.TaskFragmentOrganizer;

import androidx.annotation.NonNull;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
@@ -81,13 +80,7 @@ public class WindowExtensionsImpl implements WindowExtensions {
                    Context context = getApplication();
                    DeviceStateManagerFoldingFeatureProducer producer =
                            getFoldingFeatureProducer();
                    // TODO(b/263263909) Use the organizer to tell if an Activity is embededed.
                    // Need to improve our Dependency Injection and centralize the logic.
                    TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(command -> {
                        throw new RuntimeException("Not allowed!");
                    });
                    mWindowLayoutComponent = new WindowLayoutComponentImpl(context, organizer,
                            producer);
                    mWindowLayoutComponent = new WindowLayoutComponentImpl(context, producer);
                }
            }
        }
+39 −43
Original line number Diff line number Diff line
@@ -24,7 +24,7 @@ import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;

import android.app.Activity;
import android.app.ActivityClient;
import android.app.ActivityThread;
import android.app.Application;
import android.app.WindowConfiguration;
import android.content.ComponentCallbacks;
@@ -34,8 +34,7 @@ import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
import android.util.ArrayMap;
import android.view.WindowManager;
import android.window.TaskFragmentOrganizer;
import android.util.Log;

import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
@@ -51,7 +50,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
@@ -63,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();

@@ -85,16 +83,15 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
    private final Map<java.util.function.Consumer<WindowLayoutInfo>, Consumer<WindowLayoutInfo>>
            mJavaToExtConsumers = new ArrayMap<>();

    private final TaskFragmentOrganizer mTaskFragmentOrganizer;
    private final RawConfigurationChangedListener mRawConfigurationChangedListener =
            new RawConfigurationChangedListener();

    public WindowLayoutComponentImpl(@NonNull Context context,
            @NonNull TaskFragmentOrganizer taskFragmentOrganizer,
            @NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer) {
        ((Application) context.getApplicationContext())
                .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
        mFoldingFeatureProducer = foldingFeatureProducer;
        mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
        mTaskFragmentOrganizer = taskFragmentOrganizer;
    }

    /** Registers to listen to {@link CommonFoldingFeature} changes */
@@ -117,6 +114,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
        final Consumer<WindowLayoutInfo> extConsumer = consumer::accept;
        synchronized (mLock) {
            mJavaToExtConsumers.put(consumer, extConsumer);
            updateListenerRegistrations();
        }
        addWindowLayoutInfoListener(activity, extConsumer);
    }
@@ -170,6 +168,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
        final Consumer<WindowLayoutInfo> extConsumer;
        synchronized (mLock) {
            extConsumer = mJavaToExtConsumers.remove(consumer);
            updateListenerRegistrations();
        }
        if (extConsumer != null) {
            removeWindowLayoutInfoListener(extConsumer);
@@ -199,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() {
@@ -344,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));
        }
@@ -382,38 +395,11 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
            // Display features are not supported on secondary displays.
            return false;
        }
        final int windowingMode;
        IBinder activityToken = context.getActivityToken();
        if (activityToken != null) {
            final Configuration taskConfig = ActivityClient.getInstance().getTaskConfiguration(
                    activityToken);
            if (taskConfig == null) {
                // If we cannot determine the task configuration for any reason, it is likely that
                // we won't be able to determine its position correctly as well. DisplayFeatures'
                // bounds in this case can't be computed correctly, so we should skip.
                return false;
            }
            final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
            final WindowManager windowManager = Objects.requireNonNull(
                    context.getSystemService(WindowManager.class));
            final Rect maxBounds = windowManager.getMaximumWindowMetrics().getBounds();
            boolean isTaskExpanded = maxBounds.equals(taskBounds);
            /*
             * We need to proxy being in full screen because when a user enters PiP and exits PiP
             * the task windowingMode will report multi-window/pinned until the transition is
             * finished in WM Shell.
             * maxBounds == taskWindowBounds is a proxy check to verify the window is full screen
             */
            return isTaskExpanded;
        } else {
            // TODO(b/242674941): use task windowing mode for window context that associates with
            //  activity.
            windowingMode = context.getResources().getConfiguration().windowConfiguration
                    .getWindowingMode();
        }
        // It is recommended not to report any display features in multi-window mode, since it
        // won't be possible to synchronize the display feature positions with window movement.
        return !WindowConfiguration.inMultiWindowMode(windowingMode);

        // We do not report folding features for Activities in PiP because the bounds are
        // not updated fast enough and the window is too small for the UI to adapt.
        return context.getResources().getConfiguration().windowConfiguration
                .getWindowingMode() != WindowConfiguration.WINDOWING_MODE_PINNED;
    }

    @GuardedBy("mLock")
@@ -442,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;