Merge "Add sample WM Jetpack Extensions impl" into sc-dev

This commit is contained in:
Andrii Kulian
2021-02-05 04:55:02 +00:00
committed by Android (Google) Code Review
12 changed files with 680 additions and 268 deletions

View File

@@ -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,
}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<permissions>
<library
name="androidx.window.extensions"
file="/system_ext/framework/androidx.window.extensions.jar"/>
</permissions>

View File

@@ -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";
}
}

View File

@@ -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<ExtensionDisplayFeature> displayFeatures = getDisplayFeatures(activity);
return new ExtensionWindowLayoutInfo(displayFeatures);
}
private List<ExtensionDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
List<ExtensionDisplayFeature> 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<BaseDisplayFeature> 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();
}
}

View File

@@ -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<Activity> 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<Activity> getActivitiesListeningForLayoutChanges() {
return mWindowLayoutChangeListenerActivities;
}
protected boolean hasListeners() {
return !mWindowLayoutChangeListenerActivities.isEmpty()
|| mDeviceStateChangeListenerRegistered;
}
protected abstract void onListenersChanged();
}

View File

@@ -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<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
List<SidecarDisplayFeature> features = new ArrayList<SidecarDisplayFeature>();
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<BaseDisplayFeature> 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();
}
}
}

View File

@@ -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<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;
}
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();
}
}
}

View File

@@ -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);
}
/**

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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<BaseDisplayFeature> getDisplayFeatures() {
List<BaseDisplayFeature> features = new ArrayList<>();
String displayFeaturesString = Settings.Global.getString(mResolver, DISPLAY_FEATURES);
if (TextUtils.isEmpty(displayFeaturesString)) {
displayFeaturesString = mContext.getResources().getString(
R.string.config_display_features);
}
if (TextUtils.isEmpty(displayFeaturesString)) {
return features;
}
String[] featureStrings = displayFeaturesString.split(";");
int deviceState = getDeviceState();
for (String featureString : featureStrings) {
Matcher featureMatcher = FEATURE_PATTERN.matcher(featureString);
if (!featureMatcher.matches()) {
Log.e(TAG, "Malformed feature description format: " + featureString);
continue;
}
try {
String featureType = featureMatcher.group(1);
int type;
switch (featureType) {
case FEATURE_TYPE_FOLD:
type = 1 /* TYPE_FOLD */;
break;
case FEATURE_TYPE_HINGE:
type = 2 /* TYPE_HINGE */;
break;
default: {
Log.e(TAG, "Malformed feature type: " + featureType);
continue;
}
}
int left = Integer.parseInt(featureMatcher.group(2));
int top = Integer.parseInt(featureMatcher.group(3));
int right = Integer.parseInt(featureMatcher.group(4));
int bottom = Integer.parseInt(featureMatcher.group(5));
Rect featureRect = new Rect(left, top, right, bottom);
if (!isZero(featureRect)) {
BaseDisplayFeature feature = new BaseDisplayFeature(type, deviceState,
featureRect);
features.add(feature);
} else {
Log.w(TAG, "Read empty feature");
}
} catch (NumberFormatException e) {
Log.e(TAG, "Malformed feature description: " + featureString);
}
}
return features;
}
@Override
public void onChange(boolean selfChange, Uri uri) {
if (uri == null) {
return;
}
if (mDevicePostureUri.equals(uri)) {
mCallback.onDevicePostureChanged();
mCallback.onDisplayFeaturesChanged();
return;
}
if (mDisplayFeaturesUri.equals(uri)) {
mCallback.onDisplayFeaturesChanged();
}
}
/**
* Callback that notifies about device or display feature state changes.
*/
public interface StateChangeCallback {
/**
* Notifies about the device state update.
*/
void onDevicePostureChanged();
/**
* Notifies about the display feature config update.
*/
void onDisplayFeaturesChanged();
}
}