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

Commit 51d289b7 authored by Kenneth Ford's avatar Kenneth Ford Committed by Android (Google) Code Review
Browse files

Merge changes from topic "rear-display-qpr" into tm-qpr-dev

* changes:
  Increments Extensions API version for Rear display API's
  Adds support for enabling and disabling rear display mode.
  Adds extensions implementation for WindowAreaComponent and rear display status listeners
parents ec576c7a 22740a10
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -5899,4 +5899,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
@@ -4822,6 +4822,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());
            }
        }
    }
}