Loading core/java/android/content/res/CameraCompatibilityInfo.java 0 → 100644 +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 + '}'; } } core/tests/coretests/src/android/content/res/CameraCompatibilityInfoTest.java 0 → 100644 +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)); } } Loading
core/java/android/content/res/CameraCompatibilityInfo.java 0 → 100644 +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 + '}'; } }
core/tests/coretests/src/android/content/res/CameraCompatibilityInfoTest.java 0 → 100644 +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)); } }