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

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

Merge changes from topic "wm-config-change-fix" into sc-v2-dev

* changes:
  DO NOT MERGE Listen for config changes when updating features.
  DO NOT MERGE Add LifecycleCallback adapter.
  DO NOT MERGE Merge state producer and feature producer.
  DO NOT MERGE Remove DisplayFeature from common package.
parents 037cc9fb 6c39d4fb
Loading
Loading
Loading
Loading
+105 −14
Original line number Diff line number Diff line
@@ -18,17 +18,73 @@ package androidx.window.common;

import static androidx.window.util.ExtensionHelper.isZero;

import android.annotation.IntDef;
import android.annotation.Nullable;
import android.graphics.Rect;
import android.util.Log;

import androidx.annotation.NonNull;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/** Wrapper for both Extension and Sidecar versions of DisplayFeature. */
final class CommonDisplayFeature implements DisplayFeature {
/** A representation of a folding feature for both Extension and Sidecar.
 * For Sidecar this is the same as combining {@link androidx.window.sidecar.SidecarDeviceState} and
 * {@link androidx.window.sidecar.SidecarDisplayFeature}. For Extensions this is the mirror of
 * {@link androidx.window.extensions.layout.FoldingFeature}.
 */
public final class CommonFoldingFeature {

    private static final boolean DEBUG = false;

    public static final String TAG = CommonFoldingFeature.class.getSimpleName();

    /**
     * A common type to represent a hinge where the screen is continuous.
     */
    public static final int COMMON_TYPE_FOLD = 1;

    /**
     * A common type to represent a hinge where there is a physical gap separating multiple
     * displays.
     */
    public static final int COMMON_TYPE_HINGE = 2;

