[go: nahoru, domu]

blob: 295151b8ee7e46fccee84e7dffa35e850c54c82e [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;
import static androidx.window.ExtensionCompat.DEBUG;
import static androidx.window.Version.VERSION_0_1;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.collection.SimpleArrayMap;
import androidx.window.sidecar.SidecarDeviceState;
import androidx.window.sidecar.SidecarDisplayFeature;
import androidx.window.sidecar.SidecarInterface;
import androidx.window.sidecar.SidecarProvider;
import androidx.window.sidecar.SidecarWindowLayoutInfo;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/** Extension interface compatibility wrapper for v0.1 sidecar. */
@SuppressWarnings("deprecation")
final class SidecarCompat implements ExtensionInterfaceCompat {
private static final String TAG = "SidecarCompat";
// Map of active listeners registered with #onWindowLayoutChangeListenerAdded() and not yet
// removed by #onWindowLayoutChangeListenerRemoved().
protected final SimpleArrayMap<IBinder, Activity> mWindowListenerRegisteredContexts =
new SimpleArrayMap<>();
private ExtensionCallbackInterface mExtensionCallback;
private final SidecarAdapter mSidecarAdapter;
@VisibleForTesting
final SidecarInterface mSidecar;
SidecarCompat(Context context) {
this(SidecarProvider.getSidecarImpl(context), new SidecarAdapter());
if (mSidecar == null) {
throw new IllegalArgumentException("Sidecar provider returned null");
}
}
@VisibleForTesting
SidecarCompat(@NonNull SidecarInterface sidecar, SidecarAdapter sidecarAdapter) {
// Empty implementation to avoid null checks.
mExtensionCallback = new ExtensionCallbackInterface() {
@Override
public void onDeviceStateChanged(@NonNull DeviceState newDeviceState) {
}
@Override
public void onWindowLayoutChanged(@NonNull Activity activity,
@NonNull WindowLayoutInfo newLayout) {
}
};
mSidecar = sidecar;
mSidecarAdapter = sidecarAdapter;
}
@Override
public void setExtensionCallback(@NonNull ExtensionCallbackInterface extensionCallback) {
mExtensionCallback = extensionCallback;
mSidecar.setSidecarCallback(new SidecarInterface.SidecarCallback() {
@Override
@SuppressLint("SyntheticAccessor")
public void onDeviceStateChanged(@NonNull SidecarDeviceState newDeviceState) {
extensionCallback.onDeviceStateChanged(mSidecarAdapter.translate(newDeviceState));
for (int i = 0; i < mWindowListenerRegisteredContexts.size(); i++) {
Activity activity = mWindowListenerRegisteredContexts.valueAt(i);
IBinder windowToken = getActivityWindowToken(activity);
if (windowToken == null) {
continue;
}
SidecarWindowLayoutInfo layoutInfo = mSidecar.getWindowLayoutInfo(windowToken);
extensionCallback.onWindowLayoutChanged(activity,
mSidecarAdapter.translate(activity, layoutInfo, newDeviceState));
}
}
@Override
@SuppressLint("SyntheticAccessor")
public void onWindowLayoutChanged(@NonNull IBinder windowToken,
@NonNull SidecarWindowLayoutInfo newLayout) {
Activity activity = mWindowListenerRegisteredContexts.get(windowToken);
if (activity == null) {
Log.w(TAG, "Unable to resolve activity from window token. Missing a call"
+ "to #onWindowLayoutChangeListenerAdded()?");
return;
}
extensionCallback.onWindowLayoutChanged(activity,
mSidecarAdapter.translate(activity, newLayout, mSidecar.getDeviceState()));
}
});
}
@NonNull
@VisibleForTesting
WindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) {
IBinder windowToken = getActivityWindowToken(activity);
SidecarWindowLayoutInfo windowLayoutInfo = mSidecar.getWindowLayoutInfo(windowToken);
return mSidecarAdapter.translate(activity, windowLayoutInfo, mSidecar.getDeviceState());
}
@Override
public void onWindowLayoutChangeListenerAdded(@NonNull Activity activity) {
IBinder windowToken = getActivityWindowToken(activity);
if (windowToken != null) {
register(windowToken, activity);
} else {
FirstAttachAdapter attachAdapter = new FirstAttachAdapter(() -> {
IBinder token = getActivityWindowToken(activity);
register(token, activity);
});
activity.getWindow().getDecorView().addOnAttachStateChangeListener(attachAdapter);
}
}
private void register(IBinder windowToken, Activity activity) {
mWindowListenerRegisteredContexts.put(windowToken, activity);
mSidecar.onWindowLayoutChangeListenerAdded(windowToken);
mExtensionCallback.onWindowLayoutChanged(activity, getWindowLayoutInfo(activity));
}
@Override
public void onWindowLayoutChangeListenerRemoved(@NonNull Activity activity) {
IBinder windowToken = getActivityWindowToken(activity);
mSidecar.onWindowLayoutChangeListenerRemoved(windowToken);
mWindowListenerRegisteredContexts.remove(windowToken);
}
@Override
public void onDeviceStateListenersChanged(boolean isEmpty) {
if (!isEmpty) {
SidecarDeviceState deviceState = mSidecar.getDeviceState();
mExtensionCallback.onDeviceStateChanged(mSidecarAdapter.translate(deviceState));
}
mSidecar.onDeviceStateListenersChanged(isEmpty);
}
@SuppressLint("BanUncheckedReflection")
@Override
@SuppressWarnings("unused")
public boolean validateExtensionInterface() {
try {
// sidecar.setSidecarCallback(SidecarInterface.SidecarCallback);
Method methodSetSidecarCallback = mSidecar.getClass().getMethod("setSidecarCallback",
SidecarInterface.SidecarCallback.class);
Class<?> rSetSidecarCallback = methodSetSidecarCallback.getReturnType();
if (!rSetSidecarCallback.equals(void.class)) {
throw new NoSuchMethodException("Illegal return type for 'setSidecarCallback': "
+ rSetSidecarCallback);
}
// DO NOT REMOVE SINCE THIS IS VALIDATING THE INTERFACE.
// sidecar.getDeviceState()
SidecarDeviceState tmpDeviceState = mSidecar.getDeviceState();
// sidecar.onDeviceStateListenersChanged(boolean);
mSidecar.onDeviceStateListenersChanged(true /* isEmpty */);
// sidecar.getWindowLayoutInfo(IBinder)
Method methodGetWindowLayoutInfo = mSidecar.getClass()
.getMethod("getWindowLayoutInfo", IBinder.class);
Class<?> rtGetWindowLayoutInfo = methodGetWindowLayoutInfo.getReturnType();
if (!rtGetWindowLayoutInfo.equals(SidecarWindowLayoutInfo.class)) {
throw new NoSuchMethodException(
"Illegal return type for 'getWindowLayoutInfo': "
+ rtGetWindowLayoutInfo);
}
// sidecar.onWindowLayoutChangeListenerAdded(IBinder);
Method methodRegisterWindowLayoutChangeListener = mSidecar.getClass()
.getMethod("onWindowLayoutChangeListenerAdded", IBinder.class);
Class<?> rtRegisterWindowLayoutChangeListener =
methodRegisterWindowLayoutChangeListener.getReturnType();
if (!rtRegisterWindowLayoutChangeListener.equals(void.class)) {
throw new NoSuchMethodException(
"Illegal return type for 'onWindowLayoutChangeListenerAdded': "
+ rtRegisterWindowLayoutChangeListener);
}
// sidecar.onWindowLayoutChangeListenerRemoved(IBinder);
Method methodUnregisterWindowLayoutChangeListener = mSidecar.getClass()
.getMethod("onWindowLayoutChangeListenerRemoved", IBinder.class);
Class<?> rtUnregisterWindowLayoutChangeListener =
methodUnregisterWindowLayoutChangeListener.getReturnType();
if (!rtUnregisterWindowLayoutChangeListener.equals(void.class)) {
throw new NoSuchMethodException(
"Illegal return type for 'onWindowLayoutChangeListenerRemoved': "
+ rtUnregisterWindowLayoutChangeListener);
}
// SidecarDeviceState constructor
tmpDeviceState = new SidecarDeviceState();
// deviceState.posture
// TODO(b/172620880): Workaround for Sidecar API implementation issue.
try {
tmpDeviceState.posture = SidecarDeviceState.POSTURE_OPENED;
} catch (NoSuchFieldError error) {
if (DEBUG) {
Log.w(TAG, "Sidecar implementation doesn't conform to primary interface "
+ "version, continue to check for the secondary one "
+ VERSION_0_1 + ", error: " + error);
}
Method methodSetPosture = SidecarDeviceState.class.getMethod("setPosture",
int.class);
methodSetPosture.invoke(tmpDeviceState, SidecarDeviceState.POSTURE_OPENED);
Method methodGetPosture = SidecarDeviceState.class.getMethod("getPosture");
int posture = (int) methodGetPosture.invoke(tmpDeviceState);
if (posture != SidecarDeviceState.POSTURE_OPENED) {
throw new Exception("Invalid device posture getter/setter");
}
}
// SidecarDisplayFeature constructor
SidecarDisplayFeature displayFeature = new SidecarDisplayFeature();
// displayFeature.getRect()/setRect()
Rect tmpRect = displayFeature.getRect();
displayFeature.setRect(tmpRect);
// displayFeature.getType()/setType()
int tmpType = displayFeature.getType();
displayFeature.setType(SidecarDisplayFeature.TYPE_FOLD);
// SidecarWindowLayoutInfo constructor
SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo();
// windowLayoutInfo.displayFeatures
try {
final List<SidecarDisplayFeature> tmpDisplayFeatures =
windowLayoutInfo.displayFeatures;
// TODO(b/172620880): Workaround for Sidecar API implementation issue.
} catch (NoSuchFieldError error) {
if (DEBUG) {
Log.w(TAG, "Sidecar implementation doesn't conform to primary interface "
+ "version, continue to check for the secondary one "
+ VERSION_0_1 + ", error: " + error);
}
List<SidecarDisplayFeature> featureList = new ArrayList<>();
featureList.add(displayFeature);
Method methodSetFeatures = SidecarWindowLayoutInfo.class.getMethod(
"setDisplayFeatures", List.class);
methodSetFeatures.invoke(windowLayoutInfo, featureList);
Method methodGetFeatures = SidecarWindowLayoutInfo.class.getMethod(
"getDisplayFeatures");
@SuppressWarnings("unchecked")
final List<SidecarDisplayFeature> resultDisplayFeatures =
(List<SidecarDisplayFeature>) methodGetFeatures.invoke(windowLayoutInfo);
if (!featureList.equals(resultDisplayFeatures)) {
throw new Exception("Invalid display feature getter/setter");
}
}
return true;
} catch (Throwable t) {
if (DEBUG) {
Log.e(TAG, "Sidecar implementation doesn't conform to interface version "
+ VERSION_0_1 + ", error: " + t);
}
return false;
}
}
@Nullable
static Version getSidecarVersion() {
try {
String vendorVersion = SidecarProvider.getApiVersion();
return !TextUtils.isEmpty(vendorVersion) ? Version.parse(vendorVersion) : null;
} catch (NoClassDefFoundError e) {
if (DEBUG) {
Log.d(TAG, "Sidecar version not found");
}
return null;
} catch (UnsupportedOperationException e) {
if (DEBUG) {
Log.d(TAG, "Stub Sidecar");
}
return null;
}
}
@Nullable
private IBinder getActivityWindowToken(Activity activity) {
return activity.getWindow() != null ? activity.getWindow().getAttributes().token : null;
}
/**
* An adapter that will run a callback when a window is attached and then be removed from the
* listener set.
*/
private static class FirstAttachAdapter implements View.OnAttachStateChangeListener {
private final Runnable mCallback;
FirstAttachAdapter(Runnable callback) {
mCallback = callback;
}
@Override
public void onViewAttachedToWindow(View view) {
mCallback.run();
view.removeOnAttachStateChangeListener(this);
}
@Override
public void onViewDetachedFromWindow(View view) {
}
}
}