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

Commit 460aa22e authored by Kenneth Ford's avatar Kenneth Ford Committed by Automerger Merge Worker
Browse files

Merge changes from topic "rear-display-qpr" into tm-qpr-dev am: 51d289b7 am: 525db3b6

parents b40e48cd 525db3b6
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -5872,4 +5872,10 @@

    <!-- The number of tasks to scan to get the visibility of Home -->
    <integer name="config_maxScanTasksForHomeVisibility">10</integer>

    <!-- Device state that corresponds to rear display mode, feature provided
         through Jetpack WindowManager
         TODO(b/236022708) Move rear display state to device state config file
    -->
    <integer name="config_deviceStateRearDisplay">-1</integer>
</resources>
+1 −0
Original line number Diff line number Diff line
@@ -4830,6 +4830,7 @@
  <java-symbol type="drawable" name="ic_swap_horiz" />
  <java-symbol type="array" name="config_deviceStatesAvailableForAppRequests" />
  <java-symbol type="array" name="config_serviceStateLocationAllowedPackages" />
  <java-symbol type="integer" name="config_deviceStateRearDisplay"/>

  <!-- For app language picker -->
  <java-symbol type="string" name="system_locale_title" />
+25 −1
Original line number Diff line number Diff line
@@ -20,11 +20,14 @@ import android.app.ActivityThread;
import android.content.Context;

import androidx.annotation.NonNull;
import androidx.window.extensions.area.WindowAreaComponent;
import androidx.window.extensions.area.WindowAreaComponentImpl;
import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
import androidx.window.extensions.embedding.SplitController;
import androidx.window.extensions.layout.WindowLayoutComponent;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;


/**
 * The reference implementation of {@link WindowExtensions} that implements the initial API version.
 */
@@ -33,10 +36,12 @@ public class WindowExtensionsImpl implements WindowExtensions {
    private final Object mLock = new Object();
    private volatile WindowLayoutComponent mWindowLayoutComponent;
    private volatile SplitController mSplitController;
    private volatile WindowAreaComponent mWindowAreaComponent;

    // TODO(b/241126279) Introduce constants to better version functionality
    @Override
    public int getVendorApiLevel() {
        return 1;
        return 2;
    }

    /**
@@ -75,4 +80,23 @@ public class WindowExtensionsImpl implements WindowExtensions {
        }
        return mSplitController;
    }

    /**
     * Returns a reference implementation of {@link WindowAreaComponent} if available,
     * {@code null} otherwise. The implementation must match the API level reported in
     * {@link WindowExtensions#getWindowAreaComponent()}.
     * @return {@link WindowAreaComponent} OEM implementation.
     */
    public WindowAreaComponent getWindowAreaComponent() {
        if (mWindowAreaComponent == null) {
            synchronized (mLock) {
                if (mWindowAreaComponent == null) {
                    Context context = ActivityThread.currentApplication();
                    mWindowAreaComponent =
                            new WindowAreaComponentImpl(context);
                }
            }
        }
        return mWindowAreaComponent;
    }
}
+255 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 androidx.window.extensions.area;

import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;

import android.app.Activity;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateRequest;
import android.util.ArraySet;

import androidx.annotation.NonNull;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;

import java.util.concurrent.Executor;
import java.util.function.Consumer;

/**
 * Reference implementation of androidx.window.extensions.area OEM interface for use with
 * WindowManager Jetpack.
 *
 * This component currently supports Rear Display mode with the ability to add and remove
 * status listeners for this mode.
 *
 * The public methods in this class are thread-safe.
 **/
