Merge changes from topic "jumbo-sidecar-posture" into sc-dev

* changes:
  Update sample Sidecar/Extension implementation to allow mapping system device state to posture via a config.
  Seperate SettingsConfigProvider into producers.
This commit is contained in:
Darryl Johnson
2021-03-20 03:44:01 +00:00
committed by Android (Google) Code Review
15 changed files with 810 additions and 272 deletions

View File

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

View File

@@ -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" />

View File

@@ -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 {

View File

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

View File

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

View File

@@ -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;
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
Integer getState();
/** Returns the bounds of the feature. */
@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 int getState() {
return mState;
}
@NonNull
public Rect getRect() {
return mRect;
}
Rect getRect();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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