Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4e2786de authored by Mina Karadzic's avatar Mina Karadzic Committed by Android (Google) Code Review
Browse files

Merge "Define CameraCompatibilityInfo object." into main

parents bbb4c36f e1ce41d7
Loading
Loading
Loading
Loading
+207 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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 android.content.res;

import static android.app.WindowConfiguration.ROTATION_UNDEFINED;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;


import java.util.Objects;

/**
 * Object to communicate camera compatibility info setup.
 *
 * <p>Camera Compatibility is used to resolve major camera issues - like stretched or sideways
 * previews - for apps not adapted to large screens. The camera compatibility treatment represented
 * by this object sandboxes the eligible activity's environment to what that app most likely expects
 * given its requested orientation and current device state. Some of the most important platform
 * signals for calculating camera preview are: camera sensor orientation, device rotation, app
 * window aspect ratio and how the camera feed is rotated, all of which apps tend to make
 * assumptions about, and therefore could be changed as part of Camera Compatibility treatment.
 *
 * <p>Upon detecting eligible camera activity, Window Manager calculates the necessary changes to
 * display rotation, window bounds, camera sensor and feed, and requests the platform to reflect
 * these changes using this {@link CameraCompatibilityInfo} object.
 *
 * @hide
 */
public final class CameraCompatibilityInfo implements Parcelable {
    private final int mRotateAndCropRotation;
    private final boolean mShouldOverrideSensorOrientation;
    private final boolean mShouldLetterboxForCameraCompat;
    private final int mDisplayRotationSandbox;

    private CameraCompatibilityInfo(Builder builder) {
        mRotateAndCropRotation = builder.mRotateAndCropRotation;
        mShouldOverrideSensorOrientation = builder.mShouldOverrideSensorOrientation;
        mShouldLetterboxForCameraCompat = builder.mShouldLetterboxForCameraCompat;
        mDisplayRotationSandbox = builder.mDisplayRotationSandbox;
    }

    private CameraCompatibilityInfo(Parcel in) {
        mRotateAndCropRotation = in.readInt();
        mShouldOverrideSensorOrientation = in.readByte() != 0;
        mShouldLetterboxForCameraCompat = in.readByte() != 0;
        mDisplayRotationSandbox = in.readInt();
    }

    /**
     * By how much camera feed should be rotated for compatibility, as
     * `android.view.Surface.Rotation` enum. If none, the value is
     * `android.app.WindowConfiguration.ROTATION_UNDEFINED`.
     */
    public int getRotateAndCropRotation() {
        return mRotateAndCropRotation;
    }

    /** Whether camera sensor orientation should be sandboxed (usually to portrait). */
    public boolean shouldOverrideSensorOrientation() {
        return mShouldOverrideSensorOrientation;
    }

    /** Whether camera activity should be letterboxed, i.e. whether app bounds should be changed. */
    public boolean shouldLetterboxForCameraCompat() {
        return mShouldLetterboxForCameraCompat;
    }

    /**
     *  Display rotation that the camera compatibility app should see. If sandboxing should not be
     *  applied, the value is `android.app.WindowConfiguration.ROTATION_UNDEFINED`.
     */
    public int getDisplayRotationSandbox() {
        return mDisplayRotationSandbox;
    }

    /** Builder for {@link CameraCompatibilityInfo} */
    public static final class Builder {
        private int mRotateAndCropRotation = ROTATION_UNDEFINED;
        private boolean mShouldOverrideSensorOrientation = false;
        private boolean mShouldLetterboxForCameraCompat = false;
        private int mDisplayRotationSandbox = ROTATION_UNDEFINED;

        public Builder() {}

        /**
         *  Sets by how much camera feed should be rotated for compatibility, as
         * `android.view.Surface.Rotation` enum. If none, the value is
         * `android.app.WindowConfiguration.ROTATION_UNDEFINED`.
         */
        public Builder setRotateAndCropRotation(int rotateAndCropRotation) {
            mRotateAndCropRotation = rotateAndCropRotation;
            return this;
        }

