Loading libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java +88 −28 Original line number Diff line number Diff line Loading @@ -22,7 +22,6 @@ import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN; import static androidx.window.common.CommonFoldingFeature.parseListFromString; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; Loading @@ -30,22 +29,25 @@ import android.text.TextUtils; import android.util.Log; import android.util.SparseIntArray; import androidx.window.util.AcceptOnceConsumer; import androidx.window.util.BaseDataProducer; import androidx.window.util.DataProducer; import com.android.internal.R; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; /** * 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}. * An implementation of {@link androidx.window.util.BaseDataProducer} 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 DeviceStateManagerFoldingFeatureProducer extends BaseDataProducer<List<CommonFoldingFeature>> { public final class DeviceStateManagerFoldingFeatureProducer extends BaseDataProducer<List<CommonFoldingFeature>> { private static final String TAG = DeviceStateManagerFoldingFeatureProducer.class.getSimpleName(); private static final boolean DEBUG = false; Loading @@ -54,15 +56,11 @@ public final class DeviceStateManagerFoldingFeatureProducer extends private int mCurrentDeviceState = INVALID_DEVICE_STATE; private final DeviceStateCallback mDeviceStateCallback = (state) -> { mCurrentDeviceState = state; notifyDataChanged(); }; @NonNull private final DataProducer<String> mRawFoldSupplier; private final BaseDataProducer<String> mRawFoldSupplier; public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context, @NonNull DataProducer<String> rawFoldSupplier) { @NonNull BaseDataProducer<String> rawFoldSupplier) { mRawFoldSupplier = rawFoldSupplier; String[] deviceStatePosturePairs = context.getResources() .getStringArray(R.array.config_device_state_postures); Loading @@ -70,7 +68,8 @@ public final class DeviceStateManagerFoldingFeatureProducer extends String[] deviceStatePostureMapping = deviceStatePosturePair.split(":"); if (deviceStatePostureMapping.length != 2) { if (DEBUG) { Log.e(TAG, "Malformed device state posture pair: " + deviceStatePosturePair); Log.e(TAG, "Malformed device state posture pair: " + deviceStatePosturePair); } continue; } Loading @@ -82,7 +81,8 @@ public final class DeviceStateManagerFoldingFeatureProducer extends posture = Integer.parseInt(deviceStatePostureMapping[1]); } catch (NumberFormatException e) { if (DEBUG) { Log.e(TAG, "Failed to parse device state or posture: " + deviceStatePosturePair, Log.e(TAG, "Failed to parse device state or posture: " + deviceStatePosturePair, e); } continue; Loading @@ -92,30 +92,90 @@ public final class DeviceStateManagerFoldingFeatureProducer extends } if (mDeviceStateToPostureMap.size() > 0) { context.getSystemService(DeviceStateManager.class) .registerCallback(context.getMainExecutor(), mDeviceStateCallback); DeviceStateCallback deviceStateCallback = (state) -> { mCurrentDeviceState = state; mRawFoldSupplier.getData(this::notifyFoldingFeatureChange); }; Objects.requireNonNull(context.getSystemService(DeviceStateManager.class)) .registerCallback(context.getMainExecutor(), deviceStateCallback); } } @Override @Nullable public Optional<List<CommonFoldingFeature>> getData() { final int globalHingeState = globalHingeState(); Optional<String> displayFeaturesString = mRawFoldSupplier.getData(); if (displayFeaturesString.isEmpty() || TextUtils.isEmpty(displayFeaturesString.get())) { return Optional.empty(); /** * Add a callback to mCallbacks if there is no device state. This callback will be run * once a device state is set. Otherwise,run the callback immediately. */ private void runCallbackWhenValidState(@NonNull Consumer<List<CommonFoldingFeature>> callback, String displayFeaturesString) { if (isCurrentStateValid()) { callback.accept(calculateFoldingFeature(displayFeaturesString)); } else { // This callback will be added to mCallbacks and removed once it runs once. AcceptOnceConsumer<List<CommonFoldingFeature>> singleRunCallback = new AcceptOnceConsumer<>(this, callback); addDataChangedCallback(singleRunCallback); } } return Optional.of(parseListFromString(displayFeaturesString.get(), globalHingeState)); /** * Checks to find {@link DeviceStateManagerFoldingFeatureProducer#mCurrentDeviceState} in the * {@link DeviceStateManagerFoldingFeatureProducer#mDeviceStateToPostureMap} which was * initialized in the constructor of {@link DeviceStateManagerFoldingFeatureProducer}. * Returns a boolean value of whether the device state is valid. */ private boolean isCurrentStateValid() { // If the device state is not found in the map, indexOfKey returns a negative number. return mDeviceStateToPostureMap.indexOfKey(mCurrentDeviceState) >= 0; } @Override protected void onListenersChanged(Set<Runnable> callbacks) { protected void onListenersChanged( @NonNull Set<Consumer<List<CommonFoldingFeature>>> callbacks) { super.onListenersChanged(callbacks); if (callbacks.isEmpty()) { mRawFoldSupplier.removeDataChangedCallback(this::notifyDataChanged); mCurrentDeviceState = INVALID_DEVICE_STATE; mRawFoldSupplier.removeDataChangedCallback(this::notifyFoldingFeatureChange); } else { mRawFoldSupplier.addDataChangedCallback(this::notifyFoldingFeatureChange); } } @NonNull @Override public Optional<List<CommonFoldingFeature>> getCurrentData() { Optional<String> displayFeaturesString = mRawFoldSupplier.getCurrentData(); if (!isCurrentStateValid()) { return Optional.empty(); } else { mRawFoldSupplier.addDataChangedCallback(this::notifyDataChanged); return displayFeaturesString.map(this::calculateFoldingFeature); } } /** * Adds the data to the storeFeaturesConsumer when the data is ready. * @param storeFeaturesConsumer a consumer to collect the data when it is first available. */ public void getData(Consumer<List<CommonFoldingFeature>> storeFeaturesConsumer) { mRawFoldSupplier.getData((String displayFeaturesString) -> { if (TextUtils.isEmpty(displayFeaturesString)) { storeFeaturesConsumer.accept(new ArrayList<>()); } else { runCallbackWhenValidState(storeFeaturesConsumer, displayFeaturesString); } }); } private void notifyFoldingFeatureChange(String displayFeaturesString) { if (TextUtils.isEmpty(displayFeaturesString)) { notifyDataChanged(new ArrayList<>()); } else { notifyDataChanged(calculateFoldingFeature(displayFeaturesString)); } } private List<CommonFoldingFeature> calculateFoldingFeature(String displayFeaturesString) { final int globalHingeState = globalHingeState(); return parseListFromString(displayFeaturesString, globalHingeState); } private int globalHingeState() { Loading libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java +15 −7 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import com.android.internal.R; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; /** * Implementation of {@link androidx.window.util.DataProducer} that produces a Loading @@ -40,7 +41,7 @@ import java.util.Set; * settings where the {@link String} property is saved with the key * {@link RawFoldingFeatureProducer#DISPLAY_FEATURES}. If this value is null or empty then the * value in {@link android.content.res.Resources} is used. If both are empty then * {@link RawFoldingFeatureProducer#getData()} returns an empty object. * {@link RawFoldingFeatureProducer#getData} returns an empty object. * {@link RawFoldingFeatureProducer} listens to changes in the setting so that it can override * the system {@link CommonFoldingFeature} data. */ Loading @@ -63,12 +64,13 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> { @Override @NonNull public Optional<String> getData() { public void getData(Consumer<String> dataConsumer) { String displayFeaturesString = getFeatureString(); if (displayFeaturesString == null) { return Optional.empty(); dataConsumer.accept(""); } else { dataConsumer.accept(displayFeaturesString); } return Optional.of(displayFeaturesString); } /** Loading @@ -84,7 +86,7 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> { } @Override protected void onListenersChanged(Set<Runnable> callbacks) { protected void onListenersChanged(Set<Consumer<String>> callbacks) { if (callbacks.isEmpty()) { unregisterObserversIfNeeded(); } else { Loading @@ -92,6 +94,12 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> { } } @NonNull @Override public Optional<String> getCurrentData() { return Optional.of(getFeatureString()); } /** * Registers settings observers, if needed. When settings observers are registered for this * producer callbacks for changes in data will be triggered. Loading Loading @@ -125,7 +133,7 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> { @Override public void onChange(boolean selfChange, Uri uri) { if (mDisplayFeaturesUri.equals(uri)) { notifyDataChanged(); notifyDataChanged(getFeatureString()); } } } Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +27 −37 Original line number Diff line number Diff line Loading @@ -43,7 +43,6 @@ import androidx.window.util.DataProducer; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; Loading @@ -63,7 +62,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer; public WindowLayoutComponentImpl(Context context) { public WindowLayoutComponentImpl(@NonNull Context context) { ((Application) context.getApplicationContext()) .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged()); RawFoldingFeatureProducer foldingFeatureProducer = new RawFoldingFeatureProducer(context); Loading @@ -81,7 +80,6 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { public void addWindowLayoutInfoListener(@NonNull Activity activity, @NonNull Consumer<WindowLayoutInfo> consumer) { mWindowLayoutChangeListeners.put(activity, consumer); onDisplayFeaturesChanged(); } /** Loading @@ -89,18 +87,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { * * @param consumer no longer interested in receiving updates to {@link WindowLayoutInfo} */ public void removeWindowLayoutInfoListener( @NonNull Consumer<WindowLayoutInfo> consumer) { public void removeWindowLayoutInfoListener(@NonNull Consumer<WindowLayoutInfo> consumer) { mWindowLayoutChangeListeners.values().remove(consumer); onDisplayFeaturesChanged(); } void updateWindowLayout(@NonNull Activity activity, @NonNull WindowLayoutInfo newLayout) { Consumer<WindowLayoutInfo> consumer = mWindowLayoutChangeListeners.get(activity); if (consumer != null) { consumer.accept(newLayout); } } @NonNull Loading @@ -108,7 +96,6 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { return mWindowLayoutChangeListeners.keySet(); } @NonNull private boolean isListeningForLayoutChanges(IBinder token) { for (Activity activity: getActivitiesListeningForLayoutChanges()) { if (token.equals(activity.getWindow().getAttributes().token)) { Loading @@ -125,12 +112,12 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { /** * A convenience method to translate from the common feature state to the extensions feature * 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. * * @param state if it matches a value in {@link CommonFoldingFeature.State}, {@code null} * otherwise. @return a {@link FoldingFeature.STATE_FLAT} or * {@link FoldingFeature.STATE_HALF_OPENED} if the given state matches a value in * otherwise. @return a {@link FoldingFeature#STATE_FLAT} or * {@link FoldingFeature#STATE_HALF_OPENED} if the given state matches a value in * {@link CommonFoldingFeature.State} and {@code null} otherwise. */ @Nullable Loading @@ -144,17 +131,24 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } } private void onDisplayFeaturesChanged() { private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) { for (Activity activity : getActivitiesListeningForLayoutChanges()) { WindowLayoutInfo newLayout = getWindowLayoutInfo(activity); updateWindowLayout(activity, newLayout); // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer. Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(activity); WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(activity, storedFeatures); layoutConsumer.accept(newWindowLayout); } } @NonNull private WindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) { List<DisplayFeature> displayFeatures = getDisplayFeatures(activity); return new WindowLayoutInfo(displayFeatures); /** * Translates the {@link DisplayFeature} into a {@link WindowLayoutInfo} when a * valid state is found. * @param activity a proxy for the {@link android.view.Window} that contains the */ private WindowLayoutInfo getWindowLayoutInfo( @NonNull Activity activity, List<CommonFoldingFeature> storedFeatures) { List<DisplayFeature> displayFeatureList = getDisplayFeatures(activity, storedFeatures); return new WindowLayoutInfo(displayFeatureList); } /** Loading @@ -172,26 +166,21 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { * * @param activity a proxy for the {@link android.view.Window} that contains the * {@link DisplayFeature}. * @return a {@link List} of valid {@link DisplayFeature} that * are within the {@link android.view.Window} of the {@link Activity} */ private List<DisplayFeature> getDisplayFeatures(@NonNull Activity activity) { private List<DisplayFeature> getDisplayFeatures( @NonNull Activity activity, List<CommonFoldingFeature> storedFeatures) { List<DisplayFeature> features = new ArrayList<>(); int displayId = activity.getDisplay().getDisplayId(); if (displayId != DEFAULT_DISPLAY) { Log.w(TAG, "This sample doesn't support display features on secondary displays"); return features; } if (activity.isInMultiWindowMode()) { } else if (activity.isInMultiWindowMode()) { // 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. return features; } Optional<List<CommonFoldingFeature>> storedFeatures = mFoldingFeatureProducer.getData(); if (storedFeatures.isPresent()) { for (CommonFoldingFeature baseFeature : storedFeatures.get()) { } else { for (CommonFoldingFeature baseFeature : storedFeatures) { Integer state = convertToExtensionState(baseFeature.getState()); if (state == null) { continue; Loading @@ -205,9 +194,9 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { features.add(new FoldingFeature(featureRect, baseFeature.getType(), state)); } } } return features; } } /** * Returns {@link true} if a {@link Rect} has zero width and zero height, Loading @@ -233,7 +222,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private void onDisplayFeaturesChangedIfListening(Activity activity) { IBinder token = activity.getWindow().getAttributes().token; if (token == null || isListeningForLayoutChanges(token)) { onDisplayFeaturesChanged(); mFoldingFeatureProducer.getData( WindowLayoutComponentImpl.this::onDisplayFeaturesChanged); } } } Loading libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java +27 −30 Original line number Diff line number Diff line Loading @@ -28,41 +28,42 @@ import android.content.Context; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; import android.util.Log; import androidx.annotation.NonNull; import androidx.window.common.CommonFoldingFeature; import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; import androidx.window.common.EmptyLifecycleCallbacksAdapter; import androidx.window.common.RawFoldingFeatureProducer; import androidx.window.util.DataProducer; import androidx.window.util.BaseDataProducer; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; /** * Reference implementation of androidx.window.sidecar OEM interface for use with * WindowManager Jetpack. */ class SampleSidecarImpl extends StubSidecar { private static final String TAG = "SampleSidecar"; private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer; private List<CommonFoldingFeature> mStoredFeatures = new ArrayList<>(); SampleSidecarImpl(Context context) { ((Application) context.getApplicationContext()) .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged()); DataProducer<String> settingsFeatureProducer = new RawFoldingFeatureProducer(context); mFoldingFeatureProducer = new DeviceStateManagerFoldingFeatureProducer(context, BaseDataProducer<String> settingsFeatureProducer = new RawFoldingFeatureProducer(context); BaseDataProducer<List<CommonFoldingFeature>> foldingFeatureProducer = new DeviceStateManagerFoldingFeatureProducer(context, settingsFeatureProducer); mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); foldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); } private void setStoredFeatures(List<CommonFoldingFeature> storedFeatures) { mStoredFeatures = storedFeatures; } private void onDisplayFeaturesChanged() { private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) { setStoredFeatures(storedFeatures); updateDeviceState(getDeviceState()); for (IBinder windowToken : getWindowsListeningForLayoutChanges()) { SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken); Loading @@ -79,16 +80,16 @@ class SampleSidecarImpl extends StubSidecar { } private int deviceStateFromFeature() { List<CommonFoldingFeature> storedFeatures = mFoldingFeatureProducer.getData() .orElse(Collections.emptyList()); for (int i = 0; i < storedFeatures.size(); i++) { CommonFoldingFeature feature = storedFeatures.get(i); for (int i = 0; i < mStoredFeatures.size(); i++) { CommonFoldingFeature feature = mStoredFeatures.get(i); final int state = feature.getState(); switch (state) { case CommonFoldingFeature.COMMON_STATE_FLAT: return SidecarDeviceState.POSTURE_OPENED; case CommonFoldingFeature.COMMON_STATE_HALF_OPENED: return SidecarDeviceState.POSTURE_HALF_OPENED; case CommonFoldingFeature.COMMON_STATE_UNKNOWN: return SidecarDeviceState.POSTURE_UNKNOWN; } } return SidecarDeviceState.POSTURE_UNKNOWN; Loading @@ -109,7 +110,6 @@ class SampleSidecarImpl extends StubSidecar { private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) { int displayId = activity.getDisplay().getDisplayId(); if (displayId != DEFAULT_DISPLAY) { Log.w(TAG, "This sample doesn't support display features on secondary displays"); return Collections.emptyList(); } Loading @@ -119,10 +119,8 @@ class SampleSidecarImpl extends StubSidecar { return Collections.emptyList(); } Optional<List<CommonFoldingFeature>> storedFeatures = mFoldingFeatureProducer.getData(); List<SidecarDisplayFeature> features = new ArrayList<>(); if (storedFeatures.isPresent()) { for (CommonFoldingFeature baseFeature : storedFeatures.get()) { for (CommonFoldingFeature baseFeature : mStoredFeatures) { SidecarDisplayFeature feature = new SidecarDisplayFeature(); Rect featureRect = baseFeature.getRect(); rotateRectToDisplayRotation(displayId, featureRect); Loading @@ -131,14 +129,13 @@ class SampleSidecarImpl extends StubSidecar { feature.setType(baseFeature.getType()); features.add(feature); } } return Collections.unmodifiableList(features); } @Override protected void onListenersChanged() { if (hasListeners()) { onDisplayFeaturesChanged(); onDisplayFeaturesChanged(mStoredFeatures); } } Loading @@ -158,7 +155,7 @@ class SampleSidecarImpl extends StubSidecar { private void onDisplayFeaturesChangedForActivity(@NonNull Activity activity) { IBinder token = activity.getWindow().getAttributes().token; if (token == null || mWindowLayoutChangeListenerTokens.contains(token)) { onDisplayFeaturesChanged(); onDisplayFeaturesChanged(mStoredFeatures); } } } Loading libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java 0 → 100644 +42 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.function.Consumer; /** * A base class that works with {@link BaseDataProducer} to add/remove a consumer that should * only be used once when {@link BaseDataProducer#notifyDataChanged} is called. * @param <T> The type of data this producer returns through {@link DataProducer#getData}. */ public class AcceptOnceConsumer<T> implements Consumer<T> { private final Consumer<T> mCallback; private final DataProducer<T> mProducer; public AcceptOnceConsumer(@NonNull DataProducer<T> producer, @NonNull Consumer<T> callback) { mProducer = producer; mCallback = callback; } @Override public void accept(@NonNull T t) { mCallback.accept(t); mProducer.removeDataChangedCallback(this); } } Loading
libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java +88 −28 Original line number Diff line number Diff line Loading @@ -22,7 +22,6 @@ import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN; import static androidx.window.common.CommonFoldingFeature.parseListFromString; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; Loading @@ -30,22 +29,25 @@ import android.text.TextUtils; import android.util.Log; import android.util.SparseIntArray; import androidx.window.util.AcceptOnceConsumer; import androidx.window.util.BaseDataProducer; import androidx.window.util.DataProducer; import com.android.internal.R; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; /** * 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}. * An implementation of {@link androidx.window.util.BaseDataProducer} 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 DeviceStateManagerFoldingFeatureProducer extends BaseDataProducer<List<CommonFoldingFeature>> { public final class DeviceStateManagerFoldingFeatureProducer extends BaseDataProducer<List<CommonFoldingFeature>> { private static final String TAG = DeviceStateManagerFoldingFeatureProducer.class.getSimpleName(); private static final boolean DEBUG = false; Loading @@ -54,15 +56,11 @@ public final class DeviceStateManagerFoldingFeatureProducer extends private int mCurrentDeviceState = INVALID_DEVICE_STATE; private final DeviceStateCallback mDeviceStateCallback = (state) -> { mCurrentDeviceState = state; notifyDataChanged(); }; @NonNull private final DataProducer<String> mRawFoldSupplier; private final BaseDataProducer<String> mRawFoldSupplier; public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context, @NonNull DataProducer<String> rawFoldSupplier) { @NonNull BaseDataProducer<String> rawFoldSupplier) { mRawFoldSupplier = rawFoldSupplier; String[] deviceStatePosturePairs = context.getResources() .getStringArray(R.array.config_device_state_postures); Loading @@ -70,7 +68,8 @@ public final class DeviceStateManagerFoldingFeatureProducer extends String[] deviceStatePostureMapping = deviceStatePosturePair.split(":"); if (deviceStatePostureMapping.length != 2) { if (DEBUG) { Log.e(TAG, "Malformed device state posture pair: " + deviceStatePosturePair); Log.e(TAG, "Malformed device state posture pair: " + deviceStatePosturePair); } continue; } Loading @@ -82,7 +81,8 @@ public final class DeviceStateManagerFoldingFeatureProducer extends posture = Integer.parseInt(deviceStatePostureMapping[1]); } catch (NumberFormatException e) { if (DEBUG) { Log.e(TAG, "Failed to parse device state or posture: " + deviceStatePosturePair, Log.e(TAG, "Failed to parse device state or posture: " + deviceStatePosturePair, e); } continue; Loading @@ -92,30 +92,90 @@ public final class DeviceStateManagerFoldingFeatureProducer extends } if (mDeviceStateToPostureMap.size() > 0) { context.getSystemService(DeviceStateManager.class) .registerCallback(context.getMainExecutor(), mDeviceStateCallback); DeviceStateCallback deviceStateCallback = (state) -> { mCurrentDeviceState = state; mRawFoldSupplier.getData(this::notifyFoldingFeatureChange); }; Objects.requireNonNull(context.getSystemService(DeviceStateManager.class)) .registerCallback(context.getMainExecutor(), deviceStateCallback); } } @Override @Nullable public Optional<List<CommonFoldingFeature>> getData() { final int globalHingeState = globalHingeState(); Optional<String> displayFeaturesString = mRawFoldSupplier.getData(); if (displayFeaturesString.isEmpty() || TextUtils.isEmpty(displayFeaturesString.get())) { return Optional.empty(); /** * Add a callback to mCallbacks if there is no device state. This callback will be run * once a device state is set. Otherwise,run the callback immediately. */ private void runCallbackWhenValidState(@NonNull Consumer<List<CommonFoldingFeature>> callback, String displayFeaturesString) { if (isCurrentStateValid()) { callback.accept(calculateFoldingFeature(displayFeaturesString)); } else { // This callback will be added to mCallbacks and removed once it runs once. AcceptOnceConsumer<List<CommonFoldingFeature>> singleRunCallback = new AcceptOnceConsumer<>(this, callback); addDataChangedCallback(singleRunCallback); } } return Optional.of(parseListFromString(displayFeaturesString.get(), globalHingeState)); /** * Checks to find {@link DeviceStateManagerFoldingFeatureProducer#mCurrentDeviceState} in the * {@link DeviceStateManagerFoldingFeatureProducer#mDeviceStateToPostureMap} which was * initialized in the constructor of {@link DeviceStateManagerFoldingFeatureProducer}. * Returns a boolean value of whether the device state is valid. */ private boolean isCurrentStateValid() { // If the device state is not found in the map, indexOfKey returns a negative number. return mDeviceStateToPostureMap.indexOfKey(mCurrentDeviceState) >= 0; } @Override protected void onListenersChanged(Set<Runnable> callbacks) { protected void onListenersChanged( @NonNull Set<Consumer<List<CommonFoldingFeature>>> callbacks) { super.onListenersChanged(callbacks); if (callbacks.isEmpty()) { mRawFoldSupplier.removeDataChangedCallback(this::notifyDataChanged); mCurrentDeviceState = INVALID_DEVICE_STATE; mRawFoldSupplier.removeDataChangedCallback(this::notifyFoldingFeatureChange); } else { mRawFoldSupplier.addDataChangedCallback(this::notifyFoldingFeatureChange); } } @NonNull @Override public Optional<List<CommonFoldingFeature>> getCurrentData() { Optional<String> displayFeaturesString = mRawFoldSupplier.getCurrentData(); if (!isCurrentStateValid()) { return Optional.empty(); } else { mRawFoldSupplier.addDataChangedCallback(this::notifyDataChanged); return displayFeaturesString.map(this::calculateFoldingFeature); } } /** * Adds the data to the storeFeaturesConsumer when the data is ready. * @param storeFeaturesConsumer a consumer to collect the data when it is first available. */ public void getData(Consumer<List<CommonFoldingFeature>> storeFeaturesConsumer) { mRawFoldSupplier.getData((String displayFeaturesString) -> { if (TextUtils.isEmpty(displayFeaturesString)) { storeFeaturesConsumer.accept(new ArrayList<>()); } else { runCallbackWhenValidState(storeFeaturesConsumer, displayFeaturesString); } }); } private void notifyFoldingFeatureChange(String displayFeaturesString) { if (TextUtils.isEmpty(displayFeaturesString)) { notifyDataChanged(new ArrayList<>()); } else { notifyDataChanged(calculateFoldingFeature(displayFeaturesString)); } } private List<CommonFoldingFeature> calculateFoldingFeature(String displayFeaturesString) { final int globalHingeState = globalHingeState(); return parseListFromString(displayFeaturesString, globalHingeState); } private int globalHingeState() { Loading
libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java +15 −7 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import com.android.internal.R; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; /** * Implementation of {@link androidx.window.util.DataProducer} that produces a Loading @@ -40,7 +41,7 @@ import java.util.Set; * settings where the {@link String} property is saved with the key * {@link RawFoldingFeatureProducer#DISPLAY_FEATURES}. If this value is null or empty then the * value in {@link android.content.res.Resources} is used. If both are empty then * {@link RawFoldingFeatureProducer#getData()} returns an empty object. * {@link RawFoldingFeatureProducer#getData} returns an empty object. * {@link RawFoldingFeatureProducer} listens to changes in the setting so that it can override * the system {@link CommonFoldingFeature} data. */ Loading @@ -63,12 +64,13 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> { @Override @NonNull public Optional<String> getData() { public void getData(Consumer<String> dataConsumer) { String displayFeaturesString = getFeatureString(); if (displayFeaturesString == null) { return Optional.empty(); dataConsumer.accept(""); } else { dataConsumer.accept(displayFeaturesString); } return Optional.of(displayFeaturesString); } /** Loading @@ -84,7 +86,7 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> { } @Override protected void onListenersChanged(Set<Runnable> callbacks) { protected void onListenersChanged(Set<Consumer<String>> callbacks) { if (callbacks.isEmpty()) { unregisterObserversIfNeeded(); } else { Loading @@ -92,6 +94,12 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> { } } @NonNull @Override public Optional<String> getCurrentData() { return Optional.of(getFeatureString()); } /** * Registers settings observers, if needed. When settings observers are registered for this * producer callbacks for changes in data will be triggered. Loading Loading @@ -125,7 +133,7 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> { @Override public void onChange(boolean selfChange, Uri uri) { if (mDisplayFeaturesUri.equals(uri)) { notifyDataChanged(); notifyDataChanged(getFeatureString()); } } } Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +27 −37 Original line number Diff line number Diff line Loading @@ -43,7 +43,6 @@ import androidx.window.util.DataProducer; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; Loading @@ -63,7 +62,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer; public WindowLayoutComponentImpl(Context context) { public WindowLayoutComponentImpl(@NonNull Context context) { ((Application) context.getApplicationContext()) .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged()); RawFoldingFeatureProducer foldingFeatureProducer = new RawFoldingFeatureProducer(context); Loading @@ -81,7 +80,6 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { public void addWindowLayoutInfoListener(@NonNull Activity activity, @NonNull Consumer<WindowLayoutInfo> consumer) { mWindowLayoutChangeListeners.put(activity, consumer); onDisplayFeaturesChanged(); } /** Loading @@ -89,18 +87,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { * * @param consumer no longer interested in receiving updates to {@link WindowLayoutInfo} */ public void removeWindowLayoutInfoListener( @NonNull Consumer<WindowLayoutInfo> consumer) { public void removeWindowLayoutInfoListener(@NonNull Consumer<WindowLayoutInfo> consumer) { mWindowLayoutChangeListeners.values().remove(consumer); onDisplayFeaturesChanged(); } void updateWindowLayout(@NonNull Activity activity, @NonNull WindowLayoutInfo newLayout) { Consumer<WindowLayoutInfo> consumer = mWindowLayoutChangeListeners.get(activity); if (consumer != null) { consumer.accept(newLayout); } } @NonNull Loading @@ -108,7 +96,6 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { return mWindowLayoutChangeListeners.keySet(); } @NonNull private boolean isListeningForLayoutChanges(IBinder token) { for (Activity activity: getActivitiesListeningForLayoutChanges()) { if (token.equals(activity.getWindow().getAttributes().token)) { Loading @@ -125,12 +112,12 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { /** * A convenience method to translate from the common feature state to the extensions feature * 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. * * @param state if it matches a value in {@link CommonFoldingFeature.State}, {@code null} * otherwise. @return a {@link FoldingFeature.STATE_FLAT} or * {@link FoldingFeature.STATE_HALF_OPENED} if the given state matches a value in * otherwise. @return a {@link FoldingFeature#STATE_FLAT} or * {@link FoldingFeature#STATE_HALF_OPENED} if the given state matches a value in * {@link CommonFoldingFeature.State} and {@code null} otherwise. */ @Nullable Loading @@ -144,17 +131,24 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } } private void onDisplayFeaturesChanged() { private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) { for (Activity activity : getActivitiesListeningForLayoutChanges()) { WindowLayoutInfo newLayout = getWindowLayoutInfo(activity); updateWindowLayout(activity, newLayout); // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer. Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(activity); WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(activity, storedFeatures); layoutConsumer.accept(newWindowLayout); } } @NonNull private WindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) { List<DisplayFeature> displayFeatures = getDisplayFeatures(activity); return new WindowLayoutInfo(displayFeatures); /** * Translates the {@link DisplayFeature} into a {@link WindowLayoutInfo} when a * valid state is found. * @param activity a proxy for the {@link android.view.Window} that contains the */ private WindowLayoutInfo getWindowLayoutInfo( @NonNull Activity activity, List<CommonFoldingFeature> storedFeatures) { List<DisplayFeature> displayFeatureList = getDisplayFeatures(activity, storedFeatures); return new WindowLayoutInfo(displayFeatureList); } /** Loading @@ -172,26 +166,21 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { * * @param activity a proxy for the {@link android.view.Window} that contains the * {@link DisplayFeature}. * @return a {@link List} of valid {@link DisplayFeature} that * are within the {@link android.view.Window} of the {@link Activity} */ private List<DisplayFeature> getDisplayFeatures(@NonNull Activity activity) { private List<DisplayFeature> getDisplayFeatures( @NonNull Activity activity, List<CommonFoldingFeature> storedFeatures) { List<DisplayFeature> features = new ArrayList<>(); int displayId = activity.getDisplay().getDisplayId(); if (displayId != DEFAULT_DISPLAY) { Log.w(TAG, "This sample doesn't support display features on secondary displays"); return features; } if (activity.isInMultiWindowMode()) { } else if (activity.isInMultiWindowMode()) { // 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. return features; } Optional<List<CommonFoldingFeature>> storedFeatures = mFoldingFeatureProducer.getData(); if (storedFeatures.isPresent()) { for (CommonFoldingFeature baseFeature : storedFeatures.get()) { } else { for (CommonFoldingFeature baseFeature : storedFeatures) { Integer state = convertToExtensionState(baseFeature.getState()); if (state == null) { continue; Loading @@ -205,9 +194,9 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { features.add(new FoldingFeature(featureRect, baseFeature.getType(), state)); } } } return features; } } /** * Returns {@link true} if a {@link Rect} has zero width and zero height, Loading @@ -233,7 +222,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private void onDisplayFeaturesChangedIfListening(Activity activity) { IBinder token = activity.getWindow().getAttributes().token; if (token == null || isListeningForLayoutChanges(token)) { onDisplayFeaturesChanged(); mFoldingFeatureProducer.getData( WindowLayoutComponentImpl.this::onDisplayFeaturesChanged); } } } Loading
libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java +27 −30 Original line number Diff line number Diff line Loading @@ -28,41 +28,42 @@ import android.content.Context; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; import android.util.Log; import androidx.annotation.NonNull; import androidx.window.common.CommonFoldingFeature; import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; import androidx.window.common.EmptyLifecycleCallbacksAdapter; import androidx.window.common.RawFoldingFeatureProducer; import androidx.window.util.DataProducer; import androidx.window.util.BaseDataProducer; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; /** * Reference implementation of androidx.window.sidecar OEM interface for use with * WindowManager Jetpack. */ class SampleSidecarImpl extends StubSidecar { private static final String TAG = "SampleSidecar"; private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer; private List<CommonFoldingFeature> mStoredFeatures = new ArrayList<>(); SampleSidecarImpl(Context context) { ((Application) context.getApplicationContext()) .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged()); DataProducer<String> settingsFeatureProducer = new RawFoldingFeatureProducer(context); mFoldingFeatureProducer = new DeviceStateManagerFoldingFeatureProducer(context, BaseDataProducer<String> settingsFeatureProducer = new RawFoldingFeatureProducer(context); BaseDataProducer<List<CommonFoldingFeature>> foldingFeatureProducer = new DeviceStateManagerFoldingFeatureProducer(context, settingsFeatureProducer); mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); foldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); } private void setStoredFeatures(List<CommonFoldingFeature> storedFeatures) { mStoredFeatures = storedFeatures; } private void onDisplayFeaturesChanged() { private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) { setStoredFeatures(storedFeatures); updateDeviceState(getDeviceState()); for (IBinder windowToken : getWindowsListeningForLayoutChanges()) { SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken); Loading @@ -79,16 +80,16 @@ class SampleSidecarImpl extends StubSidecar { } private int deviceStateFromFeature() { List<CommonFoldingFeature> storedFeatures = mFoldingFeatureProducer.getData() .orElse(Collections.emptyList()); for (int i = 0; i < storedFeatures.size(); i++) { CommonFoldingFeature feature = storedFeatures.get(i); for (int i = 0; i < mStoredFeatures.size(); i++) { CommonFoldingFeature feature = mStoredFeatures.get(i); final int state = feature.getState(); switch (state) { case CommonFoldingFeature.COMMON_STATE_FLAT: return SidecarDeviceState.POSTURE_OPENED; case CommonFoldingFeature.COMMON_STATE_HALF_OPENED: return SidecarDeviceState.POSTURE_HALF_OPENED; case CommonFoldingFeature.COMMON_STATE_UNKNOWN: return SidecarDeviceState.POSTURE_UNKNOWN; } } return SidecarDeviceState.POSTURE_UNKNOWN; Loading @@ -109,7 +110,6 @@ class SampleSidecarImpl extends StubSidecar { private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) { int displayId = activity.getDisplay().getDisplayId(); if (displayId != DEFAULT_DISPLAY) { Log.w(TAG, "This sample doesn't support display features on secondary displays"); return Collections.emptyList(); } Loading @@ -119,10 +119,8 @@ class SampleSidecarImpl extends StubSidecar { return Collections.emptyList(); } Optional<List<CommonFoldingFeature>> storedFeatures = mFoldingFeatureProducer.getData(); List<SidecarDisplayFeature> features = new ArrayList<>(); if (storedFeatures.isPresent()) { for (CommonFoldingFeature baseFeature : storedFeatures.get()) { for (CommonFoldingFeature baseFeature : mStoredFeatures) { SidecarDisplayFeature feature = new SidecarDisplayFeature(); Rect featureRect = baseFeature.getRect(); rotateRectToDisplayRotation(displayId, featureRect); Loading @@ -131,14 +129,13 @@ class SampleSidecarImpl extends StubSidecar { feature.setType(baseFeature.getType()); features.add(feature); } } return Collections.unmodifiableList(features); } @Override protected void onListenersChanged() { if (hasListeners()) { onDisplayFeaturesChanged(); onDisplayFeaturesChanged(mStoredFeatures); } } Loading @@ -158,7 +155,7 @@ class SampleSidecarImpl extends StubSidecar { private void onDisplayFeaturesChangedForActivity(@NonNull Activity activity) { IBinder token = activity.getWindow().getAttributes().token; if (token == null || mWindowLayoutChangeListenerTokens.contains(token)) { onDisplayFeaturesChanged(); onDisplayFeaturesChanged(mStoredFeatures); } } } Loading
libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java 0 → 100644 +42 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.function.Consumer; /** * A base class that works with {@link BaseDataProducer} to add/remove a consumer that should * only be used once when {@link BaseDataProducer#notifyDataChanged} is called. * @param <T> The type of data this producer returns through {@link DataProducer#getData}. */ public class AcceptOnceConsumer<T> implements Consumer<T> { private final Consumer<T> mCallback; private final DataProducer<T> mProducer; public AcceptOnceConsumer(@NonNull DataProducer<T> producer, @NonNull Consumer<T> callback) { mProducer = producer; mCallback = callback; } @Override public void accept(@NonNull T t) { mCallback.accept(t); mProducer.removeDataChangedCallback(this); } }