    @IntDef({COMMON_TYPE_FOLD, COMMON_TYPE_HINGE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Type {
    }

    /**
     * A common state to represent when the state is not known. One example is if the device is
     * closed. We do not emit this value for developers but is useful for implementation reasons.
     */
    public static final int COMMON_STATE_UNKNOWN = -1;

    /**
     * A common state to represent a FLAT hinge. This is needed because the definitions in Sidecar
     * and Extensions do not match exactly.
     */
    public static final int COMMON_STATE_FLAT = 3;
    /**
     * A common state to represent a HALF_OPENED hinge. This is needed because the definitions in
     * Sidecar and Extensions do not match exactly.
     */
    public static final int COMMON_STATE_HALF_OPENED = 2;

    /**
     * The possible states for a folding hinge.
     */
    @IntDef({COMMON_STATE_FLAT, COMMON_STATE_HALF_OPENED})
    @Retention(RetentionPolicy.SOURCE)
    public @interface State {
    }

    private static final Pattern FEATURE_PATTERN =
            Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]-?(flat|half-opened)?");

@@ -38,17 +94,49 @@ final class CommonDisplayFeature implements DisplayFeature {
    private static final String PATTERN_STATE_FLAT = "flat";
    private static final String PATTERN_STATE_HALF_OPENED = "half-opened";

    // TODO(b/183049815): Support feature strings that include the state of the feature.
    /**
     * Parse a {@link List} of {@link CommonFoldingFeature} from a {@link String}.
     * @param value a {@link String} representation of multiple {@link CommonFoldingFeature}
     *              separated by a ":".
     * @param hingeState a global fallback value for a {@link CommonFoldingFeature} if one is not
     *                   specified in the input.
     * @throws IllegalArgumentException if the provided string is improperly formatted or could not
     * otherwise be parsed.
     * @see #FEATURE_PATTERN
     * @return {@link List} of {@link CommonFoldingFeature}.
     */
    static List<CommonFoldingFeature> parseListFromString(@NonNull String value,
            @State int hingeState) {
        List<CommonFoldingFeature> features = new ArrayList<>();
        String[] featureStrings =  value.split(";");
        for (String featureString : featureStrings) {
            CommonFoldingFeature feature;
            try {
                feature = CommonFoldingFeature.parseFromString(featureString, hingeState);
            } catch (IllegalArgumentException e) {
                if (DEBUG) {
                    Log.w(TAG, "Failed to parse display feature: " + featureString, e);
                }
                continue;
            }
            features.add(feature);
        }
        return features;
    }

    /**
     * Parses a display feature from a string.
     *
     * @param string A {@link String} representation of a {@link CommonFoldingFeature}.
     * @param hingeState A fallback value for the {@link State} if it is not specified in the input.
     * @throws IllegalArgumentException if the provided string is improperly formatted or could not
     *                                  otherwise be parsed.
     * @return {@link CommonFoldingFeature} represented by the {@link String} value.
     * @see #FEATURE_PATTERN
     */
    @NonNull
    static CommonDisplayFeature parseFromString(@NonNull String string) {
    private static CommonFoldingFeature parseFromString(@NonNull String string,
            @State int hingeState) {
        Matcher featureMatcher = FEATURE_PATTERN.matcher(string);
        if (!featureMatcher.matches()) {
            throw new IllegalArgumentException("Malformed feature description format: " + string);
@@ -59,10 +147,10 @@ final class CommonDisplayFeature implements DisplayFeature {
            int type;
            switch (featureType) {
                case FEATURE_TYPE_FOLD:
                    type = 1 /* TYPE_FOLD */;
                    type = COMMON_TYPE_FOLD;
                    break;
                case FEATURE_TYPE_HINGE:
                    type = 2 /* TYPE_HINGE */;
                    type = COMMON_TYPE_HINGE;
                    break;
                default: {
                    throw new IllegalArgumentException("Malformed feature type: " + featureType);
@@ -79,7 +167,7 @@ final class CommonDisplayFeature implements DisplayFeature {
            }
            String stateString = featureMatcher.group(6);
            stateString = stateString == null ? "" : stateString;
            Integer state;
            final int state;
            switch (stateString) {
                case PATTERN_STATE_FLAT:
                    state = COMMON_STATE_FLAT;
@@ -88,10 +176,10 @@ final class CommonDisplayFeature implements DisplayFeature {
                    state = COMMON_STATE_HALF_OPENED;
                    break;
                default:
                    state = null;
                    state = hingeState;
                    break;
            }
            return new CommonDisplayFeature(type, state, featureRect);
            return new CommonFoldingFeature(type, state, featureRect);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Malformed feature description: " + string, e);
        }
@@ -99,11 +187,11 @@ final class CommonDisplayFeature implements DisplayFeature {

    private final int mType;
    @Nullable
    private final Integer mState;
    private final int mState;
    @NonNull
    private final Rect mRect;

    CommonDisplayFeature(int type, @Nullable Integer state, @NonNull Rect rect) {
    CommonFoldingFeature(int type, int state, @NonNull Rect rect) {
        assertValidState(state);
        this.mType = type;
        this.mState = state;
@@ -114,16 +202,19 @@ final class CommonDisplayFeature implements DisplayFeature {
        this.mRect = rect;
    }

    /** Returns the type of the feature. */
    @Type
    public int getType() {
        return mType;
    }

    /** Returns the state of the feature, or {@code null} if the feature has no state. */
    @Nullable
    public Integer getState() {
    @State
    public int getState() {
        return mState;
    }

    /** Returns the bounds of the feature. */
    @NonNull
    public Rect getRect() {
        return mRect;
@@ -133,7 +224,7 @@ final class CommonDisplayFeature implements DisplayFeature {
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        CommonDisplayFeature that = (CommonDisplayFeature) o;
        CommonFoldingFeature that = (CommonFoldingFeature) o;
        return mType == that.mType
                && Objects.equals(mState, that.mState)
                && mRect.equals(that.mRect);
+24 −6
Original line number Diff line number Diff line
@@ -18,11 +18,15 @@ package androidx.window.common;

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

import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
import static androidx.window.common.CommonFoldingFeature.parseListFromString;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseIntArray;

@@ -30,6 +34,7 @@ import androidx.window.util.BaseDataProducer;

import com.android.internal.R;

import java.util.List;
import java.util.Optional;

/**
@@ -37,10 +42,13 @@ import java.util.Optional;
 * by mapping the state returned from {@link DeviceStateManager} to values provided in the resources
 * config at {@link R.array#config_device_state_postures}.
 */
public final class DeviceStateManagerPostureProducer extends BaseDataProducer<Integer> {
    private static final String TAG = "ConfigDevicePostureProducer";
public final class DeviceStateManagerFoldingFeatureProducer extends
        BaseDataProducer<List<CommonFoldingFeature>> {
    private static final String TAG =
            DeviceStateManagerFoldingFeatureProducer.class.getSimpleName();
    private static final boolean DEBUG = false;

    private final Context mContext;
    private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();

    private int mCurrentDeviceState = INVALID_DEVICE_STATE;
@@ -50,7 +58,8 @@ public final class DeviceStateManagerPostureProducer extends BaseDataProducer<In
        notifyDataChanged();
    };

    public DeviceStateManagerPostureProducer(@NonNull Context context) {
    public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context) {
        mContext = context;
        String[] deviceStatePosturePairs = context.getResources()
                .getStringArray(R.array.config_device_state_postures);
        for (String deviceStatePosturePair : deviceStatePosturePairs) {
@@ -86,8 +95,17 @@ public final class DeviceStateManagerPostureProducer extends BaseDataProducer<In

    @Override
    @Nullable
    public Optional<Integer> getData() {
        final int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState, -1);
        return posture != -1 ? Optional.of(posture) : Optional.empty();
    public Optional<List<CommonFoldingFeature>> getData() {
        final int globalHingeState = globalHingeState();
        String displayFeaturesString = mContext.getResources().getString(
                R.string.config_display_features);
        if (TextUtils.isEmpty(displayFeaturesString)) {
            return Optional.empty();
        }
        return Optional.of(parseListFromString(displayFeaturesString, globalHingeState));
    }

    private int globalHingeState() {
        return mDeviceStateToPostureMap.get(mCurrentDeviceState, COMMON_STATE_UNKNOWN);
    }
}
+55 −0
Original line number Diff line number Diff line
@@ -16,45 +16,40 @@

package androidx.window.common;

import android.annotation.IntDef;
import android.annotation.Nullable;
import android.graphics.Rect;
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;

import androidx.annotation.NonNull;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
 * An empty implementation of {@link Application.ActivityLifecycleCallbacks} derived classes can
 * implement the methods necessary.
 */
public class EmptyLifecycleCallbacksAdapter implements Application.ActivityLifecycleCallbacks {
    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    }

/** Wrapper for both Extension and Sidecar versions of DisplayFeature. */
public interface DisplayFeature {
    /** Returns the type of the feature. */
    int getType();
    @Override
    public void onActivityStarted(Activity activity) {
    }

    /** Returns the state of the feature, or {@code null} if the feature has no state. */
    @Nullable
    @State
    Integer getState();
    @Override
    public void onActivityResumed(Activity activity) {
    }

    /** Returns the bounds of the feature. */
    @NonNull
    Rect getRect();
    @Override
    public void onActivityPaused(Activity activity) {
    }

    /**
     * A common state to represent a FLAT hinge. This is needed because the definitions in Sidecar
     * and Extensions do not match exactly.
     */
    int COMMON_STATE_FLAT = 3;
    /**
     * A common state to represent a HALF_OPENED hinge. This is needed because the definitions in
     * Sidecar and Extensions do not match exactly.
     */
    int COMMON_STATE_HALF_OPENED = 2;
    @Override
    public void onActivityStopped(Activity activity) {
    }

    /**
     * The possible states for a folding hinge.
     */
    @IntDef({COMMON_STATE_FLAT, COMMON_STATE_HALF_OPENED})
    @Retention(RetentionPolicy.SOURCE)
    @interface State {}
    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
    }

    @Override
    public void onActivityDestroyed(Activity activity) {
    }
}
+0 −74
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.common;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;

import androidx.window.util.BaseDataProducer;

import com.android.internal.R;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * Implementation of {@link androidx.window.util.DataProducer} that produces
 * {@link CommonDisplayFeature} parsed from a string stored in the resources config at
 * {@link R.string#config_display_features}.
 */
public final class ResourceConfigDisplayFeatureProducer extends
        BaseDataProducer<List<DisplayFeature>> {
    private static final boolean DEBUG = false;
    private static final String TAG = "ResourceConfigDisplayFeatureProducer";

    private final Context mContext;

    public ResourceConfigDisplayFeatureProducer(@NonNull Context context) {
        mContext = context;
    }

    @Override
    @Nullable
    public Optional<List<DisplayFeature>> getData() {
        String displayFeaturesString = mContext.getResources().getString(
                R.string.config_display_features);
        if (TextUtils.isEmpty(displayFeaturesString)) {
            return Optional.empty();
        }

        List<DisplayFeature> features = new ArrayList<>();
        String[] featureStrings =  displayFeaturesString.split(";");
        for (String featureString : featureStrings) {
            CommonDisplayFeature feature;
            try {
                feature = CommonDisplayFeature.parseFromString(featureString);
            } catch (IllegalArgumentException e) {
                if (DEBUG) {
                    Log.w(TAG, "Failed to parse display feature: " + featureString, e);
                }
                continue;
            }
            features.add(feature);
        }
        return Optional.of(features);
    }
}
+0 −96
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.common;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;

import androidx.window.util.BaseDataProducer;

import java.util.Optional;

/**
 * Implementation of {@link androidx.window.util.DataProducer} that provides the device posture
 * as an {@link Integer} from a value stored in {@link Settings}.
 */
public final class SettingsDevicePostureProducer extends BaseDataProducer<Integer> {
    private static final String DEVICE_POSTURE = "device_posture";

    private final Uri mDevicePostureUri =
            Settings.Global.getUriFor(DEVICE_POSTURE);

    private final ContentResolver mResolver;
    private final ContentObserver mObserver;
    private boolean mRegisteredObservers;

    public SettingsDevicePostureProducer(@NonNull Context context) {
        mResolver = context.getContentResolver();
        mObserver = new SettingsObserver();
    }

    @Override
    @Nullable
    public Optional<Integer> getData() {
        int posture = Settings.Global.getInt(mResolver, DEVICE_POSTURE, -1);
        return posture == -1 ? Optional.empty() : Optional.of(posture);
    }

    /**
     * Registers settings observers, if needed. When settings observers are registered for this
     * producer callbacks for changes in data will be triggered.
     */
    public void registerObserversIfNeeded() {
        if (mRegisteredObservers) {
            return;
        }
        mRegisteredObservers = true;
        mResolver.registerContentObserver(mDevicePostureUri, false /* notifyForDescendants */,
                mObserver /* ContentObserver */);
    }

    /**
     * Unregisters settings observers, if needed. When settings observers are unregistered for this
     * producer callbacks for changes in data will not be triggered.
     */
    public void unregisterObserversIfNeeded() {
        if (!mRegisteredObservers) {
            return;
        }
        mRegisteredObservers = false;
        mResolver.unregisterContentObserver(mObserver);
    }

    private final class SettingsObserver extends ContentObserver {
        SettingsObserver() {
            super(new Handler(Looper.getMainLooper()));
        }

        @Override
        public void onChange(boolean selfChange, Uri uri) {
            if (mDevicePostureUri.equals(uri)) {
                notifyDataChanged();
            }
        }
    }
}
Loading