Merge "Specify version for aidl_interface explicitly"
diff --git a/hostsidetests/hdmicec/app/src/android/hdmicec/app/HdmiCecAudioManager.java b/hostsidetests/hdmicec/app/src/android/hdmicec/app/HdmiCecAudioManager.java
index f04490f..8167e5f 100644
--- a/hostsidetests/hdmicec/app/src/android/hdmicec/app/HdmiCecAudioManager.java
+++ b/hostsidetests/hdmicec/app/src/android/hdmicec/app/HdmiCecAudioManager.java
@@ -30,21 +30,22 @@
* A simple app that can be used to mute, unmute, set volume or get the volume status of a device.
* The actions supported are:
*
- * 1. android.hdmicec.app.MUTE: Mutes the STREAM_MUSIC of the device,
- * irrespective of the previous state.
- * Usage: START_COMMAND -a android.hdmicec.app.MUTE
- * 2. android.hdmicec.app.UNMUTE: Unmutes the STREAM_MUSIC of the device,
- * irrespective of the previous state.
- * Usage: START_COMMAND -a android.hdmicec.app.UNMUTE
- * 3. android.hdmicec.app.REPORT_VOLUME: Reports if the STREAM_MUSIC of the device is muted and
- * if not muted, the current volume level in percent.
- * Usage: START_COMMAND -a android.hdmicec.app.REPORT_VOLUME
- * 4. android.hdmicec.app.SET_VOLUME: Sets the volume of STREAM_MUSIC to a particular level.
- * Has to be used with --ei "volumePercent" x.
- * Usage: START_COMMAND -a android.hdmicec.app.SET_VOLUME --ei "volumePercent" x
+ * <p>1. android.hdmicec.app.MUTE: Mutes the STREAM_MUSIC of the device, irrespective of the
+ * previous state. Usage: START_COMMAND -a android.hdmicec.app.MUTE
*
- * where START_COMMAND is
- * adb shell am start -n "android.hdmicec.app/android.hdmicec.app.HdmiCecAudioManager"
+ * <p>2. android.hdmicec.app.UNMUTE: Unmutes the STREAM_MUSIC of the device, irrespective of the
+ * previous state. Usage: START_COMMAND -a android.hdmicec.app.UNMUTE
+ *
+ * <p>3. android.hdmicec.app.REPORT_VOLUME: Reports current volume level in percent if not muted.
+ * Adds 128 to volume percent level if the device is muted. Usage: START_COMMAND -a
+ * android.hdmicec.app.REPORT_VOLUME
+ *
+ * <p>4. android.hdmicec.app.SET_VOLUME: Sets the volume of STREAM_MUSIC to a particular level. Has
+ * to be used with --ei "volumePercent" x. Usage: START_COMMAND -a android.hdmicec.app.SET_VOLUME
+ * --ei "volumePercent" x
+ *
+ * <p>where START_COMMAND is adb shell am start -n
+ * "android.hdmicec.app/android.hdmicec.app.HdmiCecAudioManager"
*/
public class HdmiCecAudioManager extends Activity {
@@ -54,7 +55,8 @@
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
-
+ int minVolume = audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
+ int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
switch(getIntent().getAction()) {
case "android.hdmicec.app.MUTE":
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
@@ -65,20 +67,15 @@
AudioManager.ADJUST_UNMUTE, 0);
break;
case "android.hdmicec.app.REPORT_VOLUME":
+ int volumeLevel = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ int percentageVolume = 100 * volumeLevel / (maxVolume - minVolume);
if (audioManager.isStreamMute(AudioManager.STREAM_MUSIC)) {
- Log.i(TAG, "Device muted.");
- } else {
- int minVolume = audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
- int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
- int volume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
- int percentVolume = 100 * volume / (maxVolume - minVolume);
- Log.i(TAG, "Volume at " + percentVolume + "%");
+ percentageVolume += 128;
}
+ Log.i(TAG, "Volume at " + percentageVolume + "%");
break;
case "android.hdmicec.app.SET_VOLUME":
int percentVolume = getIntent().getIntExtra("volumePercent", 50);
- int minVolume = audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
- int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
int volume = minVolume + ((maxVolume - minVolume) * percentVolume / 100);
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
Log.i(TAG, "Set volume to " + volume + " (" + percentVolume + "%)");
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/AudioManagerHelper.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/AudioManagerHelper.java
new file mode 100644
index 0000000..88091c6
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/AudioManagerHelper.java
@@ -0,0 +1,136 @@
+/*
+ * 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 android.hdmicec.cts;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import com.android.tradefed.device.ITestDevice;
+
+/** Helper class to get DUT audio status using Audio manager app */
+public final class AudioManagerHelper {
+
+ /** The package name of the APK. */
+ private static final String PACKAGE = "android.hdmicec.app";
+
+ /** The class name of the main activity in the APK. */
+ private static final String CLASS = "HdmiCecAudioManager";
+
+ /** The command to launch the main activity. */
+ private static final String START_COMMAND =
+ String.format("am start -n %s/%s.%s -a ", PACKAGE, PACKAGE, CLASS);
+
+ /** The command to clear the main activity. */
+ private static final String CLEAR_COMMAND = String.format("pm clear %s", PACKAGE);
+
+ public static final int MAX_AUDIO_FORMATS = 4;
+ public static final int MAX_VALID_AUDIO_FORMATS = 2;
+
+ public static List<Integer> mSupportedAudioFormats = null;
+
+ public static void muteDevice(ITestDevice device, HdmiCecClientWrapper hdmiCecClient)
+ throws Exception {
+ // Clear activity
+ device.executeShellCommand(CLEAR_COMMAND);
+ // Clear logcat.
+ device.executeAdbCommand("logcat", "-c");
+ // Start the APK and wait for it to complete.
+ device.executeShellCommand(START_COMMAND + "android.hdmicec.app.MUTE");
+ // The audio device should send <Report Audio Status> message after mute.
+ hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.REPORT_AUDIO_STATUS);
+ }
+
+ public static void unmuteDevice(ITestDevice device, HdmiCecClientWrapper hdmiCecClient)
+ throws Exception {
+ // Clear activity
+ device.executeShellCommand(CLEAR_COMMAND);
+ // Start the APK and wait for it to complete.
+ device.executeShellCommand(START_COMMAND + "android.hdmicec.app.UNMUTE");
+ // The audio device should send <Report Audio Status> message after unmute.
+ hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.REPORT_AUDIO_STATUS);
+ }
+
+ public static boolean isDeviceMuted(ITestDevice device) throws Exception {
+ // Clear activity
+ device.executeShellCommand(CLEAR_COMMAND);
+ // Clear logcat.
+ device.executeAdbCommand("logcat", "-c");
+ // Start the APK and wait for it to complete.
+ device.executeShellCommand(START_COMMAND + "android.hdmicec.app.REPORT_VOLUME");
+ return (LogHelper.parseDutVolume(device, CLASS) >= 128);
+ }
+
+ public static void setDeviceVolume(ITestDevice device, int percentVolume) throws Exception {
+ // Clear activity
+ device.executeShellCommand(CLEAR_COMMAND);
+ // Start the APK and wait for it to complete.
+ device.executeShellCommand(
+ START_COMMAND
+ + "android.hdmicec.app.SET_VOLUME --ei "
+ + "\"volumePercent\" "
+ + percentVolume);
+ }
+
+ public static int getDutAudioVolume(ITestDevice device) throws Exception {
+ // Clear activity
+ device.executeShellCommand(CLEAR_COMMAND);
+ // Clear logcat.
+ device.executeAdbCommand("logcat", "-c");
+ // Start the APK and wait for it to complete.
+ device.executeShellCommand(START_COMMAND + "android.hdmicec.app.REPORT_VOLUME");
+ return LogHelper.parseDutVolume(device, CLASS);
+ }
+
+ public static String getRequestSadFormatsParams(ITestDevice device, boolean sendValidFormats)
+ throws Exception {
+ // Clear activity
+ device.executeShellCommand(CLEAR_COMMAND);
+ // Clear logcat.
+ device.executeAdbCommand("logcat", "-c");
+ // Start the APK and wait for it to complete.
+ device.executeShellCommand(START_COMMAND + "android.hdmicec.app.GET_SUPPORTED_SAD_FORMATS");
+ mSupportedAudioFormats = LogHelper.getSupportedAudioFormats(device);
+
+ // Create a list of all the audio format codes according to CEA-861-D. Remove the supported
+ // audio format codes from it, to get the unsupported audio format codes.
+ List<Integer> mAllCodecFormats =
+ IntStream.range(1, 15).boxed().collect(Collectors.toList());
+ List<Integer> unsupportedAudioFormats = new ArrayList<>();
+ unsupportedAudioFormats.addAll(mAllCodecFormats);
+ unsupportedAudioFormats.removeAll(mSupportedAudioFormats);
+ // Create params message for REQUEST_SHORT_AUDIO_DESCRIPTOR
+ String messageParams = "";
+ int i = 0;
+ int listIndex = 0;
+ if (sendValidFormats) {
+ while (i < Math.min(MAX_VALID_AUDIO_FORMATS, mSupportedAudioFormats.size())) {
+ messageParams += CecMessage.formatParams(mSupportedAudioFormats.get(listIndex), 2);
+ i++;
+ listIndex++;
+ }
+ listIndex = 0;
+ }
+ while (i < Math.min(MAX_AUDIO_FORMATS, unsupportedAudioFormats.size())) {
+ messageParams += CecMessage.formatParams(unsupportedAudioFormats.get(listIndex), 2);
+ i++;
+ listIndex++;
+ }
+ return messageParams;
+ }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
index 025f2d1..4605311 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
@@ -92,6 +92,14 @@
HdmiCecConstants.HDMI_DEVICE_TYPE_PROPERTY,
dutLogicalAddress.getDeviceTypeString());
}
+
+ /** This rule will skip the test if the DUT belongs to the HDMI device type deviceType. */
+ public static TestRule skipDeviceType(BaseHostJUnit4Test testPointer, int deviceType) {
+ return RequiredPropertyRule.asCsvDoesNotContainsValue(
+ testPointer,
+ HdmiCecConstants.HDMI_DEVICE_TYPE_PROPERTY,
+ Integer.toString(deviceType));
+ }
}
@Option(name = HdmiCecConstants.PHYSICAL_ADDRESS_NAME,
@@ -138,4 +146,39 @@
}
throw new Exception("Could not parse logical address from dumpsys.");
}
+
+ /**
+ * Parses the dumpsys hdmi_control to get the logical address of the current device registered
+ * as active source.
+ */
+ public LogicalAddress getDumpsysActiveSourceLogicalAddress() throws Exception {
+ String line;
+ String pattern =
+ "(.*?)"
+ + "(mActiveSource: )"
+ + "(\\(0x)"
+ + "(?<logicalAddress>\\d+)"
+ + "(, )"
+ + "(0x)"
+ + "(?<physicalAddress>\\d+)"
+ + "(\\))"
+ + "(.*?)";
+ Pattern p = Pattern.compile(pattern);
+ Matcher m;
+ ITestDevice device = getDevice();
+ String dumpsys = device.executeShellCommand("dumpsys hdmi_control");
+ BufferedReader reader = new BufferedReader(new StringReader(dumpsys));
+ while ((line = reader.readLine()) != null) {
+ m = p.matcher(line);
+ if (m.matches()) {
+ try {
+ int address = Integer.decode(m.group("logicalAddress"));
+ return LogicalAddress.getLogicalAddress(address);
+ } catch (NumberFormatException ne) {
+ throw new Exception("Could not correctly parse the logical address");
+ }
+ }
+ }
+ throw new Exception("Could not parse active source from dumpsys.");
+ }
}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/CecOperand.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecOperand.java
index 5aa547e..f04a1a7 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/CecOperand.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecOperand.java
@@ -21,6 +21,7 @@
public enum CecOperand {
FEATURE_ABORT(0x00),
+ IMAGE_VIEW_ON(0x04),
TEXT_VIEW_ON(0x0d),
SET_MENU_LANGUAGE(0x32),
STANDBY(0x36),
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java
index b926001..624559d 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java
@@ -49,6 +49,7 @@
private LogicalAddress targetDevice = LogicalAddress.UNKNOWN;
private String clientParams[];
private StringBuilder sendVendorCommand = new StringBuilder("cmd hdmi_control vendorcommand ");
+ private int physicalAddress = 0xFFFF;
public HdmiCecClientWrapper(String ...clientParams) {
this.clientParams = clientParams;
@@ -159,12 +160,16 @@
* address 2.0.0.0 */
commands.add("-p");
commands.add("2");
+ physicalAddress = 0x2000;
if (startAsTv) {
commands.add("-t");
commands.add("x");
selfDevice = LogicalAddress.TV;
}
commands.addAll(Arrays.asList(clientParams));
+ if (Arrays.asList(clientParams).contains("a")) {
+ selfDevice = LogicalAddress.AUDIO_SYSTEM;
+ }
List<String> comPorts = getValidCecClientPorts();
@@ -211,6 +216,66 @@
}
/**
+ * Broadcasts a CEC ACTIVE_SOURCE message from client device source through the output console
+ * of the cec-communication channel.
+ */
+ public void broadcastActiveSource(LogicalAddress source) throws Exception {
+ int sourcePa = (source == selfDevice) ? physicalAddress : 0xFFFF;
+ sendCecMessage(
+ source,
+ LogicalAddress.BROADCAST,
+ CecOperand.ACTIVE_SOURCE,
+ CecMessage.formatParams(sourcePa, HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH));
+ }
+
+ /**
+ * Broadcasts a CEC ACTIVE_SOURCE message with physicalAddressOfActiveDevice from client device
+ * source through the output console of the cec-communication channel.
+ */
+ public void broadcastActiveSource(LogicalAddress source, int physicalAddressOfActiveDevice)
+ throws Exception {
+ sendCecMessage(
+ source,
+ LogicalAddress.BROADCAST,
+ CecOperand.ACTIVE_SOURCE,
+ CecMessage.formatParams(
+ physicalAddressOfActiveDevice, HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH));
+ }
+
+ /**
+ * Broadcasts a CEC REPORT_PHYSICAL_ADDRESS message from client device source through the output
+ * console of the cec-communication channel.
+ */
+ public void broadcastReportPhysicalAddress(LogicalAddress source) throws Exception {
+ String deviceType = CecMessage.formatParams(source.getDeviceType());
+ int sourcePa = (source == selfDevice) ? physicalAddress : 0xFFFF;
+ String physicalAddress =
+ CecMessage.formatParams(sourcePa, HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH);
+ sendCecMessage(
+ source,
+ LogicalAddress.BROADCAST,
+ CecOperand.REPORT_PHYSICAL_ADDRESS,
+ physicalAddress + deviceType);
+ }
+
+ /**
+ * Broadcasts a CEC REPORT_PHYSICAL_ADDRESS message with physicalAddressToReport from client
+ * device source through the output console of the cec-communication channel.
+ */
+ public void broadcastReportPhysicalAddress(LogicalAddress source, int physicalAddressToReport)
+ throws Exception {
+ String deviceType = CecMessage.formatParams(source.getDeviceType());
+ String physicalAddress =
+ CecMessage.formatParams(
+ physicalAddressToReport, HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH);
+ sendCecMessage(
+ source,
+ LogicalAddress.BROADCAST,
+ CecOperand.REPORT_PHYSICAL_ADDRESS,
+ physicalAddress + deviceType);
+ }
+
+ /**
* Sends a CEC message from source device to a destination device through the output console of
* the cec-communication channel with the appended params.
*/
@@ -442,6 +507,21 @@
return selfDevice;
}
+ /** Set the physical address of the cec-client instance */
+ public void setPhysicalAddress(int newPhysicalAddress) throws Exception {
+ String command =
+ String.format(
+ "pa %02d %02d",
+ (newPhysicalAddress & 0xFF00) >> 8, newPhysicalAddress & 0xFF);
+ sendConsoleMessage(command);
+ physicalAddress = newPhysicalAddress;
+ }
+
+ /** Get the physical address of the cec-client instance, will return 0xFFFF if uninitialised */
+ public int getPhysicalAddress() {
+ return physicalAddress;
+ }
+
/**
* Kills the cec-client process that was created in init().
*/
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/LogHelper.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/LogHelper.java
index a7692f38..7b2c857 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/LogHelper.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/LogHelper.java
@@ -17,6 +17,8 @@
package android.hdmicec.cts;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import static org.junit.Assume.assumeTrue;
import com.android.tradefed.device.ITestDevice;
@@ -64,6 +66,21 @@
assertThat(testString).isIn(expectedOutputs);
}
+ /** This method will return the DUT volume. */
+ public static int parseDutVolume(ITestDevice device, String tag) throws Exception {
+ String testString = getLog(device, tag);
+ assertWithMessage("No log from Audio Manager which reports the DUT volume percentage")
+ .that(testString)
+ .isNotEqualTo("");
+ try {
+ String volume = testString.split("at")[1].trim().replaceAll("%", "");
+ return Integer.parseInt(volume);
+ } catch (NumberFormatException e) {
+ throw new NumberFormatException(
+ "Volume obtained from Audio Manager can" + "not be parsed");
+ }
+ }
+
public static void assertLogDoesNotContain(ITestDevice device, String tag,
String expectedOutput) throws Exception {
String testString = getLog(device, tag);
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/RequiredPropertyRule.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/RequiredPropertyRule.java
index f3998adb..8d44bef 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/RequiredPropertyRule.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/RequiredPropertyRule.java
@@ -16,6 +16,7 @@
package android.hdmicec.cts;
+import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import com.android.tradefed.device.ITestDevice;
@@ -99,6 +100,34 @@
}
};
}
+
+ public static RequiredPropertyRule asCsvDoesNotContainsValue(
+ final BaseHostJUnit4Test test, final String propertyName, final String propertyValue) {
+ return new RequiredPropertyRule() {
+ @Override
+ public Statement apply(final Statement base, final Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ List<String> deviceProperties =
+ Arrays.asList(
+ getDevicePropertyValue(test, propertyName)
+ .replaceAll("\\s+", "")
+ .split(","));
+ assumeFalse(
+ "The Property "
+ + propertyName
+ + " = "
+ + propertyValue
+ + " is expected to not be present in "
+ + deviceProperties.toString()
+ + " of device "
+ + test.getDevice().getSerialNumber(),
+ deviceProperties.contains(propertyValue));
+ base.evaluate();
+ }
+ };
+ }
+ };
+ }
}
-
-
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java
index b284215..391916f 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java
@@ -21,17 +21,13 @@
import com.google.common.collect.Range;
+import android.hdmicec.cts.AudioManagerHelper;
import android.hdmicec.cts.BaseHdmiCecCtsTest;
import android.hdmicec.cts.CecMessage;
import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.HdmiCecClientWrapper;
import android.hdmicec.cts.HdmiCecConstants;
-import android.hdmicec.cts.LogHelper;
import android.hdmicec.cts.LogicalAddress;
-import android.hdmicec.cts.RequiredPropertyRule;
-import android.hdmicec.cts.RequiredFeatureRule;
-import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import org.junit.After;
@@ -41,37 +37,16 @@
import org.junit.runner.RunWith;
import org.junit.Test;
-import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
/** HDMI CEC test to test system audio mode (Section 11.2.15) */
@Ignore("b/162820841")
@RunWith(DeviceJUnit4ClassRunner.class)
public final class HdmiCecSystemAudioModeTest extends BaseHdmiCecCtsTest {
- /** The package name of the APK. */
- private static final String PACKAGE = "android.hdmicec.app";
-
- /** The class name of the main activity in the APK. */
- private static final String CLASS = "HdmiCecAudioManager";
-
- /** The command to launch the main activity. */
- private static final String START_COMMAND = String.format(
- "am start -n %s/%s.%s -a ", PACKAGE, PACKAGE, CLASS);
-
- /** The command to clear the main activity. */
- private static final String CLEAR_COMMAND = String.format("pm clear %s", PACKAGE);
-
private static final LogicalAddress AUDIO_DEVICE = LogicalAddress.AUDIO_SYSTEM;
private static final int ON = 0x1;
private static final int OFF = 0x0;
- private static final int MAX_AUDIO_FORMATS = 4;
- private static final int MAX_VALID_AUDIO_FORMATS = 2;
-
- private List<Integer> mSupportedAudioFormats = null;
public HdmiCecSystemAudioModeTest() {
super(AUDIO_DEVICE, "-t", "t");
@@ -85,91 +60,6 @@
.around(CecRules.requiresDeviceType(this, AUDIO_DEVICE))
.around(hdmiCecClient);
- private String getRequestSadFormatsParams(boolean sendValidFormats) throws Exception {
- ITestDevice device = getDevice();
- // Clear activity
- device.executeShellCommand(CLEAR_COMMAND);
- // Clear logcat.
- device.executeAdbCommand("logcat", "-c");
- // Start the APK and wait for it to complete.
- device.executeShellCommand(START_COMMAND + "android.hdmicec.app.GET_SUPPORTED_SAD_FORMATS");
- mSupportedAudioFormats = LogHelper.getSupportedAudioFormats(getDevice());
-
- // Create a list of all the audio format codes according to CEA-861-D. Remove the supported
- // audio format codes from it, to get the unsupported audio format codes.
- List<Integer> mAllCodecFormats =
- IntStream.range(1, 15).boxed().collect(Collectors.toList());
- List<Integer> unsupportedAudioFormats = new ArrayList<>();
- unsupportedAudioFormats.addAll(mAllCodecFormats);
- unsupportedAudioFormats.removeAll(mSupportedAudioFormats);
- // Create params message for REQUEST_SHORT_AUDIO_DESCRIPTOR
- String messageParams = "";
- int i = 0;
- int listIndex = 0;
- if (sendValidFormats) {
- while (i < Math.min(MAX_VALID_AUDIO_FORMATS, mSupportedAudioFormats.size())) {
- messageParams +=
- CecMessage.formatParams(mSupportedAudioFormats.get(listIndex), 2);
- i++;
- listIndex++;
- }
- listIndex = 0;
- }
- while (i < Math.min(MAX_AUDIO_FORMATS, unsupportedAudioFormats.size())) {
- messageParams += CecMessage.formatParams(unsupportedAudioFormats.get(listIndex), 2);
- i++;
- listIndex++;
- }
- return messageParams;
- }
-
- private void muteDevice() throws Exception {
- ITestDevice device = getDevice();
- // Clear activity
- device.executeShellCommand(CLEAR_COMMAND);
- // Clear logcat.
- device.executeAdbCommand("logcat", "-c");
- // Start the APK and wait for it to complete.
- device.executeShellCommand(START_COMMAND + "android.hdmicec.app.MUTE");
- // The audio device should send <Report Audio Status> message after mute.
- hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.REPORT_AUDIO_STATUS);
- }
-
- private void unmuteDevice() throws Exception {
- ITestDevice device = getDevice();
- // Clear activity
- device.executeShellCommand(CLEAR_COMMAND);
- // Start the APK and wait for it to complete.
- device.executeShellCommand(START_COMMAND + "android.hdmicec.app.UNMUTE");
- // The audio device should send <Report Audio Status> message after unmute.
- hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.REPORT_AUDIO_STATUS);
- }
-
- public boolean isDeviceMuted() throws Exception {
- ITestDevice device = getDevice();
- // Clear activity
- device.executeShellCommand(CLEAR_COMMAND);
- // Clear logcat.
- device.executeAdbCommand("logcat", "-c");
- // Start the APK and wait for it to complete.
- device.executeShellCommand(START_COMMAND + "android.hdmicec.app.REPORT_VOLUME");
- try {
- LogHelper.assertLog(getDevice(), CLASS, "Device muted.");
- return true;
- } catch(Exception e) {
- return false;
- }
- }
-
- public void setDeviceVolume(int percentVolume) throws Exception {
- ITestDevice device = getDevice();
- // Clear activity
- device.executeShellCommand(CLEAR_COMMAND);
- // Start the APK and wait for it to complete.
- device.executeShellCommand(START_COMMAND + "android.hdmicec.app.SET_VOLUME --ei " +
- "\"volumePercent\" " + percentVolume);
- }
-
public void sendSystemAudioModeTermination() throws Exception {
hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
CecOperand.SYSTEM_AUDIO_MODE_REQUEST);
@@ -222,7 +112,7 @@
@After
public void resetVolume() throws Exception {
- setDeviceVolume(20);
+ AudioManagerHelper.setDeviceVolume(getDevice(), 20);
}
/**
@@ -354,14 +244,16 @@
*/
@Test
public void cect_11_2_15_8_HandleUcpMute() throws Exception {
- unmuteDevice();
+ AudioManagerHelper.unmuteDevice(getDevice(), hdmiCecClient);
hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
CecOperand.SYSTEM_AUDIO_MODE_REQUEST,
CecMessage.formatParams(HdmiCecConstants.TV_PHYSICAL_ADDRESS,
HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH));
hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, AUDIO_DEVICE,
HdmiCecConstants.CEC_CONTROL_MUTE, false);
- assertWithMessage("Device is not muted").that(isDeviceMuted()).isTrue();
+ assertWithMessage("Device is not muted")
+ .that(AudioManagerHelper.isDeviceMuted(getDevice()))
+ .isTrue();
}
/**
@@ -372,8 +264,8 @@
@Test
public void cect_11_2_15_9_ReportAudioStatus_0() throws Exception {
sendSystemAudioModeInitiation();
- unmuteDevice();
- setDeviceVolume(0);
+ AudioManagerHelper.unmuteDevice(getDevice(), hdmiCecClient);
+ AudioManagerHelper.setDeviceVolume(getDevice(), 0);
int reportedVolume = getDutAudioStatus();
assertThat(reportedVolume).isAnyOf(0, 128);
}
@@ -386,8 +278,8 @@
@Test
public void cect_11_2_15_9_ReportAudioStatus_50_unmuted() throws Exception {
sendSystemAudioModeInitiation();
- unmuteDevice();
- setDeviceVolume(50);
+ AudioManagerHelper.unmuteDevice(getDevice(), hdmiCecClient);
+ AudioManagerHelper.setDeviceVolume(getDevice(), 50);
int reportedVolume = getDutAudioStatus();
/* Allow for a range of volume, since the actual volume set will depend on the device's
volume resolution. */
@@ -402,8 +294,8 @@
@Test
public void cect_11_2_15_9_ReportAudioStatus_100_unmuted() throws Exception {
sendSystemAudioModeInitiation();
- unmuteDevice();
- setDeviceVolume(100);
+ AudioManagerHelper.unmuteDevice(getDevice(), hdmiCecClient);
+ AudioManagerHelper.setDeviceVolume(getDevice(), 100);
int reportedVolume = getDutAudioStatus();
assertThat(reportedVolume).isEqualTo(100);
}
@@ -416,7 +308,7 @@
@Test
public void cect_11_2_15_9_ReportAudioStatusMuted() throws Exception {
sendSystemAudioModeInitiation();
- muteDevice();
+ AudioManagerHelper.muteDevice(getDevice(), hdmiCecClient);
int reportedVolume = getDutAudioStatus();
/* If device is muted, the 8th bit of CEC message parameters is set and the volume will
be greater than 127. */
@@ -430,21 +322,29 @@
*/
@Test
public void cect_11_2_15_13_ValidShortAudioDescriptor() throws Exception {
- hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
- CecOperand.REQUEST_SHORT_AUDIO_DESCRIPTOR, getRequestSadFormatsParams(true));
+ hdmiCecClient.sendCecMessage(
+ LogicalAddress.TV,
+ AUDIO_DEVICE,
+ CecOperand.REQUEST_SHORT_AUDIO_DESCRIPTOR,
+ AudioManagerHelper.getRequestSadFormatsParams(getDevice(), true));
String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.TV,
CecOperand.REPORT_SHORT_AUDIO_DESCRIPTOR);
+ int numFormats =
+ Math.min(
+ AudioManagerHelper.mSupportedAudioFormats.size(),
+ AudioManagerHelper.MAX_VALID_AUDIO_FORMATS);
/* Each Short Audio Descriptor is 3 bytes long. In the first byte of the params, bits 3-6
* will have the audio format. Bit 7 will always 0 for audio format defined in CEA-861-D.
* Bits 0-2 represent (Max number of channels - 1). Discard bits 0-2 and check for the
* format.
* Iterate the params by 3 bytes(6 nibbles) and extract only the first byte(2 nibbles).
*/
- for (int i = 0; i < Math.min(mSupportedAudioFormats.size(), MAX_VALID_AUDIO_FORMATS); i++) {
+ for (int i = 0; i < numFormats; i++) {
int audioFormat =
CecMessage.getParams(message, 6 * i, 6 * i + 2) >>> 3;
assertWithMessage("Could not find audio format " + audioFormat)
- .that(mSupportedAudioFormats).contains(audioFormat);
+ .that(AudioManagerHelper.mSupportedAudioFormats)
+ .contains(audioFormat);
}
}
@@ -456,8 +356,11 @@
*/
@Test
public void cect_11_2_15_14_InvalidShortAudioDescriptor() throws Exception {
- hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
- CecOperand.REQUEST_SHORT_AUDIO_DESCRIPTOR, getRequestSadFormatsParams(false));
+ hdmiCecClient.sendCecMessage(
+ LogicalAddress.TV,
+ AUDIO_DEVICE,
+ CecOperand.REQUEST_SHORT_AUDIO_DESCRIPTOR,
+ AudioManagerHelper.getRequestSadFormatsParams(getDevice(), false));
String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.FEATURE_ABORT);
assertThat(CecOperand.getOperand(CecMessage.getParams(message, 2)))
.isEqualTo(CecOperand.REQUEST_SHORT_AUDIO_DESCRIPTOR);
@@ -472,7 +375,7 @@
*/
@Test
public void cect_11_2_15_16_UnmuteForSystemAudioRequestOn() throws Exception {
- muteDevice();
+ AudioManagerHelper.muteDevice(getDevice(), hdmiCecClient);
sendSystemAudioModeTermination();
String message = hdmiCecClient.checkExpectedOutput(CecOperand.SET_SYSTEM_AUDIO_MODE);
assertThat(CecMessage.getParams(message)).isEqualTo(OFF);
@@ -482,7 +385,9 @@
HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH));
message = hdmiCecClient.checkExpectedOutput(CecOperand.SET_SYSTEM_AUDIO_MODE);
assertThat(CecMessage.getParams(message)).isEqualTo(ON);
- assertWithMessage("Device muted").that(isDeviceMuted()).isFalse();
+ assertWithMessage("Device muted")
+ .that(AudioManagerHelper.isDeviceMuted(getDevice()))
+ .isFalse();
}
/**
@@ -501,7 +406,9 @@
sendSystemAudioModeTermination();
message = hdmiCecClient.checkExpectedOutput(CecOperand.SET_SYSTEM_AUDIO_MODE);
assertThat(CecMessage.getParams(message)).isEqualTo(OFF);
- assertWithMessage("Device not muted").that(isDeviceMuted()).isTrue();
+ assertWithMessage("Device not muted")
+ .that(AudioManagerHelper.isDeviceMuted(getDevice()))
+ .isTrue();
}
/**
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemAudioControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemAudioControlTest.java
new file mode 100644
index 0000000..319afb0
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemAudioControlTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2019 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.hdmicec.cts.common;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import android.hdmicec.cts.AudioManagerHelper;
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+/** HDMI CEC test to verify system audio control commands (Section 11.1.15, 11.2.15) */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecSystemAudioControlTest extends BaseHdmiCecCtsTest {
+
+ public boolean isDutTv;
+
+ public HdmiCecSystemAudioControlTest() {
+ super("-t", "a");
+ }
+
+ @Rule
+ public RuleChain ruleChain =
+ RuleChain.outerRule(CecRules.requiresCec(this))
+ .around(CecRules.requiresLeanback(this))
+ .around(
+ CecRules.skipDeviceType(
+ this, HdmiCecConstants.CEC_DEVICE_TYPE_AUDIO_SYSTEM))
+ .around(hdmiCecClient);
+
+ @Before
+ public void initialTestSetup() throws Exception {
+ /*
+ * Set the device volume level to 20, because if the volume were 100% or 0%, it would not
+ * be possible to detect if there was an incorrect increase or decrease in level.
+ */
+ AudioManagerHelper.setDeviceVolume(getDevice(), 20);
+ TimeUnit.SECONDS.sleep(5);
+ isDutTv = (mDutLogicalAddress.getDeviceType() == HdmiCecConstants.CEC_DEVICE_TYPE_TV);
+ if (isDutTv) {
+ int initialDutVolume = AudioManagerHelper.getDutAudioVolume(getDevice());
+ }
+ }
+
+ /**
+ * Test 11.1.15-2, 11.2.15-11
+ *
+ * <p>Tests that when System Audio Control is On, the device sends {@code
+ * <USER_CONTROL_PRESSED>} and {@code <USER_CONTROL_RELEASED>} messages when the volume up and
+ * down keys are pressed on the DUT. Test also verifies that the {@code <USER_CONTROL_PRESSED>}
+ * message has the right control param.
+ */
+ @Test
+ public void cect_VolumeUpDownUserControlPressedWhenSystemAudioControlOn() throws Exception {
+ /*
+ * TODO: Remove the assumeTrue below and let test run for playback devices when b/172539380
+ * is fixed.
+ */
+ assumeTrue(
+ "Skip for playback devices (b/172539380)",
+ mDutLogicalAddress.getDeviceType()
+ != HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE);
+ ITestDevice device = getDevice();
+ hdmiCecClient.sendCecMessage(
+ hdmiCecClient.getSelfDevice(),
+ LogicalAddress.BROADCAST,
+ CecOperand.SET_SYSTEM_AUDIO_MODE,
+ CecMessage.formatParams(1));
+ device.executeShellCommand("input keyevent KEYCODE_VOLUME_UP");
+ String message =
+ hdmiCecClient.checkExpectedOutput(
+ hdmiCecClient.getSelfDevice(), CecOperand.USER_CONTROL_PRESSED);
+ assertThat(CecMessage.getParams(message)).isEqualTo(HdmiCecConstants.CEC_CONTROL_VOLUME_UP);
+ hdmiCecClient.checkExpectedOutput(
+ hdmiCecClient.getSelfDevice(), CecOperand.USER_CONTROL_RELEASED);
+ /* TODO: b/174733146 For TV devices, assert that the volume level has not changed. */
+
+ device.executeShellCommand("input keyevent KEYCODE_VOLUME_DOWN");
+ message =
+ hdmiCecClient.checkExpectedOutput(
+ hdmiCecClient.getSelfDevice(), CecOperand.USER_CONTROL_PRESSED);
+ assertThat(CecMessage.getParams(message))
+ .isEqualTo(HdmiCecConstants.CEC_CONTROL_VOLUME_DOWN);
+ hdmiCecClient.checkExpectedOutput(
+ hdmiCecClient.getSelfDevice(), CecOperand.USER_CONTROL_RELEASED);
+ /* TODO: b/174733146 For TV devices, assert that the volume level has not changed. */
+ }
+
+ /**
+ * Test 11.1.15-3, 11.2.15-12
+ *
+ * <p>Tests that the device sends {@code <USER_CONTROL_PRESSED>} and {@code
+ * <USER_CONTROL_RELEASED>} messages when the mute key is pressed on the DUT. Test also verifies
+ * that the{@code <USER_CONTROL_PRESSED>} message has the right control param.
+ */
+ @Test
+ public void cect_MuteUserControlPressedWhenSystemAudioControlOn() throws Exception {
+ /*
+ * TODO: Remove the assumeTrue below and let test run for playback devices when b/172539380
+ * is fixed.
+ */
+ assumeTrue(
+ "Skip for playback devices (b/172539380)",
+ mDutLogicalAddress.getDeviceType()
+ != HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE);
+ ITestDevice device = getDevice();
+ hdmiCecClient.sendCecMessage(
+ hdmiCecClient.getSelfDevice(),
+ LogicalAddress.BROADCAST,
+ CecOperand.SET_SYSTEM_AUDIO_MODE,
+ CecMessage.formatParams(1));
+ device.executeShellCommand("input keyevent KEYCODE_VOLUME_MUTE");
+ String message =
+ hdmiCecClient.checkExpectedOutput(
+ hdmiCecClient.getSelfDevice(), CecOperand.USER_CONTROL_PRESSED);
+ assertThat(CecMessage.getParams(message)).isEqualTo(HdmiCecConstants.CEC_CONTROL_MUTE);
+ hdmiCecClient.checkExpectedOutput(
+ hdmiCecClient.getSelfDevice(), CecOperand.USER_CONTROL_RELEASED);
+ /* TODO: b/174733146 For TV devices, assert that the volume level has not changed. */
+ }
+
+ /**
+ * Test 11.1.15-4, 11.2.15-10
+ *
+ * <p>Tests that the device sends a {@code <GIVE_SYSTEM_AUDIO_STATUS>} message when brought out
+ * of standby
+ */
+ @Test
+ public void cect_GiveSystemAudioModeStatus() throws Exception {
+ ITestDevice device = getDevice();
+ /* Home Key to prevent device from going to deep suspend state */
+ device.executeShellCommand("input keyevent KEYCODE_HOME");
+ device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+ device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ hdmiCecClient.checkExpectedOutput(
+ hdmiCecClient.getSelfDevice(), CecOperand.GIVE_SYSTEM_AUDIO_MODE_STATUS);
+ }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemAudioControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemAudioControlTest.java
deleted file mode 100644
index 95baefc..0000000
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemAudioControlTest.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2019 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.hdmicec.cts.playback;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.hdmicec.cts.BaseHdmiCecCtsTest;
-import android.hdmicec.cts.CecMessage;
-import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.HdmiCecClientWrapper;
-import android.hdmicec.cts.HdmiCecConstants;
-import android.hdmicec.cts.LogicalAddress;
-import android.hdmicec.cts.RequiredPropertyRule;
-import android.hdmicec.cts.RequiredFeatureRule;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
-/** HDMI CEC test to verify system audio control commands (Section 11.2.15) */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public final class HdmiCecSystemAudioControlTest extends BaseHdmiCecCtsTest {
-
- private static final LogicalAddress PLAYBACK_DEVICE = LogicalAddress.PLAYBACK_1;
-
- public HdmiCecSystemAudioControlTest() {
- super(LogicalAddress.PLAYBACK_1, "-t", "a");
- }
-
- @Rule
- public RuleChain ruleChain =
- RuleChain
- .outerRule(CecRules.requiresCec(this))
- .around(CecRules.requiresLeanback(this))
- .around(CecRules.requiresDeviceType(this, LogicalAddress.PLAYBACK_1))
- .around(hdmiCecClient);
-
- /**
- * Test 11.2.15-10
- * Tests that the device sends a <GIVE_SYSTEM_AUDIO_STATUS> message when brought out of standby
- */
- @Test
- public void cect_11_2_15_10_GiveSystemAudioModeStatus() throws Exception {
- ITestDevice device = getDevice();
- /* Home Key to prevent device from going to deep suspend state */
- device.executeShellCommand("input keyevent KEYCODE_HOME");
- device.executeShellCommand("input keyevent KEYCODE_SLEEP");
- device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
- hdmiCecClient.checkExpectedOutput(LogicalAddress.AUDIO_SYSTEM,
- CecOperand.GIVE_SYSTEM_AUDIO_MODE_STATUS);
- }
-
- /**
- * Test 11.2.15-11
- * Tests that the device sends <USER_CONTROL_PRESSED> and <USER_CONTROL_RELEASED> messages when
- * the volume up and down keys are pressed on the DUT. Test also verifies that the
- * <USER_CONTROL_PRESSED> message has the right control param.
- */
- @Ignore("b/162836413")
- @Test
- public void cect_11_2_15_11_VolumeUpDownUserControlPressed() throws Exception {
- ITestDevice device = getDevice();
- hdmiCecClient.sendCecMessage(LogicalAddress.AUDIO_SYSTEM, LogicalAddress.BROADCAST,
- CecOperand.SET_SYSTEM_AUDIO_MODE, CecMessage.formatParams(1));
- device.executeShellCommand("input keyevent KEYCODE_VOLUME_UP");
- String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.AUDIO_SYSTEM,
- CecOperand.USER_CONTROL_PRESSED);
- assertThat(CecMessage.getParams(message))
- .isEqualTo(HdmiCecConstants.CEC_CONTROL_VOLUME_UP);
- hdmiCecClient.checkExpectedOutput(LogicalAddress.AUDIO_SYSTEM, CecOperand.USER_CONTROL_RELEASED);
-
-
- device.executeShellCommand("input keyevent KEYCODE_VOLUME_DOWN");
- message = hdmiCecClient.checkExpectedOutput(LogicalAddress.AUDIO_SYSTEM,
- CecOperand.USER_CONTROL_PRESSED);
- assertThat(CecMessage.getParams(message))
- .isEqualTo(HdmiCecConstants.CEC_CONTROL_VOLUME_DOWN);
- hdmiCecClient.checkExpectedOutput(LogicalAddress.AUDIO_SYSTEM, CecOperand.USER_CONTROL_RELEASED);
- }
-
- /**
- * Test 11.2.15-12
- * Tests that the device sends <USER_CONTROL_PRESSED> and <USER_CONTROL_RELEASED> messages when
- * the mute key is pressed on the DUT. Test also verifies that the <USER_CONTROL_PRESSED>
- * message has the right control param.
- */
- @Ignore("b/162836413")
- @Test
- public void cect_11_2_15_12_MuteUserControlPressed() throws Exception {
- ITestDevice device = getDevice();
- hdmiCecClient.sendCecMessage(LogicalAddress.AUDIO_SYSTEM, LogicalAddress.BROADCAST,
- CecOperand.SET_SYSTEM_AUDIO_MODE, CecMessage.formatParams(1));
- device.executeShellCommand("input keyevent KEYCODE_VOLUME_MUTE");
- String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.AUDIO_SYSTEM,
- CecOperand.USER_CONTROL_PRESSED);
- assertThat(CecMessage.getParams(message)).isEqualTo(HdmiCecConstants.CEC_CONTROL_MUTE);
- hdmiCecClient.checkExpectedOutput(LogicalAddress.AUDIO_SYSTEM, CecOperand.USER_CONTROL_RELEASED);
- }
-}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecSystemAudioControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecSystemAudioControlTest.java
new file mode 100644
index 0000000..4a4bf92
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecSystemAudioControlTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 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 android.hdmicec.cts.tv;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.hdmicec.cts.AudioManagerHelper;
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.LogicalAddress;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+/** HDMI CEC test to verify system audio control commands tests (Section 11.1.15) */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecSystemAudioControlTest extends BaseHdmiCecCtsTest {
+
+ private static final int ON = 0x1;
+ private static final int OFF = 0x0;
+
+ @Rule
+ public RuleChain ruleChain =
+ RuleChain.outerRule(CecRules.requiresCec(this))
+ .around(CecRules.requiresLeanback(this))
+ .around(CecRules.requiresDeviceType(this, LogicalAddress.TV))
+ .around(hdmiCecClient);
+
+ public HdmiCecSystemAudioControlTest() {
+ super(LogicalAddress.TV, "-t", "a");
+ }
+
+ /**
+ * Test 11.1.15-7
+ *
+ * <p>Tests that the DUT mutes its volume when the DUT receives a broadcast {@code <Set System
+ * Audio Mode>} ["On"] message
+ */
+ @Ignore("b/174733146")
+ @Test
+ public void cect_11_1_15_7_DutMutesForSetSystemAudioModeOn() throws Exception {
+ /*
+ * TODO: Call HdmiCecLocalDeviceTv.setSystemAudioMode(false) instead to turn off system
+ * audio mode after permission issue is sorted.
+ */
+ hdmiCecClient.sendCecMessage(
+ hdmiCecClient.getSelfDevice(),
+ LogicalAddress.BROADCAST,
+ CecOperand.SET_SYSTEM_AUDIO_MODE,
+ CecMessage.formatParams(OFF));
+ hdmiCecClient.sendCecMessage(
+ hdmiCecClient.getSelfDevice(),
+ LogicalAddress.BROADCAST,
+ CecOperand.SET_SYSTEM_AUDIO_MODE,
+ CecMessage.formatParams(ON));
+ assertWithMessage("Device is not muted")
+ .that(AudioManagerHelper.isDeviceMuted(getDevice()))
+ .isTrue();
+ }
+
+ /**
+ * Test 11.1.15-8
+ *
+ * <p>Tests that the DUT unmutes its volume when the DUT receives a broadcast {@code <Set System
+ * Audio Mode>} ["Off"] message
+ */
+ @Ignore("b/174733146")
+ @Test
+ public void cect_11_1_15_8_DutUnmutesForSetSystemAudioModeOff() throws Exception {
+ /*
+ * TODO: Call HdmiCecLocalDeviceTv.setSystemAudioMode(true) instead to turn off system
+ * audio mode after permission issue is sorted.
+ */
+ hdmiCecClient.sendCecMessage(
+ hdmiCecClient.getSelfDevice(),
+ LogicalAddress.BROADCAST,
+ CecOperand.SET_SYSTEM_AUDIO_MODE,
+ CecMessage.formatParams(ON));
+ hdmiCecClient.sendCecMessage(
+ hdmiCecClient.getSelfDevice(),
+ LogicalAddress.BROADCAST,
+ CecOperand.SET_SYSTEM_AUDIO_MODE,
+ CecMessage.formatParams(OFF));
+ assertWithMessage("Device is muted")
+ .that(AudioManagerHelper.isDeviceMuted(getDevice()))
+ .isFalse();
+ }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvOneTouchPlayTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvOneTouchPlayTest.java
new file mode 100644
index 0000000..ee6fafa
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvOneTouchPlayTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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 android.hdmicec.cts.tv;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.LogicalAddress;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/** HDMI CEC test to test One Touch Play features (Section 11.1.1) */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class HdmiCecTvOneTouchPlayTest extends BaseHdmiCecCtsTest {
+
+ private static final LogicalAddress TV_DEVICE = LogicalAddress.TV;
+ private static final int WAIT_TIME_MS = 300;
+ List<LogicalAddress> testDevices = new ArrayList<>();
+
+ public HdmiCecTvOneTouchPlayTest() {
+ /* Start the client as recorder, tuner and playback devices */
+ super(TV_DEVICE, "-t", "r", "-t", "t", "-t", "p");
+ testDevices.add(LogicalAddress.RECORDER_1);
+ testDevices.add(LogicalAddress.TUNER_1);
+ testDevices.add(LogicalAddress.PLAYBACK_1);
+ }
+
+ @Rule
+ public RuleChain ruleChain =
+ RuleChain.outerRule(CecRules.requiresCec(this))
+ .around(CecRules.requiresLeanback(this))
+ .around(CecRules.requiresDeviceType(this, TV_DEVICE))
+ .around(hdmiCecClient);
+
+ /**
+ * Test 11.1.1-1
+ *
+ * <p>Tests that the DUT responds to {@code <Image View On>} message correctly when the message
+ * is sent from logical addresses 0x1, 0x3 and 0x4
+ */
+ @Test
+ public void cect_11_1_1_1_RespondToImageViewOn() throws Exception {
+ for (LogicalAddress testDevice : testDevices) {
+ hdmiCecClient.sendCecMessage(testDevice, LogicalAddress.TV, CecOperand.IMAGE_VIEW_ON);
+ TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
+ hdmiCecClient.broadcastActiveSource(testDevice, hdmiCecClient.getPhysicalAddress());
+ hdmiCecClient.checkOutputDoesNotContainMessage(testDevice, CecOperand.FEATURE_ABORT);
+ assertWithMessage(
+ "Device has not registered expected logical address as active source.")
+ .that(getDumpsysActiveSourceLogicalAddress())
+ .isEqualTo(testDevice);
+ }
+ }
+
+ /**
+ * Test 11.1.1-2
+ *
+ * <p>Tests that the DUT responds to {@code <Text View On>} message correctly when the message
+ * is sent from logical addresses 0x1, 0x3 and 0x4
+ */
+ @Test
+ public void cect_11_1_1_2_RespondToTextViewOn() throws Exception {
+ for (LogicalAddress testDevice : testDevices) {
+ hdmiCecClient.sendCecMessage(testDevice, LogicalAddress.TV, CecOperand.TEXT_VIEW_ON);
+ TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
+ hdmiCecClient.broadcastActiveSource(testDevice, hdmiCecClient.getPhysicalAddress());
+ hdmiCecClient.checkOutputDoesNotContainMessage(testDevice, CecOperand.FEATURE_ABORT);
+ assertWithMessage(
+ "Device has not registered expected logical address as active source.")
+ .that(getDumpsysActiveSourceLogicalAddress())
+ .isEqualTo(testDevice);
+ }
+ }
+}
diff --git a/tests/tests/identity/src/android/security/identity/cts/DynamicAuthTest.java b/tests/tests/identity/src/android/security/identity/cts/DynamicAuthTest.java
index c5c5cd1..f61273e 100644
--- a/tests/tests/identity/src/android/security/identity/cts/DynamicAuthTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/DynamicAuthTest.java
@@ -25,6 +25,7 @@
import android.content.Context;
+import android.os.SystemClock;
import android.security.identity.EphemeralPublicKeyNotFoundException;
import android.security.identity.IdentityCredential;
import android.security.identity.IdentityCredentialException;
@@ -43,6 +44,7 @@
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -455,6 +457,140 @@
store.deleteCredentialByName(credentialName);
}
+
+ @Test
+ public void dynamicAuthWithExpirationTest() throws Exception {
+ assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+
+ Context appContext = InstrumentationRegistry.getTargetContext();
+ IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+ assumeTrue(
+ "IdentityCredential.storeStaticAuthenticationData(X509Certificate, Instant, byte[]) " +
+ "not supported",
+ Util.getFeatureVersion() >= 202101);
+
+ String credentialName = "test";
+
+ store.deleteCredentialByName(credentialName);
+ Collection<X509Certificate> certChain = ProvisioningTest.createCredential(store,
+ credentialName);
+
+ IdentityCredential credential = store.getCredentialByName(credentialName,
+ IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+ assertNotNull(credential);
+
+ credential.setAvailableAuthenticationKeys(3, 5);
+
+ Collection<X509Certificate> certificates = null;
+ certificates = credential.getAuthKeysNeedingCertification();
+ assertEquals(3, certificates.size());
+
+ // Endorse an auth-key but set expiration to 10 seconds in the future.
+ //
+ Instant now = Instant.now();
+ Instant tenSecondsFromNow = now.plusSeconds(10);
+ try {
+ X509Certificate key0Cert = certificates.iterator().next();
+ credential.storeStaticAuthenticationData(key0Cert,
+ tenSecondsFromNow,
+ new byte[]{52, 53, 44});
+ certificates = credential.getAuthKeysNeedingCertification();
+ } catch (IdentityCredentialException e) {
+ e.printStackTrace();
+ assertTrue(false);
+ }
+ assertEquals(2, certificates.size());
+ assertArrayEquals(
+ new int[]{0, 0, 0},
+ credential.getAuthenticationDataUsageCount());
+ // Check that presentation works.
+ try {
+ IdentityCredential tc = store.getCredentialByName(credentialName,
+ IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+ KeyPair ekp = tc.createEphemeralKeyPair();
+ KeyPair rekp = Util.createEphemeralKeyPair();
+ tc.setReaderEphemeralPublicKey(rekp.getPublic());
+ byte[] st = Util.buildSessionTranscript(ekp);
+ Map<String, Collection<String>> etr = new LinkedHashMap<>();
+ etr.put("org.iso.18013-5.2019", Arrays.asList("First name", "Last name"));
+ ResultData rd = tc.getEntries(
+ Util.createItemsRequest(etr, null),
+ etr,
+ st,
+ null);
+ } catch (IdentityCredentialException e) {
+ e.printStackTrace();
+ assertTrue(false);
+ }
+ credential = store.getCredentialByName(credentialName,
+ IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+ assertArrayEquals(
+ new int[]{1, 0, 0},
+ credential.getAuthenticationDataUsageCount());
+
+ SystemClock.sleep(11 * 1000);
+
+ certificates = credential.getAuthKeysNeedingCertification();
+ assertEquals(3, certificates.size());
+
+ // Check that presentation now fails..
+ try {
+ IdentityCredential tc = store.getCredentialByName(credentialName,
+ IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+ KeyPair ekp = tc.createEphemeralKeyPair();
+ KeyPair rekp = Util.createEphemeralKeyPair();
+ tc.setReaderEphemeralPublicKey(rekp.getPublic());
+ byte[] st = Util.buildSessionTranscript(ekp);
+ Map<String, Collection<String>> etr = new LinkedHashMap<>();
+ etr.put("org.iso.18013-5.2019", Arrays.asList("First name", "Last name"));
+ ResultData rd = tc.getEntries(
+ Util.createItemsRequest(etr, null),
+ etr,
+ st,
+ null);
+ assertTrue(false);
+ } catch (NoAuthenticationKeyAvailableException e) {
+ // This is the expected path...
+ } catch (IdentityCredentialException e) {
+ e.printStackTrace();
+ assertTrue(false);
+ }
+ credential = store.getCredentialByName(credentialName,
+ IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+ assertArrayEquals(
+ new int[]{1, 0, 0},
+ credential.getAuthenticationDataUsageCount());
+
+ // Check that it works if we use setAllowUsingExpiredKeys(true)
+ try {
+ IdentityCredential tc = store.getCredentialByName(credentialName,
+ IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+ tc.setAllowUsingExpiredKeys(true); // <-- this is the call that makes the difference!
+ KeyPair ekp = tc.createEphemeralKeyPair();
+ KeyPair rekp = Util.createEphemeralKeyPair();
+ tc.setReaderEphemeralPublicKey(rekp.getPublic());
+ byte[] st = Util.buildSessionTranscript(ekp);
+ Map<String, Collection<String>> etr = new LinkedHashMap<>();
+ etr.put("org.iso.18013-5.2019", Arrays.asList("First name", "Last name"));
+ ResultData rd = tc.getEntries(
+ Util.createItemsRequest(etr, null),
+ etr,
+ st,
+ null);
+ } catch (IdentityCredentialException e) {
+ e.printStackTrace();
+ assertTrue(false);
+ }
+ credential = store.getCredentialByName(credentialName,
+ IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+ assertArrayEquals(
+ new int[]{2, 0, 0},
+ credential.getAuthenticationDataUsageCount());
+
+ // ... and we're done. Clean up after ourselves.
+ store.deleteCredentialByName(credentialName);
+ }
+
// TODO: test storeStaticAuthenticationData() throwing UnknownAuthenticationKeyException
// on an unknown auth key
}
diff --git a/tests/tests/identity/src/android/security/identity/cts/ProvisioningTest.java b/tests/tests/identity/src/android/security/identity/cts/ProvisioningTest.java
index 519e915..b78cb98 100644
--- a/tests/tests/identity/src/android/security/identity/cts/ProvisioningTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/ProvisioningTest.java
@@ -48,6 +48,7 @@
import java.io.ByteArrayOutputStream;
import java.security.KeyPair;
import java.security.InvalidKeyException;
+import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
@@ -409,7 +410,7 @@
}
@Test
- public void deleteCredential()
+ public void deleteCredentialByName()
throws IdentityCredentialException, CborException, CertificateEncodingException {
assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
@@ -447,6 +448,92 @@
}
@Test
+ public void deleteCredential()
+ throws IdentityCredentialException, CborException, CertificateEncodingException {
+ assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+
+ Context appContext = InstrumentationRegistry.getTargetContext();
+ IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+ assumeTrue("IdentityCredential.delete() not supported", Util.getFeatureVersion() >= 202101);
+
+ store.deleteCredentialByName("test");
+ assertNull(store.deleteCredentialByName("test"));
+ Collection<X509Certificate> certificateChain = createCredential(store, "test");
+
+ // Deleting the credential involves destroying the keys referenced in the returned
+ // certificateChain... so get an encoded blob we can turn into a X509 cert when
+ // checking the deletion receipt below, post-deletion.
+ byte[] encodedCredentialCert = certificateChain.iterator().next().getEncoded();
+
+ IdentityCredential credential = store.getCredentialByName("test",
+ IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+ assertNotNull(credential);
+
+ byte[] challenge = new byte[]{0x20, 0x21};
+ byte[] proofOfDeletionSignature = credential.delete(challenge);
+ byte[] proofOfDeletion = Util.coseSign1GetData(proofOfDeletionSignature);
+
+ // Check the returned CBOR is what is expected.
+ String pretty = Util.cborPrettyPrint(proofOfDeletion);
+ assertEquals("['ProofOfDeletion', 'org.iso.18013-5.2019.mdl', [0x20, 0x21], false]", pretty);
+
+ try {
+ assertTrue(Util.coseSign1CheckSignature(
+ proofOfDeletionSignature,
+ new byte[0], // Additional data
+ certificateChain.iterator().next().getPublicKey()));
+ } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+ e.printStackTrace();
+ assertTrue(false);
+ }
+
+ // Finally, check deleting an already deleted credential returns the expected.
+ assertNull(store.deleteCredentialByName("test"));
+ }
+
+ @Test
+ public void proofOfOwnership()
+ throws IdentityCredentialException, CborException, CertificateEncodingException {
+ assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+
+ Context appContext = InstrumentationRegistry.getTargetContext();
+ IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+ assumeTrue("IdentityCredential.proveOwnership() not supported", Util.getFeatureVersion() >= 202101);
+
+ store.deleteCredentialByName("test");
+ assertNull(store.deleteCredentialByName("test"));
+ Collection<X509Certificate> certificateChain = createCredential(store, "test");
+
+ byte[] encodedCredentialCert = certificateChain.iterator().next().getEncoded();
+
+ IdentityCredential credential = store.getCredentialByName("test",
+ IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+ assertNotNull(credential);
+
+ byte[] challenge = new byte[]{0x12, 0x22};
+ byte[] proofOfOwnershipSignature = credential.proveOwnership(challenge);
+ byte[] proofOfOwnership = Util.coseSign1GetData(proofOfOwnershipSignature);
+
+ // Check the returned CBOR is what is expected.
+ String pretty = Util.cborPrettyPrint(proofOfOwnership);
+ assertEquals("['ProofOfOwnership', 'org.iso.18013-5.2019.mdl', [0x12, 0x22], false]",
+ pretty);
+
+ try {
+ assertTrue(Util.coseSign1CheckSignature(
+ proofOfOwnershipSignature,
+ new byte[0], // Additional data
+ certificateChain.iterator().next().getPublicKey()));
+ } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+ e.printStackTrace();
+ assertTrue(false);
+ }
+
+ // Finally, check the credential is still there
+ assertNotNull(store.deleteCredentialByName("test"));
+ }
+
+ @Test
public void testProvisionAndRetrieve() throws IdentityCredentialException, CborException {
assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
@@ -1016,4 +1103,201 @@
store.deleteCredentialByName("test");
}
+ @Test
+ public void testUpdateCredential() throws IdentityCredentialException, CborException, NoSuchAlgorithmException {
+ assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+
+ Context appContext = InstrumentationRegistry.getTargetContext();
+ IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+ assumeTrue("IdentityCredential.update() not supported", Util.getFeatureVersion() >= 202101);
+
+ // Create the credential...
+ //
+ String credentialName = "test";
+ String exampleDocType = "org.example.myDocType";
+ String exampleNs = "org.example.ns";
+ byte[] challenge = {0x01, 0x02};
+ int acpId = 3;
+ store.deleteCredentialByName(credentialName);
+ WritableIdentityCredential wc = store.createCredential(credentialName, exampleDocType);
+ Collection<X509Certificate> certChain = wc.getCredentialKeyCertificateChain(challenge);
+ AccessControlProfile noAuthProfile =
+ new AccessControlProfile.Builder(new AccessControlProfileId(acpId))
+ .setUserAuthenticationRequired(false)
+ .build();
+ Collection<AccessControlProfileId> idsNoAuth = new ArrayList<AccessControlProfileId>();
+ idsNoAuth.add(new AccessControlProfileId(acpId));
+ PersonalizationData personalizationData =
+ new PersonalizationData.Builder()
+ .addAccessControlProfile(noAuthProfile)
+ .putEntry(exampleNs, "first_name", idsNoAuth, Util.cborEncodeString("John"))
+ .putEntry(exampleNs, "last_name", idsNoAuth, Util.cborEncodeString("Smith"))
+ .build();
+ byte[] proofOfProvisioningSignature = wc.personalize(personalizationData);
+ byte[] proofOfProvisioning = Util.coseSign1GetData(proofOfProvisioningSignature);
+ byte[] proofOfProvisioningSha256 = MessageDigest.getInstance("SHA-256").digest(proofOfProvisioning);
+ String pretty = "";
+ try {
+ pretty = Util.cborPrettyPrint(proofOfProvisioning);
+ } catch (CborException e) {
+ e.printStackTrace();
+ assertTrue(false);
+ }
+ assertEquals("[\n"
+ + " 'ProofOfProvisioning',\n"
+ + " '" + exampleDocType + "',\n"
+ + " [\n"
+ + " {\n"
+ + " 'id' : " + acpId + "\n"
+ + " }\n"
+ + " ],\n"
+ + " {\n"
+ + " '" + exampleNs + "' : [\n"
+ + " {\n"
+ + " 'name' : 'first_name',\n"
+ + " 'value' : 'John',\n"
+ + " 'accessControlProfiles' : [" + acpId + "]\n"
+ + " },\n"
+ + " {\n"
+ + " 'name' : 'last_name',\n"
+ + " 'value' : 'Smith',\n"
+ + " 'accessControlProfiles' : [" + acpId + "]\n"
+ + " }\n"
+ + " ]\n"
+ + " },\n"
+ + " false\n"
+ + "]", pretty);
+ try {
+ assertTrue(Util.coseSign1CheckSignature(
+ proofOfProvisioningSignature,
+ new byte[0], // Additional data
+ certChain.iterator().next().getPublicKey()));
+ } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+ e.printStackTrace();
+ assertTrue(false);
+ }
+
+ IdentityCredential credential = store.getCredentialByName("test",
+ IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+
+ // Configure to use 3 auth keys and endorse all of them
+ credential.setAvailableAuthenticationKeys(3, 5);
+ Collection<X509Certificate> certificates = credential.getAuthKeysNeedingCertification();
+ assertEquals(3, certificates.size());
+ for (X509Certificate cert : certificates) {
+ credential.storeStaticAuthenticationData(cert, new byte[]{1, 2});
+ // Check each cert has the correct ProofOfProvisioning SHA-256 in the
+ // ProofOfBinding CBOR stored at OID 1.3.6.1.4.1.11129.2.1.26
+ byte[] popSha256FromCert = Util.getPopSha256FromAuthKeyCert(cert);
+ assertArrayEquals(popSha256FromCert, proofOfProvisioningSha256);
+ }
+ assertEquals(0, credential.getAuthKeysNeedingCertification().size());
+
+ // Update the credential
+ AccessControlProfile updNoAuthProfile =
+ new AccessControlProfile.Builder(new AccessControlProfileId(31))
+ .setUserAuthenticationRequired(false)
+ .build();
+ Collection<AccessControlProfileId> updIds = new ArrayList<AccessControlProfileId>();
+ updIds.add(new AccessControlProfileId(31));
+ String updNs = "org.iso.other_ns";
+ PersonalizationData updPd =
+ new PersonalizationData.Builder()
+ .addAccessControlProfile(updNoAuthProfile)
+ .putEntry(updNs, "first_name", updIds, Util.cborEncodeString("Lawrence"))
+ .putEntry(updNs, "last_name", updIds, Util.cborEncodeString("Waterhouse"))
+ .build();
+ byte[] updProofOfProvisioningSignature = credential.update(updPd);
+
+ // Check the ProofOfProvisioning for the updated data (contents _and_ signature)
+ byte[] updProofOfProvisioning = Util.coseSign1GetData(updProofOfProvisioningSignature);
+ byte[] updProofOfProvisioningSha256 = MessageDigest.getInstance("SHA-256").digest(updProofOfProvisioning);
+ try {
+ pretty = Util.cborPrettyPrint(updProofOfProvisioning);
+ } catch (CborException e) {
+ e.printStackTrace();
+ assertTrue(false);
+ }
+ assertEquals("[\n"
+ + " 'ProofOfProvisioning',\n"
+ + " '" + exampleDocType + "',\n"
+ + " [\n"
+ + " {\n"
+ + " 'id' : 31\n"
+ + " }\n"
+ + " ],\n"
+ + " {\n"
+ + " 'org.iso.other_ns' : [\n"
+ + " {\n"
+ + " 'name' : 'first_name',\n"
+ + " 'value' : 'Lawrence',\n"
+ + " 'accessControlProfiles' : [31]\n"
+ + " },\n"
+ + " {\n"
+ + " 'name' : 'last_name',\n"
+ + " 'value' : 'Waterhouse',\n"
+ + " 'accessControlProfiles' : [31]\n"
+ + " }\n"
+ + " ]\n"
+ + " },\n"
+ + " false\n"
+ + "]", pretty);
+ try {
+ assertTrue(Util.coseSign1CheckSignature(
+ updProofOfProvisioningSignature,
+ new byte[0], // Additional data
+ certChain.iterator().next().getPublicKey()));
+ } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+ e.printStackTrace();
+ assertTrue(false);
+ }
+ // Check the returned CredentialKey cert chain from the now updated
+ // IdentityCredential matches the original certificate chain.
+ //
+ Collection<X509Certificate> readBackCertChain =
+ credential.getCredentialKeyCertificateChain();
+ assertEquals(certChain.size(), readBackCertChain.size());
+ Iterator<X509Certificate> it = readBackCertChain.iterator();
+ for (X509Certificate expectedCert : certChain) {
+ X509Certificate readBackCert = it.next();
+ assertEquals(expectedCert, readBackCert);
+ }
+
+ // Check that the credential is still configured to use 3 auth keys and
+ // that they all need replacement... then check and endorse the
+ // replacements
+ Collection<X509Certificate> updCertificates = credential.getAuthKeysNeedingCertification();
+ assertEquals(3, updCertificates.size());
+ for (X509Certificate cert : updCertificates) {
+ credential.storeStaticAuthenticationData(cert, new byte[]{1, 2});
+ // Check each cert has the correct - *updated* - ProofOfProvisioning SHA-256 in the
+ // ProofOfBinding CBOR stored at OID 1.3.6.1.4.1.11129.2.1.26
+ byte[] popSha256FromCert = Util.getPopSha256FromAuthKeyCert(cert);
+ assertArrayEquals(popSha256FromCert, updProofOfProvisioningSha256);
+ }
+ assertEquals(0, credential.getAuthKeysNeedingCertification().size());
+
+ // Check we can read back the updated data and it matches what we
+ // updated it to.
+ Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
+ entriesToRequest.put(updNs,
+ Arrays.asList("first_name",
+ "last_name"));
+ ResultData rd = credential.getEntries(
+ Util.createItemsRequest(entriesToRequest, null),
+ entriesToRequest,
+ null,
+ null);
+
+ Collection<String> resultNamespaces = rd.getNamespaces();
+ assertEquals(resultNamespaces.size(), 1);
+ assertEquals(updNs, resultNamespaces.iterator().next());
+ assertEquals(2, rd.getEntryNames(updNs).size());
+
+ assertEquals("Lawrence", Util.getStringEntry(rd, updNs, "first_name"));
+ assertEquals("Waterhouse", Util.getStringEntry(rd, updNs, "last_name"));
+
+ store.deleteCredentialByName("test");
+ }
+
}
diff --git a/tests/tests/identity/src/android/security/identity/cts/Util.java b/tests/tests/identity/src/android/security/identity/cts/Util.java
index bbd6ee7..733a401 100644
--- a/tests/tests/identity/src/android/security/identity/cts/Util.java
+++ b/tests/tests/identity/src/android/security/identity/cts/Util.java
@@ -20,6 +20,8 @@
import android.security.identity.IdentityCredentialStore;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.FeatureInfo;
import android.os.SystemProperties;
import android.security.keystore.KeyProperties;
import android.util.Log;
@@ -68,6 +70,9 @@
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECPoint;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1OctetString;
+
import co.nstant.in.cbor.CborBuilder;
import co.nstant.in.cbor.CborDecoder;
import co.nstant.in.cbor.CborEncoder;
@@ -90,6 +95,40 @@
class Util {
private static final String TAG = "Util";
+ // Returns 0 if not implemented. Otherwise returns the feature version.
+ //
+ static int getFeatureVersion() {
+ Context appContext = InstrumentationRegistry.getTargetContext();
+ PackageManager pm = appContext.getPackageManager();
+
+ int featureVersionFromPm = 0;
+ if (pm.hasSystemFeature(PackageManager.FEATURE_IDENTITY_CREDENTIAL_HARDWARE)) {
+ FeatureInfo info = null;
+ FeatureInfo[] infos = pm.getSystemAvailableFeatures();
+ for (int n = 0; n < infos.length; n++) {
+ FeatureInfo i = infos[n];
+ if (i.name.equals(PackageManager.FEATURE_IDENTITY_CREDENTIAL_HARDWARE)) {
+ info = i;
+ break;
+ }
+ }
+ if (info != null) {
+ featureVersionFromPm = info.version;
+ }
+ }
+
+ // Use of the system feature is not required since Android 12. So for Android 11
+ // return 202009 which is the feature version shipped with Android 11.
+ if (featureVersionFromPm == 0) {
+ IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+ if (store != null) {
+ featureVersionFromPm = 202009;
+ }
+ }
+
+ return featureVersionFromPm;
+ }
+
static byte[] canonicalizeCbor(byte[] encodedCbor) throws CborException {
ByteArrayInputStream bais = new ByteArrayInputStream(encodedCbor);
List<DataItem> dataItems = new CborDecoder(bais).decode();
@@ -1262,4 +1301,63 @@
}
return false;
}
+
+ // Returns true if, and only if, the Direct Access Identity Credential HAL (and credstore) is
+ // implemented on the device under test.
+ static boolean isDirectAccessHalImplemented() {
+ Context appContext = InstrumentationRegistry.getTargetContext();
+ IdentityCredentialStore store = IdentityCredentialStore.getDirectAccessInstance(appContext);
+ if (store != null) {
+ return true;
+ }
+ return false;
+ }
+
+ static byte[] getPopSha256FromAuthKeyCert(X509Certificate cert) {
+ byte[] octetString = cert.getExtensionValue("1.3.6.1.4.1.11129.2.1.26");
+ if (octetString == null) {
+ return null;
+ }
+ Util.hexdump("octetString", octetString);
+
+ try {
+ ASN1InputStream asn1InputStream = new ASN1InputStream(octetString);
+ byte[] cborBytes = ((ASN1OctetString) asn1InputStream.readObject()).getOctets();
+ Util.hexdump("cborBytes", cborBytes);
+
+ ByteArrayInputStream bais = new ByteArrayInputStream(cborBytes);
+ List<DataItem> dataItems = new CborDecoder(bais).decode();
+ if (dataItems.size() != 1) {
+ throw new RuntimeException("Expected 1 item, found " + dataItems.size());
+ }
+ if (!(dataItems.get(0) instanceof co.nstant.in.cbor.model.Array)) {
+ throw new RuntimeException("Item is not a map");
+ }
+ co.nstant.in.cbor.model.Array array = (co.nstant.in.cbor.model.Array) dataItems.get(0);
+ List<DataItem> items = array.getDataItems();
+ if (items.size() < 2) {
+ throw new RuntimeException("Expected at least 2 array items, found " + items.size());
+ }
+ if (!(items.get(0) instanceof UnicodeString)) {
+ throw new RuntimeException("First array item is not a string");
+ }
+ String id = ((UnicodeString) items.get(0)).getString();
+ if (!id.equals("ProofOfBinding")) {
+ throw new RuntimeException("Expected ProofOfBinding, got " + id);
+ }
+ if (!(items.get(1) instanceof ByteString)) {
+ throw new RuntimeException("Second array item is not a bytestring");
+ }
+ byte[] popSha256 = ((ByteString) items.get(1)).getBytes();
+ if (popSha256.length != 32) {
+ throw new RuntimeException("Expected bstr to be 32 bytes, it is " + popSha256.length);
+ }
+ return popSha256;
+ } catch (IOException e) {
+ throw new RuntimeException("Error decoding extension data", e);
+ } catch (CborException e) {
+ throw new RuntimeException("Error decoding data", e);
+ }
+ }
+
}
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index 3b57336..e0c8c1e 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -4460,7 +4460,7 @@
<!-- Allows access to keyguard secure storage. Only allowed for system processes.
@hide -->
<permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|setup" />
<!-- Allows applications to set the initial lockscreen state.
<p>Not for use by third-party applications. @hide -->