        /** Sets whether camera sensor orientation should be sandboxed (usually to portrait). */
        public Builder setShouldOverrideSensorOrientation(boolean shouldOverrideSensorOrientation) {
            mShouldOverrideSensorOrientation = shouldOverrideSensorOrientation;
            return this;
        }

        /**
         * Sets whether camera activity should be letterboxed, i.e. whether app bounds should be
         * changed.
         */
        public Builder setShouldLetterboxForCameraCompat(boolean shouldLetterboxForCameraCompat) {
            mShouldLetterboxForCameraCompat = shouldLetterboxForCameraCompat;
            return this;
        }

        /**
         *  Sets the display rotation that the camera compatibility app should see. If sandboxing
         *  should not be applied, the value should be
         *  `android.app.WindowConfiguration.ROTATION_UNDEFINED`.
         */
        public Builder setDisplayRotationSandbox(int displayRotationSandbox) {
            mDisplayRotationSandbox = displayRotationSandbox;
            return this;
        }

        /** Builds a {@link CameraCompatibilityInfo} object. */
        public CameraCompatibilityInfo build() {
            return new CameraCompatibilityInfo(this);
        }
    }

    public static final Creator<CameraCompatibilityInfo> CREATOR =
            new Creator<CameraCompatibilityInfo>() {
                @Override
                public CameraCompatibilityInfo createFromParcel(Parcel in) {
                    return new CameraCompatibilityInfo(in);
                }

                @Override
                public CameraCompatibilityInfo[] newArray(int size) {
                    return new CameraCompatibilityInfo[size];
                }
            };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeInt(mRotateAndCropRotation);
        dest.writeBoolean(mShouldOverrideSensorOrientation);
        dest.writeBoolean(mShouldLetterboxForCameraCompat);
        dest.writeInt(mDisplayRotationSandbox);
    }

    @Override
    public int hashCode() {
        return Objects.hash(mRotateAndCropRotation,
                mShouldOverrideSensorOrientation, mShouldLetterboxForCameraCompat,
                mDisplayRotationSandbox);
    }

    @Override
    public boolean equals(@Nullable Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof CameraCompatibilityInfo that)) {
            return false;
        }
        return mRotateAndCropRotation == that.mRotateAndCropRotation
                && mShouldOverrideSensorOrientation == that.mShouldOverrideSensorOrientation
                && mShouldLetterboxForCameraCompat == that.mShouldLetterboxForCameraCompat
                && mDisplayRotationSandbox == that.mDisplayRotationSandbox;
    }

    /** Whether any camera compat mode changes are requested via this object. */
    public static boolean isCameraCompatModeActive(CameraCompatibilityInfo cameraCompatMode) {
        return cameraCompatMode.mRotateAndCropRotation != ROTATION_UNDEFINED
                || cameraCompatMode.mShouldOverrideSensorOrientation
                || cameraCompatMode.mShouldLetterboxForCameraCompat
                || cameraCompatMode.mDisplayRotationSandbox != ROTATION_UNDEFINED;
    }

    @Override
    public String toString() {
        return "CameraCompatibilityInfo{"
                + "mRotateAndCropRotation=" + mRotateAndCropRotation
                + ", mShouldOverrideSensorOrientation=" + mShouldOverrideSensorOrientation
                + ", mShouldLetterboxForCameraCompat=" + mShouldLetterboxForCameraCompat
                + ", mDisplayRotationSandbox=" + mDisplayRotationSandbox
                + '}';
    }
}
+135 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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 android.content.res;

import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;

import android.os.Parcel;
import android.platform.test.annotations.Presubmit;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import org.junit.Test;
import org.junit.runner.RunWith;