public class WindowAreaComponentImpl implements WindowAreaComponent,
        DeviceStateManager.DeviceStateCallback {

    private final Object mLock = new Object();

    private final DeviceStateManager mDeviceStateManager;
    private final Executor mExecutor;

    @GuardedBy("mLock")
    private final ArraySet<Consumer<Integer>> mRearDisplayStatusListeners = new ArraySet<>();
    private final int mRearDisplayState;
    @WindowAreaSessionState
    private int mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE;

    @GuardedBy("mLock")
    private int mCurrentDeviceState = INVALID_DEVICE_STATE;
    @GuardedBy("mLock")
    private int mCurrentDeviceBaseState = INVALID_DEVICE_STATE;
    @GuardedBy("mLock")
    private DeviceStateRequest mDeviceStateRequest;

    public WindowAreaComponentImpl(@NonNull Context context) {
        mDeviceStateManager = context.getSystemService(DeviceStateManager.class);
        mExecutor = context.getMainExecutor();

        // TODO(b/236022708) Move rear display state to device state config file
        mRearDisplayState = context.getResources().getInteger(
                R.integer.config_deviceStateRearDisplay);

        mDeviceStateManager.registerCallback(mExecutor, this);
    }

    /**
     * Adds a listener interested in receiving updates on the RearDisplayStatus
     * of the device. Because this is being called from the OEM provided
     * extensions, we will post the result of the listener on the executor
     * provided by the developer at the initial call site.
     *
     * Depending on the initial state of the device, we will return either
     * {@link WindowAreaComponent#STATUS_AVAILABLE} or
     * {@link WindowAreaComponent#STATUS_UNAVAILABLE} if the feature is supported or not in that
     * state respectively. When the rear display feature is triggered, we update the status to be
     * {@link WindowAreaComponent#STATUS_UNAVAILABLE}. TODO(b/240727590) Prefix with AREA_
     *
     * TODO(b/239833099) Add a STATUS_ACTIVE option to let apps know if a feature is currently
     * enabled.
     *
     * @param consumer {@link Consumer} interested in receiving updates to the status of
     * rear display mode.
     */
    public void addRearDisplayStatusListener(
            @NonNull Consumer<@WindowAreaStatus Integer> consumer) {
        synchronized (mLock) {
            mRearDisplayStatusListeners.add(consumer);

            // If current device state is still invalid, we haven't gotten our initial value yet
            if (mCurrentDeviceState == INVALID_DEVICE_STATE) {
                return;
            }
            consumer.accept(getCurrentStatus());
        }
    }

    /**
     * Removes a listener no longer interested in receiving updates.
     * @param consumer no longer interested in receiving updates to RearDisplayStatus
     */
    public void removeRearDisplayStatusListener(
            @NonNull Consumer<@WindowAreaStatus Integer> consumer) {
        synchronized (mLock) {
            mRearDisplayStatusListeners.remove(consumer);
        }
    }

    /**
     * Creates and starts a rear display session and provides updates to the
     * callback provided. Because this is being called from the OEM provided
     * extensions, we will post the result of the listener on the executor
     * provided by the developer at the initial call site.
     *
     * When we enable rear display mode, we submit a request to {@link DeviceStateManager}
     * to override the device state to the state that corresponds to RearDisplay
     * mode. When the {@link DeviceStateRequest} is activated, we let the
     * consumer know that the session is active by sending
     * {@link WindowAreaComponent#SESSION_STATE_ACTIVE}.
     *
     * @param activity to provide updates to the client on
     * the status of the Session
     * @param rearDisplaySessionCallback to provide updates to the client on
     * the status of the Session
     */
    public void startRearDisplaySession(@NonNull Activity activity,
            @NonNull Consumer<@WindowAreaSessionState Integer> rearDisplaySessionCallback) {
        synchronized (mLock) {
            if (mDeviceStateRequest != null) {
                // Rear display session is already active
                throw new IllegalStateException(
                        "Unable to start new rear display session as one is already active");
            }
            mDeviceStateRequest = DeviceStateRequest.newBuilder(mRearDisplayState).build();
            mDeviceStateManager.requestState(
                    mDeviceStateRequest,
                    mExecutor,
                    new DeviceStateRequestCallbackAdapter(rearDisplaySessionCallback)
            );
        }
    }

    /**
     * Ends the current rear display session and provides updates to the
     * callback provided. Because this is being called from the OEM provided
     * extensions, we will post the result of the listener on the executor
     * provided by the developer.
     */
    public void endRearDisplaySession() {
        synchronized (mLock) {
            if (mDeviceStateRequest != null || isRearDisplayActive()) {
                mDeviceStateRequest = null;
                mDeviceStateManager.cancelStateRequest();
            } else {
                throw new IllegalStateException(
                        "Unable to cancel a rear display session as there is no active session");
            }
        }
    }

    @Override
    public void onBaseStateChanged(int state) {
        synchronized (mLock) {
            mCurrentDeviceBaseState = state;
            if (state == mCurrentDeviceState) {
                updateStatusConsumers(getCurrentStatus());
            }
        }
    }

    @Override
    public void onStateChanged(int state) {
        synchronized (mLock) {
            mCurrentDeviceState = state;
            updateStatusConsumers(getCurrentStatus());
        }
    }

    @GuardedBy("mLock")
    private int getCurrentStatus() {
        if (mRearDisplaySessionStatus == WindowAreaComponent.SESSION_STATE_ACTIVE
                || isRearDisplayActive()) {
            return WindowAreaComponent.STATUS_UNAVAILABLE;
        }
        return WindowAreaComponent.STATUS_AVAILABLE;
    }

    /**
     * Helper method to determine if a rear display session is currently active by checking
     * if the current device configuration matches that of rear display. This would be true
     * if there is a device override currently active (base state != current state) and the current
     * state is that which corresponds to {@code mRearDisplayState}
     * @return {@code true} if the device is in rear display mode and {@code false} if not
     */
    @GuardedBy("mLock")
    private boolean isRearDisplayActive() {
        return (mCurrentDeviceState != mCurrentDeviceBaseState) && (mCurrentDeviceState
                == mRearDisplayState);
    }

    @GuardedBy("mLock")
    private void updateStatusConsumers(@WindowAreaStatus int windowAreaStatus) {
        synchronized (mLock) {
            for (int i = 0; i < mRearDisplayStatusListeners.size(); i++) {
                mRearDisplayStatusListeners.valueAt(i).accept(windowAreaStatus);
            }
        }
    }

    /**
     * Callback for the {@link DeviceStateRequest} to be notified of when the request has been
     * activated or cancelled. This callback provides information to the client library
     * on the status of the RearDisplay session through {@code mRearDisplaySessionCallback}
     */
    private class DeviceStateRequestCallbackAdapter implements DeviceStateRequest.Callback {

        private final Consumer<Integer> mRearDisplaySessionCallback;

        DeviceStateRequestCallbackAdapter(@NonNull Consumer<Integer> callback) {
            mRearDisplaySessionCallback = callback;
        }

        @Override
        public void onRequestActivated(@NonNull DeviceStateRequest request) {
            synchronized (mLock) {
                if (request.equals(mDeviceStateRequest)) {
                    mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_ACTIVE;
                    mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus);
                    updateStatusConsumers(getCurrentStatus());
                }
            }
        }

        @Override
        public void onRequestCanceled(DeviceStateRequest request) {
            synchronized (mLock) {
                if (request.equals(mDeviceStateRequest)) {
                    mDeviceStateRequest = null;
                }
                mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE;
                mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus);
                updateStatusConsumers(getCurrentStatus());
            }
        }
    }
}