[automerge] DO NOT MERGE Merge state producer and feature producer. 2p: e5c6ad5d79

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/16376128

Bug: 205342008
Change-Id: I512f40adf1c439298fdcd2d7a313a9cdc2b48e18
This commit is contained in:
Diego Vela
2022-03-08 05:04:36 +00:00
committed by Presubmit Automerger Backend
8 changed files with 196 additions and 396 deletions

View File

@@ -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);

View File

@@ -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);
} }
} }

View File

@@ -1,60 +0,0 @@
/*
* 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.IntDef;
import android.annotation.Nullable;
import android.graphics.Rect;
import androidx.annotation.NonNull;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** Wrapper for both Extension and Sidecar versions of DisplayFeature. */
public interface DisplayFeature {
/** Returns the type of the feature. */
int getType();
/** Returns the state of the feature, or {@code null} if the feature has no state. */
@Nullable
@State
Integer getState();
/** Returns the bounds of the feature. */
@NonNull
Rect getRect();
/**
* 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;
/**
* The possible states for a folding hinge.
*/
@IntDef({COMMON_STATE_FLAT, COMMON_STATE_HALF_OPENED})
@Retention(RetentionPolicy.SOURCE)
@interface State {}
}

View File

@@ -1,74 +0,0 @@
/*
* 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);
}
}

View File

@@ -1,96 +0,0 @@
/*
* 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();
}
}
}
}

View File

@@ -16,8 +16,10 @@
package androidx.window.common; package androidx.window.common;
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.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.database.ContentObserver; import android.database.ContentObserver;
@@ -26,22 +28,19 @@ import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.provider.Settings; import android.provider.Settings;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import androidx.window.util.BaseDataProducer; import androidx.window.util.BaseDataProducer;
import java.util.ArrayList; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
/** /**
* Implementation of {@link androidx.window.util.DataProducer} that produces * Implementation of {@link androidx.window.util.DataProducer} that produces
* {@link CommonDisplayFeature} parsed from a string stored in {@link Settings}. * {@link CommonFoldingFeature} parsed from a string stored in {@link Settings}.
*/ */
public final class SettingsDisplayFeatureProducer public final class SettingsDisplayFeatureProducer
extends BaseDataProducer<List<DisplayFeature>> { extends BaseDataProducer<List<CommonFoldingFeature>> {
private static final boolean DEBUG = false;
private static final String TAG = "SettingsDisplayFeatureProducer";
private static final String DISPLAY_FEATURES = "display_features"; private static final String DISPLAY_FEATURES = "display_features";
private final Uri mDisplayFeaturesUri = private final Uri mDisplayFeaturesUri =
@@ -57,32 +56,17 @@ public final class SettingsDisplayFeatureProducer
} }
@Override @Override
@Nullable @NonNull
public Optional<List<DisplayFeature>> getData() { public Optional<List<CommonFoldingFeature>> getData() {
String displayFeaturesString = Settings.Global.getString(mResolver, DISPLAY_FEATURES); String displayFeaturesString = Settings.Global.getString(mResolver, DISPLAY_FEATURES);
if (displayFeaturesString == null) { if (displayFeaturesString == null) {
return Optional.empty(); return Optional.empty();
} }
List<DisplayFeature> features = new ArrayList<>();
if (TextUtils.isEmpty(displayFeaturesString)) { if (TextUtils.isEmpty(displayFeaturesString)) {
return Optional.of(features); return Optional.of(Collections.emptyList());
} }
String[] featureStrings = displayFeaturesString.split(";"); return Optional.of(parseListFromString(displayFeaturesString, COMMON_STATE_UNKNOWN));
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);
} }
/** /**

View File

@@ -18,8 +18,8 @@ package androidx.window.extensions.layout;
import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.DEFAULT_DISPLAY;
import static androidx.window.common.DisplayFeature.COMMON_STATE_FLAT; import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT;
import static androidx.window.common.DisplayFeature.COMMON_STATE_HALF_OPENED; import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED;
import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation; import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect; import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
@@ -30,10 +30,8 @@ import android.graphics.Rect;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.window.common.DeviceStateManagerPostureProducer; import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.DisplayFeature; import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.ResourceConfigDisplayFeatureProducer;
import androidx.window.common.SettingsDevicePostureProducer;
import androidx.window.common.SettingsDisplayFeatureProducer; import androidx.window.common.SettingsDisplayFeatureProducer;
import androidx.window.util.DataProducer; import androidx.window.util.DataProducer;
import androidx.window.util.PriorityDataProducer; import androidx.window.util.PriorityDataProducer;
@@ -56,32 +54,20 @@ import java.util.function.Consumer;
*/ */
public class WindowLayoutComponentImpl implements WindowLayoutComponent { public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private static final String TAG = "SampleExtension"; private static final String TAG = "SampleExtension";
private static WindowLayoutComponent sInstance;
private final Map<Activity, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners = private final Map<Activity, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners =
new HashMap<>(); new HashMap<>();
private final SettingsDevicePostureProducer mSettingsDevicePostureProducer;
private final DataProducer<Integer> mDevicePostureProducer;
private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer; private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer;
private final DataProducer<List<DisplayFeature>> mDisplayFeatureProducer; private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
public WindowLayoutComponentImpl(Context context) { public WindowLayoutComponentImpl(Context context) {
mSettingsDevicePostureProducer = new SettingsDevicePostureProducer(context);
mDevicePostureProducer = new PriorityDataProducer<>(List.of(
mSettingsDevicePostureProducer,
new DeviceStateManagerPostureProducer(context)
));
mSettingsDisplayFeatureProducer = new SettingsDisplayFeatureProducer(context); mSettingsDisplayFeatureProducer = new SettingsDisplayFeatureProducer(context);
mDisplayFeatureProducer = new PriorityDataProducer<>(List.of( mFoldingFeatureProducer = new PriorityDataProducer<>(List.of(
mSettingsDisplayFeatureProducer, mSettingsDisplayFeatureProducer,
new ResourceConfigDisplayFeatureProducer(context) new DeviceStateManagerFoldingFeatureProducer(context)
)); ));
mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
mDevicePostureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
mDisplayFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
} }
/** /**
@@ -122,39 +108,20 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
return !mWindowLayoutChangeListeners.isEmpty(); return !mWindowLayoutChangeListeners.isEmpty();
} }
/**
* Calculate the {@link DisplayFeature.State} from the feature or the device posture producer.
* If the given {@link DisplayFeature.State} is not valid then {@code null} will be returned.
* The {@link FoldingFeature} should be ignored in the case of an invalid
* {@link DisplayFeature.State}.
*
* @param feature a {@link DisplayFeature} to provide the feature state if present.
* @return {@link DisplayFeature.State} of the hinge if present or the state from the posture
* produce if present.
*/
@Nullable
private Integer getFeatureState(DisplayFeature feature) {
Integer featureState = feature.getState();
Optional<Integer> posture = mDevicePostureProducer.getData();
Integer state = featureState == null ? posture.orElse(null) : featureState;
return convertToExtensionState(state);
}
/** /**
* A convenience method to translate from the common feature state to the extensions feature * A convenience method to translate from the common feature state to the extensions feature
* state. More specifically, translates from {@link DisplayFeature.State} to * state. More specifically, translates from {@link CommonFoldingFeature.State} to
* {@link FoldingFeature.STATE_FLAT} or {@link FoldingFeature.STATE_HALF_OPENED}. If it is not * {@link FoldingFeature.STATE_FLAT} or {@link FoldingFeature.STATE_HALF_OPENED}. If it is not
* possible to translate, then we will return a {@code null} value. * possible to translate, then we will return a {@code null} value.
* *
* @param state if it matches a value in {@link DisplayFeature.State}, {@code null} otherwise. * @param state if it matches a value in {@link CommonFoldingFeature.State}, {@code null}
* @return a {@link FoldingFeature.STATE_FLAT} or {@link FoldingFeature.STATE_HALF_OPENED} if * otherwise. @return a {@link FoldingFeature.STATE_FLAT} or
* the given state matches a value in {@link DisplayFeature.State} and {@code null} otherwise. * {@link FoldingFeature.STATE_HALF_OPENED} if the given state matches a value in
* {@link CommonFoldingFeature.State} and {@code null} otherwise.
*/ */
@Nullable @Nullable
private Integer convertToExtensionState(@Nullable Integer state) { private Integer convertToExtensionState(int state) {
if (state == null) { // The null check avoids a NullPointerException. if (state == COMMON_STATE_FLAT) {
return null;
} else if (state == COMMON_STATE_FLAT) {
return FoldingFeature.STATE_FLAT; return FoldingFeature.STATE_FLAT;
} else if (state == COMMON_STATE_HALF_OPENED) { } else if (state == COMMON_STATE_HALF_OPENED) {
return FoldingFeature.STATE_HALF_OPENED; return FoldingFeature.STATE_HALF_OPENED;
@@ -172,33 +139,30 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
@NonNull @NonNull
private WindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) { private WindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) {
List<androidx.window.extensions.layout.DisplayFeature> displayFeatures = List<DisplayFeature> displayFeatures = getDisplayFeatures(activity);
getDisplayFeatures(activity);
return new WindowLayoutInfo(displayFeatures); return new WindowLayoutInfo(displayFeatures);
} }
/** /**
* Translate from the {@link DisplayFeature} to * Translate from the {@link CommonFoldingFeature} to
* {@link androidx.window.extensions.layout.DisplayFeature} for a given {@link Activity}. If a * {@link DisplayFeature} for a given {@link Activity}. If a
* {@link DisplayFeature} is not valid then it will be omitted. * {@link CommonFoldingFeature} is not valid then it will be omitted.
* *
* For a {@link FoldingFeature} the bounds are localized into the {@link Activity} window * For a {@link FoldingFeature} the bounds are localized into the {@link Activity} window
* coordinate space and the state is calculated either from {@link DisplayFeature#getState()} or * coordinate space and the state is calculated from {@link CommonFoldingFeature#getState()}.
* {@link #mDisplayFeatureProducer}. The state from {@link #mDisplayFeatureProducer} may not be * The state from {@link #mFoldingFeatureProducer} may not be valid since
* valid since {@link #mDisplayFeatureProducer} is a general state controller. If the state is * {@link #mFoldingFeatureProducer} is a general state controller. If the state is not valid,
* not valid, the {@link FoldingFeature} is omitted from the {@link List} of * the {@link FoldingFeature} is omitted from the {@link List} of {@link DisplayFeature}. If
* {@link androidx.window.extensions.layout.DisplayFeature}. If the bounds are not valid, * the bounds are not valid, constructing a {@link FoldingFeature} will throw an
* constructing a {@link FoldingFeature} will throw an {@link IllegalArgumentException} since * {@link IllegalArgumentException} since this can cause negative UI effects down stream.
* this can cause negative UI effects down stream.
* *
* @param activity a proxy for the {@link android.view.Window} that contains the * @param activity a proxy for the {@link android.view.Window} that contains the
* {@link androidx.window.extensions.layout.DisplayFeature}. * {@link DisplayFeature}.
* @return a {@link List} of valid {@link androidx.window.extensions.layout.DisplayFeature} that * @return a {@link List} of valid {@link DisplayFeature} that
* are within the {@link android.view.Window} of the {@link Activity} * are within the {@link android.view.Window} of the {@link Activity}
*/ */
private List<androidx.window.extensions.layout.DisplayFeature> getDisplayFeatures( private List<DisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
@NonNull Activity activity) { List<DisplayFeature> features = new ArrayList<>();
List<androidx.window.extensions.layout.DisplayFeature> features = new ArrayList<>();
int displayId = activity.getDisplay().getDisplayId(); int displayId = activity.getDisplay().getDisplayId();
if (displayId != DEFAULT_DISPLAY) { if (displayId != DEFAULT_DISPLAY) {
Log.w(TAG, "This sample doesn't support display features on secondary displays"); Log.w(TAG, "This sample doesn't support display features on secondary displays");
@@ -211,11 +175,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
return features; return features;
} }
Optional<List<DisplayFeature>> storedFeatures = mDisplayFeatureProducer.getData(); Optional<List<CommonFoldingFeature>> storedFeatures = mFoldingFeatureProducer.getData();
if (storedFeatures.isPresent()) { if (storedFeatures.isPresent()) {
for (CommonFoldingFeature baseFeature : storedFeatures.get()) {
for (DisplayFeature baseFeature : storedFeatures.get()) { Integer state = convertToExtensionState(baseFeature.getState());
Integer state = getFeatureState(baseFeature);
if (state == null) { if (state == null) {
continue; continue;
} }
@@ -223,8 +186,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
rotateRectToDisplayRotation(displayId, featureRect); rotateRectToDisplayRotation(displayId, featureRect);
transformToWindowSpaceRect(activity, featureRect); transformToWindowSpaceRect(activity, featureRect);
features.add(new FoldingFeature(featureRect, baseFeature.getType(), features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
getFeatureState(baseFeature)));
} }
} }
return features; return features;
@@ -232,10 +194,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private void updateRegistrations() { private void updateRegistrations() {
if (hasListeners()) { if (hasListeners()) {
mSettingsDevicePostureProducer.registerObserversIfNeeded();
mSettingsDisplayFeatureProducer.registerObserversIfNeeded(); mSettingsDisplayFeatureProducer.registerObserversIfNeeded();
} else { } else {
mSettingsDevicePostureProducer.unregisterObserversIfNeeded();
mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded(); mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded();
} }

View File

@@ -29,10 +29,8 @@ import android.os.IBinder;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.window.common.DeviceStateManagerPostureProducer; import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.DisplayFeature; import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.ResourceConfigDisplayFeatureProducer;
import androidx.window.common.SettingsDevicePostureProducer;
import androidx.window.common.SettingsDisplayFeatureProducer; import androidx.window.common.SettingsDisplayFeatureProducer;
import androidx.window.util.DataProducer; import androidx.window.util.DataProducer;
import androidx.window.util.PriorityDataProducer; import androidx.window.util.PriorityDataProducer;
@@ -48,36 +46,23 @@ import java.util.Optional;
*/ */
class SampleSidecarImpl extends StubSidecar { class SampleSidecarImpl extends StubSidecar {
private static final String TAG = "SampleSidecar"; private static final String TAG = "SampleSidecar";
private static final boolean DEBUG = false;
private final SettingsDevicePostureProducer mSettingsDevicePostureProducer; private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
private final DataProducer<Integer> mDevicePostureProducer;
private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer; private final SettingsDisplayFeatureProducer mSettingsFoldingFeatureProducer;
private final DataProducer<List<DisplayFeature>> mDisplayFeatureProducer;
SampleSidecarImpl(Context context) { SampleSidecarImpl(Context context) {
mSettingsDevicePostureProducer = new SettingsDevicePostureProducer(context); mSettingsFoldingFeatureProducer = new SettingsDisplayFeatureProducer(context);
mDevicePostureProducer = new PriorityDataProducer<>(List.of( mFoldingFeatureProducer = new PriorityDataProducer<>(List.of(
mSettingsDevicePostureProducer, mSettingsFoldingFeatureProducer,
new DeviceStateManagerPostureProducer(context) new DeviceStateManagerFoldingFeatureProducer(context)
)); ));
mSettingsDisplayFeatureProducer = new SettingsDisplayFeatureProducer(context); mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
mDisplayFeatureProducer = new PriorityDataProducer<>(List.of(
mSettingsDisplayFeatureProducer,
new ResourceConfigDisplayFeatureProducer(context)
));
mDevicePostureProducer.addDataChangedCallback(this::onDevicePostureChanged);
mDisplayFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
}
private void onDevicePostureChanged() {
updateDeviceState(getDeviceState());
} }
private void onDisplayFeaturesChanged() { private void onDisplayFeaturesChanged() {
updateDeviceState(getDeviceState());
for (IBinder windowToken : getWindowsListeningForLayoutChanges()) { for (IBinder windowToken : getWindowsListeningForLayoutChanges()) {
SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken); SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken);
updateWindowLayout(windowToken, newLayout); updateWindowLayout(windowToken, newLayout);
@@ -87,27 +72,21 @@ class SampleSidecarImpl extends StubSidecar {
@NonNull @NonNull
@Override @Override
public SidecarDeviceState getDeviceState() { public SidecarDeviceState getDeviceState() {
Optional<Integer> posture = mDevicePostureProducer.getData();
SidecarDeviceState deviceState = new SidecarDeviceState(); SidecarDeviceState deviceState = new SidecarDeviceState();
deviceState.posture = posture.orElse(deviceStateFromFeature()); deviceState.posture = deviceStateFromFeature();
return deviceState; return deviceState;
} }
private int deviceStateFromFeature() { private int deviceStateFromFeature() {
List<DisplayFeature> storedFeatures = mDisplayFeatureProducer.getData() List<CommonFoldingFeature> storedFeatures = mFoldingFeatureProducer.getData()
.orElse(Collections.emptyList()); .orElse(Collections.emptyList());
for (int i = 0; i < storedFeatures.size(); i++) { for (int i = 0; i < storedFeatures.size(); i++) {
DisplayFeature feature = storedFeatures.get(i); CommonFoldingFeature feature = storedFeatures.get(i);
final int state = feature.getState() == null ? -1 : feature.getState(); final int state = feature.getState();
if (DEBUG && feature.getState() == null) {
Log.d(TAG, "feature#getState was null for DisplayFeature: " + feature);
}
switch (state) { switch (state) {
case DisplayFeature.COMMON_STATE_FLAT: case CommonFoldingFeature.COMMON_STATE_FLAT:
return SidecarDeviceState.POSTURE_OPENED; return SidecarDeviceState.POSTURE_OPENED;
case DisplayFeature.COMMON_STATE_HALF_OPENED: case CommonFoldingFeature.COMMON_STATE_HALF_OPENED:
return SidecarDeviceState.POSTURE_HALF_OPENED; return SidecarDeviceState.POSTURE_HALF_OPENED;
} }
} }
@@ -127,22 +106,22 @@ class SampleSidecarImpl extends StubSidecar {
} }
private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) { private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
List<SidecarDisplayFeature> features = new ArrayList<SidecarDisplayFeature>();
int displayId = activity.getDisplay().getDisplayId(); int displayId = activity.getDisplay().getDisplayId();
if (displayId != DEFAULT_DISPLAY) { if (displayId != DEFAULT_DISPLAY) {
Log.w(TAG, "This sample doesn't support display features on secondary displays"); Log.w(TAG, "This sample doesn't support display features on secondary displays");
return features; return Collections.emptyList();
} }
if (activity.isInMultiWindowMode()) { if (activity.isInMultiWindowMode()) {
// It is recommended not to report any display features in multi-window mode, since it // It is recommended not to report any display features in multi-window mode, since it
// won't be possible to synchronize the display feature positions with window movement. // won't be possible to synchronize the display feature positions with window movement.
return features; return Collections.emptyList();
} }
Optional<List<DisplayFeature>> storedFeatures = mDisplayFeatureProducer.getData(); Optional<List<CommonFoldingFeature>> storedFeatures = mFoldingFeatureProducer.getData();
List<SidecarDisplayFeature> features = new ArrayList<>();
if (storedFeatures.isPresent()) { if (storedFeatures.isPresent()) {
for (DisplayFeature baseFeature : storedFeatures.get()) { for (CommonFoldingFeature baseFeature : storedFeatures.get()) {
SidecarDisplayFeature feature = new SidecarDisplayFeature(); SidecarDisplayFeature feature = new SidecarDisplayFeature();
Rect featureRect = baseFeature.getRect(); Rect featureRect = baseFeature.getRect();
rotateRectToDisplayRotation(displayId, featureRect); rotateRectToDisplayRotation(displayId, featureRect);
@@ -152,17 +131,15 @@ class SampleSidecarImpl extends StubSidecar {
features.add(feature); features.add(feature);
} }
} }
return features; return Collections.unmodifiableList(features);
} }
@Override @Override
protected void onListenersChanged() { protected void onListenersChanged() {
if (hasListeners()) { if (hasListeners()) {
mSettingsDevicePostureProducer.registerObserversIfNeeded(); mSettingsFoldingFeatureProducer.registerObserversIfNeeded();
mSettingsDisplayFeatureProducer.registerObserversIfNeeded();
} else { } else {
mSettingsDevicePostureProducer.unregisterObserversIfNeeded(); mSettingsFoldingFeatureProducer.unregisterObserversIfNeeded();
mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded();
} }
} }
} }