@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class CameraCompatibilityInfoTest {

    @Test
    public void testDefault() {
        final CameraCompatibilityInfo info = new CameraCompatibilityInfo.Builder()
                .build();

        assertEquals(ROTATION_UNDEFINED, info.getRotateAndCropRotation());
        assertFalse(info.shouldOverrideSensorOrientation());
        assertFalse(info.shouldLetterboxForCameraCompat());
        assertEquals(ROTATION_UNDEFINED, info.getDisplayRotationSandbox());
    }

    @Test
    public void testBuilderAndGetters() {
        final CameraCompatibilityInfo info = new CameraCompatibilityInfo.Builder()
                .setRotateAndCropRotation(ROTATION_90)
                .setShouldOverrideSensorOrientation(true)
                .setShouldLetterboxForCameraCompat(true)
                .setDisplayRotationSandbox(ROTATION_270)
                .build();

        assertEquals(ROTATION_90, info.getRotateAndCropRotation());
        assertTrue(info.shouldOverrideSensorOrientation());
        assertTrue(info.shouldLetterboxForCameraCompat());
        assertEquals(ROTATION_270, info.getDisplayRotationSandbox());
    }

    @Test
    public void testEqualsAndHashCode() {
        final CameraCompatibilityInfo info1 = new CameraCompatibilityInfo.Builder()
                .setRotateAndCropRotation(ROTATION_90)
                .build();
        final CameraCompatibilityInfo info2 = new CameraCompatibilityInfo.Builder()
                .setRotateAndCropRotation(ROTATION_90)
                .build();
        final CameraCompatibilityInfo info3 = new CameraCompatibilityInfo.Builder()
                .setShouldOverrideSensorOrientation(true)
                .build();

        assertEquals(info1, info2);
        assertEquals(info1.hashCode(), info2.hashCode());
        assertNotEquals(info1, info3);
        assertNotEquals(info1.hashCode(), info3.hashCode());
        assertNotEquals(null, info1);
        assertNotEquals(new Object(), info1);
    }

    @Test
    public void testParcelable() {
        final CameraCompatibilityInfo originalInfo = new CameraCompatibilityInfo.Builder()
                .setRotateAndCropRotation(ROTATION_90)
                .setShouldOverrideSensorOrientation(true)
                .setShouldLetterboxForCameraCompat(true)
                .setDisplayRotationSandbox(ROTATION_90)
                .build();

        final Parcel parcel = Parcel.obtain();
        originalInfo.writeToParcel(parcel, 0);
        parcel.setDataPosition(0);

        final CameraCompatibilityInfo newInfo = CameraCompatibilityInfo.CREATOR.createFromParcel(
                parcel);
        parcel.recycle();

        assertEquals(originalInfo, newInfo);
    }

    @Test
    public void testIsCameraCompatModeActive() {
        // Default object should not be active.
        final CameraCompatibilityInfo defaultInfo = new CameraCompatibilityInfo.Builder().build();
        assertFalse(CameraCompatibilityInfo.isCameraCompatModeActive(defaultInfo));

        // Any non-default value should make it active.
        final CameraCompatibilityInfo info1 = new CameraCompatibilityInfo.Builder()
                .setRotateAndCropRotation(ROTATION_90)
                .build();
        assertTrue(CameraCompatibilityInfo.isCameraCompatModeActive(info1));

        final CameraCompatibilityInfo info2 = new CameraCompatibilityInfo.Builder()
                .setShouldOverrideSensorOrientation(true)
                .build();
        assertTrue(CameraCompatibilityInfo.isCameraCompatModeActive(info2));

        final CameraCompatibilityInfo info3 = new CameraCompatibilityInfo.Builder()
                .setShouldLetterboxForCameraCompat(true)
                .build();
        assertTrue(CameraCompatibilityInfo.isCameraCompatModeActive(info3));

        final CameraCompatibilityInfo info4 = new CameraCompatibilityInfo.Builder()
                .setDisplayRotationSandbox(ROTATION_90)
                .build();
        assertTrue(CameraCompatibilityInfo.isCameraCompatModeActive(info4));
    }
}