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

Commit 34ef47d2 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-plus-aosp

* changes:
  [automerge] DO NOT MERGE Listen for config changes when updating features. 2p: 6c39d4fb
  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 586488e7 09d12332
Loading
Loading
Loading
Loading
+105 −14
Original line number Original line Diff line number Diff line
@@ -18,17 +18,73 @@ package androidx.window.common;


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


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


import androidx.annotation.NonNull;
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.Objects;
import java.util.regex.Matcher;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.Pattern;


/** Wrapper for both Extension and Sidecar versions of DisplayFeature. */
/** A representation of a folding feature for both Extension and Sidecar.
final class CommonDisplayFeature implements DisplayFeature {
 * 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 =
    private static final Pattern FEATURE_PATTERN =
            Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]-?(flat|half-opened)?");
            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_FLAT = "flat";
    private static final String PATTERN_STATE_HALF_OPENED = "half-opened";
    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.
     * 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
     * @throws IllegalArgumentException if the provided string is improperly formatted or could not
     *                                  otherwise be parsed.
     *                                  otherwise be parsed.
     * @return {@link CommonFoldingFeature} represented by the {@link String} value.
     * @see #FEATURE_PATTERN
     * @see #FEATURE_PATTERN
     */
     */
    @NonNull
    @NonNull
    static CommonDisplayFeature parseFromString(@NonNull String string) {
    private static CommonFoldingFeature parseFromString(@NonNull String string,
            @State int hingeState) {
        Matcher featureMatcher = FEATURE_PATTERN.matcher(string);
        Matcher featureMatcher = FEATURE_PATTERN.matcher(string);
        if (!featureMatcher.matches()) {
        if (!featureMatcher.matches()) {
            throw new IllegalArgumentException("Malformed feature description format: " + string);
            throw new IllegalArgumentException("Malformed feature description format: " + string);
@@ -59,10 +147,10 @@ final class CommonDisplayFeature implements DisplayFeature {
            int type;
            int type;
            switch (featureType) {
            switch (featureType) {
                case FEATURE_TYPE_FOLD:
                case FEATURE_TYPE_FOLD:
                    type = 1 /* TYPE_FOLD */;
                    type = COMMON_TYPE_FOLD;
                    break;
                    break;
                case FEATURE_TYPE_HINGE:
                case FEATURE_TYPE_HINGE:
                    type = 2 /* TYPE_HINGE */;
                    type = COMMON_TYPE_HINGE;
                    break;
                    break;
                default: {
                default: {
                    throw new IllegalArgumentException("Malformed feature type: " + featureType);
                    throw new IllegalArgumentException("Malformed feature type: " + featureType);
@@ -79,7 +167,7 @@ final class CommonDisplayFeature implements DisplayFeature {
            }
            }
            String stateString = featureMatcher.group(6);
            String stateString = featureMatcher.group(6);
            stateString = stateString == null ? "" : stateString;
            stateString = stateString == null ? "" : stateString;
            Integer state;
            final int state;
            switch (stateString) {
            switch (stateString) {
                case PATTERN_STATE_FLAT:
                case PATTERN_STATE_FLAT:
                    state = COMMON_STATE_FLAT;
                    state = COMMON_STATE_FLAT;
@@ -88,10 +176,10 @@ final class CommonDisplayFeature implements DisplayFeature {
                    state = COMMON_STATE_HALF_OPENED;
                    state = COMMON_STATE_HALF_OPENED;
                    break;
                    break;
                default:
                default:
                    state = null;
                    state = hingeState;
                    break;
                    break;
            }
            }
            return new CommonDisplayFeature(type, state, featureRect);
            return new CommonFoldingFeature(type, state, featureRect);
        } catch (NumberFormatException e) {
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Malformed feature description: " + string, e);
            throw new IllegalArgumentException("Malformed feature description: " + string, e);
        }
        }
@@ -99,11 +187,11 @@ final class CommonDisplayFeature implements DisplayFeature {


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


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


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


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


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


import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
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.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
import android.text.TextUtils;
import android.util.Log;
import android.util.Log;
import android.util.SparseIntArray;
import android.util.SparseIntArray;


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


import com.android.internal.R;
import com.android.internal.R;


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


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


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


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


    @Override
    @Override
    @Nullable
    @Nullable
    public Optional<Integer> getData() {
    public Optional<List<CommonFoldingFeature>> getData() {
        final int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState, -1);
        final int globalHingeState = globalHingeState();
        return posture != -1 ? Optional.of(posture) : Optional.empty();
        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 Original line Diff line number Diff line
@@ -16,45 +16,40 @@


package androidx.window.common;
package androidx.window.common;


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


import androidx.annotation.NonNull;
/**

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


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


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


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


    /**
    @Override
     * A common state to represent a FLAT hinge. This is needed because the definitions in Sidecar
    public void onActivityStopped(Activity activity) {
     * 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
     * The possible states for a folding hinge.
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
     */
    }
    @IntDef({COMMON_STATE_FLAT, COMMON_STATE_HALF_OPENED})
    @Retention(RetentionPolicy.SOURCE)
    @interface State {}


    @Override
    public void onActivityDestroyed(Activity activity) {
    }
}
}
+0 −74
Original line number Original line 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 Original line 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