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:
committed by
Android (Google) Code Review
commit
30cefedcdb
@@ -4649,6 +4649,10 @@
|
|||||||
<!-- WindowsManager JetPack display features -->
|
<!-- WindowsManager JetPack display features -->
|
||||||
<string name="config_display_features" translatable="false" />
|
<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.
|
<!-- Aspect ratio of letterboxing for fixed orientation. Values <= 1.0 will be ignored.
|
||||||
Note: Activity min/max aspect ratio restrictions will still be respected.
|
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
|
Therefore this override can control the maximum screen area that can be occupied by
|
||||||
|
|||||||
@@ -4182,6 +4182,7 @@
|
|||||||
<java-symbol type="dimen" name="default_background_blur_radius" />
|
<java-symbol type="dimen" name="default_background_blur_radius" />
|
||||||
<java-symbol type="array" name="config_keep_warming_services" />
|
<java-symbol type="array" name="config_keep_warming_services" />
|
||||||
<java-symbol type="string" name="config_display_features" />
|
<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_height" />
|
||||||
<java-symbol type="dimen" name="controls_thumbnail_image_max_width" />
|
<java-symbol type="dimen" name="controls_thumbnail_image_max_width" />
|
||||||
|
|||||||
@@ -30,13 +30,20 @@ android_library_import {
|
|||||||
|
|
||||||
java_library {
|
java_library {
|
||||||
name: "androidx.window.sidecar",
|
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"],
|
static_libs: ["window-sidecar"],
|
||||||
installable: true,
|
installable: true,
|
||||||
sdk_version: "core_platform",
|
sdk_version: "core_platform",
|
||||||
system_ext_specific: true,
|
system_ext_specific: true,
|
||||||
libs: ["framework", "androidx.annotation_annotation",],
|
libs: [
|
||||||
required: ["androidx.window.sidecar.xml",],
|
"framework",
|
||||||
|
"androidx.annotation_annotation",
|
||||||
|
],
|
||||||
|
required: ["androidx.window.sidecar.xml"],
|
||||||
}
|
}
|
||||||
|
|
||||||
prebuilt_etc {
|
prebuilt_etc {
|
||||||
@@ -58,13 +65,20 @@ android_library_import {
|
|||||||
|
|
||||||
java_library {
|
java_library {
|
||||||
name: "androidx.window.extensions",
|
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"],
|
static_libs: ["window-extensions"],
|
||||||
installable: true,
|
installable: true,
|
||||||
sdk_version: "core_platform",
|
sdk_version: "core_platform",
|
||||||
system_ext_specific: true,
|
system_ext_specific: true,
|
||||||
libs: ["framework", "androidx.annotation_annotation",],
|
libs: [
|
||||||
required: ["androidx.window.extensions.xml",],
|
"framework",
|
||||||
|
"androidx.annotation_annotation",
|
||||||
|
],
|
||||||
|
required: ["androidx.window.extensions.xml"],
|
||||||
}
|
}
|
||||||
|
|
||||||
prebuilt_etc {
|
prebuilt_etc {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,39 +14,23 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package androidx.window.util;
|
package androidx.window.common;
|
||||||
|
|
||||||
|
import android.annotation.Nullable;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
/** Wrapper for both Extension and Sidecar versions of DisplayFeature. */
|
/** Wrapper for both Extension and Sidecar versions of DisplayFeature. */
|
||||||
public class BaseDisplayFeature {
|
public interface DisplayFeature {
|
||||||
private final int mType;
|
/** Returns the type of the feature. */
|
||||||
private final int mState;
|
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
|
@NonNull
|
||||||
public final Rect mRect;
|
Rect getRect();
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,11 +27,17 @@ import android.graphics.Rect;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.window.util.BaseDisplayFeature;
|
import androidx.window.common.DeviceStateManagerPostureProducer;
|
||||||
import androidx.window.util.SettingsConfigProvider;
|
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.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reference implementation of androidx.window.extensions OEM interface for use with
|
* 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.
|
* production builds since the interface can still change before reaching stable version.
|
||||||
* Please refer to {@link androidx.window.sidecar.SampleSidecarImpl} instead.
|
* Please refer to {@link androidx.window.sidecar.SampleSidecarImpl} instead.
|
||||||
*/
|
*/
|
||||||
class SampleExtensionImpl extends StubExtension implements
|
class SampleExtensionImpl extends StubExtension {
|
||||||
SettingsConfigProvider.StateChangeCallback {
|
|
||||||
private static final String TAG = "SampleExtension";
|
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) {
|
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
|
private void onDevicePostureChanged() {
|
||||||
public void onDevicePostureChanged() {
|
updateDeviceState(new ExtensionDeviceState(getDevicePosture()));
|
||||||
updateDeviceState(new ExtensionDeviceState(mConfigProvider.getDeviceState()));
|
|
||||||
|
// 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
|
private int getDevicePosture() {
|
||||||
public void onDisplayFeaturesChanged() {
|
Optional<Integer> posture = mDevicePostureProducer.getData();
|
||||||
|
return posture.orElse(ExtensionDeviceState.POSTURE_UNKNOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDisplayFeaturesChanged() {
|
||||||
for (Activity activity : getActivitiesListeningForLayoutChanges()) {
|
for (Activity activity : getActivitiesListeningForLayoutChanges()) {
|
||||||
ExtensionWindowLayoutInfo newLayout = getWindowLayoutInfo(activity);
|
ExtensionWindowLayoutInfo newLayout = getWindowLayoutInfo(activity);
|
||||||
updateWindowLayout(activity, newLayout);
|
updateWindowLayout(activity, newLayout);
|
||||||
@@ -84,13 +113,20 @@ class SampleExtensionImpl extends StubExtension implements
|
|||||||
return features;
|
return features;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<BaseDisplayFeature> storedFeatures = mConfigProvider.getDisplayFeatures();
|
Optional<List<DisplayFeature>> storedFeatures = mDisplayFeatureProducer.getData();
|
||||||
for (BaseDisplayFeature baseFeature : storedFeatures) {
|
if (storedFeatures.isPresent()) {
|
||||||
Rect featureRect = baseFeature.getRect();
|
int posture = getDevicePosture();
|
||||||
rotateRectToDisplayRotation(displayId, featureRect);
|
|
||||||
transformToWindowSpaceRect(activity, featureRect);
|
for (DisplayFeature baseFeature : storedFeatures.get()) {
|
||||||
features.add(new ExtensionFoldingFeature(featureRect, baseFeature.getType(),
|
Rect featureRect = baseFeature.getRect();
|
||||||
baseFeature.getState()));
|
rotateRectToDisplayRotation(displayId, featureRect);
|
||||||
|
transformToWindowSpaceRect(activity, featureRect);
|
||||||
|
|
||||||
|
Integer featureState = baseFeature.getState();
|
||||||
|
|
||||||
|
features.add(new ExtensionFoldingFeature(featureRect, baseFeature.getType(),
|
||||||
|
featureState == null ? posture : featureState));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return features;
|
return features;
|
||||||
}
|
}
|
||||||
@@ -98,9 +134,11 @@ class SampleExtensionImpl extends StubExtension implements
|
|||||||
@Override
|
@Override
|
||||||
protected void onListenersChanged() {
|
protected void onListenersChanged() {
|
||||||
if (hasListeners()) {
|
if (hasListeners()) {
|
||||||
mConfigProvider.registerObserversIfNeeded();
|
mSettingsDevicePostureProducer.registerObserversIfNeeded();
|
||||||
|
mSettingsDisplayFeatureProducer.registerObserversIfNeeded();
|
||||||
} else {
|
} else {
|
||||||
mConfigProvider.unregisterObserversIfNeeded();
|
mSettingsDevicePostureProducer.unregisterObserversIfNeeded();
|
||||||
|
mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
onDevicePostureChanged();
|
onDevicePostureChanged();
|
||||||
|
|||||||
@@ -29,33 +29,53 @@ import android.os.IBinder;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.window.util.BaseDisplayFeature;
|
import androidx.window.common.DeviceStateManagerPostureProducer;
|
||||||
import androidx.window.util.SettingsConfigProvider;
|
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.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reference implementation of androidx.window.sidecar OEM interface for use with
|
* Reference implementation of androidx.window.sidecar OEM interface for use with
|
||||||
* WindowManager Jetpack.
|
* WindowManager Jetpack.
|
||||||
*/
|
*/
|
||||||
class SampleSidecarImpl extends StubSidecar implements
|
class SampleSidecarImpl extends StubSidecar {
|
||||||
SettingsConfigProvider.StateChangeCallback {
|
|
||||||
private static final String TAG = "SampleSidecar";
|
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) {
|
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
|
private void onDevicePostureChanged() {
|
||||||
public void onDevicePostureChanged() {
|
|
||||||
updateDeviceState(getDeviceState());
|
updateDeviceState(getDeviceState());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void onDisplayFeaturesChanged() {
|
||||||
public void onDisplayFeaturesChanged() {
|
|
||||||
for (IBinder windowToken : getWindowsListeningForLayoutChanges()) {
|
for (IBinder windowToken : getWindowsListeningForLayoutChanges()) {
|
||||||
SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken);
|
SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken);
|
||||||
updateWindowLayout(windowToken, newLayout);
|
updateWindowLayout(windowToken, newLayout);
|
||||||
@@ -65,8 +85,10 @@ class SampleSidecarImpl extends StubSidecar implements
|
|||||||
@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 = mConfigProvider.getDeviceState();
|
deviceState.posture = posture.orElse(SidecarDeviceState.POSTURE_UNKNOWN);
|
||||||
return deviceState;
|
return deviceState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,15 +118,17 @@ class SampleSidecarImpl extends StubSidecar implements
|
|||||||
return features;
|
return features;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<BaseDisplayFeature> storedFeatures = mConfigProvider.getDisplayFeatures();
|
Optional<List<DisplayFeature>> storedFeatures = mDisplayFeatureProducer.getData();
|
||||||
for (BaseDisplayFeature baseFeature : storedFeatures) {
|
if (storedFeatures.isPresent()) {
|
||||||
SidecarDisplayFeature feature = new SidecarDisplayFeature();
|
for (DisplayFeature baseFeature : storedFeatures.get()) {
|
||||||
Rect featureRect = baseFeature.getRect();
|
SidecarDisplayFeature feature = new SidecarDisplayFeature();
|
||||||
rotateRectToDisplayRotation(displayId, featureRect);
|
Rect featureRect = baseFeature.getRect();
|
||||||
transformToWindowSpaceRect(activity, featureRect);
|
rotateRectToDisplayRotation(displayId, featureRect);
|
||||||
feature.setRect(featureRect);
|
transformToWindowSpaceRect(activity, featureRect);
|
||||||
feature.setType(baseFeature.getType());
|
feature.setRect(featureRect);
|
||||||
features.add(feature);
|
feature.setType(baseFeature.getType());
|
||||||
|
features.add(feature);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return features;
|
return features;
|
||||||
}
|
}
|
||||||
@@ -112,9 +136,11 @@ class SampleSidecarImpl extends StubSidecar implements
|
|||||||
@Override
|
@Override
|
||||||
protected void onListenersChanged() {
|
protected void onListenersChanged() {
|
||||||
if (hasListeners()) {
|
if (hasListeners()) {
|
||||||
mConfigProvider.registerObserversIfNeeded();
|
mSettingsDevicePostureProducer.registerObserversIfNeeded();
|
||||||
|
mSettingsDisplayFeatureProducer.registerObserversIfNeeded();
|
||||||
} else {
|
} else {
|
||||||
mConfigProvider.unregisterObserversIfNeeded();
|
mSettingsDevicePostureProducer.unregisterObserversIfNeeded();
|
||||||
|
mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user