Merge changes from topic "wm-config-change-fix" into sc-v2-dev
* changes: DO NOT MERGE Listen for config changes when updating features. DO NOT MERGE Add LifecycleCallback adapter. DO NOT MERGE Merge state producer and feature producer. DO NOT MERGE Remove DisplayFeature from common package.
This commit is contained in:
@@ -18,17 +18,73 @@ package androidx.window.common;
|
|||||||
|
|
||||||
import static androidx.window.util.ExtensionHelper.isZero;
|
import static androidx.window.util.ExtensionHelper.isZero;
|
||||||
|
|
||||||
|
import android.annotation.IntDef;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/** Wrapper for both Extension and Sidecar versions of DisplayFeature. */
|
/** A representation of a folding feature for both Extension and Sidecar.
|
||||||
final class CommonDisplayFeature implements DisplayFeature {
|
* For Sidecar this is the same as combining {@link androidx.window.sidecar.SidecarDeviceState} and
|
||||||
|
* {@link androidx.window.sidecar.SidecarDisplayFeature}. For Extensions this is the mirror of
|
||||||
|
* {@link androidx.window.extensions.layout.FoldingFeature}.
|
||||||
|
*/
|
||||||
|
public final class CommonFoldingFeature {
|
||||||
|
|
||||||
|
private static final boolean DEBUG = false;
|
||||||
|
|
||||||
|
public static final String TAG = CommonFoldingFeature.class.getSimpleName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A common type to represent a hinge where the screen is continuous.
|
||||||
|
*/
|
||||||
|
public static final int COMMON_TYPE_FOLD = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A common type to represent a hinge where there is a physical gap separating multiple
|
||||||
|
* displays.
|
||||||
|
*/
|
||||||
|
public static final int COMMON_TYPE_HINGE = 2;
|
||||||
|
|
||||||
|
@IntDef({COMMON_TYPE_FOLD, COMMON_TYPE_HINGE})
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
public @interface Type {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A common state to represent when the state is not known. One example is if the device is
|
||||||
|
* closed. We do not emit this value for developers but is useful for implementation reasons.
|
||||||
|
*/
|
||||||
|
public static final int COMMON_STATE_UNKNOWN = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A common state to represent a FLAT hinge. This is needed because the definitions in Sidecar
|
||||||
|
* and Extensions do not match exactly.
|
||||||
|
*/
|
||||||
|
public static final int COMMON_STATE_FLAT = 3;
|
||||||
|
/**
|
||||||
|
* A common state to represent a HALF_OPENED hinge. This is needed because the definitions in
|
||||||
|
* Sidecar and Extensions do not match exactly.
|
||||||
|
*/
|
||||||
|
public static final int COMMON_STATE_HALF_OPENED = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The possible states for a folding hinge.
|
||||||
|
*/
|
||||||
|
@IntDef({COMMON_STATE_FLAT, COMMON_STATE_HALF_OPENED})
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
public @interface State {
|
||||||
|
}
|
||||||
|
|
||||||
private static final Pattern FEATURE_PATTERN =
|
private static final Pattern FEATURE_PATTERN =
|
||||||
Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]-?(flat|half-opened)?");
|
Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]-?(flat|half-opened)?");
|
||||||
|
|
||||||
@@ -38,17 +94,49 @@ final class CommonDisplayFeature implements DisplayFeature {
|
|||||||
private static final String PATTERN_STATE_FLAT = "flat";
|
private static final String PATTERN_STATE_FLAT = "flat";
|
||||||
private static final String PATTERN_STATE_HALF_OPENED = "half-opened";
|
private static final String PATTERN_STATE_HALF_OPENED = "half-opened";
|
||||||
|
|
||||||
// TODO(b/183049815): Support feature strings that include the state of the feature.
|
/**
|
||||||
|
* Parse a {@link List} of {@link CommonFoldingFeature} from a {@link String}.
|
||||||
|
* @param value a {@link String} representation of multiple {@link CommonFoldingFeature}
|
||||||
|
* separated by a ":".
|
||||||
|
* @param hingeState a global fallback value for a {@link CommonFoldingFeature} if one is not
|
||||||
|
* specified in the input.
|
||||||
|
* @throws IllegalArgumentException if the provided string is improperly formatted or could not
|
||||||
|
* otherwise be parsed.
|
||||||
|
* @see #FEATURE_PATTERN
|
||||||
|
* @return {@link List} of {@link CommonFoldingFeature}.
|
||||||
|
*/
|
||||||
|
static List<CommonFoldingFeature> parseListFromString(@NonNull String value,
|
||||||
|
@State int hingeState) {
|
||||||
|
List<CommonFoldingFeature> features = new ArrayList<>();
|
||||||
|
String[] featureStrings = value.split(";");
|
||||||
|
for (String featureString : featureStrings) {
|
||||||
|
CommonFoldingFeature feature;
|
||||||
|
try {
|
||||||
|
feature = CommonFoldingFeature.parseFromString(featureString, hingeState);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.w(TAG, "Failed to parse display feature: " + featureString, e);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
features.add(feature);
|
||||||
|
}
|
||||||
|
return features;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a display feature from a string.
|
* Parses a display feature from a string.
|
||||||
*
|
*
|
||||||
|
* @param string A {@link String} representation of a {@link CommonFoldingFeature}.
|
||||||
|
* @param hingeState A fallback value for the {@link State} if it is not specified in the input.
|
||||||
* @throws IllegalArgumentException if the provided string is improperly formatted or could not
|
* @throws IllegalArgumentException if the provided string is improperly formatted or could not
|
||||||
* otherwise be parsed.
|
* otherwise be parsed.
|
||||||
|
* @return {@link CommonFoldingFeature} represented by the {@link String} value.
|
||||||
* @see #FEATURE_PATTERN
|
* @see #FEATURE_PATTERN
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
static CommonDisplayFeature parseFromString(@NonNull String string) {
|
private static CommonFoldingFeature parseFromString(@NonNull String string,
|
||||||
|
@State int hingeState) {
|
||||||
Matcher featureMatcher = FEATURE_PATTERN.matcher(string);
|
Matcher featureMatcher = FEATURE_PATTERN.matcher(string);
|
||||||
if (!featureMatcher.matches()) {
|
if (!featureMatcher.matches()) {
|
||||||
throw new IllegalArgumentException("Malformed feature description format: " + string);
|
throw new IllegalArgumentException("Malformed feature description format: " + string);
|
||||||
@@ -59,10 +147,10 @@ final class CommonDisplayFeature implements DisplayFeature {
|
|||||||
int type;
|
int type;
|
||||||
switch (featureType) {
|
switch (featureType) {
|
||||||
case FEATURE_TYPE_FOLD:
|
case FEATURE_TYPE_FOLD:
|
||||||
type = 1 /* TYPE_FOLD */;
|
type = COMMON_TYPE_FOLD;
|
||||||
break;
|
break;
|
||||||
case FEATURE_TYPE_HINGE:
|
case FEATURE_TYPE_HINGE:
|
||||||
type = 2 /* TYPE_HINGE */;
|
type = COMMON_TYPE_HINGE;
|
||||||
break;
|
break;
|
||||||
default: {
|
default: {
|
||||||
throw new IllegalArgumentException("Malformed feature type: " + featureType);
|
throw new IllegalArgumentException("Malformed feature type: " + featureType);
|
||||||
@@ -79,7 +167,7 @@ final class CommonDisplayFeature implements DisplayFeature {
|
|||||||
}
|
}
|
||||||
String stateString = featureMatcher.group(6);
|
String stateString = featureMatcher.group(6);
|
||||||
stateString = stateString == null ? "" : stateString;
|
stateString = stateString == null ? "" : stateString;
|
||||||
Integer state;
|
final int state;
|
||||||
switch (stateString) {
|
switch (stateString) {
|
||||||
case PATTERN_STATE_FLAT:
|
case PATTERN_STATE_FLAT:
|
||||||
state = COMMON_STATE_FLAT;
|
state = COMMON_STATE_FLAT;
|
||||||
@@ -88,10 +176,10 @@ final class CommonDisplayFeature implements DisplayFeature {
|
|||||||
state = COMMON_STATE_HALF_OPENED;
|
state = COMMON_STATE_HALF_OPENED;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
state = null;
|
state = hingeState;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return new CommonDisplayFeature(type, state, featureRect);
|
return new CommonFoldingFeature(type, state, featureRect);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new IllegalArgumentException("Malformed feature description: " + string, e);
|
throw new IllegalArgumentException("Malformed feature description: " + string, e);
|
||||||
}
|
}
|
||||||
@@ -99,11 +187,11 @@ final class CommonDisplayFeature implements DisplayFeature {
|
|||||||
|
|
||||||
private final int mType;
|
private final int mType;
|
||||||
@Nullable
|
@Nullable
|
||||||
private final Integer mState;
|
private final int mState;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final Rect mRect;
|
private final Rect mRect;
|
||||||
|
|
||||||
CommonDisplayFeature(int type, @Nullable Integer state, @NonNull Rect rect) {
|
CommonFoldingFeature(int type, int state, @NonNull Rect rect) {
|
||||||
assertValidState(state);
|
assertValidState(state);
|
||||||
this.mType = type;
|
this.mType = type;
|
||||||
this.mState = state;
|
this.mState = state;
|
||||||
@@ -114,16 +202,19 @@ final class CommonDisplayFeature implements DisplayFeature {
|
|||||||
this.mRect = rect;
|
this.mRect = rect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the type of the feature. */
|
||||||
|
@Type
|
||||||
public int getType() {
|
public int getType() {
|
||||||
return mType;
|
return mType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the state of the feature, or {@code null} if the feature has no state. */
|
/** Returns the state of the feature, or {@code null} if the feature has no state. */
|
||||||
@Nullable
|
@State
|
||||||
public Integer getState() {
|
public int getState() {
|
||||||
return mState;
|
return mState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the bounds of the feature. */
|
||||||
@NonNull
|
@NonNull
|
||||||
public Rect getRect() {
|
public Rect getRect() {
|
||||||
return mRect;
|
return mRect;
|
||||||
@@ -133,7 +224,7 @@ final class CommonDisplayFeature implements DisplayFeature {
|
|||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
CommonDisplayFeature that = (CommonDisplayFeature) o;
|
CommonFoldingFeature that = (CommonFoldingFeature) o;
|
||||||
return mType == that.mType
|
return mType == that.mType
|
||||||
&& Objects.equals(mState, that.mState)
|
&& Objects.equals(mState, that.mState)
|
||||||
&& mRect.equals(that.mRect);
|
&& mRect.equals(that.mRect);
|
||||||
@@ -18,11 +18,15 @@ package androidx.window.common;
|
|||||||
|
|
||||||
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
|
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
|
||||||
|
|
||||||
|
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
|
||||||
|
import static androidx.window.common.CommonFoldingFeature.parseListFromString;
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.hardware.devicestate.DeviceStateManager;
|
import android.hardware.devicestate.DeviceStateManager;
|
||||||
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
|
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.SparseIntArray;
|
import android.util.SparseIntArray;
|
||||||
|
|
||||||
@@ -30,6 +34,7 @@ import androidx.window.util.BaseDataProducer;
|
|||||||
|
|
||||||
import com.android.internal.R;
|
import com.android.internal.R;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,10 +42,13 @@ import java.util.Optional;
|
|||||||
* by mapping the state returned from {@link DeviceStateManager} to values provided in the resources
|
* by mapping the state returned from {@link DeviceStateManager} to values provided in the resources
|
||||||
* config at {@link R.array#config_device_state_postures}.
|
* config at {@link R.array#config_device_state_postures}.
|
||||||
*/
|
*/
|
||||||
public final class DeviceStateManagerPostureProducer extends BaseDataProducer<Integer> {
|
public final class DeviceStateManagerFoldingFeatureProducer extends
|
||||||
private static final String TAG = "ConfigDevicePostureProducer";
|
BaseDataProducer<List<CommonFoldingFeature>> {
|
||||||
|
private static final String TAG =
|
||||||
|
DeviceStateManagerFoldingFeatureProducer.class.getSimpleName();
|
||||||
private static final boolean DEBUG = false;
|
private static final boolean DEBUG = false;
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();
|
private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();
|
||||||
|
|
||||||
private int mCurrentDeviceState = INVALID_DEVICE_STATE;
|
private int mCurrentDeviceState = INVALID_DEVICE_STATE;
|
||||||
@@ -50,7 +58,8 @@ public final class DeviceStateManagerPostureProducer extends BaseDataProducer<In
|
|||||||
notifyDataChanged();
|
notifyDataChanged();
|
||||||
};
|
};
|
||||||
|
|
||||||
public DeviceStateManagerPostureProducer(@NonNull Context context) {
|
public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context) {
|
||||||
|
mContext = context;
|
||||||
String[] deviceStatePosturePairs = context.getResources()
|
String[] deviceStatePosturePairs = context.getResources()
|
||||||
.getStringArray(R.array.config_device_state_postures);
|
.getStringArray(R.array.config_device_state_postures);
|
||||||
for (String deviceStatePosturePair : deviceStatePosturePairs) {
|
for (String deviceStatePosturePair : deviceStatePosturePairs) {
|
||||||
@@ -86,8 +95,17 @@ public final class DeviceStateManagerPostureProducer extends BaseDataProducer<In
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public Optional<Integer> getData() {
|
public Optional<List<CommonFoldingFeature>> getData() {
|
||||||
final int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState, -1);
|
final int globalHingeState = globalHingeState();
|
||||||
return posture != -1 ? Optional.of(posture) : Optional.empty();
|
String displayFeaturesString = mContext.getResources().getString(
|
||||||
|
R.string.config_display_features);
|
||||||
|
if (TextUtils.isEmpty(displayFeaturesString)) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.of(parseListFromString(displayFeaturesString, globalHingeState));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int globalHingeState() {
|
||||||
|
return mDeviceStateToPostureMap.get(mCurrentDeviceState, COMMON_STATE_UNKNOWN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package androidx.window.common;
|
|
||||||
|
|
||||||
import android.annotation.IntDef;
|
|
||||||
import android.annotation.Nullable;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
|
|
||||||
/** Wrapper for both Extension and Sidecar versions of DisplayFeature. */
|
|
||||||
public interface DisplayFeature {
|
|
||||||
/** Returns the type of the feature. */
|
|
||||||
int getType();
|
|
||||||
|
|
||||||
/** Returns the state of the feature, or {@code null} if the feature has no state. */
|
|
||||||
@Nullable
|
|
||||||
@State
|
|
||||||
Integer getState();
|
|
||||||
|
|
||||||
/** Returns the bounds of the feature. */
|
|
||||||
@NonNull
|
|
||||||
Rect getRect();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A common state to represent a FLAT hinge. This is needed because the definitions in Sidecar
|
|
||||||
* and Extensions do not match exactly.
|
|
||||||
*/
|
|
||||||
int COMMON_STATE_FLAT = 3;
|
|
||||||
/**
|
|
||||||
* A common state to represent a HALF_OPENED hinge. This is needed because the definitions in
|
|
||||||
* Sidecar and Extensions do not match exactly.
|
|
||||||
*/
|
|
||||||
int COMMON_STATE_HALF_OPENED = 2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The possible states for a folding hinge.
|
|
||||||
*/
|
|
||||||
@IntDef({COMMON_STATE_FLAT, COMMON_STATE_HALF_OPENED})
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
@interface State {}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* 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.app.Activity;
|
||||||
|
import android.app.Application;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An empty implementation of {@link Application.ActivityLifecycleCallbacks} derived classes can
|
||||||
|
* implement the methods necessary.
|
||||||
|
*/
|
||||||
|
public class EmptyLifecycleCallbacksAdapter implements Application.ActivityLifecycleCallbacks {
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityStarted(Activity activity) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResumed(Activity activity) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityPaused(Activity activity) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityStopped(Activity activity) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityDestroyed(Activity activity) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package androidx.window.common;
|
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
|
||||||
import android.annotation.Nullable;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.window.util.BaseDataProducer;
|
|
||||||
|
|
||||||
import com.android.internal.R;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementation of {@link androidx.window.util.DataProducer} that produces
|
|
||||||
* {@link CommonDisplayFeature} parsed from a string stored in the resources config at
|
|
||||||
* {@link R.string#config_display_features}.
|
|
||||||
*/
|
|
||||||
public final class ResourceConfigDisplayFeatureProducer extends
|
|
||||||
BaseDataProducer<List<DisplayFeature>> {
|
|
||||||
private static final boolean DEBUG = false;
|
|
||||||
private static final String TAG = "ResourceConfigDisplayFeatureProducer";
|
|
||||||
|
|
||||||
private final Context mContext;
|
|
||||||
|
|
||||||
public ResourceConfigDisplayFeatureProducer(@NonNull Context context) {
|
|
||||||
mContext = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Nullable
|
|
||||||
public Optional<List<DisplayFeature>> getData() {
|
|
||||||
String displayFeaturesString = mContext.getResources().getString(
|
|
||||||
R.string.config_display_features);
|
|
||||||
if (TextUtils.isEmpty(displayFeaturesString)) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<DisplayFeature> features = new ArrayList<>();
|
|
||||||
String[] featureStrings = displayFeaturesString.split(";");
|
|
||||||
for (String featureString : featureStrings) {
|
|
||||||
CommonDisplayFeature feature;
|
|
||||||
try {
|
|
||||||
feature = CommonDisplayFeature.parseFromString(featureString);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.w(TAG, "Failed to parse display feature: " + featureString, e);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
features.add(feature);
|
|
||||||
}
|
|
||||||
return Optional.of(features);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package androidx.window.common;
|
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
|
||||||
import android.annotation.Nullable;
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.ContentObserver;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.provider.Settings;
|
|
||||||
|
|
||||||
import androidx.window.util.BaseDataProducer;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementation of {@link androidx.window.util.DataProducer} that provides the device posture
|
|
||||||
* as an {@link Integer} from a value stored in {@link Settings}.
|
|
||||||
*/
|
|
||||||
public final class SettingsDevicePostureProducer extends BaseDataProducer<Integer> {
|
|
||||||
private static final String DEVICE_POSTURE = "device_posture";
|
|
||||||
|
|
||||||
private final Uri mDevicePostureUri =
|
|
||||||
Settings.Global.getUriFor(DEVICE_POSTURE);
|
|
||||||
|
|
||||||
private final ContentResolver mResolver;
|
|
||||||
private final ContentObserver mObserver;
|
|
||||||
private boolean mRegisteredObservers;
|
|
||||||
|
|
||||||
public SettingsDevicePostureProducer(@NonNull Context context) {
|
|
||||||
mResolver = context.getContentResolver();
|
|
||||||
mObserver = new SettingsObserver();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Nullable
|
|
||||||
public Optional<Integer> getData() {
|
|
||||||
int posture = Settings.Global.getInt(mResolver, DEVICE_POSTURE, -1);
|
|
||||||
return posture == -1 ? Optional.empty() : Optional.of(posture);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers settings observers, if needed. When settings observers are registered for this
|
|
||||||
* producer callbacks for changes in data will be triggered.
|
|
||||||
*/
|
|
||||||
public void registerObserversIfNeeded() {
|
|
||||||
if (mRegisteredObservers) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mRegisteredObservers = true;
|
|
||||||
mResolver.registerContentObserver(mDevicePostureUri, false /* notifyForDescendants */,
|
|
||||||
mObserver /* ContentObserver */);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregisters settings observers, if needed. When settings observers are unregistered for this
|
|
||||||
* producer callbacks for changes in data will not be triggered.
|
|
||||||
*/
|
|
||||||
public void unregisterObserversIfNeeded() {
|
|
||||||
if (!mRegisteredObservers) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mRegisteredObservers = false;
|
|
||||||
mResolver.unregisterContentObserver(mObserver);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class SettingsObserver extends ContentObserver {
|
|
||||||
SettingsObserver() {
|
|
||||||
super(new Handler(Looper.getMainLooper()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChange(boolean selfChange, Uri uri) {
|
|
||||||
if (mDevicePostureUri.equals(uri)) {
|
|
||||||
notifyDataChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,8 +16,10 @@
|
|||||||
|
|
||||||
package androidx.window.common;
|
package androidx.window.common;
|
||||||
|
|
||||||
|
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
|
||||||
|
import static androidx.window.common.CommonFoldingFeature.parseListFromString;
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.ContentObserver;
|
import android.database.ContentObserver;
|
||||||
@@ -26,22 +28,19 @@ import android.os.Handler;
|
|||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.window.util.BaseDataProducer;
|
import androidx.window.util.BaseDataProducer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of {@link androidx.window.util.DataProducer} that produces
|
* Implementation of {@link androidx.window.util.DataProducer} that produces
|
||||||
* {@link CommonDisplayFeature} parsed from a string stored in {@link Settings}.
|
* {@link CommonFoldingFeature} parsed from a string stored in {@link Settings}.
|
||||||
*/
|
*/
|
||||||
public final class SettingsDisplayFeatureProducer
|
public final class SettingsDisplayFeatureProducer
|
||||||
extends BaseDataProducer<List<DisplayFeature>> {
|
extends BaseDataProducer<List<CommonFoldingFeature>> {
|
||||||
private static final boolean DEBUG = false;
|
|
||||||
private static final String TAG = "SettingsDisplayFeatureProducer";
|
|
||||||
private static final String DISPLAY_FEATURES = "display_features";
|
private static final String DISPLAY_FEATURES = "display_features";
|
||||||
|
|
||||||
private final Uri mDisplayFeaturesUri =
|
private final Uri mDisplayFeaturesUri =
|
||||||
@@ -57,32 +56,17 @@ public final class SettingsDisplayFeatureProducer
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@NonNull
|
||||||
public Optional<List<DisplayFeature>> getData() {
|
public Optional<List<CommonFoldingFeature>> getData() {
|
||||||
String displayFeaturesString = Settings.Global.getString(mResolver, DISPLAY_FEATURES);
|
String displayFeaturesString = Settings.Global.getString(mResolver, DISPLAY_FEATURES);
|
||||||
if (displayFeaturesString == null) {
|
if (displayFeaturesString == null) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<DisplayFeature> features = new ArrayList<>();
|
|
||||||
if (TextUtils.isEmpty(displayFeaturesString)) {
|
if (TextUtils.isEmpty(displayFeaturesString)) {
|
||||||
return Optional.of(features);
|
return Optional.of(Collections.emptyList());
|
||||||
}
|
}
|
||||||
String[] featureStrings = displayFeaturesString.split(";");
|
return Optional.of(parseListFromString(displayFeaturesString, COMMON_STATE_UNKNOWN));
|
||||||
|
|
||||||
for (String featureString : featureStrings) {
|
|
||||||
CommonDisplayFeature feature;
|
|
||||||
try {
|
|
||||||
feature = CommonDisplayFeature.parseFromString(featureString);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.w(TAG, "Failed to parse display feature: " + featureString, e);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
features.add(feature);
|
|
||||||
}
|
|
||||||
return Optional.of(features);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import android.app.Activity;
|
|||||||
import android.app.ActivityClient;
|
import android.app.ActivityClient;
|
||||||
import android.app.ActivityOptions;
|
import android.app.ActivityOptions;
|
||||||
import android.app.ActivityThread;
|
import android.app.ActivityThread;
|
||||||
import android.app.Application.ActivityLifecycleCallbacks;
|
|
||||||
import android.app.Instrumentation;
|
import android.app.Instrumentation;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -41,6 +40,8 @@ import android.os.Looper;
|
|||||||
import android.window.TaskFragmentInfo;
|
import android.window.TaskFragmentInfo;
|
||||||
import android.window.WindowContainerTransaction;
|
import android.window.WindowContainerTransaction;
|
||||||
|
|
||||||
|
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -759,11 +760,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
return shouldRetainAssociatedContainer(finishingContainer, associatedContainer);
|
return shouldRetainAssociatedContainer(finishingContainer, associatedContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class LifecycleCallbacks implements ActivityLifecycleCallbacks {
|
private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityPostCreated(Activity activity, Bundle savedInstanceState) {
|
public void onActivityPostCreated(Activity activity, Bundle savedInstanceState) {
|
||||||
@@ -774,30 +771,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
SplitController.this.onActivityCreated(activity);
|
SplitController.this.onActivityCreated(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityStarted(Activity activity) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResumed(Activity activity) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityPaused(Activity activity) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityStopped(Activity activity) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityDestroyed(Activity activity) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityConfigurationChanged(Activity activity) {
|
public void onActivityConfigurationChanged(Activity activity) {
|
||||||
SplitController.this.onActivityConfigurationChanged(activity);
|
SplitController.this.onActivityConfigurationChanged(activity);
|
||||||
|
|||||||
@@ -18,22 +18,24 @@ package androidx.window.extensions.layout;
|
|||||||
|
|
||||||
import static android.view.Display.DEFAULT_DISPLAY;
|
import static android.view.Display.DEFAULT_DISPLAY;
|
||||||
|
|
||||||
import static androidx.window.common.DisplayFeature.COMMON_STATE_FLAT;
|
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT;
|
||||||
import static androidx.window.common.DisplayFeature.COMMON_STATE_HALF_OPENED;
|
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED;
|
||||||
import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
|
import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
|
||||||
import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
|
import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
|
||||||
|
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.window.common.DeviceStateManagerPostureProducer;
|
import androidx.window.common.CommonFoldingFeature;
|
||||||
import androidx.window.common.DisplayFeature;
|
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
|
||||||
import androidx.window.common.ResourceConfigDisplayFeatureProducer;
|
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
|
||||||
import androidx.window.common.SettingsDevicePostureProducer;
|
|
||||||
import androidx.window.common.SettingsDisplayFeatureProducer;
|
import androidx.window.common.SettingsDisplayFeatureProducer;
|
||||||
import androidx.window.util.DataProducer;
|
import androidx.window.util.DataProducer;
|
||||||
import androidx.window.util.PriorityDataProducer;
|
import androidx.window.util.PriorityDataProducer;
|
||||||
@@ -56,36 +58,27 @@ import java.util.function.Consumer;
|
|||||||
*/
|
*/
|
||||||
public class WindowLayoutComponentImpl implements WindowLayoutComponent {
|
public class WindowLayoutComponentImpl implements WindowLayoutComponent {
|
||||||
private static final String TAG = "SampleExtension";
|
private static final String TAG = "SampleExtension";
|
||||||
private static WindowLayoutComponent sInstance;
|
|
||||||
|
|
||||||
private final Map<Activity, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners =
|
private final Map<Activity, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners =
|
||||||
new HashMap<>();
|
new HashMap<>();
|
||||||
|
|
||||||
private final SettingsDevicePostureProducer mSettingsDevicePostureProducer;
|
|
||||||
private final DataProducer<Integer> mDevicePostureProducer;
|
|
||||||
|
|
||||||
private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer;
|
private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer;
|
||||||
private final DataProducer<List<DisplayFeature>> mDisplayFeatureProducer;
|
private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
|
||||||
|
|
||||||
public WindowLayoutComponentImpl(Context context) {
|
public WindowLayoutComponentImpl(Context context) {
|
||||||
mSettingsDevicePostureProducer = new SettingsDevicePostureProducer(context);
|
((Application) context.getApplicationContext())
|
||||||
mDevicePostureProducer = new PriorityDataProducer<>(List.of(
|
.registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
|
||||||
mSettingsDevicePostureProducer,
|
|
||||||
new DeviceStateManagerPostureProducer(context)
|
|
||||||
));
|
|
||||||
|
|
||||||
mSettingsDisplayFeatureProducer = new SettingsDisplayFeatureProducer(context);
|
mSettingsDisplayFeatureProducer = new SettingsDisplayFeatureProducer(context);
|
||||||
mDisplayFeatureProducer = new PriorityDataProducer<>(List.of(
|
mFoldingFeatureProducer = new PriorityDataProducer<>(List.of(
|
||||||
mSettingsDisplayFeatureProducer,
|
mSettingsDisplayFeatureProducer,
|
||||||
new ResourceConfigDisplayFeatureProducer(context)
|
new DeviceStateManagerFoldingFeatureProducer(context)
|
||||||
));
|
));
|
||||||
|
mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
|
||||||
mDevicePostureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
|
|
||||||
mDisplayFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a listener interested in receiving updates to {@link WindowLayoutInfo}
|
* Adds a listener interested in receiving updates to {@link WindowLayoutInfo}
|
||||||
|
*
|
||||||
* @param activity hosting a {@link android.view.Window}
|
* @param activity hosting a {@link android.view.Window}
|
||||||
* @param consumer interested in receiving updates to {@link WindowLayoutInfo}
|
* @param consumer interested in receiving updates to {@link WindowLayoutInfo}
|
||||||
*/
|
*/
|
||||||
@@ -97,6 +90,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a listener no longer interested in receiving updates.
|
* Removes a listener no longer interested in receiving updates.
|
||||||
|
*
|
||||||
* @param consumer no longer interested in receiving updates to {@link WindowLayoutInfo}
|
* @param consumer no longer interested in receiving updates to {@link WindowLayoutInfo}
|
||||||
*/
|
*/
|
||||||
public void removeWindowLayoutInfoListener(
|
public void removeWindowLayoutInfoListener(
|
||||||
@@ -118,43 +112,34 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
|
|||||||
return mWindowLayoutChangeListeners.keySet();
|
return mWindowLayoutChangeListeners.keySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Boolean isListeningForLayoutChanges(IBinder token) {
|
||||||
|
for (Activity activity: getActivitiesListeningForLayoutChanges()) {
|
||||||
|
if (token.equals(activity.getWindow().getAttributes().token)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
protected boolean hasListeners() {
|
protected boolean hasListeners() {
|
||||||
return !mWindowLayoutChangeListeners.isEmpty();
|
return !mWindowLayoutChangeListeners.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the {@link DisplayFeature.State} from the feature or the device posture producer.
|
|
||||||
* If the given {@link DisplayFeature.State} is not valid then {@code null} will be returned.
|
|
||||||
* The {@link FoldingFeature} should be ignored in the case of an invalid
|
|
||||||
* {@link DisplayFeature.State}.
|
|
||||||
*
|
|
||||||
* @param feature a {@link DisplayFeature} to provide the feature state if present.
|
|
||||||
* @return {@link DisplayFeature.State} of the hinge if present or the state from the posture
|
|
||||||
* produce if present.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
private Integer getFeatureState(DisplayFeature feature) {
|
|
||||||
Integer featureState = feature.getState();
|
|
||||||
Optional<Integer> posture = mDevicePostureProducer.getData();
|
|
||||||
Integer state = featureState == null ? posture.orElse(null) : featureState;
|
|
||||||
return convertToExtensionState(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A convenience method to translate from the common feature state to the extensions feature
|
* A convenience method to translate from the common feature state to the extensions feature
|
||||||
* state. More specifically, translates from {@link DisplayFeature.State} to
|
* state. More specifically, translates from {@link CommonFoldingFeature.State} to
|
||||||
* {@link FoldingFeature.STATE_FLAT} or {@link FoldingFeature.STATE_HALF_OPENED}. If it is not
|
* {@link FoldingFeature.STATE_FLAT} or {@link FoldingFeature.STATE_HALF_OPENED}. If it is not
|
||||||
* possible to translate, then we will return a {@code null} value.
|
* possible to translate, then we will return a {@code null} value.
|
||||||
*
|
*
|
||||||
* @param state if it matches a value in {@link DisplayFeature.State}, {@code null} otherwise.
|
* @param state if it matches a value in {@link CommonFoldingFeature.State}, {@code null}
|
||||||
* @return a {@link FoldingFeature.STATE_FLAT} or {@link FoldingFeature.STATE_HALF_OPENED} if
|
* otherwise. @return a {@link FoldingFeature.STATE_FLAT} or
|
||||||
* the given state matches a value in {@link DisplayFeature.State} and {@code null} otherwise.
|
* {@link FoldingFeature.STATE_HALF_OPENED} if the given state matches a value in
|
||||||
|
* {@link CommonFoldingFeature.State} and {@code null} otherwise.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private Integer convertToExtensionState(@Nullable Integer state) {
|
private Integer convertToExtensionState(int state) {
|
||||||
if (state == null) { // The null check avoids a NullPointerException.
|
if (state == COMMON_STATE_FLAT) {
|
||||||
return null;
|
|
||||||
} else if (state == COMMON_STATE_FLAT) {
|
|
||||||
return FoldingFeature.STATE_FLAT;
|
return FoldingFeature.STATE_FLAT;
|
||||||
} else if (state == COMMON_STATE_HALF_OPENED) {
|
} else if (state == COMMON_STATE_HALF_OPENED) {
|
||||||
return FoldingFeature.STATE_HALF_OPENED;
|
return FoldingFeature.STATE_HALF_OPENED;
|
||||||
@@ -172,33 +157,30 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private WindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) {
|
private WindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) {
|
||||||
List<androidx.window.extensions.layout.DisplayFeature> displayFeatures =
|
List<DisplayFeature> displayFeatures = getDisplayFeatures(activity);
|
||||||
getDisplayFeatures(activity);
|
|
||||||
return new WindowLayoutInfo(displayFeatures);
|
return new WindowLayoutInfo(displayFeatures);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translate from the {@link DisplayFeature} to
|
* Translate from the {@link CommonFoldingFeature} to
|
||||||
* {@link androidx.window.extensions.layout.DisplayFeature} for a given {@link Activity}. If a
|
* {@link DisplayFeature} for a given {@link Activity}. If a
|
||||||
* {@link DisplayFeature} is not valid then it will be omitted.
|
* {@link CommonFoldingFeature} is not valid then it will be omitted.
|
||||||
*
|
*
|
||||||
* For a {@link FoldingFeature} the bounds are localized into the {@link Activity} window
|
* For a {@link FoldingFeature} the bounds are localized into the {@link Activity} window
|
||||||
* coordinate space and the state is calculated either from {@link DisplayFeature#getState()} or
|
* coordinate space and the state is calculated from {@link CommonFoldingFeature#getState()}.
|
||||||
* {@link #mDisplayFeatureProducer}. The state from {@link #mDisplayFeatureProducer} may not be
|
* The state from {@link #mFoldingFeatureProducer} may not be valid since
|
||||||
* valid since {@link #mDisplayFeatureProducer} is a general state controller. If the state is
|
* {@link #mFoldingFeatureProducer} is a general state controller. If the state is not valid,
|
||||||
* not valid, the {@link FoldingFeature} is omitted from the {@link List} of
|
* the {@link FoldingFeature} is omitted from the {@link List} of {@link DisplayFeature}. If
|
||||||
* {@link androidx.window.extensions.layout.DisplayFeature}. If the bounds are not valid,
|
* the bounds are not valid, constructing a {@link FoldingFeature} will throw an
|
||||||
* constructing a {@link FoldingFeature} will throw an {@link IllegalArgumentException} since
|
* {@link IllegalArgumentException} since this can cause negative UI effects down stream.
|
||||||
* this can cause negative UI effects down stream.
|
|
||||||
*
|
*
|
||||||
* @param activity a proxy for the {@link android.view.Window} that contains the
|
* @param activity a proxy for the {@link android.view.Window} that contains the
|
||||||
* {@link androidx.window.extensions.layout.DisplayFeature}.
|
* {@link DisplayFeature}.
|
||||||
* @return a {@link List} of valid {@link androidx.window.extensions.layout.DisplayFeature} that
|
* @return a {@link List} of valid {@link DisplayFeature} that
|
||||||
* are within the {@link android.view.Window} of the {@link Activity}
|
* are within the {@link android.view.Window} of the {@link Activity}
|
||||||
*/
|
*/
|
||||||
private List<androidx.window.extensions.layout.DisplayFeature> getDisplayFeatures(
|
private List<DisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
|
||||||
@NonNull Activity activity) {
|
List<DisplayFeature> features = new ArrayList<>();
|
||||||
List<androidx.window.extensions.layout.DisplayFeature> features = new ArrayList<>();
|
|
||||||
int displayId = activity.getDisplay().getDisplayId();
|
int displayId = activity.getDisplay().getDisplayId();
|
||||||
if (displayId != DEFAULT_DISPLAY) {
|
if (displayId != DEFAULT_DISPLAY) {
|
||||||
Log.w(TAG, "This sample doesn't support display features on secondary displays");
|
Log.w(TAG, "This sample doesn't support display features on secondary displays");
|
||||||
@@ -211,11 +193,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
|
|||||||
return features;
|
return features;
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<List<DisplayFeature>> storedFeatures = mDisplayFeatureProducer.getData();
|
Optional<List<CommonFoldingFeature>> storedFeatures = mFoldingFeatureProducer.getData();
|
||||||
if (storedFeatures.isPresent()) {
|
if (storedFeatures.isPresent()) {
|
||||||
|
for (CommonFoldingFeature baseFeature : storedFeatures.get()) {
|
||||||
for (DisplayFeature baseFeature : storedFeatures.get()) {
|
Integer state = convertToExtensionState(baseFeature.getState());
|
||||||
Integer state = getFeatureState(baseFeature);
|
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -223,8 +204,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
|
|||||||
rotateRectToDisplayRotation(displayId, featureRect);
|
rotateRectToDisplayRotation(displayId, featureRect);
|
||||||
transformToWindowSpaceRect(activity, featureRect);
|
transformToWindowSpaceRect(activity, featureRect);
|
||||||
|
|
||||||
features.add(new FoldingFeature(featureRect, baseFeature.getType(),
|
features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
|
||||||
getFeatureState(baseFeature)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return features;
|
return features;
|
||||||
@@ -232,13 +212,31 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
|
|||||||
|
|
||||||
private void updateRegistrations() {
|
private void updateRegistrations() {
|
||||||
if (hasListeners()) {
|
if (hasListeners()) {
|
||||||
mSettingsDevicePostureProducer.registerObserversIfNeeded();
|
|
||||||
mSettingsDisplayFeatureProducer.registerObserversIfNeeded();
|
mSettingsDisplayFeatureProducer.registerObserversIfNeeded();
|
||||||
} else {
|
} else {
|
||||||
mSettingsDevicePostureProducer.unregisterObserversIfNeeded();
|
|
||||||
mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded();
|
mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
onDisplayFeaturesChanged();
|
onDisplayFeaturesChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class NotifyOnConfigurationChanged extends EmptyLifecycleCallbacksAdapter {
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(activity, savedInstanceState);
|
||||||
|
onDisplayFeaturesChangedIfListening(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityConfigurationChanged(Activity activity) {
|
||||||
|
super.onActivityConfigurationChanged(activity);
|
||||||
|
onDisplayFeaturesChangedIfListening(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDisplayFeaturesChangedIfListening(Activity activity) {
|
||||||
|
IBinder token = activity.getWindow().getAttributes().token;
|
||||||
|
if (token == null || isListeningForLayoutChanges(token)) {
|
||||||
|
onDisplayFeaturesChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,16 +23,17 @@ import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ActivityThread;
|
import android.app.ActivityThread;
|
||||||
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.window.common.DeviceStateManagerPostureProducer;
|
import androidx.window.common.CommonFoldingFeature;
|
||||||
import androidx.window.common.DisplayFeature;
|
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
|
||||||
import androidx.window.common.ResourceConfigDisplayFeatureProducer;
|
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
|
||||||
import androidx.window.common.SettingsDevicePostureProducer;
|
|
||||||
import androidx.window.common.SettingsDisplayFeatureProducer;
|
import androidx.window.common.SettingsDisplayFeatureProducer;
|
||||||
import androidx.window.util.DataProducer;
|
import androidx.window.util.DataProducer;
|
||||||
import androidx.window.util.PriorityDataProducer;
|
import androidx.window.util.PriorityDataProducer;
|
||||||
@@ -48,36 +49,25 @@ import java.util.Optional;
|
|||||||
*/
|
*/
|
||||||
class SampleSidecarImpl extends StubSidecar {
|
class SampleSidecarImpl extends StubSidecar {
|
||||||
private static final String TAG = "SampleSidecar";
|
private static final String TAG = "SampleSidecar";
|
||||||
private static final boolean DEBUG = false;
|
|
||||||
|
|
||||||
private final SettingsDevicePostureProducer mSettingsDevicePostureProducer;
|
private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
|
||||||
private final DataProducer<Integer> mDevicePostureProducer;
|
|
||||||
|
|
||||||
private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer;
|
private final SettingsDisplayFeatureProducer mSettingsFoldingFeatureProducer;
|
||||||
private final DataProducer<List<DisplayFeature>> mDisplayFeatureProducer;
|
|
||||||
|
|
||||||
SampleSidecarImpl(Context context) {
|
SampleSidecarImpl(Context context) {
|
||||||
mSettingsDevicePostureProducer = new SettingsDevicePostureProducer(context);
|
((Application) context.getApplicationContext())
|
||||||
mDevicePostureProducer = new PriorityDataProducer<>(List.of(
|
.registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
|
||||||
mSettingsDevicePostureProducer,
|
mSettingsFoldingFeatureProducer = new SettingsDisplayFeatureProducer(context);
|
||||||
new DeviceStateManagerPostureProducer(context)
|
mFoldingFeatureProducer = new PriorityDataProducer<>(List.of(
|
||||||
|
mSettingsFoldingFeatureProducer,
|
||||||
|
new DeviceStateManagerFoldingFeatureProducer(context)
|
||||||
));
|
));
|
||||||
|
|
||||||
mSettingsDisplayFeatureProducer = new SettingsDisplayFeatureProducer(context);
|
mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
|
||||||
mDisplayFeatureProducer = new PriorityDataProducer<>(List.of(
|
|
||||||
mSettingsDisplayFeatureProducer,
|
|
||||||
new ResourceConfigDisplayFeatureProducer(context)
|
|
||||||
));
|
|
||||||
|
|
||||||
mDevicePostureProducer.addDataChangedCallback(this::onDevicePostureChanged);
|
|
||||||
mDisplayFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onDevicePostureChanged() {
|
|
||||||
updateDeviceState(getDeviceState());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDisplayFeaturesChanged() {
|
private void onDisplayFeaturesChanged() {
|
||||||
|
updateDeviceState(getDeviceState());
|
||||||
for (IBinder windowToken : getWindowsListeningForLayoutChanges()) {
|
for (IBinder windowToken : getWindowsListeningForLayoutChanges()) {
|
||||||
SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken);
|
SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken);
|
||||||
updateWindowLayout(windowToken, newLayout);
|
updateWindowLayout(windowToken, newLayout);
|
||||||
@@ -87,27 +77,21 @@ class SampleSidecarImpl extends StubSidecar {
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public SidecarDeviceState getDeviceState() {
|
public SidecarDeviceState getDeviceState() {
|
||||||
Optional<Integer> posture = mDevicePostureProducer.getData();
|
|
||||||
|
|
||||||
SidecarDeviceState deviceState = new SidecarDeviceState();
|
SidecarDeviceState deviceState = new SidecarDeviceState();
|
||||||
deviceState.posture = posture.orElse(deviceStateFromFeature());
|
deviceState.posture = deviceStateFromFeature();
|
||||||
return deviceState;
|
return deviceState;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int deviceStateFromFeature() {
|
private int deviceStateFromFeature() {
|
||||||
List<DisplayFeature> storedFeatures = mDisplayFeatureProducer.getData()
|
List<CommonFoldingFeature> storedFeatures = mFoldingFeatureProducer.getData()
|
||||||
.orElse(Collections.emptyList());
|
.orElse(Collections.emptyList());
|
||||||
for (int i = 0; i < storedFeatures.size(); i++) {
|
for (int i = 0; i < storedFeatures.size(); i++) {
|
||||||
DisplayFeature feature = storedFeatures.get(i);
|
CommonFoldingFeature feature = storedFeatures.get(i);
|
||||||
final int state = feature.getState() == null ? -1 : feature.getState();
|
final int state = feature.getState();
|
||||||
if (DEBUG && feature.getState() == null) {
|
|
||||||
Log.d(TAG, "feature#getState was null for DisplayFeature: " + feature);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case DisplayFeature.COMMON_STATE_FLAT:
|
case CommonFoldingFeature.COMMON_STATE_FLAT:
|
||||||
return SidecarDeviceState.POSTURE_OPENED;
|
return SidecarDeviceState.POSTURE_OPENED;
|
||||||
case DisplayFeature.COMMON_STATE_HALF_OPENED:
|
case CommonFoldingFeature.COMMON_STATE_HALF_OPENED:
|
||||||
return SidecarDeviceState.POSTURE_HALF_OPENED;
|
return SidecarDeviceState.POSTURE_HALF_OPENED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,22 +111,22 @@ class SampleSidecarImpl extends StubSidecar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
|
private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
|
||||||
List<SidecarDisplayFeature> features = new ArrayList<SidecarDisplayFeature>();
|
|
||||||
int displayId = activity.getDisplay().getDisplayId();
|
int displayId = activity.getDisplay().getDisplayId();
|
||||||
if (displayId != DEFAULT_DISPLAY) {
|
if (displayId != DEFAULT_DISPLAY) {
|
||||||
Log.w(TAG, "This sample doesn't support display features on secondary displays");
|
Log.w(TAG, "This sample doesn't support display features on secondary displays");
|
||||||
return features;
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activity.isInMultiWindowMode()) {
|
if (activity.isInMultiWindowMode()) {
|
||||||
// It is recommended not to report any display features in multi-window mode, since it
|
// It is recommended not to report any display features in multi-window mode, since it
|
||||||
// won't be possible to synchronize the display feature positions with window movement.
|
// won't be possible to synchronize the display feature positions with window movement.
|
||||||
return features;
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<List<DisplayFeature>> storedFeatures = mDisplayFeatureProducer.getData();
|
Optional<List<CommonFoldingFeature>> storedFeatures = mFoldingFeatureProducer.getData();
|
||||||
|
List<SidecarDisplayFeature> features = new ArrayList<>();
|
||||||
if (storedFeatures.isPresent()) {
|
if (storedFeatures.isPresent()) {
|
||||||
for (DisplayFeature baseFeature : storedFeatures.get()) {
|
for (CommonFoldingFeature baseFeature : storedFeatures.get()) {
|
||||||
SidecarDisplayFeature feature = new SidecarDisplayFeature();
|
SidecarDisplayFeature feature = new SidecarDisplayFeature();
|
||||||
Rect featureRect = baseFeature.getRect();
|
Rect featureRect = baseFeature.getRect();
|
||||||
rotateRectToDisplayRotation(displayId, featureRect);
|
rotateRectToDisplayRotation(displayId, featureRect);
|
||||||
@@ -152,17 +136,37 @@ class SampleSidecarImpl extends StubSidecar {
|
|||||||
features.add(feature);
|
features.add(feature);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return features;
|
return Collections.unmodifiableList(features);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onListenersChanged() {
|
protected void onListenersChanged() {
|
||||||
if (hasListeners()) {
|
if (hasListeners()) {
|
||||||
mSettingsDevicePostureProducer.registerObserversIfNeeded();
|
mSettingsFoldingFeatureProducer.registerObserversIfNeeded();
|
||||||
mSettingsDisplayFeatureProducer.registerObserversIfNeeded();
|
onDisplayFeaturesChanged();
|
||||||
} else {
|
} else {
|
||||||
mSettingsDevicePostureProducer.unregisterObserversIfNeeded();
|
mSettingsFoldingFeatureProducer.unregisterObserversIfNeeded();
|
||||||
mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class NotifyOnConfigurationChanged extends EmptyLifecycleCallbacksAdapter {
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(activity, savedInstanceState);
|
||||||
|
onDisplayFeaturesChangedForActivity(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityConfigurationChanged(Activity activity) {
|
||||||
|
super.onActivityConfigurationChanged(activity);
|
||||||
|
onDisplayFeaturesChangedForActivity(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDisplayFeaturesChangedForActivity(@NonNull Activity activity) {
|
||||||
|
IBinder token = activity.getWindow().getAttributes().token;
|
||||||
|
if (token == null || mWindowLayoutChangeListenerTokens.contains(token)) {
|
||||||
|
onDisplayFeaturesChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import java.util.Set;
|
|||||||
abstract class StubSidecar implements SidecarInterface {
|
abstract class StubSidecar implements SidecarInterface {
|
||||||
|
|
||||||
private SidecarCallback mSidecarCallback;
|
private SidecarCallback mSidecarCallback;
|
||||||
private final Set<IBinder> mWindowLayoutChangeListenerTokens = new HashSet<>();
|
final Set<IBinder> mWindowLayoutChangeListenerTokens = new HashSet<>();
|
||||||
private boolean mDeviceStateChangeListenerRegistered;
|
private boolean mDeviceStateChangeListenerRegistered;
|
||||||
|
|
||||||
StubSidecar() {
|
StubSidecar() {
|
||||||
|
|||||||
Reference in New Issue
Block a user