blob: 7a3fbf3ad9b8e5491e88f00241e56f68ce82d86d [file] [log] [blame]
/*
* Copyright 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.extensions;
import static android.view.Display.DEFAULT_DISPLAY;
import static androidx.window.extensions.ExtensionHelper.getWindowDisplay;
import static androidx.window.extensions.ExtensionHelper.isInMultiWindow;
import static androidx.window.extensions.ExtensionHelper.rotateRectToDisplayRotation;
import static androidx.window.extensions.ExtensionHelper.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 SettingsExtensionImpl extends StubExtension {
private static final String TAG = "SettingsExtension";
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;
}
}
}
SettingsExtensionImpl(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()) {
ExtensionWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken);
updateWindowLayout(windowToken, newLayout);
}
}
@NonNull
@Override
public ExtensionDeviceState getDeviceState() {
ContentResolver resolver = mContext.getContentResolver();
int posture = Settings.Global.getInt(resolver, DEVICE_POSTURE,
ExtensionDeviceState.POSTURE_UNKNOWN);
return new ExtensionDeviceState(posture);
}
@NonNull
@Override
public ExtensionWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) {
List<ExtensionDisplayFeature> displayFeatures = readDisplayFeatures(windowToken);
return new ExtensionWindowLayoutInfo(displayFeatures);
}
private List<ExtensionDisplayFeature> readDisplayFeatures(IBinder windowToken) {
List<ExtensionDisplayFeature> features = new ArrayList<ExtensionDisplayFeature>();
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 = ExtensionDisplayFeature.TYPE_FOLD;
break;
case FEATURE_TYPE_HINGE:
type = ExtensionDisplayFeature.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()) {
ExtensionDisplayFeature feature =
new ExtensionDisplayFeature(featureRect, 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();
}
}
}