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

Commit 571d4a6d authored by Kenneth Ford's avatar Kenneth Ford
Browse files

Adds extensions implementation for WindowAreaComponent and rear display status listeners

This cl includes the functionality of adding/removing
listeners for Rear Display status.

Bug: 207686851
Test: ExtensionWindowAreaComponentTest
Change-Id: I2d4d519a5e4933cd2b1319f2da99845f647006eb
parent 83d28ff7
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -5884,4 +5884,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
@@ -4812,6 +4812,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" />
+23 −0
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,6 +36,7 @@ public class WindowExtensionsImpl implements WindowExtensions {
    private final Object mLock = new Object();
    private volatile WindowLayoutComponent mWindowLayoutComponent;
    private volatile SplitController mSplitController;
    private volatile WindowAreaComponent mWindowAreaComponent;

    @Override
    public int getVendorApiLevel() {
@@ -75,4 +79,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;
    }
}
+189 −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.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;

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

    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 RearDisplay feature is triggered, we update the status to be
     * {@link WindowAreaComponent#STATUS_UNAVAILABLE}.
     *
     * 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 RearDisplaySession 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) {
        // Todo("Adding in chained cl")
    }

    /**
     * Ends the current RearDisplaySession 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() {
        // Todo("Adding in chained cl")
    }

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

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

    @GuardedBy("mLock")
    private int getCurrentStatus() {
        if (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);
            }
        }
    }
}