diff options
15 files changed, 808 insertions, 270 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b0327a5decee..b4ef63fbcce3 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4649,6 +4649,10 @@ <!-- WindowsManager JetPack display features --> <string name="config_display_features" translatable="false" /> + <!-- Map of System DeviceState supplied by DeviceStateManager to WM Jetpack posture. Must be in + the format [System DeviceState]:[WM Jetpack Posture], for example: "0:1". --> + <string-array name="config_device_state_postures" translatable="false" /> + <!-- Aspect ratio of letterboxing for fixed orientation. Values <= 1.0 will be ignored. Note: Activity min/max aspect ratio restrictions will still be respected. Therefore this override can control the maximum screen area that can be occupied by diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index cdeb71c7f162..1d74d85fb9db 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4182,6 +4182,7 @@ <java-symbol type="dimen" name="default_background_blur_radius" /> <java-symbol type="array" name="config_keep_warming_services" /> <java-symbol type="string" name="config_display_features" /> + <java-symbol type="array" name="config_device_state_postures" /> <java-symbol type="dimen" name="controls_thumbnail_image_max_height" /> <java-symbol type="dimen" name="controls_thumbnail_image_max_width" /> diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp index d8f00bbc1ad3..dc4b5636a246 100644 --- a/libs/WindowManager/Jetpack/Android.bp +++ b/libs/WindowManager/Jetpack/Android.bp @@ -30,13 +30,20 @@ android_library_import { java_library { name: "androidx.window.sidecar", - srcs: ["src/androidx/window/sidecar/**/*.java", "src/androidx/window/util/**/*.java"], + srcs: [ + "src/androidx/window/sidecar/**/*.java", + "src/androidx/window/util/**/*.java", + "src/androidx/window/common/**/*.java", + ], static_libs: ["window-sidecar"], installable: true, sdk_version: "core_platform", system_ext_specific: true, - libs: ["framework", "androidx.annotation_annotation",], - required: ["androidx.window.sidecar.xml",], + libs: [ + "framework", + "androidx.annotation_annotation", + ], + required: ["androidx.window.sidecar.xml"], } prebuilt_etc { @@ -58,13 +65,20 @@ android_library_import { java_library { name: "androidx.window.extensions", - srcs: ["src/androidx/window/extensions/**/*.java", "src/androidx/window/util/**/*.java"], + srcs: [ + "src/androidx/window/extensions/**/*.java", + "src/androidx/window/util/**/*.java", + "src/androidx/window/common/**/*.java", + ], static_libs: ["window-extensions"], installable: true, sdk_version: "core_platform", system_ext_specific: true, - libs: ["framework", "androidx.annotation_annotation",], - required: ["androidx.window.extensions.xml",], + libs: [ + "framework", + "androidx.annotation_annotation", + ], + required: ["androidx.window.extensions.xml"], } prebuilt_etc { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonDisplayFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonDisplayFeature.java new file mode 100644 index 000000000000..e6ad011e617e --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonDisplayFeature.java @@ -0,0 +1,128 @@ +/* + * 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 static androidx.window.util.ExtensionHelper.isZero; + +import android.annotation.Nullable; +import android.graphics.Rect; + +import androidx.annotation.NonNull; + +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 { + private static final Pattern FEATURE_PATTERN = + Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]"); + + private static final String FEATURE_TYPE_FOLD = "fold"; + private static final String FEATURE_TYPE_HINGE = "hinge"; + + // TODO(b/183049815): Support feature strings that include the state of the feature. + /** + * Parses a display feature from a string. + * + * @throws IllegalArgumentException if the provided string is improperly formatted or could not + * otherwise be parsed. + * + * @see #FEATURE_PATTERN + */ + @NonNull + static CommonDisplayFeature parseFromString(@NonNull String string) { + Matcher featureMatcher = FEATURE_PATTERN.matcher(string); + if (!featureMatcher.matches()) { + throw new IllegalArgumentException("Malformed feature description format: " + string); + } + try { + String featureType = featureMatcher.group(1); + int type; + switch (featureType) { + case FEATURE_TYPE_FOLD: + type = 1 /* TYPE_FOLD */; + break; + case FEATURE_TYPE_HINGE: + type = 2 /* TYPE_HINGE */; + break; + default: { + throw new IllegalArgumentException("Malformed feature type: " + featureType); + } + } + + int left = Integer.parseInt(featureMatcher.group(2)); + int top = Integer.parseInt(featureMatcher.group(3)); + int right = Integer.parseInt(featureMatcher.group(4)); + int bottom = Integer.parseInt(featureMatcher.group(5)); + Rect featureRect = new Rect(left, top, right, bottom); + if (isZero(featureRect)) { + throw new IllegalArgumentException("Feature has empty bounds: " + string); + } + + return new CommonDisplayFeature(type, null, featureRect); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Malformed feature description: " + string, e); + } + } + + private final int mType; + @Nullable + private final Integer mState; + @NonNull + private final Rect mRect; + + CommonDisplayFeature(int type, @Nullable Integer state, @NonNull Rect rect) { + this.mType = type; + this.mState = state; + if (rect.width() == 0 && rect.height() == 0) { + throw new IllegalArgumentException( + "Display feature rectangle cannot have zero width and height simultaneously."); + } + this.mRect = rect; + } + + public int getType() { + return mType; + } + + /** Returns the state of the feature, or {@code null} if the feature has no state. */ + @Nullable + public Integer getState() { + return mState; + } + + @NonNull + public Rect getRect() { + return mRect; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CommonDisplayFeature that = (CommonDisplayFeature) o; + return mType == that.mType + && Objects.equals(mState, that.mState) + && mRect.equals(that.mRect); + } + + @Override + public int hashCode() { + return Objects.hash(mType, mState, mRect); + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerPostureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerPostureProducer.java new file mode 100644 index 000000000000..fa9a5a8b7a1b --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerPostureProducer.java @@ -0,0 +1,93 @@ +/* + * 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 static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; + +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.util.Log; +import android.util.SparseIntArray; + +import androidx.window.util.BaseDataProducer; + +import com.android.internal.R; + +import java.util.Optional; + +/** + * An implementation of {@link androidx.window.util.DataProducer} that returns the device's posture + * 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"; + private static final boolean DEBUG = false; + + private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray(); + + private int mCurrentDeviceState = INVALID_DEVICE_STATE; + + private final DeviceStateCallback mDeviceStateCallback = (state) -> { + mCurrentDeviceState = state; + notifyDataChanged(); + }; + + public DeviceStateManagerPostureProducer(@NonNull Context context) { + String[] deviceStatePosturePairs = context.getResources() + .getStringArray(R.array.config_device_state_postures); + for (String deviceStatePosturePair : deviceStatePosturePairs) { + String[] deviceStatePostureMapping = deviceStatePosturePair.split(":"); + if (deviceStatePostureMapping.length != 2) { + if (DEBUG) { + Log.e(TAG, "Malformed device state posture pair: " + deviceStatePosturePair); + } + continue; + } + + int deviceState; + int posture; + try { + deviceState = Integer.parseInt(deviceStatePostureMapping[0]); + posture = Integer.parseInt(deviceStatePostureMapping[1]); + } catch (NumberFormatException e) { + if (DEBUG) { + Log.e(TAG, "Failed to parse device state or posture: " + deviceStatePosturePair, + e); + } + continue; + } + + mDeviceStateToPostureMap.put(deviceState, posture); + } + + if (mDeviceStateToPostureMap.size() > 0) { + context.getSystemService(DeviceStateManager.class) + .registerCallback(context.getMainExecutor(), mDeviceStateCallback); + } + } + + @Override + @Nullable + public Optional<Integer> getData() { + final int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState, -1); + return posture != -1 ? Optional.of(posture) : Optional.empty(); + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DisplayFeature.java index b74a2a4bd47d..b6c4c436d0b1 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DisplayFeature.java @@ -14,39 +14,23 @@ * limitations under the License. */ -package androidx.window.util; +package androidx.window.common; +import android.annotation.Nullable; import android.graphics.Rect; import androidx.annotation.NonNull; /** Wrapper for both Extension and Sidecar versions of DisplayFeature. */ -public class BaseDisplayFeature { - private final int mType; - private final int mState; - @NonNull - public final Rect mRect; - - public BaseDisplayFeature(int type, int state, @NonNull Rect rect) { - this.mType = type; - this.mState = state; - if (rect.width() == 0 && rect.height() == 0) { - throw new IllegalArgumentException( - "Display feature rectangle cannot have zero width and height simultaneously."); - } - this.mRect = rect; - } - - public int getType() { - return mType; - } +public interface DisplayFeature { + /** Returns the type of the feature. */ + int getType(); - public int getState() { - return mState; - } + /** Returns the state of the feature, or {@code null} if the feature has no state. */ + @Nullable + Integer getState(); + /** Returns the bounds of the feature. */ @NonNull - public Rect getRect() { - return mRect; - } + Rect getRect(); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/ResourceConfigDisplayFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/ResourceConfigDisplayFeatureProducer.java new file mode 100644 index 000000000000..cd2cadc082e1 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/ResourceConfigDisplayFeatureProducer.java @@ -0,0 +1,74 @@ +/* + * 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); + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDevicePostureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDevicePostureProducer.java new file mode 100644 index 000000000000..2026df3fa979 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDevicePostureProducer.java @@ -0,0 +1,96 @@ +/* + * 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(); + } + } + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java new file mode 100644 index 000000000000..040662657a74 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java @@ -0,0 +1,125 @@ +/* + * 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 android.text.TextUtils; +import android.util.Log; + +import androidx.window.util.BaseDataProducer; + +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 {@link Settings}. + */ +public final class SettingsDisplayFeatureProducer + extends BaseDataProducer<List<DisplayFeature>> { + private static final boolean DEBUG = false; + private static final String TAG = "SettingsDisplayFeatureProducer"; + private static final String DISPLAY_FEATURES = "display_features"; + + private final Uri mDisplayFeaturesUri = + Settings.Global.getUriFor(DISPLAY_FEATURES); + + private final ContentResolver mResolver; + private final ContentObserver mObserver; + private boolean mRegisteredObservers; + + public SettingsDisplayFeatureProducer(@NonNull Context context) { + mResolver = context.getContentResolver(); + mObserver = new SettingsObserver(); + } + + @Override + @Nullable + public Optional<List<DisplayFeature>> getData() { + String displayFeaturesString = Settings.Global.getString(mResolver, DISPLAY_FEATURES); + if (displayFeaturesString == null) { + return Optional.empty(); + } + + List<DisplayFeature> features = new ArrayList<>(); + if (TextUtils.isEmpty(displayFeaturesString)) { + return Optional.of(features); + } + 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); + } + + /** + * 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(mDisplayFeaturesUri, 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 (mDisplayFeaturesUri.equals(uri)) { + notifyDataChanged(); + } + } + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java index 5c91cf41bfc6..ce9be6a7bba3 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java @@ -27,11 +27,17 @@ import android.graphics.Rect; import android.util.Log; import androidx.annotation.NonNull; -import androidx.window.util.BaseDisplayFeature; -import androidx.window.util.SettingsConfigProvider; +import androidx.window.common.DeviceStateManagerPostureProducer; +import androidx.window.common.DisplayFeature; +import androidx.window.common.ResourceConfigDisplayFeatureProducer; +import androidx.window.common.SettingsDevicePostureProducer; +import androidx.window.common.SettingsDisplayFeatureProducer; +import androidx.window.util.DataProducer; +import androidx.window.util.PriorityDataProducer; import java.util.ArrayList; import java.util.List; +import java.util.Optional; /** * Reference implementation of androidx.window.extensions OEM interface for use with @@ -41,23 +47,46 @@ import java.util.List; * production builds since the interface can still change before reaching stable version. * Please refer to {@link androidx.window.sidecar.SampleSidecarImpl} instead. */ -class SampleExtensionImpl extends StubExtension implements - SettingsConfigProvider.StateChangeCallback { +class SampleExtensionImpl extends StubExtension { private static final String TAG = "SampleExtension"; - private final SettingsConfigProvider mConfigProvider; + private final SettingsDevicePostureProducer mSettingsDevicePostureProducer; + private final DataProducer<Integer> mDevicePostureProducer; + + private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer; + private final DataProducer<List<DisplayFeature>> mDisplayFeatureProducer; SampleExtensionImpl(Context context) { - mConfigProvider = new SettingsConfigProvider(context, this); + mSettingsDevicePostureProducer = new SettingsDevicePostureProducer(context); + mDevicePostureProducer = new PriorityDataProducer<>(List.of( + mSettingsDevicePostureProducer, + new DeviceStateManagerPostureProducer(context) + )); + + mSettingsDisplayFeatureProducer = new SettingsDisplayFeatureProducer(context); + mDisplayFeatureProducer = new PriorityDataProducer<>(List.of( + mSettingsDisplayFeatureProducer, + new ResourceConfigDisplayFeatureProducer(context) + )); + + mDevicePostureProducer.addDataChangedCallback(this::onDevicePostureChanged); + mDisplayFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); } - @Override - public void onDevicePostureChanged() { - updateDeviceState(new ExtensionDeviceState(mConfigProvider.getDeviceState())); + private void onDevicePostureChanged() { + updateDeviceState(new ExtensionDeviceState(getDevicePosture())); + + // Trigger a change in display features as the posture will be used in place of the feature + // state if the state is left unset by the producer. + onDisplayFeaturesChanged(); } - @Override - public void onDisplayFeaturesChanged() { + private int getDevicePosture() { + Optional<Integer> posture = mDevicePostureProducer.getData(); + return posture.orElse(ExtensionDeviceState.POSTURE_UNKNOWN); + } + + private void onDisplayFeaturesChanged() { for (Activity activity : getActivitiesListeningForLayoutChanges()) { ExtensionWindowLayoutInfo newLayout = getWindowLayoutInfo(activity); updateWindowLayout(activity, newLayout); @@ -84,13 +113,20 @@ class SampleExtensionImpl extends StubExtension implements return features; } - List<BaseDisplayFeature> storedFeatures = mConfigProvider.getDisplayFeatures(); - for (BaseDisplayFeature baseFeature : storedFeatures) { - Rect featureRect = baseFeature.getRect(); - rotateRectToDisplayRotation(displayId, featureRect); - transformToWindowSpaceRect(activity, featureRect); - features.add(new ExtensionFoldingFeature(featureRect, baseFeature.getType(), - baseFeature.getState())); + Optional<List<DisplayFeature>> storedFeatures = mDisplayFeatureProducer.getData(); + if (storedFeatures.isPresent()) { + int posture = getDevicePosture(); + + for (DisplayFeature baseFeature : storedFeatures.get()) { + Rect featureRect = baseFeature.getRect(); + rotateRectToDisplayRotation(displayId, featureRect); + transformToWindowSpaceRect(activity, featureRect); + + Integer featureState = baseFeature.getState(); + + features.add(new ExtensionFoldingFeature(featureRect, baseFeature.getType(), + featureState == null ? posture : featureState)); + } } return features; } @@ -98,9 +134,11 @@ class SampleExtensionImpl extends StubExtension implements @Override protected void onListenersChanged() { if (hasListeners()) { - mConfigProvider.registerObserversIfNeeded(); + mSettingsDevicePostureProducer.registerObserversIfNeeded(); + mSettingsDisplayFeatureProducer.registerObserversIfNeeded(); } else { - mConfigProvider.unregisterObserversIfNeeded(); + mSettingsDevicePostureProducer.unregisterObserversIfNeeded(); + mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded(); } onDevicePostureChanged(); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java index d3700f88d97f..ece198cad818 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java @@ -29,33 +29,53 @@ import android.os.IBinder; import android.util.Log; import androidx.annotation.NonNull; -import androidx.window.util.BaseDisplayFeature; -import androidx.window.util.SettingsConfigProvider; +import androidx.window.common.DeviceStateManagerPostureProducer; +import androidx.window.common.DisplayFeature; +import androidx.window.common.ResourceConfigDisplayFeatureProducer; +import androidx.window.common.SettingsDevicePostureProducer; +import androidx.window.common.SettingsDisplayFeatureProducer; +import androidx.window.util.DataProducer; +import androidx.window.util.PriorityDataProducer; import java.util.ArrayList; import java.util.List; +import java.util.Optional; /** * Reference implementation of androidx.window.sidecar OEM interface for use with * WindowManager Jetpack. */ -class SampleSidecarImpl extends StubSidecar implements - SettingsConfigProvider.StateChangeCallback { +class SampleSidecarImpl extends StubSidecar { private static final String TAG = "SampleSidecar"; - private final SettingsConfigProvider mConfigProvider; + private final SettingsDevicePostureProducer mSettingsDevicePostureProducer; + private final DataProducer<Integer> mDevicePostureProducer; + + private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer; + private final DataProducer<List<DisplayFeature>> mDisplayFeatureProducer; SampleSidecarImpl(Context context) { - mConfigProvider = new SettingsConfigProvider(context, this); + mSettingsDevicePostureProducer = new SettingsDevicePostureProducer(context); + mDevicePostureProducer = new PriorityDataProducer<>(List.of( + mSettingsDevicePostureProducer, + new DeviceStateManagerPostureProducer(context) + )); + + mSettingsDisplayFeatureProducer = new SettingsDisplayFeatureProducer(context); + mDisplayFeatureProducer = new PriorityDataProducer<>(List.of( + mSettingsDisplayFeatureProducer, + new ResourceConfigDisplayFeatureProducer(context) + )); + + mDevicePostureProducer.addDataChangedCallback(this::onDevicePostureChanged); + mDisplayFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); } - @Override - public void onDevicePostureChanged() { + private void onDevicePostureChanged() { updateDeviceState(getDeviceState()); } - @Override - public void onDisplayFeaturesChanged() { + private void onDisplayFeaturesChanged() { for (IBinder windowToken : getWindowsListeningForLayoutChanges()) { SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken); updateWindowLayout(windowToken, newLayout); @@ -65,8 +85,10 @@ class SampleSidecarImpl extends StubSidecar implements @NonNull @Override public SidecarDeviceState getDeviceState() { + Optional<Integer> posture = mDevicePostureProducer.getData(); + SidecarDeviceState deviceState = new SidecarDeviceState(); - deviceState.posture = mConfigProvider.getDeviceState(); + deviceState.posture = posture.orElse(SidecarDeviceState.POSTURE_UNKNOWN); return deviceState; } @@ -96,15 +118,17 @@ class SampleSidecarImpl extends StubSidecar implements return features; } - List<BaseDisplayFeature> storedFeatures = mConfigProvider.getDisplayFeatures(); - for (BaseDisplayFeature baseFeature : storedFeatures) { - SidecarDisplayFeature feature = new SidecarDisplayFeature(); - Rect featureRect = baseFeature.getRect(); - rotateRectToDisplayRotation(displayId, featureRect); - transformToWindowSpaceRect(activity, featureRect); - feature.setRect(featureRect); - feature.setType(baseFeature.getType()); - features.add(feature); + Optional<List<DisplayFeature>> storedFeatures = mDisplayFeatureProducer.getData(); + if (storedFeatures.isPresent()) { + for (DisplayFeature baseFeature : storedFeatures.get()) { + SidecarDisplayFeature feature = new SidecarDisplayFeature(); + Rect featureRect = baseFeature.getRect(); + rotateRectToDisplayRotation(displayId, featureRect); + transformToWindowSpaceRect(activity, featureRect); + feature.setRect(featureRect); + feature.setType(baseFeature.getType()); + features.add(feature); + } } return features; } @@ -112,9 +136,11 @@ class SampleSidecarImpl extends StubSidecar implements @Override protected void onListenersChanged() { if (hasListeners()) { - mConfigProvider.registerObserversIfNeeded(); + mSettingsDevicePostureProducer.registerObserversIfNeeded(); + mSettingsDisplayFeatureProducer.registerObserversIfNeeded(); } else { - mConfigProvider.unregisterObserversIfNeeded(); + mSettingsDevicePostureProducer.unregisterObserversIfNeeded(); + mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded(); } } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java new file mode 100644 index 000000000000..0a46703451ab --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java @@ -0,0 +1,52 @@ +/* + * 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.util; + +import androidx.annotation.NonNull; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Base class that provides the implementation for the callback mechanism of the + * {@link DataProducer} API. + * + * @param <T> The type of data this producer returns through {@link #getData()}. + */ +public abstract class BaseDataProducer<T> implements DataProducer<T> { + private final Set<Runnable> mCallbacks = new LinkedHashSet<>(); + + @Override + public final void addDataChangedCallback(@NonNull Runnable callback) { + mCallbacks.add(callback); + } + + @Override + public final void removeDataChangedCallback(@NonNull Runnable callback) { + mCallbacks.remove(callback); + } + + /** + * Called to notify all registered callbacks that the data provided by {@link #getData()} has + * changed. + */ + protected void notifyDataChanged() { + for (Runnable callback : mCallbacks) { + callback.run(); + } + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java new file mode 100644 index 000000000000..d4d1a23b756b --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java @@ -0,0 +1,43 @@ +/* + * 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.util; + +import android.annotation.NonNull; + +import java.util.Optional; + +/** + * Produces data through {@link #getData()} and provides a mechanism for receiving a callback when + * the data managed by the produces has changed. + * + * @param <T> The type of data this producer returns through {@link #getData()}. + */ +public interface DataProducer<T> { + /** + * Returns the data currently stored in the provider, or {@link Optional#empty()} if the + * provider has no data. + */ + Optional<T> getData(); + + /** + * Adds a callback to be notified when the data returned from {@link #getData()} has changed. + */ + void addDataChangedCallback(@NonNull Runnable callback); + + /** Removes a callback previously added with {@link #addDataChangedCallback(Runnable)}. */ + void removeDataChangedCallback(@NonNull Runnable callback); +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/PriorityDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/PriorityDataProducer.java new file mode 100644 index 000000000000..990ae20cc934 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/PriorityDataProducer.java @@ -0,0 +1,56 @@ +/* + * 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.util; + +import android.annotation.Nullable; + +import java.util.List; +import java.util.Optional; + +/** + * Implementation of {@link DataProducer} that delegates calls to {@link #getData()} to the list of + * provided child producers. + * <p> + * The value returned is based on the precedence of the supplied children where the producer with + * index 0 has a higher precedence than producers that come later in the list. When a producer with + * a higher precedence has a non-empty value returned from {@link #getData()}, its value will be + * returned from an instance of this class, ignoring all other producers with lower precedence. + * + * @param <T> The type of data this producer returns through {@link #getData()}. + */ +public final class PriorityDataProducer<T> extends BaseDataProducer<T> { + private final List<DataProducer<T>> mChildProducers; + + public PriorityDataProducer(List<DataProducer<T>> childProducers) { + mChildProducers = childProducers; + for (DataProducer<T> childProducer : mChildProducers) { + childProducer.addDataChangedCallback(this::notifyDataChanged); + } + } + + @Nullable + @Override + public Optional<T> getData() { + for (DataProducer<T> childProducer : mChildProducers) { + final Optional<T> data = childProducer.getData(); + if (data.isPresent()) { + return data; + } + } + return Optional.empty(); + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java deleted file mode 100644 index 6dd190ce2519..000000000000 --- a/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java +++ /dev/null @@ -1,196 +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.util; - -import static androidx.window.util.ExtensionHelper.isZero; - -import android.content.ContentResolver; -import android.content.Context; -import android.database.ContentObserver; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Handler; -import android.os.Looper; -import android.provider.Settings; -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.android.internal.R; - -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Device and display feature state provider that uses Settings as the source. - */ -public final class SettingsConfigProvider extends ContentObserver { - private static final String TAG = "SettingsConfigProvider"; - private static final String DEVICE_POSTURE = "device_posture"; - private static final String DISPLAY_FEATURES = "display_features"; - - private static final Pattern FEATURE_PATTERN = - Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]"); - - private static final String FEATURE_TYPE_FOLD = "fold"; - private static final String FEATURE_TYPE_HINGE = "hinge"; - - private final Uri mDevicePostureUri = - Settings.Global.getUriFor(DEVICE_POSTURE); - private final Uri mDisplayFeaturesUri = - Settings.Global.getUriFor(DISPLAY_FEATURES); - private final Context mContext; - private final ContentResolver mResolver; - private final StateChangeCallback mCallback; - private boolean mRegisteredObservers; - - public SettingsConfigProvider(@NonNull Context context, @NonNull StateChangeCallback callback) { - super(new Handler(Looper.getMainLooper())); - mContext = context; - mResolver = context.getContentResolver(); - mCallback = callback; - } - - /** - * Registers the content observers for Settings keys that store device state and display feature - * configurations. - */ - public void registerObserversIfNeeded() { - if (mRegisteredObservers) { - return; - } - mRegisteredObservers = true; - mResolver.registerContentObserver(mDevicePostureUri, false /* notifyForDescendants */, - this /* ContentObserver */); - mResolver.registerContentObserver(mDisplayFeaturesUri, false /* notifyForDescendants */, - this /* ContentObserver */); - } - - /** - * Unregisters the content observers that are tracking the state changes. - * @see #registerObserversIfNeeded() - */ - public void unregisterObserversIfNeeded() { - if (!mRegisteredObservers) { - return; - } - mRegisteredObservers = false; - mResolver.unregisterContentObserver(this); - } - - /** - * Gets the device posture int stored in Settings. - */ - public int getDeviceState() { - return Settings.Global.getInt(mResolver, DEVICE_POSTURE, - 0 /* POSTURE_UNKNOWN */); - } - - /** - * Gets the list of all display feature configs stored in Settings. Uses a custom - * {@link BaseDisplayFeature} class to report the config to be translated for actual - * containers in Sidecar or Extensions. - */ - public List<BaseDisplayFeature> getDisplayFeatures() { - List<BaseDisplayFeature> features = new ArrayList<>(); - String displayFeaturesString = Settings.Global.getString(mResolver, DISPLAY_FEATURES); - if (TextUtils.isEmpty(displayFeaturesString)) { - displayFeaturesString = mContext.getResources().getString( - R.string.config_display_features); - } - if (TextUtils.isEmpty(displayFeaturesString)) { - return features; - } - String[] featureStrings = displayFeaturesString.split(";"); - - int deviceState = getDeviceState(); - - for (String featureString : featureStrings) { - Matcher featureMatcher = FEATURE_PATTERN.matcher(featureString); - if (!featureMatcher.matches()) { - Log.e(TAG, "Malformed feature description format: " + featureString); - continue; - } - try { - String featureType = featureMatcher.group(1); - int type; - switch (featureType) { - case FEATURE_TYPE_FOLD: - type = 1 /* TYPE_FOLD */; - break; - case FEATURE_TYPE_HINGE: - type = 2 /* TYPE_HINGE */; - break; - default: { - Log.e(TAG, "Malformed feature type: " + featureType); - continue; - } - } - - int left = Integer.parseInt(featureMatcher.group(2)); - int top = Integer.parseInt(featureMatcher.group(3)); - int right = Integer.parseInt(featureMatcher.group(4)); - int bottom = Integer.parseInt(featureMatcher.group(5)); - Rect featureRect = new Rect(left, top, right, bottom); - if (!isZero(featureRect)) { - BaseDisplayFeature feature = new BaseDisplayFeature(type, deviceState, - featureRect); - features.add(feature); - } else { - Log.w(TAG, "Read empty feature"); - } - } catch (NumberFormatException e) { - Log.e(TAG, "Malformed feature description: " + featureString); - } - } - return features; - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - if (uri == null) { - return; - } - - if (mDevicePostureUri.equals(uri)) { - mCallback.onDevicePostureChanged(); - mCallback.onDisplayFeaturesChanged(); - return; - } - if (mDisplayFeaturesUri.equals(uri)) { - mCallback.onDisplayFeaturesChanged(); - } - } - - /** - * Callback that notifies about device or display feature state changes. - */ - public interface StateChangeCallback { - /** - * Notifies about the device state update. - */ - void onDevicePostureChanged(); - - /** - * Notifies about the display feature config update. - */ - void onDisplayFeaturesChanged(); - } -} |