summaryrefslogtreecommitdiff
path: root/media
diff options
context:
space:
mode:
Diffstat (limited to 'media')
-rw-r--r--media/java/android/media/MediaCodecInfo.java15
-rw-r--r--media/java/android/media/MediaRecorder.java2
-rw-r--r--media/java/android/media/quality/MediaQualityContract.java38
-rw-r--r--media/tests/projection/Android.bp8
-rw-r--r--media/tests/projection/AndroidManifest.xml2
-rw-r--r--media/tests/projection/AndroidTest.xml9
-rw-r--r--media/tests/projection/src/android/media/projection/MediaProjectionStoppingTest.java293
7 files changed, 357 insertions, 10 deletions
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index f3b21bfdaa3c..3b560b7a880e 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -219,13 +219,14 @@ public final class MediaCodecInfo {
private static final int DEFAULT_MAX_SUPPORTED_INSTANCES = 32;
private static final int MAX_SUPPORTED_INSTANCES_LIMIT = 256;
- private static final class LazyHolder {
- private static final Range<Integer> SIZE_RANGE = Process.is64Bit()
- ? Range.create(1, 32768)
- : Range.create(1, MediaProperties.resolution_limit_32bit().orElse(4096));
- }
- private static Range<Integer> getSizeRange() {
- return LazyHolder.SIZE_RANGE;
+ private static Range<Integer> SIZE_RANGE;
+ private static synchronized Range<Integer> getSizeRange() {
+ if (SIZE_RANGE == null) {
+ SIZE_RANGE = Process.is64Bit()
+ ? Range.create(1, 32768)
+ : Range.create(1, MediaProperties.resolution_limit_32bit().orElse(4096));
+ }
+ return SIZE_RANGE;
}
// found stuff that is not supported by framework (=> this should not happen)
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 7af78b81cda5..03bcc6afc1b7 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -1299,6 +1299,7 @@ public class MediaRecorder implements AudioRouting,
* start() or before setOutputFormat().
* @throws IOException if prepare fails otherwise.
*/
+ @RequiresPermission(value = android.Manifest.permission.RECORD_AUDIO, conditional = true)
public void prepare() throws IllegalStateException, IOException
{
if (mPath != null) {
@@ -1337,6 +1338,7 @@ public class MediaRecorder implements AudioRouting,
* @throws IllegalStateException if it is called before
* prepare() or when the camera is already in use by another app.
*/
+ @RequiresPermission(value = android.Manifest.permission.RECORD_AUDIO, conditional = true)
public native void start() throws IllegalStateException;
/**
diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java
index fccdba8e727f..ece87a66556f 100644
--- a/media/java/android/media/quality/MediaQualityContract.java
+++ b/media/java/android/media/quality/MediaQualityContract.java
@@ -72,6 +72,43 @@ public class MediaQualityContract {
*/
public static final String LEVEL_OFF = "level_off";
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(prefix = "COLOR_TEMP", value = {
+ COLOR_TEMP_USER,
+ COLOR_TEMP_COOL,
+ COLOR_TEMP_STANDARD,
+ COLOR_TEMP_WARM,
+ COLOR_TEMP_USER_HDR10PLUS,
+ COLOR_TEMP_COOL_HDR10PLUS,
+ COLOR_TEMP_STANDARD_HDR10PLUS,
+ COLOR_TEMP_WARM_HDR10PLUS,
+ COLOR_TEMP_FMMSDR,
+ COLOR_TEMP_FMMHDR,
+ })
+ public @interface ColorTempValue {}
+
+ /** @hide */
+ public static final String COLOR_TEMP_USER = "color_temp_user";
+ /** @hide */
+ public static final String COLOR_TEMP_COOL = "color_temp_cool";
+ /** @hide */
+ public static final String COLOR_TEMP_STANDARD = "color_temp_standard";
+ /** @hide */
+ public static final String COLOR_TEMP_WARM = "color_temp_warm";
+ /** @hide */
+ public static final String COLOR_TEMP_USER_HDR10PLUS = "color_temp_user_hdr10plus";
+ /** @hide */
+ public static final String COLOR_TEMP_COOL_HDR10PLUS = "color_temp_cool_hdr10plus";
+ /** @hide */
+ public static final String COLOR_TEMP_STANDARD_HDR10PLUS = "color_temp_standard_hdr10plus";
+ /** @hide */
+ public static final String COLOR_TEMP_WARM_HDR10PLUS = "color_temp_warm_hdr10plus";
+ /** @hide */
+ public static final String COLOR_TEMP_FMMSDR = "color_temp_fmmsdr";
+ /** @hide */
+ public static final String COLOR_TEMP_FMMHDR = "color_temp_fmmhdr";
+
/**
* @hide
@@ -82,7 +119,6 @@ public class MediaQualityContract {
String PARAMETER_NAME = "_name";
String PARAMETER_PACKAGE = "_package";
String PARAMETER_INPUT_ID = "_input_id";
- String VENDOR_PARAMETERS = "_vendor_parameters";
}
/**
diff --git a/media/tests/projection/Android.bp b/media/tests/projection/Android.bp
index 94db2c02eb28..48621e4e2094 100644
--- a/media/tests/projection/Android.bp
+++ b/media/tests/projection/Android.bp
@@ -16,7 +16,6 @@ android_test {
name: "MediaProjectionTests",
srcs: ["**/*.java"],
-
libs: [
"android.test.base.stubs.system",
"android.test.mock.stubs.system",
@@ -30,6 +29,7 @@ android_test {
"frameworks-base-testutils",
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
+ "cts-mediaprojection-common",
"testng",
"testables",
"truth",
@@ -42,7 +42,11 @@ android_test {
"libstaticjvmtiagent",
],
- test_suites: ["device-tests"],
+ data: [
+ ":CtsMediaProjectionTestCasesHelperApp",
+ ],
+
+ test_suites: ["general-tests"],
platform_apis: true,
diff --git a/media/tests/projection/AndroidManifest.xml b/media/tests/projection/AndroidManifest.xml
index 0c9760400ce0..514fb5f689c9 100644
--- a/media/tests/projection/AndroidManifest.xml
+++ b/media/tests/projection/AndroidManifest.xml
@@ -20,6 +20,8 @@
android:sharedUserId="com.android.uid.test">
<uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
<uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
<application android:debuggable="true"
android:testOnly="true">
diff --git a/media/tests/projection/AndroidTest.xml b/media/tests/projection/AndroidTest.xml
index f64930a0eb3f..99b42d1cd263 100644
--- a/media/tests/projection/AndroidTest.xml
+++ b/media/tests/projection/AndroidTest.xml
@@ -22,6 +22,15 @@
<option name="install-arg" value="-t" />
<option name="test-file-name" value="MediaProjectionTests.apk" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="force-install-mode" value="FULL"/>ss
+ <option name="test-file-name" value="CtsMediaProjectionTestCasesHelperApp.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
+ </target_preparer>
<option name="test-tag" value="MediaProjectionTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
diff --git a/media/tests/projection/src/android/media/projection/MediaProjectionStoppingTest.java b/media/tests/projection/src/android/media/projection/MediaProjectionStoppingTest.java
new file mode 100644
index 000000000000..0b84e01c4d02
--- /dev/null
+++ b/media/tests/projection/src/android/media/projection/MediaProjectionStoppingTest.java
@@ -0,0 +1,293 @@
+/*
+ * 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.media.projection;
+
+import static com.android.compatibility.common.util.FeatureUtil.isAutomotive;
+import static com.android.compatibility.common.util.FeatureUtil.isTV;
+import static com.android.compatibility.common.util.FeatureUtil.isWatch;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.Manifest;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.media.cts.MediaProjectionRule;
+import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
+
+import com.android.compatibility.common.util.ApiTest;
+import com.android.compatibility.common.util.FrameworkSpecificTest;
+import com.android.media.projection.flags.Flags;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test {@link MediaProjection} stopping behavior.
+ *
+ * Run with:
+ * atest MediaProjectionTests:MediaProjectionStoppingTest
+ */
+@FrameworkSpecificTest
+public class MediaProjectionStoppingTest {
+ private static final String TAG = "MediaProjectionStoppingTest";
+ private static final int STOP_DIALOG_WAIT_TIMEOUT_MS = 5000;
+ private static final String CALL_HELPER_START_CALL = "start_call";
+ private static final String CALL_HELPER_STOP_CALL = "stop_call";
+ private static final String STOP_DIALOG_TITLE_RES_ID = "android:id/alertTitle";
+ private static final String STOP_DIALOG_CLOSE_BUTTON_RES_ID = "android:id/button2";
+
+ @Rule public MediaProjectionRule mMediaProjectionRule = new MediaProjectionRule();
+
+ private Context mContext;
+ private int mTimeoutMs;
+ private TelecomManager mTelecomManager;
+ private TelephonyManager mTelephonyManager;
+ private TestCallStateListener mTestCallStateListener;
+
+ @Before
+ public void setUp() throws InterruptedException {
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ runWithShellPermissionIdentity(
+ () -> {
+ mContext.getPackageManager()
+ .revokeRuntimePermission(
+ mContext.getPackageName(),
+ Manifest.permission.SYSTEM_ALERT_WINDOW,
+ new UserHandle(mContext.getUserId()));
+ });
+ mTimeoutMs = 1000;
+
+ mTestCallStateListener = new TestCallStateListener(mContext);
+ }
+
+ @After
+ public void cleanup() {
+ mTestCallStateListener.release();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+ @ApiTest(apis = "android.media.projection.MediaProjection.Callback#onStop")
+ public void testMediaProjectionStop_callStartedAfterMediaProjection_doesNotStop()
+ throws Exception {
+ assumeTrue(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELECOM));
+
+ mMediaProjectionRule.startMediaProjection();
+
+ CountDownLatch latch = new CountDownLatch(1);
+ mMediaProjectionRule.registerCallback(
+ new MediaProjection.Callback() {
+ @Override
+ public void onStop() {
+ latch.countDown();
+ }
+ });
+ mMediaProjectionRule.createVirtualDisplay();
+
+ try {
+ startPhoneCall();
+ } finally {
+ endPhoneCall();
+ }
+
+ assertWithMessage("MediaProjection should not be stopped on call end")
+ .that(latch.await(mTimeoutMs, TimeUnit.MILLISECONDS)).isFalse();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+ @RequiresFlagsDisabled(Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ @ApiTest(apis = "android.media.projection.MediaProjection.Callback#onStop")
+ public void
+ testMediaProjectionStop_callStartedBeforeMediaProjection_stopDialogFlagDisabled__shouldStop()
+ throws Exception {
+ assumeTrue(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELECOM));
+ CountDownLatch latch = new CountDownLatch(1);
+ try {
+ startPhoneCall();
+
+ mMediaProjectionRule.startMediaProjection();
+
+ mMediaProjectionRule.registerCallback(
+ new MediaProjection.Callback() {
+ @Override
+ public void onStop() {
+ latch.countDown();
+ }
+ });
+ mMediaProjectionRule.createVirtualDisplay();
+
+ } finally {
+ endPhoneCall();
+ }
+
+ assertWithMessage("MediaProjection was not stopped after call end")
+ .that(latch.await(mTimeoutMs, TimeUnit.MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ @RequiresFlagsEnabled({
+ Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END,
+ Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END
+ })
+ public void
+ callEnds_mediaProjectionStartedDuringCallAndIsActive_stopDialogFlagEnabled_showsStopDialog()
+ throws Exception {
+ // MediaProjection stop Dialog is only available on phones.
+ assumeFalse(isWatch());
+ assumeFalse(isAutomotive());
+ assumeFalse(isTV());
+
+ assumeTrue(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELECOM));
+
+ try {
+ startPhoneCall();
+ mMediaProjectionRule.startMediaProjection();
+
+ mMediaProjectionRule.registerCallback(
+ new MediaProjection.Callback() {
+ @Override
+ public void onStop() {
+ fail(
+ "MediaProjection should not be stopped when"
+ + " FLAG_SHOW_STOP_DIALOG_POST_CALL_END is enabled");
+ }
+ });
+ mMediaProjectionRule.createVirtualDisplay();
+
+ } finally {
+ endPhoneCall();
+ }
+
+ UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ boolean isDialogShown =
+ device.wait(
+ Until.hasObject(By.res(STOP_DIALOG_TITLE_RES_ID)),
+ STOP_DIALOG_WAIT_TIMEOUT_MS);
+ assertWithMessage("Stop dialog should be visible").that(isDialogShown).isTrue();
+
+ // Find and click the "Close" button
+ boolean hasCloseButton =
+ device.wait(
+ Until.hasObject(By.res(STOP_DIALOG_CLOSE_BUTTON_RES_ID)),
+ STOP_DIALOG_WAIT_TIMEOUT_MS);
+ if (hasCloseButton) {
+ device.findObject(By.res(STOP_DIALOG_CLOSE_BUTTON_RES_ID)).click();
+ Log.d(TAG, "Clicked on 'Close' button to dismiss the stop dialog.");
+ } else {
+ fail("Close button not found, unable to dismiss stop dialog.");
+ }
+ }
+
+ private void startPhoneCall() throws InterruptedException {
+ mTestCallStateListener.assertCallState(false);
+ mContext.startActivity(getCallHelperIntent(CALL_HELPER_START_CALL));
+ mTestCallStateListener.waitForNextCallState(true, mTimeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ private void endPhoneCall() throws InterruptedException {
+ mTestCallStateListener.assertCallState(true);
+ mContext.startActivity(getCallHelperIntent(CALL_HELPER_STOP_CALL));
+ mTestCallStateListener.waitForNextCallState(false, mTimeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ private Intent getCallHelperIntent(String action) {
+ return new Intent(action)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ .setComponent(
+ new ComponentName(
+ "android.media.projection.cts.helper",
+ "android.media.projection.cts.helper.CallHelperActivity"));
+ }
+
+ private static final class TestCallStateListener extends TelephonyCallback
+ implements TelephonyCallback.CallStateListener {
+ private final BlockingQueue<Boolean> mCallStates = new LinkedBlockingQueue<>();
+ private final TelecomManager mTelecomManager;
+ private final TelephonyManager mTelephonyManager;
+
+ private TestCallStateListener(Context context) throws InterruptedException {
+ mTelecomManager = context.getSystemService(TelecomManager.class);
+ mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ mCallStates.offer(isInCall());
+
+ assertThat(mCallStates.take()).isFalse();
+
+ runWithShellPermissionIdentity(
+ () ->
+ mTelephonyManager.registerTelephonyCallback(
+ context.getMainExecutor(), this));
+ }
+
+ public void release() {
+ runWithShellPermissionIdentity(
+ () -> mTelephonyManager.unregisterTelephonyCallback(this));
+ }
+
+ @Override
+ public void onCallStateChanged(int state) {
+ mCallStates.offer(isInCall());
+ }
+
+ public void waitForNextCallState(boolean expectedCallState, long timeout, TimeUnit unit)
+ throws InterruptedException {
+ String message =
+ String.format(
+ "Call was not %s after timeout",
+ expectedCallState ? "started" : "ended");
+
+ boolean value;
+ do {
+ value = mCallStates.poll(timeout, unit);
+ } while (value != expectedCallState);
+ assertWithMessage(message).that(value).isEqualTo(expectedCallState);
+ }
+
+ private boolean isInCall() {
+ return runWithShellPermissionIdentity(mTelecomManager::isInCall);
+ }
+
+ public void assertCallState(boolean expected) {
+ assertWithMessage("Unexpected call state").that(isInCall()).isEqualTo(expected);
+ }
+ }
+}