diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp index 7fbbb61e469d4..4612ba28d1db6 100644 --- a/libs/WindowManager/Jetpack/Android.bp +++ b/libs/WindowManager/Jetpack/Android.bp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Sidecar android_library_import { name: "window-sidecar", aars: ["window-sidecar-release.aar"], @@ -20,7 +21,7 @@ android_library_import { java_library { name: "androidx.window.sidecar", - srcs: ["src/**/*.java"], + srcs: ["src/androidx/window/sidecar/**/*.java", "src/androidx/window/util/**/*.java"], static_libs: ["window-sidecar"], installable: true, sdk_version: "core_platform", @@ -36,3 +37,31 @@ prebuilt_etc { src: "androidx.window.sidecar.xml", filename_from_src: true, } + +// Extensions +// NOTE: This module is still under active development and must not +// be used in production. Use 'androidx.window.sidecar' instead. +android_library_import { + name: "window-extensions", + aars: ["window-extensions-release.aar"], + sdk_version: "current", +} + +java_library { + name: "androidx.window.extensions", + srcs: ["src/androidx/window/extensions/**/*.java", "src/androidx/window/util/**/*.java"], + static_libs: ["window-extensions"], + installable: true, + sdk_version: "core_platform", + system_ext_specific: true, + libs: ["framework", "androidx.annotation_annotation",], + required: ["androidx.window.extensions.xml",], +} + +prebuilt_etc { + name: "androidx.window.extensions.xml", + system_ext_specific: true, + sub_dir: "permissions", + src: "androidx.window.extensions.xml", + filename_from_src: true, +} diff --git a/libs/WindowManager/Jetpack/androidx.window.extensions.xml b/libs/WindowManager/Jetpack/androidx.window.extensions.xml new file mode 100644 index 0000000000000..34264aa3ade3f --- /dev/null +++ b/libs/WindowManager/Jetpack/androidx.window.extensions.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java new file mode 100644 index 0000000000000..b7a60392c5125 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java @@ -0,0 +1,41 @@ +/* + * 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.extensions; + +import android.content.Context; + +/** + * Provider class that will instantiate the library implementation. It must be included in the + * vendor library, and the vendor implementation must match the signature of this class. + */ +public class ExtensionProvider { + /** + * Provides a simple implementation of {@link ExtensionInterface} that can be replaced by + * an OEM by overriding this method. + */ + public static ExtensionInterface getExtensionImpl(Context context) { + return new SampleExtensionImpl(context); + } + + /** + * The support library will use this method to check API version compatibility. + * @return API version string in MAJOR.MINOR.PATCH-description format. + */ + public static String getApiVersion() { + return "1.0.0-settings_sample"; + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java new file mode 100644 index 0000000000000..0bf6965beb5ff --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java @@ -0,0 +1,109 @@ +/* + * 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.extensions; + +import static android.view.Display.DEFAULT_DISPLAY; + +import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation; +import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Rect; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.window.util.BaseDisplayFeature; +import androidx.window.util.SettingsConfigProvider; + +import java.util.ArrayList; +import java.util.List; + +/** + * Reference implementation of androidx.window.extensions OEM interface for use with + * WindowManager Jetpack. + * + * NOTE: This version is a work in progress and under active development. It MUST NOT be used in + * production builds since the interface can still change before reaching stable version. + * Please refer to {@link androidx.window.sidecar.SampleSidecarImpl} instead. + */ +class SampleExtensionImpl extends StubExtension implements + SettingsConfigProvider.StateChangeCallback { + private static final String TAG = "SampleExtension"; + + private final SettingsConfigProvider mConfigProvider; + + SampleExtensionImpl(Context context) { + mConfigProvider = new SettingsConfigProvider(context, this); + } + + @Override + public void onDevicePostureChanged() { + updateDeviceState(new ExtensionDeviceState(mConfigProvider.getDeviceState())); + } + + @Override + public void onDisplayFeaturesChanged() { + for (Activity activity : getActivitiesListeningForLayoutChanges()) { + ExtensionWindowLayoutInfo newLayout = getWindowLayoutInfo(activity); + updateWindowLayout(activity, newLayout); + } + } + + @NonNull + private ExtensionWindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) { + List displayFeatures = getDisplayFeatures(activity); + return new ExtensionWindowLayoutInfo(displayFeatures); + } + + private List getDisplayFeatures(@NonNull Activity activity) { + List features = new ArrayList<>(); + int displayId = activity.getDisplayId(); + if (displayId != DEFAULT_DISPLAY) { + Log.w(TAG, "This sample doesn't support display features on secondary displays"); + return features; + } + + 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; + } + + List storedFeatures = mConfigProvider.getDisplayFeatures(); + for (BaseDisplayFeature baseFeature : storedFeatures) { + Rect featureRect = baseFeature.getRect(); + rotateRectToDisplayRotation(displayId, featureRect); + transformToWindowSpaceRect(activity, featureRect); + features.add(new ExtensionFoldingFeature(featureRect, baseFeature.getType(), + baseFeature.getState())); + } + return features; + } + + @Override + protected void onListenersChanged() { + if (hasListeners()) { + mConfigProvider.registerObserversIfNeeded(); + } else { + mConfigProvider.unregisterObserversIfNeeded(); + } + + onDevicePostureChanged(); + onDisplayFeaturesChanged(); + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java new file mode 100644 index 0000000000000..b0895efc75a9e --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java @@ -0,0 +1,86 @@ +/* + * 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.extensions; + +import android.app.Activity; + +import androidx.annotation.NonNull; + +import java.util.HashSet; +import java.util.Set; + +/** + * Basic implementation of the {@link ExtensionInterface}. An OEM can choose to use it as the base + * class for their implementation. + */ +abstract class StubExtension implements ExtensionInterface { + + private ExtensionCallback mExtensionCallback; + private final Set mWindowLayoutChangeListenerActivities = new HashSet<>(); + private boolean mDeviceStateChangeListenerRegistered; + + StubExtension() { + } + + @Override + public void setExtensionCallback(@NonNull ExtensionCallback extensionCallback) { + this.mExtensionCallback = extensionCallback; + } + + @Override + public void onWindowLayoutChangeListenerAdded(@NonNull Activity activity) { + this.mWindowLayoutChangeListenerActivities.add(activity); + this.onListenersChanged(); + } + + @Override + public void onWindowLayoutChangeListenerRemoved(@NonNull Activity activity) { + this.mWindowLayoutChangeListenerActivities.remove(activity); + this.onListenersChanged(); + } + + @Override + public void onDeviceStateListenersChanged(boolean isEmpty) { + this.mDeviceStateChangeListenerRegistered = !isEmpty; + this.onListenersChanged(); + } + + void updateDeviceState(ExtensionDeviceState newState) { + if (this.mExtensionCallback != null) { + mExtensionCallback.onDeviceStateChanged(newState); + } + } + + void updateWindowLayout(@NonNull Activity activity, + @NonNull ExtensionWindowLayoutInfo newLayout) { + if (this.mExtensionCallback != null) { + mExtensionCallback.onWindowLayoutChanged(activity, newLayout); + } + } + + @NonNull + Set getActivitiesListeningForLayoutChanges() { + return mWindowLayoutChangeListenerActivities; + } + + protected boolean hasListeners() { + return !mWindowLayoutChangeListenerActivities.isEmpty() + || mDeviceStateChangeListenerRegistered; + } + + protected abstract void onListenersChanged(); +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java new file mode 100644 index 0000000000000..1094a0e2b4da9 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2020 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.sidecar; + +import static android.view.Display.DEFAULT_DISPLAY; + +import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation; +import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect; + +import android.app.Activity; +import android.app.ActivityThread; +import android.content.Context; +import android.graphics.Rect; +import android.os.IBinder; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.window.util.BaseDisplayFeature; +import androidx.window.util.SettingsConfigProvider; + +import java.util.ArrayList; +import java.util.List; + +/** + * Reference implementation of androidx.window.sidecar OEM interface for use with + * WindowManager Jetpack. + */ +class SampleSidecarImpl extends StubSidecar implements + SettingsConfigProvider.StateChangeCallback { + private static final String TAG = "SampleSidecar"; + + private final SettingsConfigProvider mConfigProvider; + + SampleSidecarImpl(Context context) { + mConfigProvider = new SettingsConfigProvider(context, this); + } + + @Override + public void onDevicePostureChanged() { + updateDeviceState(getDeviceState()); + } + + @Override + public void onDisplayFeaturesChanged() { + for (IBinder windowToken : getWindowsListeningForLayoutChanges()) { + SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken); + updateWindowLayout(windowToken, newLayout); + } + } + + @NonNull + @Override + public SidecarDeviceState getDeviceState() { + SidecarDeviceState deviceState = new SidecarDeviceState(); + deviceState.posture = mConfigProvider.getDeviceState(); + return deviceState; + } + + @NonNull + @Override + public SidecarWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) { + Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken); + SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo(); + if (activity == null) { + return windowLayoutInfo; + } + windowLayoutInfo.displayFeatures = getDisplayFeatures(activity); + return windowLayoutInfo; + } + + private List getDisplayFeatures(@NonNull Activity activity) { + List features = new ArrayList(); + int displayId = activity.getDisplayId(); + if (displayId != DEFAULT_DISPLAY) { + Log.w(TAG, "This sample doesn't support display features on secondary displays"); + return features; + } + + 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; + } + + List storedFeatures = mConfigProvider.getDisplayFeatures(); + for (BaseDisplayFeature baseFeature : storedFeatures) { + SidecarDisplayFeature feature = new SidecarDisplayFeature(); + Rect featureRect = baseFeature.getRect(); + rotateRectToDisplayRotation(displayId, featureRect); + transformToWindowSpaceRect(activity, featureRect); + feature.setRect(featureRect); + feature.setType(baseFeature.getType()); + features.add(feature); + } + return features; + } + + @Override + protected void onListenersChanged() { + if (hasListeners()) { + mConfigProvider.registerObserversIfNeeded(); + } else { + mConfigProvider.unregisterObserversIfNeeded(); + } + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java deleted file mode 100644 index 5397302f6882a..0000000000000 --- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright (C) 2020 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.sidecar; - -import static android.view.Display.DEFAULT_DISPLAY; - -import static androidx.window.sidecar.SidecarHelper.getWindowDisplay; -import static androidx.window.sidecar.SidecarHelper.isInMultiWindow; -import static androidx.window.sidecar.SidecarHelper.rotateRectToDisplayRotation; -import static androidx.window.sidecar.SidecarHelper.transformToWindowSpaceRect; - -import android.content.ContentResolver; -import android.content.Context; -import android.database.ContentObserver; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.provider.Settings; -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.android.internal.R; - -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -class SettingsSidecarImpl extends StubSidecar { - private static final String TAG = "SettingsSidecar"; - - private static final String DEVICE_POSTURE = "device_posture"; - private static final String DISPLAY_FEATURES = "display_features"; - - private static final Pattern FEATURE_PATTERN = - Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]"); - - private static final String FEATURE_TYPE_FOLD = "fold"; - private static final String FEATURE_TYPE_HINGE = "hinge"; - - private Context mContext; - private SettingsObserver mSettingsObserver; - - final class SettingsObserver extends ContentObserver { - private final Uri mDevicePostureUri = - Settings.Global.getUriFor(DEVICE_POSTURE); - private final Uri mDisplayFeaturesUri = - Settings.Global.getUriFor(DISPLAY_FEATURES); - private final ContentResolver mResolver = mContext.getContentResolver(); - private boolean mRegisteredObservers; - - - private SettingsObserver() { - super(new Handler(Looper.getMainLooper())); - } - - private void registerObserversIfNeeded() { - if (mRegisteredObservers) { - return; - } - mRegisteredObservers = true; - mResolver.registerContentObserver(mDevicePostureUri, false /* notifyForDescendents */, - this /* ContentObserver */); - mResolver.registerContentObserver(mDisplayFeaturesUri, false /* notifyForDescendents */, - this /* ContentObserver */); - } - - private void unregisterObserversIfNeeded() { - if (!mRegisteredObservers) { - return; - } - mRegisteredObservers = false; - mResolver.unregisterContentObserver(this); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - if (uri == null) { - return; - } - - if (mDevicePostureUri.equals(uri)) { - updateDevicePosture(); - return; - } - if (mDisplayFeaturesUri.equals(uri)) { - updateDisplayFeatures(); - return; - } - } - } - - SettingsSidecarImpl(Context context) { - mContext = context; - mSettingsObserver = new SettingsObserver(); - } - - private void updateDevicePosture() { - updateDeviceState(getDeviceState()); - } - - /** Update display features with values read from settings. */ - private void updateDisplayFeatures() { - for (IBinder windowToken : getWindowsListeningForLayoutChanges()) { - SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken); - updateWindowLayout(windowToken, newLayout); - } - } - - @NonNull - @Override - public SidecarDeviceState getDeviceState() { - ContentResolver resolver = mContext.getContentResolver(); - int posture = Settings.Global.getInt(resolver, DEVICE_POSTURE, - SidecarDeviceState.POSTURE_UNKNOWN); - SidecarDeviceState deviceState = new SidecarDeviceState(); - deviceState.posture = posture; - return deviceState; - } - - @NonNull - @Override - public SidecarWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) { - List displayFeatures = readDisplayFeatures(windowToken); - SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo(); - windowLayoutInfo.displayFeatures = displayFeatures; - return windowLayoutInfo; - } - - private List readDisplayFeatures(IBinder windowToken) { - List features = new ArrayList(); - int displayId = getWindowDisplay(windowToken); - if (displayId != DEFAULT_DISPLAY) { - Log.w(TAG, "This sample doesn't support display features on secondary displays"); - return features; - } - - if (isInMultiWindow(windowToken)) { - // 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; - } - - ContentResolver resolver = mContext.getContentResolver(); - String displayFeaturesString = Settings.Global.getString(resolver, DISPLAY_FEATURES); - if (TextUtils.isEmpty(displayFeaturesString)) { - displayFeaturesString = mContext.getResources().getString( - R.string.config_display_features); - } - if (TextUtils.isEmpty(displayFeaturesString)) { - return features; - } - - String[] featureStrings = displayFeaturesString.split(";"); - for (String featureString : featureStrings) { - Matcher featureMatcher = FEATURE_PATTERN.matcher(featureString); - if (!featureMatcher.matches()) { - Log.e(TAG, "Malformed feature description format: " + featureString); - continue; - } - try { - String featureType = featureMatcher.group(1); - int type; - switch (featureType) { - case FEATURE_TYPE_FOLD: - type = SidecarDisplayFeature.TYPE_FOLD; - break; - case FEATURE_TYPE_HINGE: - type = SidecarDisplayFeature.TYPE_HINGE; - break; - default: { - Log.e(TAG, "Malformed feature type: " + featureType); - continue; - } - } - - int left = Integer.parseInt(featureMatcher.group(2)); - int top = Integer.parseInt(featureMatcher.group(3)); - int right = Integer.parseInt(featureMatcher.group(4)); - int bottom = Integer.parseInt(featureMatcher.group(5)); - Rect featureRect = new Rect(left, top, right, bottom); - rotateRectToDisplayRotation(featureRect, displayId); - transformToWindowSpaceRect(featureRect, windowToken); - if (isNotZero(featureRect)) { - SidecarDisplayFeature feature = new SidecarDisplayFeature(); - feature.setRect(featureRect); - feature.setType(type); - features.add(feature); - } else { - Log.w(TAG, "Failed to adjust feature to window"); - } - } catch (NumberFormatException e) { - Log.e(TAG, "Malformed feature description: " + featureString); - } - } - return features; - } - - private static boolean isNotZero(Rect rect) { - return rect.height() > 0 || rect.width() > 0; - } - - @Override - protected void onListenersChanged() { - if (mSettingsObserver == null) { - return; - } - - if (hasListeners()) { - mSettingsObserver.registerObserversIfNeeded(); - } else { - mSettingsObserver.unregisterObserversIfNeeded(); - } - } -} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java index 0b4915ed5dac3..e6f8388b031f2 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java @@ -28,7 +28,7 @@ public class SidecarProvider { * an OEM by overriding this method. */ public static SidecarInterface getSidecarImpl(Context context) { - return new SettingsSidecarImpl(context); + return new SampleSidecarImpl(context); } /** diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.java new file mode 100644 index 0000000000000..b74a2a4bd47d3 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.util; + +import android.graphics.Rect; + +import androidx.annotation.NonNull; + +/** Wrapper for both Extension and Sidecar versions of DisplayFeature. */ +public class BaseDisplayFeature { + private final int mType; + private final int mState; + @NonNull + public final Rect mRect; + + public BaseDisplayFeature(int type, int state, @NonNull Rect rect) { + this.mType = type; + this.mState = state; + if (rect.width() == 0 && rect.height() == 0) { + throw new IllegalArgumentException( + "Display feature rectangle cannot have zero width and height simultaneously."); + } + this.mRect = rect; + } + + public int getType() { + return mType; + } + + public int getState() { + return mState; + } + + @NonNull + public Rect getRect() { + return mRect; + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java similarity index 62% rename from libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java rename to libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java index e5b6cff17b265..2a593f15a9de6 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -14,30 +14,36 @@ * limitations under the License. */ -package androidx.window.sidecar; +package androidx.window.util; -import static android.view.Display.INVALID_DISPLAY; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import android.app.Activity; -import android.app.ActivityThread; import android.graphics.Rect; import android.hardware.display.DisplayManagerGlobal; -import android.os.IBinder; import android.view.DisplayInfo; import android.view.Surface; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; -class SidecarHelper { +/** + * Util class for both Sidecar and Extensions. + */ +public final class ExtensionHelper { + + private ExtensionHelper() { + // Util class, no instances should be created. + } + /** - * Rotate the input rectangle specified in default display orientation to the current display + * Rotates the input rectangle specified in default display orientation to the current display * rotation. */ - static void rotateRectToDisplayRotation(Rect inOutRect, int displayId) { + public static void rotateRectToDisplayRotation(int displayId, Rect inOutRect) { DisplayManagerGlobal dmGlobal = DisplayManagerGlobal.getInstance(); DisplayInfo displayInfo = dmGlobal.getDisplayInfo(displayId); int rotation = displayInfo.rotation; @@ -52,7 +58,7 @@ class SidecarHelper { } /** - * Rotate the input rectangle within parent bounds for a given delta. + * Rotates the input rectangle within parent bounds for a given delta. */ private static void rotateBounds(Rect inOutRect, int parentWidth, int parentHeight, @Surface.Rotation int delta) { @@ -79,9 +85,9 @@ class SidecarHelper { } } - /** Transform rectangle from absolute coordinate space to the window coordinate space. */ - static void transformToWindowSpaceRect(Rect inOutRect, IBinder windowToken) { - Rect windowRect = getWindowBounds(windowToken); + /** Transforms rectangle from absolute coordinate space to the window coordinate space. */ + public static void transformToWindowSpaceRect(Activity activity, Rect inOutRect) { + Rect windowRect = getWindowBounds(activity); if (windowRect == null) { inOutRect.setEmpty(); return; @@ -95,32 +101,17 @@ class SidecarHelper { } /** - * Get the current window bounds in absolute coordinates. - * NOTE: Only works with Activity windows. + * Gets the current window bounds in absolute coordinates. */ @Nullable - private static Rect getWindowBounds(IBinder windowToken) { - Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken); - return activity != null - ? activity.getWindowManager().getCurrentWindowMetrics().getBounds() - : null; + private static Rect getWindowBounds(@NonNull Activity activity) { + return activity.getWindowManager().getCurrentWindowMetrics().getBounds(); } /** - * Check if this window is an Activity window that is in multi-window mode. + * Checks if both dimensions of the given rect are zero at the same time. */ - static boolean isInMultiWindow(IBinder windowToken) { - Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken); - return activity != null && activity.isInMultiWindowMode(); - } - - /** - * Get the id of the parent display for the window. - * NOTE: Only works with Activity windows. - */ - static int getWindowDisplay(IBinder windowToken) { - Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken); - return activity != null - ? activity.getWindowManager().getDefaultDisplay().getDisplayId() : INVALID_DISPLAY; + public static boolean isZero(@NonNull Rect rect) { + return rect.height() == 0 && rect.width() == 0; } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java new file mode 100644 index 0000000000000..6dd190ce25197 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.util; + +import static androidx.window.util.ExtensionHelper.isZero; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.graphics.Rect; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.android.internal.R; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Device and display feature state provider that uses Settings as the source. + */ +public final class SettingsConfigProvider extends ContentObserver { + private static final String TAG = "SettingsConfigProvider"; + private static final String DEVICE_POSTURE = "device_posture"; + private static final String DISPLAY_FEATURES = "display_features"; + + private static final Pattern FEATURE_PATTERN = + Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]"); + + private static final String FEATURE_TYPE_FOLD = "fold"; + private static final String FEATURE_TYPE_HINGE = "hinge"; + + private final Uri mDevicePostureUri = + Settings.Global.getUriFor(DEVICE_POSTURE); + private final Uri mDisplayFeaturesUri = + Settings.Global.getUriFor(DISPLAY_FEATURES); + private final Context mContext; + private final ContentResolver mResolver; + private final StateChangeCallback mCallback; + private boolean mRegisteredObservers; + + public SettingsConfigProvider(@NonNull Context context, @NonNull StateChangeCallback callback) { + super(new Handler(Looper.getMainLooper())); + mContext = context; + mResolver = context.getContentResolver(); + mCallback = callback; + } + + /** + * Registers the content observers for Settings keys that store device state and display feature + * configurations. + */ + public void registerObserversIfNeeded() { + if (mRegisteredObservers) { + return; + } + mRegisteredObservers = true; + mResolver.registerContentObserver(mDevicePostureUri, false /* notifyForDescendants */, + this /* ContentObserver */); + mResolver.registerContentObserver(mDisplayFeaturesUri, false /* notifyForDescendants */, + this /* ContentObserver */); + } + + /** + * Unregisters the content observers that are tracking the state changes. + * @see #registerObserversIfNeeded() + */ + public void unregisterObserversIfNeeded() { + if (!mRegisteredObservers) { + return; + } + mRegisteredObservers = false; + mResolver.unregisterContentObserver(this); + } + + /** + * Gets the device posture int stored in Settings. + */ + public int getDeviceState() { + return Settings.Global.getInt(mResolver, DEVICE_POSTURE, + 0 /* POSTURE_UNKNOWN */); + } + + /** + * Gets the list of all display feature configs stored in Settings. Uses a custom + * {@link BaseDisplayFeature} class to report the config to be translated for actual + * containers in Sidecar or Extensions. + */ + public List getDisplayFeatures() { + List features = new ArrayList<>(); + String displayFeaturesString = Settings.Global.getString(mResolver, DISPLAY_FEATURES); + if (TextUtils.isEmpty(displayFeaturesString)) { + displayFeaturesString = mContext.getResources().getString( + R.string.config_display_features); + } + if (TextUtils.isEmpty(displayFeaturesString)) { + return features; + } + String[] featureStrings = displayFeaturesString.split(";"); + + int deviceState = getDeviceState(); + + for (String featureString : featureStrings) { + Matcher featureMatcher = FEATURE_PATTERN.matcher(featureString); + if (!featureMatcher.matches()) { + Log.e(TAG, "Malformed feature description format: " + featureString); + continue; + } + try { + String featureType = featureMatcher.group(1); + int type; + switch (featureType) { + case FEATURE_TYPE_FOLD: + type = 1 /* TYPE_FOLD */; + break; + case FEATURE_TYPE_HINGE: + type = 2 /* TYPE_HINGE */; + break; + default: { + Log.e(TAG, "Malformed feature type: " + featureType); + continue; + } + } + + int left = Integer.parseInt(featureMatcher.group(2)); + int top = Integer.parseInt(featureMatcher.group(3)); + int right = Integer.parseInt(featureMatcher.group(4)); + int bottom = Integer.parseInt(featureMatcher.group(5)); + Rect featureRect = new Rect(left, top, right, bottom); + if (!isZero(featureRect)) { + BaseDisplayFeature feature = new BaseDisplayFeature(type, deviceState, + featureRect); + features.add(feature); + } else { + Log.w(TAG, "Read empty feature"); + } + } catch (NumberFormatException e) { + Log.e(TAG, "Malformed feature description: " + featureString); + } + } + return features; + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (uri == null) { + return; + } + + if (mDevicePostureUri.equals(uri)) { + mCallback.onDevicePostureChanged(); + mCallback.onDisplayFeaturesChanged(); + return; + } + if (mDisplayFeaturesUri.equals(uri)) { + mCallback.onDisplayFeaturesChanged(); + } + } + + /** + * Callback that notifies about device or display feature state changes. + */ + public interface StateChangeCallback { + /** + * Notifies about the device state update. + */ + void onDevicePostureChanged(); + + /** + * Notifies about the display feature config update. + */ + void onDisplayFeaturesChanged(); + } +} diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar new file mode 100644 index 0000000000000..7b306b00afd93 Binary files /dev/null and b/libs/WindowManager/Jetpack/window-extensions-release.aar differ