SidecarCompat.java

/*
 * 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.DeviceState.POSTURE_MAX_KNOWN;
import static androidx.window.DeviceState.POSTURE_UNKNOWN;
import static androidx.window.ExtensionHelper.DEBUG;
import static androidx.window.Version.VERSION_0_1;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Rect;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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";

    private SidecarInterface mSidecar;

    SidecarCompat(Context context) {
        mSidecar = SidecarProvider.getSidecarImpl(context);
        if (mSidecar == null) {
            throw new IllegalArgumentException("Sidecar provider returned null");
        }
    }

    @Override
    public void setExtensionCallback(@NonNull ExtensionCallbackInterface extensionCallback) {
        mSidecar.setSidecarCallback(new SidecarInterface.SidecarCallback() {
            @Override
            @SuppressLint("SyntheticAccessor")
            public void onDeviceStateChanged(@NonNull SidecarDeviceState newDeviceState) {
                extensionCallback.onDeviceStateChanged(deviceStateFromSidecar(newDeviceState));
            }

            @Override
            @SuppressLint("SyntheticAccessor")
            public void onWindowLayoutChanged(@NonNull IBinder windowToken,
                    @NonNull SidecarWindowLayoutInfo newLayout) {
                extensionCallback.onWindowLayoutChanged(windowToken,
                        windowLayoutInfoFromSidecar(newLayout));
            }
        });
    }

    @NonNull
    @Override
    public WindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) {
        SidecarWindowLayoutInfo windowLayoutInfo = mSidecar.getWindowLayoutInfo(windowToken);
        return windowLayoutInfoFromSidecar(windowLayoutInfo);
    }

    @Override
    public void onWindowLayoutChangeListenerAdded(@NonNull IBinder windowToken) {
        mSidecar.onWindowLayoutChangeListenerAdded(windowToken);
    }

    @Override
    public void onWindowLayoutChangeListenerRemoved(@NonNull IBinder windowToken) {
        mSidecar.onWindowLayoutChangeListenerRemoved(windowToken);
    }

    @NonNull
    @Override
    public DeviceState getDeviceState() {
        SidecarDeviceState deviceState = mSidecar.getDeviceState();
        return deviceStateFromSidecar(deviceState);
    }

    @Override
    public void onDeviceStateListenersChanged(boolean isEmpty) {
        mSidecar.onDeviceStateListenersChanged(isEmpty);
    }

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

            // 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
            tmpDeviceState.posture = SidecarDeviceState.POSTURE_OPENED;

            // 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
            final List<SidecarDisplayFeature> tmpDisplayFeatures = windowLayoutInfo.displayFeatures;

            return true;
        } catch (Exception e) {
            if (DEBUG) {
                Log.e(TAG, "Extension implementation doesn't conform to interface version "
                        + VERSION_0_1 + ", error: " + e);
            }
            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;
        }
    }

    private static DisplayFeature displayFeatureFromExtension(SidecarDisplayFeature feature) {
        return new DisplayFeature(feature.getRect(), feature.getType());
    }

    @NonNull
    private static List<DisplayFeature> displayFeatureListFromSidecar(
            SidecarWindowLayoutInfo sidecarWindowLayoutInfo) {
        List<DisplayFeature> displayFeatures = new ArrayList<>();
        if (sidecarWindowLayoutInfo.displayFeatures == null) {
            return displayFeatures;
        }

        for (SidecarDisplayFeature sidecarFeature : sidecarWindowLayoutInfo.displayFeatures) {
            displayFeatures.add(displayFeatureFromExtension(sidecarFeature));
        }
        return displayFeatures;
    }

    @NonNull
    private static WindowLayoutInfo windowLayoutInfoFromSidecar(
            @Nullable SidecarWindowLayoutInfo extensionInfo) {
        if (extensionInfo == null) {
            return new WindowLayoutInfo(new ArrayList<>());
        }

        List<DisplayFeature> displayFeatures = displayFeatureListFromSidecar(extensionInfo);
        return new WindowLayoutInfo(displayFeatures);
    }

    @DeviceState.Posture
    private static int postureFromSidecar(SidecarDeviceState sidecarDeviceState) {
        int sidecarPosture = sidecarDeviceState.posture;
        if (sidecarPosture > POSTURE_MAX_KNOWN) {
            if (DEBUG) {
                Log.d(TAG, "Unknown posture reported, WindowManager library should be updated");
            }
            return POSTURE_UNKNOWN;
        }
        return sidecarPosture;
    }

    @NonNull
    private static DeviceState deviceStateFromSidecar(
            @Nullable SidecarDeviceState sidecarDeviceState) {
        if (sidecarDeviceState == null) {
            return new DeviceState(POSTURE_UNKNOWN);
        }

        int posture = postureFromSidecar(sidecarDeviceState);
        return new DeviceState(posture);
    }
}