diff options
Diffstat (limited to 'libs/WindowManager/Jetpack')
7 files changed, 533 insertions, 0 deletions
diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp new file mode 100644 index 000000000000..4f4364f72fef --- /dev/null +++ b/libs/WindowManager/Jetpack/Android.bp @@ -0,0 +1,38 @@ +// 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. + +android_library_import { + name: "window-sidecar", + aars: ["window-sidecar-release.aar"], + sdk_version: "current", +} + +java_library { + name: "androidx.window.sidecar", + srcs: ["src/**/*.java"], + static_libs: ["window-sidecar"], + installable: true, + sdk_version: "core_platform", + vendor: true, + libs: ["framework", "androidx.annotation_annotation",], + required: ["androidx.window.sidecar.xml",], +} + +prebuilt_etc { + name: "androidx.window.sidecar.xml", + vendor: true, + sub_dir: "permissions", + src: "androidx.window.sidecar.xml", + filename_from_src: true, +} diff --git a/libs/WindowManager/Jetpack/androidx.window.sidecar.xml b/libs/WindowManager/Jetpack/androidx.window.sidecar.xml new file mode 100644 index 000000000000..f88a5f4ae039 --- /dev/null +++ b/libs/WindowManager/Jetpack/androidx.window.sidecar.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<permissions> + <library + name="androidx.window.sidecar" + file="/vendor/framework/androidx.window.sidecar.jar"/> +</permissions> diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java new file mode 100644 index 000000000000..92e575804bbe --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java @@ -0,0 +1,222 @@ +/* + * 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 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<SidecarDisplayFeature> displayFeatures = readDisplayFeatures(windowToken); + SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo(); + windowLayoutInfo.displayFeatures = displayFeatures; + return windowLayoutInfo; + } + + private List<SidecarDisplayFeature> readDisplayFeatures(IBinder windowToken) { + List<SidecarDisplayFeature> features = new ArrayList<SidecarDisplayFeature>(); + int displayId = getWindowDisplay(windowToken); + if (displayId != DEFAULT_DISPLAY) { + Log.w(TAG, "This sample doesn't support display features on secondary displays"); + return features; + } + + ContentResolver resolver = mContext.getContentResolver(); + final String displayFeaturesString = Settings.Global.getString(resolver, DISPLAY_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; + } + 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 (!featureRect.isEmpty()) { + 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; + } + + @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/SidecarHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java new file mode 100644 index 000000000000..e5b6cff17b26 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java @@ -0,0 +1,126 @@ +/* + * 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.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.Nullable; + +class SidecarHelper { + /** + * Rotate the input rectangle specified in default display orientation to the current display + * rotation. + */ + static void rotateRectToDisplayRotation(Rect inOutRect, int displayId) { + DisplayManagerGlobal dmGlobal = DisplayManagerGlobal.getInstance(); + DisplayInfo displayInfo = dmGlobal.getDisplayInfo(displayId); + int rotation = displayInfo.rotation; + + boolean isSideRotation = rotation == ROTATION_90 || rotation == ROTATION_270; + int displayWidth = isSideRotation ? displayInfo.logicalHeight : displayInfo.logicalWidth; + int displayHeight = isSideRotation ? displayInfo.logicalWidth : displayInfo.logicalHeight; + + inOutRect.intersect(0, 0, displayWidth, displayHeight); + + rotateBounds(inOutRect, displayWidth, displayHeight, rotation); + } + + /** + * Rotate the input rectangle within parent bounds for a given delta. + */ + private static void rotateBounds(Rect inOutRect, int parentWidth, int parentHeight, + @Surface.Rotation int delta) { + int origLeft = inOutRect.left; + switch (delta) { + case ROTATION_0: + return; + case ROTATION_90: + inOutRect.left = inOutRect.top; + inOutRect.top = parentWidth - inOutRect.right; + inOutRect.right = inOutRect.bottom; + inOutRect.bottom = parentWidth - origLeft; + return; + case ROTATION_180: + inOutRect.left = parentWidth - inOutRect.right; + inOutRect.right = parentWidth - origLeft; + return; + case ROTATION_270: + inOutRect.left = parentHeight - inOutRect.bottom; + inOutRect.bottom = inOutRect.right; + inOutRect.right = parentHeight - inOutRect.top; + inOutRect.top = origLeft; + return; + } + } + + /** Transform rectangle from absolute coordinate space to the window coordinate space. */ + static void transformToWindowSpaceRect(Rect inOutRect, IBinder windowToken) { + Rect windowRect = getWindowBounds(windowToken); + if (windowRect == null) { + inOutRect.setEmpty(); + return; + } + if (!Rect.intersects(inOutRect, windowRect)) { + inOutRect.setEmpty(); + return; + } + inOutRect.intersect(windowRect); + inOutRect.offset(-windowRect.left, -windowRect.top); + } + + /** + * Get the current window bounds in absolute coordinates. + * NOTE: Only works with Activity windows. + */ + @Nullable + private static Rect getWindowBounds(IBinder windowToken) { + Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken); + return activity != null + ? activity.getWindowManager().getCurrentWindowMetrics().getBounds() + : null; + } + + /** + * Check if this window is an Activity window that is in multi-window mode. + */ + 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; + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java new file mode 100644 index 000000000000..0b4915ed5dac --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java @@ -0,0 +1,41 @@ +/* + * 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 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 SidecarProvider { + /** + * Provide a simple implementation of {@link SidecarInterface} that can be replaced by + * an OEM by overriding this method. + */ + public static SidecarInterface getSidecarImpl(Context context) { + return new SettingsSidecarImpl(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 "0.1.0-settings_sample"; + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/StubSidecar.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/StubSidecar.java new file mode 100644 index 000000000000..199c37315c07 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/StubSidecar.java @@ -0,0 +1,85 @@ +/* + * 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 android.os.IBinder; + +import androidx.annotation.NonNull; + +import java.util.HashSet; +import java.util.Set; + +/** + * Basic implementation of the {@link SidecarInterface}. An OEM can choose to use it as the base + * class for their implementation. + */ +abstract class StubSidecar implements SidecarInterface { + + private SidecarCallback mSidecarCallback; + private final Set<IBinder> mWindowLayoutChangeListenerTokens = new HashSet<>(); + private boolean mDeviceStateChangeListenerRegistered; + + StubSidecar() { + } + + @Override + public void setSidecarCallback(@NonNull SidecarCallback sidecarCallback) { + this.mSidecarCallback = sidecarCallback; + } + + @Override + public void onWindowLayoutChangeListenerAdded(@NonNull IBinder iBinder) { + this.mWindowLayoutChangeListenerTokens.add(iBinder); + this.onListenersChanged(); + } + + @Override + public void onWindowLayoutChangeListenerRemoved(@NonNull IBinder iBinder) { + this.mWindowLayoutChangeListenerTokens.remove(iBinder); + this.onListenersChanged(); + } + + @Override + public void onDeviceStateListenersChanged(boolean isEmpty) { + this.mDeviceStateChangeListenerRegistered = !isEmpty; + this.onListenersChanged(); + } + + void updateDeviceState(SidecarDeviceState newState) { + if (this.mSidecarCallback != null) { + mSidecarCallback.onDeviceStateChanged(newState); + } + } + + void updateWindowLayout(@NonNull IBinder windowToken, + @NonNull SidecarWindowLayoutInfo newLayout) { + if (this.mSidecarCallback != null) { + mSidecarCallback.onWindowLayoutChanged(windowToken, newLayout); + } + } + + @NonNull + Set<IBinder> getWindowsListeningForLayoutChanges() { + return mWindowLayoutChangeListenerTokens; + } + + protected boolean hasListeners() { + return !mWindowLayoutChangeListenerTokens.isEmpty() || mDeviceStateChangeListenerRegistered; + } + + protected abstract void onListenersChanged(); +} diff --git a/libs/WindowManager/Jetpack/window-sidecar-release.aar b/libs/WindowManager/Jetpack/window-sidecar-release.aar Binary files differnew file mode 100644 index 000000000000..50f101d7d181 --- /dev/null +++ b/libs/WindowManager/Jetpack/window-sidecar-release.aar |