Merge "Rename vts-core to vts"
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..d97975c
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,3 @@
+third_party {
+  license_type: NOTICE
+}
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
deleted file mode 100644
index e69de29..0000000
--- a/MODULE_LICENSE_APACHE2
+++ /dev/null
diff --git a/audio/common/all-versions/default/service/android.hardware.audio.service.rc b/audio/common/all-versions/default/service/android.hardware.audio.service.rc
index 63d2542..f7e1e24 100644
--- a/audio/common/all-versions/default/service/android.hardware.audio.service.rc
+++ b/audio/common/all-versions/default/service/android.hardware.audio.service.rc
@@ -5,5 +5,5 @@
     group audio camera drmrpc inet media mediadrm net_bt net_bt_admin net_bw_acct wakelock
     capabilities BLOCK_SUSPEND
     ioprio rt 4
-    writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks
+    task_profiles ProcessCapacityHigh HighPerformance
     onrestart restart audioserver
diff --git a/bluetooth/1.0/default/android.hardware.bluetooth@1.0-service.rc b/bluetooth/1.0/default/android.hardware.bluetooth@1.0-service.rc
index 9fa128d..def59de 100644
--- a/bluetooth/1.0/default/android.hardware.bluetooth@1.0-service.rc
+++ b/bluetooth/1.0/default/android.hardware.bluetooth@1.0-service.rc
@@ -4,5 +4,5 @@
     capabilities BLOCK_SUSPEND NET_ADMIN SYS_NICE
     user bluetooth
     group bluetooth
-    writepid /dev/stune/foreground/tasks
+    task_profiles HighPerformance
 
diff --git a/bluetooth/1.1/default/android.hardware.bluetooth@1.1-service.rc b/bluetooth/1.1/default/android.hardware.bluetooth@1.1-service.rc
index 49f0be3..5c7cbf4 100644
--- a/bluetooth/1.1/default/android.hardware.bluetooth@1.1-service.rc
+++ b/bluetooth/1.1/default/android.hardware.bluetooth@1.1-service.rc
@@ -5,5 +5,5 @@
     capabilities BLOCK_SUSPEND NET_ADMIN SYS_NICE
     user bluetooth
     group bluetooth
-    writepid /dev/stune/foreground/tasks
+    task_profiles HighPerformance
 
diff --git a/boot/1.1/vts/functional/VtsHalBootV1_1TargetTest.cpp b/boot/1.1/vts/functional/VtsHalBootV1_1TargetTest.cpp
index 7c58ef3..30b965d 100644
--- a/boot/1.1/vts/functional/VtsHalBootV1_1TargetTest.cpp
+++ b/boot/1.1/vts/functional/VtsHalBootV1_1TargetTest.cpp
@@ -76,7 +76,11 @@
     for (const auto value : ValidMergeStatusValues()) {
         EXPECT_TRUE(boot->setSnapshotMergeStatus(value).withDefault(false));
         auto status = boot->getSnapshotMergeStatus();
-        EXPECT_EQ(status, value);
+        if (value == MergeStatus::SNAPSHOTTED) {
+            EXPECT_TRUE(status == MergeStatus::SNAPSHOTTED || status == MergeStatus::NONE);
+        } else {
+            EXPECT_EQ(status, value);
+        }
     }
 }
 
diff --git a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-external-service.rc b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-external-service.rc
index 64cf321..52ade97 100644
--- a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-external-service.rc
+++ b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-external-service.rc
@@ -5,4 +5,4 @@
     group audio camera input drmrpc usb
     ioprio rt 4
     capabilities SYS_NICE
-    writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+    task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy.rc b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy.rc
index e8549ed..63ded90 100644
--- a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy.rc
+++ b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy.rc
@@ -7,4 +7,4 @@
     group audio camera input drmrpc
     ioprio rt 4
     capabilities SYS_NICE
-    writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+    task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy_64.rc b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy_64.rc
index 2dfac76..953d1af 100644
--- a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy_64.rc
+++ b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy_64.rc
@@ -7,4 +7,4 @@
     group audio camera input drmrpc
     ioprio rt 4
     capabilities SYS_NICE
-    writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+    task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service.rc b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service.rc
index 913561b..f7ac9f8 100644
--- a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service.rc
+++ b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service.rc
@@ -5,4 +5,4 @@
     group audio camera input drmrpc
     ioprio rt 4
     capabilities SYS_NICE
-    writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+    task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service_64.rc b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service_64.rc
index fd4826e..a32dd46 100644
--- a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service_64.rc
+++ b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service_64.rc
@@ -5,4 +5,4 @@
     group audio camera input drmrpc
     ioprio rt 4
     capabilities SYS_NICE
-    writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+    task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-external-service.rc b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-external-service.rc
index 107097e..b3b06b2 100644
--- a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-external-service.rc
+++ b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-external-service.rc
@@ -6,4 +6,4 @@
     group audio camera input drmrpc usb
     ioprio rt 4
     capabilities SYS_NICE
-    writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+    task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy.rc b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy.rc
index b45158a..7c5e69b 100644
--- a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy.rc
+++ b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy.rc
@@ -8,4 +8,4 @@
     group audio camera input drmrpc
     ioprio rt 4
     capabilities SYS_NICE
-    writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+    task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy_64.rc b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy_64.rc
index 955b28e..49bca8f 100644
--- a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy_64.rc
+++ b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy_64.rc
@@ -8,4 +8,4 @@
     group audio camera input drmrpc
     ioprio rt 4
     capabilities SYS_NICE
-    writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+    task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service.rc b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service.rc
index c065815..4bd1fb4 100644
--- a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service.rc
+++ b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service.rc
@@ -6,4 +6,4 @@
     group audio camera input drmrpc
     ioprio rt 4
     capabilities SYS_NICE
-    writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+    task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service_64.rc b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service_64.rc
index 63dd11d..b444325 100644
--- a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service_64.rc
+++ b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service_64.rc
@@ -6,4 +6,4 @@
     group audio camera input drmrpc
     ioprio rt 4
     capabilities SYS_NICE
-    writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+    task_profiles CameraServiceCapacity MaxPerformance
diff --git a/identity/aidl/Android.bp b/identity/aidl/Android.bp
index 7298c7d..14aef8e 100644
--- a/identity/aidl/Android.bp
+++ b/identity/aidl/Android.bp
@@ -18,5 +18,8 @@
             },
         },
     },
-    versions: ["1"],
+    versions: [
+        "1",
+        "2",
+    ],
 }
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/.hash b/identity/aidl/aidl_api/android.hardware.identity/2/.hash
new file mode 100644
index 0000000..1b2c1b6
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/.hash
@@ -0,0 +1 @@
+3b0b10b618dbc4bf283aa2bf78833ad3de0a5928
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/Certificate.aidl b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/Certificate.aidl
new file mode 100644
index 0000000..7e3002d
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/Certificate.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@VintfStability
+parcelable Certificate {
+  byte[] encodedCertificate;
+}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/CipherSuite.aidl b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/CipherSuite.aidl
new file mode 100644
index 0000000..447203f
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/CipherSuite.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@Backing(type="int") @VintfStability
+enum CipherSuite {
+  CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 = 1,
+}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/HardwareInformation.aidl b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/HardwareInformation.aidl
new file mode 100644
index 0000000..e1296e0
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/HardwareInformation.aidl
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@VintfStability
+parcelable HardwareInformation {
+  @utf8InCpp String credentialStoreName;
+  @utf8InCpp String credentialStoreAuthorName;
+  int dataChunkSize;
+  boolean isDirectAccess;
+  @utf8InCpp String[] supportedDocTypes;
+}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IIdentityCredential.aidl b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IIdentityCredential.aidl
new file mode 100644
index 0000000..e8e93f8
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IIdentityCredential.aidl
@@ -0,0 +1,31 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@VintfStability
+interface IIdentityCredential {
+  byte[] deleteCredential();
+  byte[] createEphemeralKeyPair();
+  void setReaderEphemeralPublicKey(in byte[] publicKey);
+  long createAuthChallenge();
+  void startRetrieval(in android.hardware.identity.SecureAccessControlProfile[] accessControlProfiles, in android.hardware.keymaster.HardwareAuthToken authToken, in byte[] itemsRequest, in byte[] signingKeyBlob, in byte[] sessionTranscript, in byte[] readerSignature, in int[] requestCounts);
+  void startRetrieveEntryValue(in @utf8InCpp String nameSpace, in @utf8InCpp String name, in int entrySize, in int[] accessControlProfileIds);
+  byte[] retrieveEntryValue(in byte[] encryptedContent);
+  void finishRetrieval(out byte[] mac, out byte[] deviceNameSpaces);
+  android.hardware.identity.Certificate generateSigningKeyPair(out byte[] signingKeyBlob);
+  void setRequestedNamespaces(in android.hardware.identity.RequestNamespace[] requestNamespaces);
+}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IIdentityCredentialStore.aidl b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IIdentityCredentialStore.aidl
new file mode 100644
index 0000000..5dafb76
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IIdentityCredentialStore.aidl
@@ -0,0 +1,37 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@VintfStability
+interface IIdentityCredentialStore {
+  android.hardware.identity.HardwareInformation getHardwareInformation();
+  android.hardware.identity.IWritableIdentityCredential createCredential(in @utf8InCpp String docType, in boolean testCredential);
+  android.hardware.identity.IIdentityCredential getCredential(in android.hardware.identity.CipherSuite cipherSuite, in byte[] credentialData);
+  const int STATUS_OK = 0;
+  const int STATUS_FAILED = 1;
+  const int STATUS_CIPHER_SUITE_NOT_SUPPORTED = 2;
+  const int STATUS_INVALID_DATA = 3;
+  const int STATUS_INVALID_AUTH_TOKEN = 4;
+  const int STATUS_INVALID_ITEMS_REQUEST_MESSAGE = 5;
+  const int STATUS_READER_SIGNATURE_CHECK_FAILED = 6;
+  const int STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND = 7;
+  const int STATUS_USER_AUTHENTICATION_FAILED = 8;
+  const int STATUS_READER_AUTHENTICATION_FAILED = 9;
+  const int STATUS_NO_ACCESS_CONTROL_PROFILES = 10;
+  const int STATUS_NOT_IN_REQUEST_MESSAGE = 11;
+  const int STATUS_SESSION_TRANSCRIPT_MISMATCH = 12;
+}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IWritableIdentityCredential.aidl b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IWritableIdentityCredential.aidl
new file mode 100644
index 0000000..c5ac9d6
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IWritableIdentityCredential.aidl
@@ -0,0 +1,28 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@VintfStability
+interface IWritableIdentityCredential {
+  android.hardware.identity.Certificate[] getAttestationCertificate(in byte[] attestationApplicationId, in byte[] attestationChallenge);
+  void startPersonalization(in int accessControlProfileCount, in int[] entryCounts);
+  android.hardware.identity.SecureAccessControlProfile addAccessControlProfile(in int id, in android.hardware.identity.Certificate readerCertificate, in boolean userAuthenticationRequired, in long timeoutMillis, in long secureUserId);
+  void beginAddEntry(in int[] accessControlProfileIds, in @utf8InCpp String nameSpace, in @utf8InCpp String name, in int entrySize);
+  byte[] addEntryValue(in byte[] content);
+  void finishAddingEntries(out byte[] credentialData, out byte[] proofOfProvisioningSignature);
+  void setExpectedProofOfProvisioningSize(in int expectedProofOfProvisioningSize);
+}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/RequestDataItem.aidl b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/RequestDataItem.aidl
new file mode 100644
index 0000000..24ec26a
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/RequestDataItem.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@VintfStability
+parcelable RequestDataItem {
+  @utf8InCpp String name;
+  long size;
+  int[] accessControlProfileIds;
+}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/RequestNamespace.aidl b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/RequestNamespace.aidl
new file mode 100644
index 0000000..af00f3b
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/RequestNamespace.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@VintfStability
+parcelable RequestNamespace {
+  @utf8InCpp String namespaceName;
+  android.hardware.identity.RequestDataItem[] items;
+}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/SecureAccessControlProfile.aidl b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/SecureAccessControlProfile.aidl
new file mode 100644
index 0000000..dfc1ad0
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/SecureAccessControlProfile.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@VintfStability
+parcelable SecureAccessControlProfile {
+  int id;
+  android.hardware.identity.Certificate readerCertificate;
+  boolean userAuthenticationRequired;
+  long timeoutMillis;
+  long secureUserId;
+  byte[] mac;
+}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl
index 58b90b5..e8e93f8 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl
@@ -27,4 +27,5 @@
   byte[] retrieveEntryValue(in byte[] encryptedContent);
   void finishRetrieval(out byte[] mac, out byte[] deviceNameSpaces);
   android.hardware.identity.Certificate generateSigningKeyPair(out byte[] signingKeyBlob);
+  void setRequestedNamespaces(in android.hardware.identity.RequestNamespace[] requestNamespaces);
 }
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IWritableIdentityCredential.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IWritableIdentityCredential.aidl
index 32f283c..c5ac9d6 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IWritableIdentityCredential.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IWritableIdentityCredential.aidl
@@ -24,4 +24,5 @@
   void beginAddEntry(in int[] accessControlProfileIds, in @utf8InCpp String nameSpace, in @utf8InCpp String name, in int entrySize);
   byte[] addEntryValue(in byte[] content);
   void finishAddingEntries(out byte[] credentialData, out byte[] proofOfProvisioningSignature);
+  void setExpectedProofOfProvisioningSize(in int expectedProofOfProvisioningSize);
 }
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestDataItem.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestDataItem.aidl
new file mode 100644
index 0000000..24ec26a
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestDataItem.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@VintfStability
+parcelable RequestDataItem {
+  @utf8InCpp String name;
+  long size;
+  int[] accessControlProfileIds;
+}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestNamespace.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestNamespace.aidl
new file mode 100644
index 0000000..af00f3b
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestNamespace.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@VintfStability
+parcelable RequestNamespace {
+  @utf8InCpp String namespaceName;
+  android.hardware.identity.RequestDataItem[] items;
+}
diff --git a/identity/aidl/android/hardware/identity/IIdentityCredential.aidl b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
index 7d14f03..7cd25e6 100644
--- a/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
+++ b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
@@ -17,6 +17,7 @@
 package android.hardware.identity;
 
 import android.hardware.identity.Certificate;
+import android.hardware.identity.RequestNamespace;
 import android.hardware.identity.SecureAccessControlProfile;
 import android.hardware.keymaster.HardwareAuthToken;
 
@@ -82,6 +83,8 @@
     /**
      * Start an entry retrieval process.
      *
+     * The setRequestedNamespaces() method will be called before this method.
+     *
      * This method be called after createEphemeralKeyPair(), setReaderEphemeralPublicKey(),
      * createAuthChallenge() and before startRetrieveEntry(). This method call is followed by
      * multiple calls of startRetrieveEntryValue(), retrieveEntryValue(), and finally
@@ -274,7 +277,7 @@
      *            "DeviceAuthentication",
      *            SessionTranscript,
      *            DocType,
-     *            DeviceNameSpaceBytes,
+     *            DeviceNameSpacesBytes,
      *        ]
      *
      *        DocType = tstr
@@ -343,4 +346,16 @@
      * @return an X.509 certificate for the new signing key, signed by the credential key.
      */
     Certificate generateSigningKeyPair(out byte[] signingKeyBlob);
+
+    /**
+     * Sets the namespaces and data items (including their size and access control profiles)
+     * which will be requested. This method must be called before startRetrieval() is called.
+     *
+     * This information is provided to make it possible for a HAL implementation to
+     * incrementally build up cryptographically authenticated data which includes the
+     * DeviceNameSpaces CBOR.
+     *
+     * @param requestNamespaces Namespaces and data items which will be requested.
+     */
+    void setRequestedNamespaces(in RequestNamespace[] requestNamespaces);
 }
diff --git a/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl b/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
index 9673821..b7ad283 100644
--- a/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
+++ b/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
@@ -120,6 +120,8 @@
      *
      * startPersonalization must not be called more than once.
      *
+     * The setExpectedProofOfProvisioningSize() method will be called before this method.
+     *
      * @param accessControlProfileCount specifies the number of access control profiles that will
      *     be provisioned with addAccessControlProfile().
      *
@@ -140,7 +142,8 @@
      * with STATUS_INVALID_DATA.
      *
      * @param id a numeric identifier that must be unique within the context of a Credential and may
-     *     be used to reference the profile. If this is not satisfied the call fails with
+     *     be used to reference the profile. This id must be non-negative and less than 32 (allowing
+     *     for a total of 32 profiles). If this is not satisfied the call fails with
      *     STATUS_INVALID_DATA.
      *
      * @param readerCertificate if non-empty, specifies a single X.509 certificate (not a chain of
@@ -287,4 +290,16 @@
      */
     void finishAddingEntries(out byte[] credentialData,
         out byte[] proofOfProvisioningSignature);
+
+    /**
+     * Sets the expected size of the ProofOfProvisioning returned by finishAddingEntries(). This
+     * method must be called before startPersonalization() is called.
+     *
+     * This information is provided to make it possible for a HAL implementation to
+     * incrementally build up cryptographically authenticated data which includes the
+     * ProofOfProvisioning CBOR.
+     *
+     * @param expectedProofOfProvisioningSize the expected size of ProofOfProvisioning.
+     */
+    void setExpectedProofOfProvisioningSize(in int expectedProofOfProvisioningSize);
 }
diff --git a/identity/aidl/android/hardware/identity/RequestDataItem.aidl b/identity/aidl/android/hardware/identity/RequestDataItem.aidl
new file mode 100644
index 0000000..05bc762
--- /dev/null
+++ b/identity/aidl/android/hardware/identity/RequestDataItem.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.hardware.identity;
+
+@VintfStability
+parcelable RequestDataItem {
+    /**
+     * The data item name being requested, for example "driving_privileges".
+     */
+    @utf8InCpp String name;
+
+    /**
+     * The size of the data item value.
+     *
+     * Data item values are always encoded as CBOR so this is the length of
+     * the CBOR encoding of the value.
+     */
+    long size;
+
+    /**
+     * The access control profile ids this data item is configured with.
+     */
+    int[] accessControlProfileIds;
+}
diff --git a/identity/aidl/android/hardware/identity/RequestNamespace.aidl b/identity/aidl/android/hardware/identity/RequestNamespace.aidl
new file mode 100644
index 0000000..4d61506
--- /dev/null
+++ b/identity/aidl/android/hardware/identity/RequestNamespace.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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.hardware.identity;
+
+import android.hardware.identity.RequestDataItem;
+
+@VintfStability
+parcelable RequestNamespace {
+    /**
+     * The name of the namespace that items are being requested from, for
+     * example "org.iso.18013.5.1".
+     */
+    @utf8InCpp String namespaceName;
+
+    /**
+     * The data items requested.
+     */
+    RequestDataItem[] items;
+}
diff --git a/identity/aidl/default/IdentityCredential.cpp b/identity/aidl/default/IdentityCredential.cpp
index aaae1f6..ff4107a 100644
--- a/identity/aidl/default/IdentityCredential.cpp
+++ b/identity/aidl/default/IdentityCredential.cpp
@@ -25,6 +25,7 @@
 #include <string.h>
 
 #include <android-base/logging.h>
+#include <android-base/stringprintf.h>
 
 #include <cppbor.h>
 #include <cppbor_parse.h>
@@ -32,6 +33,7 @@
 namespace aidl::android::hardware::identity {
 
 using ::aidl::android::hardware::keymaster::Timestamp;
+using ::android::base::StringPrintf;
 using ::std::optional;
 
 using namespace ::android::hardware::identity;
@@ -253,6 +255,12 @@
     return true;
 }
 
+ndk::ScopedAStatus IdentityCredential::setRequestedNamespaces(
+        const vector<RequestNamespace>& requestNamespaces) {
+    requestNamespaces_ = requestNamespaces;
+    return ndk::ScopedAStatus::ok();
+}
+
 ndk::ScopedAStatus IdentityCredential::startRetrieval(
         const vector<SecureAccessControlProfile>& accessControlProfiles,
         const HardwareAuthToken& authToken, const vector<uint8_t>& itemsRequest,
@@ -447,7 +455,7 @@
                         "Type mismatch in nameSpaces map"));
             }
             string requestedNamespace = nsKey->value();
-            vector<string> requestedKeys;
+            set<string> requestedKeys;
             for (size_t m = 0; m < nsInnerMap->size(); m++) {
                 const auto& [innerMapKeyItem, innerMapValueItem] = (*nsInnerMap)[m];
                 const cppbor::Tstr* nameItem = innerMapKeyItem->asTstr();
@@ -459,13 +467,13 @@
                             IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
                             "Type mismatch in value in nameSpaces map"));
                 }
-                requestedKeys.push_back(nameItem->value());
+                requestedKeys.insert(nameItem->value());
             }
             requestedNameSpacesAndNames_[requestedNamespace] = requestedKeys;
         }
     }
 
-    // Finally, validate all the access control profiles in the requestData.
+    // Validate all the access control profiles in the requestData.
     bool haveAuthToken = (authToken.mac.size() > 0);
     for (const auto& profile : accessControlProfiles) {
         if (!secureAccessControlProfileCheckMac(profile, storageKey_)) {
@@ -496,10 +504,118 @@
     itemsRequest_ = itemsRequest;
     signingKeyBlob_ = signingKeyBlob;
 
+    // Finally, calculate the size of DeviceNameSpaces. We need to know it ahead of time.
+    expectedDeviceNameSpacesSize_ = calcDeviceNameSpacesSize();
+
     numStartRetrievalCalls_ += 1;
     return ndk::ScopedAStatus::ok();
 }
 
+size_t cborNumBytesForLength(size_t length) {
+    if (length < 24) {
+        return 0;
+    } else if (length <= 0xff) {
+        return 1;
+    } else if (length <= 0xffff) {
+        return 2;
+    } else if (length <= 0xffffffff) {
+        return 4;
+    }
+    return 8;
+}
+
+size_t cborNumBytesForTstr(const string& value) {
+    return 1 + cborNumBytesForLength(value.size()) + value.size();
+}
+
+size_t IdentityCredential::calcDeviceNameSpacesSize() {
+    /*
+     * This is how DeviceNameSpaces is defined:
+     *
+     *        DeviceNameSpaces = {
+     *            * NameSpace => DeviceSignedItems
+     *        }
+     *        DeviceSignedItems = {
+     *            + DataItemName => DataItemValue
+     *        }
+     *
+     *        Namespace = tstr
+     *        DataItemName = tstr
+     *        DataItemValue = any
+     *
+     * This function will calculate its length using knowledge of how CBOR is
+     * encoded.
+     */
+    size_t ret = 0;
+    size_t numNamespacesWithValues = 0;
+    for (const RequestNamespace& rns : requestNamespaces_) {
+        vector<RequestDataItem> itemsToInclude;
+
+        for (const RequestDataItem& rdi : rns.items) {
+            // If we have a CBOR request message, skip if item isn't in it
+            if (itemsRequest_.size() > 0) {
+                const auto& it = requestedNameSpacesAndNames_.find(rns.namespaceName);
+                if (it == requestedNameSpacesAndNames_.end()) {
+                    continue;
+                }
+                const set<string>& dataItemNames = it->second;
+                if (dataItemNames.find(rdi.name) == dataItemNames.end()) {
+                    continue;
+                }
+            }
+
+            // Access is granted if at least one of the profiles grants access.
+            //
+            // If an item is configured without any profiles, access is denied.
+            //
+            bool authorized = false;
+            for (auto id : rdi.accessControlProfileIds) {
+                auto it = profileIdToAccessCheckResult_.find(id);
+                if (it != profileIdToAccessCheckResult_.end()) {
+                    int accessControlForProfile = it->second;
+                    if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
+                        authorized = true;
+                        break;
+                    }
+                }
+            }
+            if (!authorized) {
+                continue;
+            }
+
+            itemsToInclude.push_back(rdi);
+        }
+
+        // If no entries are to be in the namespace, we don't include it...
+        if (itemsToInclude.size() == 0) {
+            continue;
+        }
+
+        // Key: NameSpace
+        ret += cborNumBytesForTstr(rns.namespaceName);
+
+        // Value: Open the DeviceSignedItems map
+        ret += 1 + cborNumBytesForLength(itemsToInclude.size());
+
+        for (const RequestDataItem& item : itemsToInclude) {
+            // Key: DataItemName
+            ret += cborNumBytesForTstr(item.name);
+
+            // Value: DataItemValue - entryData.size is the length of serialized CBOR so we use
+            // that.
+            ret += item.size;
+        }
+
+        numNamespacesWithValues++;
+    }
+
+    // Now that we now the nunber of namespaces with values, we know how many
+    // bytes the DeviceNamespaces map in the beginning is going to take up.
+    ret += 1 + cborNumBytesForLength(numNamespacesWithValues);
+
+    return ret;
+}
+
 ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue(
         const string& nameSpace, const string& name, int32_t entrySize,
         const vector<int32_t>& accessControlProfileIds) {
@@ -558,8 +674,8 @@
                     IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
                     "Name space was not requested in startRetrieval"));
         }
-        const auto& dataItemNames = it->second;
-        if (std::find(dataItemNames.begin(), dataItemNames.end(), name) == dataItemNames.end()) {
+        const set<string>& dataItemNames = it->second;
+        if (dataItemNames.find(name) == dataItemNames.end()) {
             return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
                     IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
                     "Data item name in name space was not requested in startRetrieval"));
@@ -653,6 +769,17 @@
     }
     vector<uint8_t> encodedDeviceNameSpaces = deviceNameSpacesMap_.encode();
 
+    if (encodedDeviceNameSpaces.size() != expectedDeviceNameSpacesSize_) {
+        LOG(ERROR) << "encodedDeviceNameSpaces is " << encodedDeviceNameSpaces.size() << " bytes, "
+                   << "was expecting " << expectedDeviceNameSpacesSize_;
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_INVALID_DATA,
+                StringPrintf(
+                        "Unexpected CBOR size %zd for encodedDeviceNameSpaces, was expecting %zd",
+                        encodedDeviceNameSpaces.size(), expectedDeviceNameSpacesSize_)
+                        .c_str()));
+    }
+
     // If there's no signing key or no sessionTranscript or no reader ephemeral
     // public key, we return the empty MAC.
     optional<vector<uint8_t>> mac;
diff --git a/identity/aidl/default/IdentityCredential.h b/identity/aidl/default/IdentityCredential.h
index 6072afe..a8bad88 100644
--- a/identity/aidl/default/IdentityCredential.h
+++ b/identity/aidl/default/IdentityCredential.h
@@ -32,15 +32,17 @@
 
 using ::aidl::android::hardware::keymaster::HardwareAuthToken;
 using ::std::map;
+using ::std::set;
 using ::std::string;
 using ::std::vector;
 
-using MapStringToVectorOfStrings = map<string, vector<string>>;
-
 class IdentityCredential : public BnIdentityCredential {
   public:
     IdentityCredential(const vector<uint8_t>& credentialData)
-        : credentialData_(credentialData), numStartRetrievalCalls_(0), authChallenge_(0) {}
+        : credentialData_(credentialData),
+          numStartRetrievalCalls_(0),
+          authChallenge_(0),
+          expectedDeviceNameSpacesSize_(0) {}
 
     // Parses and decrypts credentialData_, return a status code from
     // IIdentityCredentialStore. Must be called right after construction.
@@ -51,6 +53,8 @@
     ndk::ScopedAStatus createEphemeralKeyPair(vector<uint8_t>* outKeyPair) override;
     ndk::ScopedAStatus setReaderEphemeralPublicKey(const vector<uint8_t>& publicKey) override;
     ndk::ScopedAStatus createAuthChallenge(int64_t* outChallenge) override;
+    ndk::ScopedAStatus setRequestedNamespaces(
+            const vector<RequestNamespace>& requestNamespaces) override;
     ndk::ScopedAStatus startRetrieval(
             const vector<SecureAccessControlProfile>& accessControlProfiles,
             const HardwareAuthToken& authToken, const vector<uint8_t>& itemsRequest,
@@ -86,6 +90,9 @@
     // Set by createAuthChallenge()
     uint64_t authChallenge_;
 
+    // Set by setRequestedNamespaces()
+    vector<RequestNamespace> requestNamespaces_;
+
     // Set at startRetrieval() time.
     map<int32_t, int> profileIdToAccessCheckResult_;
     vector<uint8_t> signingKeyBlob_;
@@ -93,16 +100,21 @@
     std::unique_ptr<cppbor::Item> sessionTranscriptItem_;
     vector<uint8_t> itemsRequest_;
     vector<int32_t> requestCountsRemaining_;
-    MapStringToVectorOfStrings requestedNameSpacesAndNames_;
+    map<string, set<string>> requestedNameSpacesAndNames_;
     cppbor::Map deviceNameSpacesMap_;
     cppbor::Map currentNameSpaceDeviceNameSpacesMap_;
 
+    // Calculated at startRetrieval() time.
+    size_t expectedDeviceNameSpacesSize_;
+
     // Set at startRetrieveEntryValue() time.
     string currentNameSpace_;
     string currentName_;
     size_t entryRemainingBytes_;
     vector<uint8_t> entryValue_;
     vector<uint8_t> entryAdditionalData_;
+
+    size_t calcDeviceNameSpacesSize();
 };
 
 }  // namespace aidl::android::hardware::identity
diff --git a/identity/aidl/default/WritableIdentityCredential.cpp b/identity/aidl/default/WritableIdentityCredential.cpp
index bce913a..8bc4b49 100644
--- a/identity/aidl/default/WritableIdentityCredential.cpp
+++ b/identity/aidl/default/WritableIdentityCredential.cpp
@@ -22,6 +22,7 @@
 #include <android/hardware/identity/support/IdentityCredentialSupport.h>
 
 #include <android-base/logging.h>
+#include <android-base/stringprintf.h>
 
 #include <cppbor/cppbor.h>
 #include <cppbor/cppbor_parse.h>
@@ -34,6 +35,7 @@
 
 namespace aidl::android::hardware::identity {
 
+using ::android::base::StringPrintf;
 using ::std::optional;
 using namespace ::android::hardware::identity;
 
@@ -44,6 +46,8 @@
         return false;
     }
     storageKey_ = random.value();
+    startPersonalizationCalled_ = false;
+    firstEntry_ = true;
 
     return true;
 }
@@ -103,8 +107,20 @@
     return ndk::ScopedAStatus::ok();
 }
 
+ndk::ScopedAStatus WritableIdentityCredential::setExpectedProofOfProvisioningSize(
+        int32_t expectedProofOfProvisioningSize) {
+    expectedProofOfProvisioningSize_ = expectedProofOfProvisioningSize;
+    return ndk::ScopedAStatus::ok();
+}
+
 ndk::ScopedAStatus WritableIdentityCredential::startPersonalization(
         int32_t accessControlProfileCount, const vector<int32_t>& entryCounts) {
+    if (startPersonalizationCalled_) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "startPersonalization called already"));
+    }
+
+    startPersonalizationCalled_ = true;
     numAccessControlProfileRemaining_ = accessControlProfileCount;
     remainingEntryCounts_ = entryCounts;
     entryNameSpace_ = "";
@@ -128,6 +144,19 @@
                 "numAccessControlProfileRemaining_ is 0 and expected non-zero"));
     }
 
+    if (accessControlProfileIds_.find(id) != accessControlProfileIds_.end()) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_INVALID_DATA,
+                "Access Control Profile id must be unique"));
+    }
+    accessControlProfileIds_.insert(id);
+
+    if (id < 0 || id >= 32) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_INVALID_DATA,
+                "Access Control Profile id must be non-negative and less than 32"));
+    }
+
     // Spec requires if |userAuthenticationRequired| is false, then |timeoutMillis| must also
     // be zero.
     if (!userAuthenticationRequired && timeoutMillis != 0) {
@@ -183,12 +212,20 @@
     }
 
     // Handle initial beginEntry() call.
-    if (entryNameSpace_ == "") {
+    if (firstEntry_) {
+        firstEntry_ = false;
         entryNameSpace_ = nameSpace;
+        allNameSpaces_.insert(nameSpace);
     }
 
     // If the namespace changed...
     if (nameSpace != entryNameSpace_) {
+        if (allNameSpaces_.find(nameSpace) != allNameSpaces_.end()) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_INVALID_DATA,
+                    "Name space cannot be added in interleaving fashion"));
+        }
+
         // Then check that all entries in the previous namespace have been added..
         if (remainingEntryCounts_[0] != 0) {
             return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
@@ -196,6 +233,8 @@
                     "New namespace but a non-zero number of entries remain to be added"));
         }
         remainingEntryCounts_.erase(remainingEntryCounts_.begin());
+        remainingEntryCounts_[0] -= 1;
+        allNameSpaces_.insert(nameSpace);
 
         if (signedDataCurrentNamespace_.size() > 0) {
             signedDataNamespaces_.add(entryNameSpace_, std::move(signedDataCurrentNamespace_));
@@ -328,6 +367,18 @@
 
 ndk::ScopedAStatus WritableIdentityCredential::finishAddingEntries(
         vector<uint8_t>* outCredentialData, vector<uint8_t>* outProofOfProvisioningSignature) {
+    if (numAccessControlProfileRemaining_ != 0) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_INVALID_DATA,
+                "numAccessControlProfileRemaining_ is not 0 and expected zero"));
+    }
+
+    if (remainingEntryCounts_.size() > 1 || remainingEntryCounts_[0] != 0) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_INVALID_DATA,
+                "More entry spaces remain than startPersonalization configured"));
+    }
+
     if (signedDataCurrentNamespace_.size() > 0) {
         signedDataNamespaces_.add(entryNameSpace_, std::move(signedDataCurrentNamespace_));
     }
@@ -339,6 +390,16 @@
             .add(testCredential_);
     vector<uint8_t> encodedCbor = popArray.encode();
 
+    if (encodedCbor.size() != expectedProofOfProvisioningSize_) {
+        LOG(ERROR) << "CBOR for proofOfProvisioning is " << encodedCbor.size() << " bytes, "
+                   << "was expecting " << expectedProofOfProvisioningSize_;
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_INVALID_DATA,
+                StringPrintf("Unexpected CBOR size %zd for proofOfProvisioning, was expecting %zd",
+                             encodedCbor.size(), expectedProofOfProvisioningSize_)
+                        .c_str()));
+    }
+
     optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_,
                                                                  encodedCbor,  // payload
                                                                  {},           // additionalData
diff --git a/identity/aidl/default/WritableIdentityCredential.h b/identity/aidl/default/WritableIdentityCredential.h
index 4b6fca8..5645852 100644
--- a/identity/aidl/default/WritableIdentityCredential.h
+++ b/identity/aidl/default/WritableIdentityCredential.h
@@ -21,9 +21,11 @@
 #include <android/hardware/identity/support/IdentityCredentialSupport.h>
 
 #include <cppbor.h>
+#include <set>
 
 namespace aidl::android::hardware::identity {
 
+using ::std::set;
 using ::std::string;
 using ::std::vector;
 
@@ -41,6 +43,9 @@
                                                  const vector<uint8_t>& attestationChallenge,
                                                  vector<Certificate>* outCertificateChain) override;
 
+    ndk::ScopedAStatus setExpectedProofOfProvisioningSize(
+            int32_t expectedProofOfProvisioningSize) override;
+
     ndk::ScopedAStatus startPersonalization(int32_t accessControlProfileCount,
                                             const vector<int32_t>& entryCounts) override;
 
@@ -60,12 +65,14 @@
             vector<uint8_t>* outCredentialData,
             vector<uint8_t>* outProofOfProvisioningSignature) override;
 
-    // private:
+  private:
     string docType_;
     bool testCredential_;
 
     // This is set in initialize().
     vector<uint8_t> storageKey_;
+    bool startPersonalizationCalled_;
+    bool firstEntry_;
 
     // These are set in getAttestationCertificate().
     vector<uint8_t> credentialPrivKey_;
@@ -78,6 +85,10 @@
     cppbor::Array signedDataAccessControlProfiles_;
     cppbor::Map signedDataNamespaces_;
     cppbor::Array signedDataCurrentNamespace_;
+    size_t expectedProofOfProvisioningSize_;
+
+    // This field is initialized in addAccessControlProfile
+    set<int32_t> accessControlProfileIds_;
 
     // These fields are initialized during beginAddEntry()
     size_t entryRemainingBytes_;
@@ -86,6 +97,7 @@
     string entryName_;
     vector<int32_t> entryAccessControlProfileIds_;
     vector<uint8_t> entryBytes_;
+    set<string> allNameSpaces_;
 };
 
 }  // namespace aidl::android::hardware::identity
diff --git a/identity/aidl/vts/Android.bp b/identity/aidl/vts/Android.bp
index ef8beb4..e4780bf 100644
--- a/identity/aidl/vts/Android.bp
+++ b/identity/aidl/vts/Android.bp
@@ -4,7 +4,11 @@
         "VtsHalTargetTestDefaults",
         "use_libaidlvintf_gtest_helper_static",
     ],
-    srcs: ["VtsHalIdentityTargetTest.cpp"],
+    srcs: [
+        "VtsHalIdentityEndToEndTest.cpp",
+        "VtsIWritableIdentityCredentialTests.cpp",
+        "VtsIdentityTestUtils.cpp",
+    ],
     shared_libs: [
         "libbinder",
         "libcrypto",
diff --git a/identity/aidl/vts/VtsHalIdentityTargetTest.cpp b/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp
similarity index 69%
rename from identity/aidl/vts/VtsHalIdentityTargetTest.cpp
rename to identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp
index ea37fdc..807feab 100644
--- a/identity/aidl/vts/VtsHalIdentityTargetTest.cpp
+++ b/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp
@@ -28,8 +28,11 @@
 #include <future>
 #include <map>
 
+#include "VtsIdentityTestUtils.h"
+
 namespace android::hardware::identity {
 
+using std::endl;
 using std::map;
 using std::optional;
 using std::string;
@@ -41,51 +44,6 @@
 
 using ::android::hardware::keymaster::HardwareAuthToken;
 
-// ---------------------------------------------------------------------------
-// Test Data.
-// ---------------------------------------------------------------------------
-
-struct TestEntryData {
-    TestEntryData(string nameSpace, string name, vector<int32_t> profileIds)
-        : nameSpace(nameSpace), name(name), profileIds(profileIds) {}
-
-    TestEntryData(string nameSpace, string name, const string& value, vector<int32_t> profileIds)
-        : TestEntryData(nameSpace, name, profileIds) {
-        valueCbor = cppbor::Tstr(((const char*)value.data())).encode();
-    }
-    TestEntryData(string nameSpace, string name, const vector<uint8_t>& value,
-                  vector<int32_t> profileIds)
-        : TestEntryData(nameSpace, name, profileIds) {
-        valueCbor = cppbor::Bstr(value).encode();
-    }
-    TestEntryData(string nameSpace, string name, bool value, vector<int32_t> profileIds)
-        : TestEntryData(nameSpace, name, profileIds) {
-        valueCbor = cppbor::Bool(value).encode();
-    }
-    TestEntryData(string nameSpace, string name, int64_t value, vector<int32_t> profileIds)
-        : TestEntryData(nameSpace, name, profileIds) {
-        if (value >= 0) {
-            valueCbor = cppbor::Uint(value).encode();
-        } else {
-            valueCbor = cppbor::Nint(-value).encode();
-        }
-    }
-
-    string nameSpace;
-    string name;
-    vector<uint8_t> valueCbor;
-    vector<int32_t> profileIds;
-};
-
-struct TestProfile {
-    uint16_t id;
-    vector<uint8_t> readerCertificate;
-    bool userAuthenticationRequired;
-    uint64_t timeoutMillis;
-};
-
-// ----------------------------------------------------------------
-
 class IdentityAidl : public testing::TestWithParam<std::string> {
   public:
     virtual void SetUp() override {
@@ -108,39 +66,26 @@
 TEST_P(IdentityAidl, createAndRetrieveCredential) {
     // First, generate a key-pair for the reader since its public key will be
     // part of the request data.
-    optional<vector<uint8_t>> readerKeyPKCS8 = support::createEcKeyPair();
-    ASSERT_TRUE(readerKeyPKCS8);
-    optional<vector<uint8_t>> readerPublicKey =
-            support::ecKeyPairGetPublicKey(readerKeyPKCS8.value());
-    optional<vector<uint8_t>> readerKey = support::ecKeyPairGetPrivateKey(readerKeyPKCS8.value());
-    string serialDecimal = "1234";
-    string issuer = "Android Open Source Project";
-    string subject = "Android IdentityCredential VTS Test";
-    time_t validityNotBefore = time(nullptr);
-    time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
-    optional<vector<uint8_t>> readerCertificate = support::ecPublicKeyGenerateCertificate(
-            readerPublicKey.value(), readerKey.value(), serialDecimal, issuer, subject,
-            validityNotBefore, validityNotAfter);
+    vector<uint8_t> readerKey;
+    optional<vector<uint8_t>> readerCertificate =
+            test_utils::GenerateReaderCertificate("1234", readerKey);
     ASSERT_TRUE(readerCertificate);
 
     // Make the portrait image really big (just shy of 256 KiB) to ensure that
     // the chunking code gets exercised.
     vector<uint8_t> portraitImage;
-    portraitImage.resize(256 * 1024 - 10);
-    for (size_t n = 0; n < portraitImage.size(); n++) {
-        portraitImage[n] = (uint8_t)n;
-    }
+    test_utils::SetImageData(portraitImage);
 
     // Access control profiles:
-    const vector<TestProfile> testProfiles = {// Profile 0 (reader authentication)
-                                              {0, readerCertificate.value(), false, 0},
-                                              // Profile 1 (no authentication)
-                                              {1, {}, false, 0}};
+    const vector<test_utils::TestProfile> testProfiles = {// Profile 0 (reader authentication)
+                                                          {0, readerCertificate.value(), false, 0},
+                                                          // Profile 1 (no authentication)
+                                                          {1, {}, false, 0}};
 
     HardwareAuthToken authToken;
 
     // Here's the actual test data:
-    const vector<TestEntryData> testEntries = {
+    const vector<test_utils::TestEntryData> testEntries = {
             {"PersonalData", "Last name", string("Turing"), vector<int32_t>{0, 1}},
             {"PersonalData", "Birth date", string("19120623"), vector<int32_t>{0, 1}},
             {"PersonalData", "First name", string("Alan"), vector<int32_t>{0, 1}},
@@ -155,67 +100,38 @@
 
     string cborPretty;
     sp<IWritableIdentityCredential> writableCredential;
-    string docType = "org.iso.18013-5.2019.mdl";
-    bool testCredential = true;
-    ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &writableCredential)
-                        .isOk());
-    ASSERT_NE(writableCredential, nullptr);
+    ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
 
     string challenge = "attestationChallenge";
-    // TODO: set it to something random and check it's in the cert chain
-    vector<uint8_t> attestationApplicationId = {};
-    vector<uint8_t> attestationChallenge(challenge.begin(), challenge.end());
-    vector<Certificate> attestationCertificates;
-    ASSERT_TRUE(writableCredential
-                        ->getAttestationCertificate(attestationApplicationId, attestationChallenge,
-                                                    &attestationCertificates)
-                        .isOk());
-    ASSERT_GE(attestationCertificates.size(), 2);
+    test_utils::AttestationData attData(writableCredential, challenge, {});
+    ASSERT_TRUE(attData.result.isOk())
+            << attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl;
+    ASSERT_EQ(binder::Status::EX_NONE, attData.result.exceptionCode());
+    ASSERT_EQ(IIdentityCredentialStore::STATUS_OK, attData.result.serviceSpecificErrorCode());
 
+    // TODO: set it to something random and check it's in the cert chain
+    ASSERT_GE(attData.attestationCertificate.size(), 2);
+
+    // This is kinda of a hack but we need to give the size of
+    // ProofOfProvisioning that we'll expect to receive.
+    const int32_t expectedProofOfProvisioningSize = 262861 - 326 + readerCertificate.value().size();
+    // OK to fail, not available in v1 HAL
+    writableCredential->setExpectedProofOfProvisioningSize(expectedProofOfProvisioningSize);
     ASSERT_TRUE(
             writableCredential->startPersonalization(testProfiles.size(), testEntriesEntryCounts)
                     .isOk());
 
-    vector<SecureAccessControlProfile> returnedSecureProfiles;
-    for (const auto& testProfile : testProfiles) {
-        SecureAccessControlProfile profile;
-        Certificate cert;
-        cert.encodedCertificate = testProfile.readerCertificate;
-        ASSERT_TRUE(writableCredential
-                            ->addAccessControlProfile(testProfile.id, cert,
-                                                      testProfile.userAuthenticationRequired,
-                                                      testProfile.timeoutMillis,
-                                                      0,  // secureUserId
-                                                      &profile)
-                            .isOk());
-        ASSERT_EQ(testProfile.id, profile.id);
-        ASSERT_EQ(testProfile.readerCertificate, profile.readerCertificate.encodedCertificate);
-        ASSERT_EQ(testProfile.userAuthenticationRequired, profile.userAuthenticationRequired);
-        ASSERT_EQ(testProfile.timeoutMillis, profile.timeoutMillis);
-        ASSERT_EQ(support::kAesGcmTagSize + support::kAesGcmIvSize, profile.mac.size());
-        returnedSecureProfiles.push_back(profile);
-    }
+    optional<vector<SecureAccessControlProfile>> secureProfiles =
+            test_utils::AddAccessControlProfiles(writableCredential, testProfiles);
+    ASSERT_TRUE(secureProfiles);
 
     // Uses TestEntryData* pointer as key and values are the encrypted blobs. This
     // is a little hacky but it works well enough.
-    map<const TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
+    map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
 
     for (const auto& entry : testEntries) {
-        vector<vector<uint8_t>> chunks =
-                support::chunkVector(entry.valueCbor, hwInfo.dataChunkSize);
-
-        ASSERT_TRUE(writableCredential
-                            ->beginAddEntry(entry.profileIds, entry.nameSpace, entry.name,
-                                            entry.valueCbor.size())
-                            .isOk());
-
-        vector<vector<uint8_t>> encryptedChunks;
-        for (const auto& chunk : chunks) {
-            vector<uint8_t> encryptedChunk;
-            ASSERT_TRUE(writableCredential->addEntryValue(chunk, &encryptedChunk).isOk());
-            encryptedChunks.push_back(encryptedChunk);
-        }
-        encryptedBlobs[&entry] = encryptedChunks;
+        ASSERT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize,
+                                         encryptedBlobs, true));
     }
 
     vector<uint8_t> credentialData;
@@ -276,8 +192,8 @@
             "]",
             cborPretty);
 
-    optional<vector<uint8_t>> credentialPubKey =
-            support::certificateChainGetTopMostKey(attestationCertificates[0].encodedCertificate);
+    optional<vector<uint8_t>> credentialPubKey = support::certificateChainGetTopMostKey(
+            attData.attestationCertificate[0].encodedCertificate);
     ASSERT_TRUE(credentialPubKey);
     EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
                                                  {},  // Additional data
@@ -347,8 +263,8 @@
                                          .add(cppbor::Semantic(24, itemsRequestBytes))
                                          .encode();
     optional<vector<uint8_t>> readerSignature =
-            support::coseSignEcDsa(readerKey.value(), {},  // content
-                                   dataToSign,             // detached content
+            support::coseSignEcDsa(readerKey, {},  // content
+                                   dataToSign,     // detached content
                                    readerCertificate.value());
     ASSERT_TRUE(readerSignature);
 
@@ -357,8 +273,10 @@
     Certificate signingKeyCertificate;
     ASSERT_TRUE(credential->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk());
 
+    vector<RequestNamespace> requestedNamespaces = test_utils::buildRequestNamespaces(testEntries);
+    ASSERT_TRUE(credential->setRequestedNamespaces(requestedNamespaces).isOk());
     ASSERT_TRUE(credential
-                        ->startRetrieval(returnedSecureProfiles, authToken, itemsRequestBytes,
+                        ->startRetrieval(secureProfiles.value(), authToken, itemsRequestBytes,
                                          signingKeyBlob, sessionTranscriptBytes,
                                          readerSignature.value(), testEntriesEntryCounts)
                         .isOk());
@@ -405,6 +323,8 @@
     cppbor::Array deviceAuthentication;
     deviceAuthentication.add("DeviceAuthentication");
     deviceAuthentication.add(sessionTranscript.clone());
+
+    string docType = "org.iso.18013-5.2019.mdl";
     deviceAuthentication.add(docType);
     deviceAuthentication.add(cppbor::Semantic(24, deviceNameSpacesBytes));
     vector<uint8_t> encodedDeviceAuthentication = deviceAuthentication.encode();
diff --git a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
new file mode 100644
index 0000000..724aaa1
--- /dev/null
+++ b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
@@ -0,0 +1,695 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "VtsIWritableIdentityCredentialTests"
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <android-base/logging.h>
+#include <android/hardware/identity/IIdentityCredentialStore.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+#include <gtest/gtest.h>
+#include <future>
+#include <map>
+
+#include "VtsIdentityTestUtils.h"
+
+namespace android::hardware::identity {
+
+using std::endl;
+using std::map;
+using std::optional;
+using std::string;
+using std::vector;
+
+using ::android::sp;
+using ::android::String16;
+using ::android::binder::Status;
+
+class IdentityCredentialTests : public testing::TestWithParam<string> {
+  public:
+    virtual void SetUp() override {
+        credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
+                String16(GetParam().c_str()));
+        ASSERT_NE(credentialStore_, nullptr);
+    }
+
+    sp<IIdentityCredentialStore> credentialStore_;
+};
+
+TEST_P(IdentityCredentialTests, verifyAttestationWithEmptyChallenge) {
+    Status result;
+    sp<IWritableIdentityCredential> writableCredential;
+    ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+    vector<uint8_t> attestationChallenge;
+    vector<Certificate> attestationCertificate;
+    vector<uint8_t> attestationApplicationId = {};
+    result = writableCredential->getAttestationCertificate(
+            attestationApplicationId, attestationChallenge, &attestationCertificate);
+
+    EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+                               << endl;
+
+    EXPECT_TRUE(test_utils::ValidateAttestationCertificate(attestationCertificate));
+}
+
+TEST_P(IdentityCredentialTests, verifyAttestationSuccessWithChallenge) {
+    Status result;
+    sp<IWritableIdentityCredential> writableCredential;
+    ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+    string challenge = "NotSoRandomChallenge1NotSoRandomChallenge1NotSoRandomChallenge1";
+    vector<uint8_t> attestationChallenge(challenge.begin(), challenge.end());
+    vector<Certificate> attestationCertificate;
+    vector<uint8_t> attestationApplicationId = {};
+
+    result = writableCredential->getAttestationCertificate(
+            attestationApplicationId, attestationChallenge, &attestationCertificate);
+
+    EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+                               << endl;
+
+    EXPECT_TRUE(test_utils::ValidateAttestationCertificate(attestationCertificate));
+}
+
+TEST_P(IdentityCredentialTests, verifyAttestationDoubleCallFails) {
+    Status result;
+    sp<IWritableIdentityCredential> writableCredential;
+    ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+    string challenge = "NotSoRandomChallenge1";
+    test_utils::AttestationData attData(writableCredential, challenge, {});
+    ASSERT_TRUE(test_utils::ValidateAttestationCertificate(attData.attestationCertificate));
+
+    string challenge2 = "NotSoRandomChallenge2";
+    test_utils::AttestationData attData2(writableCredential, challenge2, {});
+    EXPECT_FALSE(attData2.result.isOk()) << attData2.result.exceptionCode() << "; "
+                                         << attData2.result.exceptionMessage() << endl;
+    EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, attData2.result.exceptionCode());
+    EXPECT_EQ(IIdentityCredentialStore::STATUS_FAILED, attData2.result.serviceSpecificErrorCode());
+}
+
+TEST_P(IdentityCredentialTests, verifyStartPersonalization) {
+    Status result;
+    sp<IWritableIdentityCredential> writableCredential;
+    ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+    // First call should go through
+    const vector<int32_t> entryCounts = {2, 4};
+    result = writableCredential->startPersonalization(5, entryCounts);
+    ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+                               << endl;
+
+    // Call personalization again to check if repeat call is allowed.
+    result = writableCredential->startPersonalization(7, entryCounts);
+
+    // Second call to startPersonalization should have failed.
+    EXPECT_FALSE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+                                << endl;
+    EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
+    EXPECT_EQ(IIdentityCredentialStore::STATUS_FAILED, result.serviceSpecificErrorCode());
+}
+
+TEST_P(IdentityCredentialTests, verifyStartPersonalizationMin) {
+    Status result;
+    sp<IWritableIdentityCredential> writableCredential;
+    ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+    // Verify minimal number of profile count and entry count
+    const vector<int32_t> entryCounts = {1, 1};
+    writableCredential->startPersonalization(1, entryCounts);
+    EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+                               << endl;
+}
+
+TEST_P(IdentityCredentialTests, verifyStartPersonalizationZero) {
+    Status result;
+    sp<IWritableIdentityCredential> writableCredential;
+    ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+    const vector<int32_t> entryCounts = {0};
+    writableCredential->startPersonalization(0, entryCounts);
+    EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+                               << endl;
+}
+
+TEST_P(IdentityCredentialTests, verifyStartPersonalizationOne) {
+    Status result;
+    sp<IWritableIdentityCredential> writableCredential;
+    ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+    // Verify minimal number of profile count and entry count
+    const vector<int32_t> entryCounts = {1};
+    writableCredential->startPersonalization(1, entryCounts);
+    EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+                               << endl;
+}
+
+TEST_P(IdentityCredentialTests, verifyStartPersonalizationLarge) {
+    Status result;
+    sp<IWritableIdentityCredential> writableCredential;
+    ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+    // Verify set a large number of profile count and entry count is ok
+    const vector<int32_t> entryCounts = {3000};
+    writableCredential->startPersonalization(3500, entryCounts);
+    EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+                               << endl;
+}
+
+TEST_P(IdentityCredentialTests, verifyProfileNumberMismatchShouldFail) {
+    Status result;
+    sp<IWritableIdentityCredential> writableCredential;
+    ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+    // Enter mismatched entry and profile numbers
+    const vector<int32_t> entryCounts = {5, 6};
+    writableCredential->startPersonalization(5, entryCounts);
+    ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+                               << endl;
+
+    optional<vector<uint8_t>> readerCertificate = test_utils::GenerateReaderCertificate("12345");
+    ASSERT_TRUE(readerCertificate);
+
+    const vector<test_utils::TestProfile> testProfiles = {// Profile 0 (reader authentication)
+                                                          {1, readerCertificate.value(), false, 0},
+                                                          {2, readerCertificate.value(), true, 1},
+                                                          // Profile 4 (no authentication)
+                                                          {4, {}, false, 0}};
+
+    optional<vector<SecureAccessControlProfile>> secureProfiles =
+            test_utils::AddAccessControlProfiles(writableCredential, testProfiles);
+    ASSERT_TRUE(secureProfiles);
+
+    vector<uint8_t> credentialData;
+    vector<uint8_t> proofOfProvisioningSignature;
+    result =
+            writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
+
+    // finishAddingEntries should fail because the number of addAccessControlProfile mismatched with
+    // startPersonalization, and begintest_utils::AddEntry was not called.
+    EXPECT_FALSE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+                                << endl;
+    EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
+    EXPECT_EQ(IIdentityCredentialStore::STATUS_INVALID_DATA, result.serviceSpecificErrorCode());
+}
+
+TEST_P(IdentityCredentialTests, verifyDuplicateProfileId) {
+    Status result;
+    sp<IWritableIdentityCredential> writableCredential;
+    ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+    const vector<int32_t> entryCounts = {3, 6};
+    writableCredential->startPersonalization(3, entryCounts);
+    ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+                               << endl;
+
+    const vector<test_utils::TestProfile> testProfiles = {// first profile should go though
+                                                          {1, {}, true, 2},
+                                                          // same id, different
+                                                          // authentication requirement
+                                                          {1, {}, true, 1},
+                                                          // same id, different certificate
+                                                          {1, {}, false, 0}};
+
+    bool expectOk = true;
+    for (const auto& testProfile : testProfiles) {
+        SecureAccessControlProfile profile;
+        Certificate cert;
+        cert.encodedCertificate = testProfile.readerCertificate;
+        result = writableCredential->addAccessControlProfile(
+                testProfile.id, cert, testProfile.userAuthenticationRequired,
+                testProfile.timeoutMillis, 0, &profile);
+
+        if (expectOk) {
+            expectOk = false;
+            // for profile should be allowed though as there are no duplications
+            // yet.
+            ASSERT_TRUE(result.isOk())
+                    << result.exceptionCode() << "; " << result.exceptionMessage()
+                    << "test profile id = " << testProfile.id << endl;
+
+            ASSERT_EQ(testProfile.id, profile.id);
+            ASSERT_EQ(testProfile.readerCertificate, profile.readerCertificate.encodedCertificate);
+            ASSERT_EQ(testProfile.userAuthenticationRequired, profile.userAuthenticationRequired);
+            ASSERT_EQ(testProfile.timeoutMillis, profile.timeoutMillis);
+            ASSERT_EQ(support::kAesGcmTagSize + support::kAesGcmIvSize, profile.mac.size());
+        } else {
+            // should not allow duplicate id profiles.
+            ASSERT_FALSE(result.isOk())
+                    << result.exceptionCode() << "; " << result.exceptionMessage()
+                    << ". Test profile id = " << testProfile.id
+                    << ", timeout=" << testProfile.timeoutMillis << endl;
+            ASSERT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
+            ASSERT_EQ(IIdentityCredentialStore::STATUS_INVALID_DATA,
+                      result.serviceSpecificErrorCode());
+        }
+    }
+}
+
+TEST_P(IdentityCredentialTests, verifyOneProfileAndEntryPass) {
+    Status result;
+
+    HardwareInformation hwInfo;
+    ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
+
+    sp<IWritableIdentityCredential> writableCredential;
+    ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+    string challenge = "NotSoRandomChallenge1";
+    test_utils::AttestationData attData(writableCredential, challenge, {});
+    EXPECT_TRUE(attData.result.isOk())
+            << attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl;
+
+    optional<vector<uint8_t>> readerCertificate1 = test_utils::GenerateReaderCertificate("123456");
+    ASSERT_TRUE(readerCertificate1);
+
+    const vector<int32_t> entryCounts = {1u};
+    size_t expectedPoPSize = 186 + readerCertificate1.value().size();
+    // OK to fail, not available in v1 HAL
+    writableCredential->setExpectedProofOfProvisioningSize(expectedPoPSize);
+    result = writableCredential->startPersonalization(1, entryCounts);
+    ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+                               << endl;
+
+    const vector<test_utils::TestProfile> testProfiles = {{1, readerCertificate1.value(), true, 1}};
+
+    optional<vector<SecureAccessControlProfile>> secureProfiles =
+            test_utils::AddAccessControlProfiles(writableCredential, testProfiles);
+    ASSERT_TRUE(secureProfiles);
+
+    const vector<test_utils::TestEntryData> testEntries1 = {
+            {"Name Space", "Last name", string("Turing"), vector<int32_t>{0, 1}},
+    };
+
+    map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
+    for (const auto& entry : testEntries1) {
+        ASSERT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize,
+                                         encryptedBlobs, true));
+    }
+
+    vector<uint8_t> credentialData;
+    vector<uint8_t> proofOfProvisioningSignature;
+    result =
+            writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
+
+    EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+                               << endl;
+
+    optional<vector<uint8_t>> proofOfProvisioning =
+            support::coseSignGetPayload(proofOfProvisioningSignature);
+    ASSERT_TRUE(proofOfProvisioning);
+    string cborPretty =
+            support::cborPrettyPrint(proofOfProvisioning.value(), 32, {"readerCertificate"});
+    EXPECT_EQ(
+            "[\n"
+            "  'ProofOfProvisioning',\n"
+            "  'org.iso.18013-5.2019.mdl',\n"
+            "  [\n"
+            "    {\n"
+            "      'id' : 1,\n"
+            "      'readerCertificate' : <not printed>,\n"
+            "      'userAuthenticationRequired' : true,\n"
+            "      'timeoutMillis' : 1,\n"
+            "    },\n"
+            "  ],\n"
+            "  {\n"
+            "    'Name Space' : [\n"
+            "      {\n"
+            "        'name' : 'Last name',\n"
+            "        'value' : 'Turing',\n"
+            "        'accessControlProfiles' : [0, 1, ],\n"
+            "      },\n"
+            "    ],\n"
+            "  },\n"
+            "  true,\n"
+            "]",
+            cborPretty);
+
+    optional<vector<uint8_t>> credentialPubKey = support::certificateChainGetTopMostKey(
+            attData.attestationCertificate[0].encodedCertificate);
+    ASSERT_TRUE(credentialPubKey);
+    EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
+                                                 {},  // Additional data
+                                                 credentialPubKey.value()));
+}
+
+TEST_P(IdentityCredentialTests, verifyManyProfilesAndEntriesPass) {
+    Status result;
+
+    HardwareInformation hwInfo;
+    ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
+
+    sp<IWritableIdentityCredential> writableCredential;
+    ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+    string challenge = "NotSoRandomChallenge";
+    test_utils::AttestationData attData(writableCredential, challenge, {});
+    EXPECT_TRUE(attData.result.isOk())
+            << attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl;
+
+    optional<vector<uint8_t>> readerCertificate1 = test_utils::GenerateReaderCertificate("123456");
+    ASSERT_TRUE(readerCertificate1);
+
+    optional<vector<uint8_t>> readerCertificate2 = test_utils::GenerateReaderCertificate("1256");
+    ASSERT_TRUE(readerCertificate2);
+
+    const vector<test_utils::TestProfile> testProfiles = {
+            {1, readerCertificate1.value(), true, 1},
+            {2, readerCertificate2.value(), true, 2},
+    };
+    const vector<int32_t> entryCounts = {1u, 3u, 1u, 1u, 2u};
+    size_t expectedPoPSize =
+            525021 + readerCertificate1.value().size() + readerCertificate2.value().size();
+    // OK to fail, not available in v1 HAL
+    writableCredential->setExpectedProofOfProvisioningSize(expectedPoPSize);
+    result = writableCredential->startPersonalization(testProfiles.size(), entryCounts);
+    ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+                               << endl;
+
+    optional<vector<SecureAccessControlProfile>> secureProfiles =
+            test_utils::AddAccessControlProfiles(writableCredential, testProfiles);
+    ASSERT_TRUE(secureProfiles);
+
+    vector<uint8_t> portraitImage1;
+    test_utils::SetImageData(portraitImage1);
+
+    vector<uint8_t> portraitImage2;
+    test_utils::SetImageData(portraitImage2);
+
+    const vector<test_utils::TestEntryData> testEntries1 = {
+            {"Name Space 1", "Last name", string("Turing"), vector<int32_t>{1, 2}},
+            {"Name Space2", "Home address", string("Maida Vale, London, England"),
+             vector<int32_t>{1}},
+            {"Name Space2", "Work address", string("Maida Vale2, London, England"),
+             vector<int32_t>{2}},
+            {"Name Space2", "Trailer address", string("Maida, London, England"),
+             vector<int32_t>{1}},
+            {"Image", "Portrait image", portraitImage1, vector<int32_t>{1}},
+            {"Image2", "Work image", portraitImage2, vector<int32_t>{1, 2}},
+            {"Name Space3", "xyzw", string("random stuff"), vector<int32_t>{1, 2}},
+            {"Name Space3", "Something", string("Some string"), vector<int32_t>{2}},
+    };
+
+    map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
+    for (const auto& entry : testEntries1) {
+        EXPECT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize,
+                                         encryptedBlobs, true));
+    }
+
+    vector<uint8_t> credentialData;
+    vector<uint8_t> proofOfProvisioningSignature;
+    result =
+            writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
+
+    EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+                               << endl;
+
+    optional<vector<uint8_t>> proofOfProvisioning =
+            support::coseSignGetPayload(proofOfProvisioningSignature);
+    ASSERT_TRUE(proofOfProvisioning);
+    string cborPretty = support::cborPrettyPrint(proofOfProvisioning.value(),
+                                                 32,  //
+                                                 {"readerCertificate"});
+    EXPECT_EQ(
+            "[\n"
+            "  'ProofOfProvisioning',\n"
+            "  'org.iso.18013-5.2019.mdl',\n"
+            "  [\n"
+            "    {\n"
+            "      'id' : 1,\n"
+            "      'readerCertificate' : <not printed>,\n"
+            "      'userAuthenticationRequired' : true,\n"
+            "      'timeoutMillis' : 1,\n"
+            "    },\n"
+            "    {\n"
+            "      'id' : 2,\n"
+            "      'readerCertificate' : <not printed>,\n"
+            "      'userAuthenticationRequired' : true,\n"
+            "      'timeoutMillis' : 2,\n"
+            "    },\n"
+            "  ],\n"
+            "  {\n"
+            "    'Name Space 1' : [\n"
+            "      {\n"
+            "        'name' : 'Last name',\n"
+            "        'value' : 'Turing',\n"
+            "        'accessControlProfiles' : [1, 2, ],\n"
+            "      },\n"
+            "    ],\n"
+            "    'Name Space2' : [\n"
+            "      {\n"
+            "        'name' : 'Home address',\n"
+            "        'value' : 'Maida Vale, London, England',\n"
+            "        'accessControlProfiles' : [1, ],\n"
+            "      },\n"
+            "      {\n"
+            "        'name' : 'Work address',\n"
+            "        'value' : 'Maida Vale2, London, England',\n"
+            "        'accessControlProfiles' : [2, ],\n"
+            "      },\n"
+            "      {\n"
+            "        'name' : 'Trailer address',\n"
+            "        'value' : 'Maida, London, England',\n"
+            "        'accessControlProfiles' : [1, ],\n"
+            "      },\n"
+            "    ],\n"
+            "    'Image' : [\n"
+            "      {\n"
+            "        'name' : 'Portrait image',\n"
+            "        'value' : <bstr size=262134 sha1=941e372f654d86c32d88fae9e41b706afbfd02bb>,\n"
+            "        'accessControlProfiles' : [1, ],\n"
+            "      },\n"
+            "    ],\n"
+            "    'Image2' : [\n"
+            "      {\n"
+            "        'name' : 'Work image',\n"
+            "        'value' : <bstr size=262134 sha1=941e372f654d86c32d88fae9e41b706afbfd02bb>,\n"
+            "        'accessControlProfiles' : [1, 2, ],\n"
+            "      },\n"
+            "    ],\n"
+            "    'Name Space3' : [\n"
+            "      {\n"
+            "        'name' : 'xyzw',\n"
+            "        'value' : 'random stuff',\n"
+            "        'accessControlProfiles' : [1, 2, ],\n"
+            "      },\n"
+            "      {\n"
+            "        'name' : 'Something',\n"
+            "        'value' : 'Some string',\n"
+            "        'accessControlProfiles' : [2, ],\n"
+            "      },\n"
+            "    ],\n"
+            "  },\n"
+            "  true,\n"
+            "]",
+            cborPretty);
+
+    optional<vector<uint8_t>> credentialPubKey = support::certificateChainGetTopMostKey(
+            attData.attestationCertificate[0].encodedCertificate);
+    ASSERT_TRUE(credentialPubKey);
+    EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
+                                                 {},  // Additional data
+                                                 credentialPubKey.value()));
+}
+
+TEST_P(IdentityCredentialTests, verifyEmptyNameSpaceMixedWithNonEmptyWorks) {
+    Status result;
+
+    HardwareInformation hwInfo;
+    ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
+
+    sp<IWritableIdentityCredential> writableCredential;
+    ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+    string challenge = "NotSoRandomChallenge";
+    test_utils::AttestationData attData(writableCredential, challenge, {});
+    ASSERT_TRUE(attData.result.isOk())
+            << attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl;
+
+    optional<vector<uint8_t>> readerCertificate1 = test_utils::GenerateReaderCertificate("123456");
+    ASSERT_TRUE(readerCertificate1);
+
+    optional<vector<uint8_t>> readerCertificate2 =
+            test_utils::GenerateReaderCertificate("123456987987987987987987");
+    ASSERT_TRUE(readerCertificate2);
+
+    const vector<int32_t> entryCounts = {2u, 2u};
+    size_t expectedPoPSize =
+            377 + readerCertificate1.value().size() + readerCertificate2.value().size();
+    ;
+    // OK to fail, not available in v1 HAL
+    writableCredential->setExpectedProofOfProvisioningSize(expectedPoPSize);
+    writableCredential->startPersonalization(3, entryCounts);
+    ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+                               << endl;
+
+    const vector<test_utils::TestProfile> testProfiles = {{0, readerCertificate1.value(), false, 0},
+                                                          {1, readerCertificate2.value(), true, 1},
+                                                          {2, {}, false, 0}};
+
+    optional<vector<SecureAccessControlProfile>> secureProfiles =
+            test_utils::AddAccessControlProfiles(writableCredential, testProfiles);
+    ASSERT_TRUE(secureProfiles);
+
+    const vector<test_utils::TestEntryData> testEntries1 = {
+            // test empty name space
+            {"", "t name", string("Turing"), vector<int32_t>{2}},
+            {"", "Birth", string("19120623"), vector<int32_t>{2}},
+            {"Name Space", "Last name", string("Turing"), vector<int32_t>{0, 1}},
+            {"Name Space", "Birth date", string("19120623"), vector<int32_t>{0, 1}},
+    };
+
+    map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
+    for (const auto& entry : testEntries1) {
+        EXPECT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize,
+                                         encryptedBlobs, true));
+    }
+
+    vector<uint8_t> credentialData;
+    vector<uint8_t> proofOfProvisioningSignature;
+    result =
+            writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
+
+    EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+                               << endl;
+}
+
+TEST_P(IdentityCredentialTests, verifyInterleavingEntryNameSpaceOrderingFails) {
+    Status result;
+
+    HardwareInformation hwInfo;
+    ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
+
+    sp<IWritableIdentityCredential> writableCredential;
+    ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+    string challenge = "NotSoRandomChallenge";
+    test_utils::AttestationData attData(writableCredential, challenge, {});
+    ASSERT_TRUE(attData.result.isOk())
+            << attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl;
+
+    // Enter mismatched entry and profile numbers.
+    // Technically the 2nd name space of "Name Space" occurs intermittently, 2
+    // before "Image" and 2 after image, which is not correct.  All of same name
+    // space should occur together.  Let's see if this fails.
+    const vector<int32_t> entryCounts = {2u, 1u, 2u};
+    writableCredential->startPersonalization(3, entryCounts);
+    ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+                               << endl;
+
+    optional<vector<uint8_t>> readerCertificate1 = test_utils::GenerateReaderCertificate("123456");
+    ASSERT_TRUE(readerCertificate1);
+
+    optional<vector<uint8_t>> readerCertificate2 =
+            test_utils::GenerateReaderCertificate("123456987987987987987987");
+    ASSERT_TRUE(readerCertificate2);
+
+    const vector<test_utils::TestProfile> testProfiles = {{0, readerCertificate1.value(), false, 0},
+                                                          {1, readerCertificate2.value(), true, 1},
+                                                          {2, {}, false, 0}};
+
+    optional<vector<SecureAccessControlProfile>> secureProfiles =
+            test_utils::AddAccessControlProfiles(writableCredential, testProfiles);
+    ASSERT_TRUE(secureProfiles);
+
+    const vector<test_utils::TestEntryData> testEntries1 = {
+            // test empty name space
+            {"Name Space", "Last name", string("Turing"), vector<int32_t>{0, 1}},
+            {"Name Space", "Birth date", string("19120623"), vector<int32_t>{0, 1}},
+    };
+
+    map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
+    for (const auto& entry : testEntries1) {
+        EXPECT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize,
+                                         encryptedBlobs, true));
+    }
+    const test_utils::TestEntryData testEntry2 = {"Image", "Portrait image", string("asdfs"),
+                                                  vector<int32_t>{0, 1}};
+
+    EXPECT_TRUE(test_utils::AddEntry(writableCredential, testEntry2, hwInfo.dataChunkSize,
+                                     encryptedBlobs, true));
+
+    // We expect this to fail because the namespace is out of order, all "Name Space"
+    // should have been called together
+    const vector<test_utils::TestEntryData> testEntries3 = {
+            {"Name Space", "First name", string("Alan"), vector<int32_t>{0, 1}},
+            {"Name Space", "Home address", string("Maida Vale, London, England"),
+             vector<int32_t>{0}},
+    };
+
+    for (const auto& entry : testEntries3) {
+        EXPECT_FALSE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize,
+                                          encryptedBlobs, false));
+    }
+
+    vector<uint8_t> credentialData;
+    vector<uint8_t> proofOfProvisioningSignature;
+    result =
+            writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
+
+    // should fail because test_utils::AddEntry should have failed earlier.
+    EXPECT_FALSE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+                                << endl;
+    EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
+    EXPECT_EQ(IIdentityCredentialStore::STATUS_INVALID_DATA, result.serviceSpecificErrorCode());
+}
+
+TEST_P(IdentityCredentialTests, verifyAccessControlProfileIdOutOfRange) {
+    sp<IWritableIdentityCredential> writableCredential;
+    ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+    const vector<int32_t> entryCounts = {1};
+    Status result = writableCredential->startPersonalization(1, entryCounts);
+    ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+                               << endl;
+
+    SecureAccessControlProfile profile;
+
+    // This should fail because the id is >= 32
+    result = writableCredential->addAccessControlProfile(32,     // id
+                                                         {},     // readerCertificate
+                                                         false,  // userAuthenticationRequired
+                                                         0,      // timeoutMillis
+                                                         42,     // secureUserId
+                                                         &profile);
+    ASSERT_FALSE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage();
+    ASSERT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
+    ASSERT_EQ(IIdentityCredentialStore::STATUS_INVALID_DATA, result.serviceSpecificErrorCode());
+
+    // This should fail because the id is < 0
+    result = writableCredential->addAccessControlProfile(-1,     // id
+                                                         {},     // readerCertificate
+                                                         false,  // userAuthenticationRequired
+                                                         0,      // timeoutMillis
+                                                         42,     // secureUserId
+                                                         &profile);
+    ASSERT_FALSE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage();
+    ASSERT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
+    ASSERT_EQ(IIdentityCredentialStore::STATUS_INVALID_DATA, result.serviceSpecificErrorCode());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        Identity, IdentityCredentialTests,
+        testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
+        android::PrintInstanceNameToString);
+
+}  // namespace android::hardware::identity
diff --git a/identity/aidl/vts/VtsIdentityTestUtils.cpp b/identity/aidl/vts/VtsIdentityTestUtils.cpp
new file mode 100644
index 0000000..48e47dc
--- /dev/null
+++ b/identity/aidl/vts/VtsIdentityTestUtils.cpp
@@ -0,0 +1,203 @@
+/*
+ * Copyright 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.
+ */
+
+#include "VtsIdentityTestUtils.h"
+
+#include <aidl/Gtest.h>
+#include <map>
+
+namespace android::hardware::identity::test_utils {
+
+using std::endl;
+using std::map;
+using std::optional;
+using std::string;
+using std::vector;
+
+using ::android::sp;
+using ::android::String16;
+using ::android::binder::Status;
+
+bool SetupWritableCredential(sp<IWritableIdentityCredential>& writableCredential,
+                             sp<IIdentityCredentialStore>& credentialStore) {
+    if (credentialStore == nullptr) {
+        return false;
+    }
+
+    string docType = "org.iso.18013-5.2019.mdl";
+    bool testCredential = true;
+    Status result = credentialStore->createCredential(docType, testCredential, &writableCredential);
+
+    if (result.isOk() && writableCredential != nullptr) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
+optional<vector<uint8_t>> GenerateReaderCertificate(string serialDecimal) {
+    vector<uint8_t> privKey;
+    return GenerateReaderCertificate(serialDecimal, privKey);
+}
+
+optional<vector<uint8_t>> GenerateReaderCertificate(string serialDecimal,
+                                                    vector<uint8_t>& readerPrivateKey) {
+    optional<vector<uint8_t>> readerKeyPKCS8 = support::createEcKeyPair();
+    if (!readerKeyPKCS8) {
+        return {};
+    }
+
+    optional<vector<uint8_t>> readerPublicKey =
+            support::ecKeyPairGetPublicKey(readerKeyPKCS8.value());
+    optional<vector<uint8_t>> readerKey = support::ecKeyPairGetPrivateKey(readerKeyPKCS8.value());
+    if (!readerPublicKey || !readerKey) {
+        return {};
+    }
+
+    readerPrivateKey = readerKey.value();
+
+    string issuer = "Android Open Source Project";
+    string subject = "Android IdentityCredential VTS Test";
+    time_t validityNotBefore = time(nullptr);
+    time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
+
+    return support::ecPublicKeyGenerateCertificate(readerPublicKey.value(), readerKey.value(),
+                                                   serialDecimal, issuer, subject,
+                                                   validityNotBefore, validityNotAfter);
+}
+
+optional<vector<SecureAccessControlProfile>> AddAccessControlProfiles(
+        sp<IWritableIdentityCredential>& writableCredential,
+        const vector<TestProfile>& testProfiles) {
+    Status result;
+
+    vector<SecureAccessControlProfile> secureProfiles;
+
+    for (const auto& testProfile : testProfiles) {
+        SecureAccessControlProfile profile;
+        Certificate cert;
+        cert.encodedCertificate = testProfile.readerCertificate;
+        result = writableCredential->addAccessControlProfile(
+                testProfile.id, cert, testProfile.userAuthenticationRequired,
+                testProfile.timeoutMillis, 0, &profile);
+
+        // Don't use assert so all errors can be outputed.  Then return
+        // instead of exit even on errors so caller can decide.
+        EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+                                   << "test profile id = " << testProfile.id << endl;
+        EXPECT_EQ(testProfile.id, profile.id);
+        EXPECT_EQ(testProfile.readerCertificate, profile.readerCertificate.encodedCertificate);
+        EXPECT_EQ(testProfile.userAuthenticationRequired, profile.userAuthenticationRequired);
+        EXPECT_EQ(testProfile.timeoutMillis, profile.timeoutMillis);
+        EXPECT_EQ(support::kAesGcmTagSize + support::kAesGcmIvSize, profile.mac.size());
+
+        if (!result.isOk() || testProfile.id != profile.id ||
+            testProfile.readerCertificate != profile.readerCertificate.encodedCertificate ||
+            testProfile.userAuthenticationRequired != profile.userAuthenticationRequired ||
+            testProfile.timeoutMillis != profile.timeoutMillis ||
+            support::kAesGcmTagSize + support::kAesGcmIvSize != profile.mac.size()) {
+            return {};
+        }
+
+        secureProfiles.push_back(profile);
+    }
+
+    return secureProfiles;
+}
+
+// Most test expects this function to pass. So we will print out additional
+// value if failed so more debug data can be provided.
+bool AddEntry(sp<IWritableIdentityCredential>& writableCredential, const TestEntryData& entry,
+              int dataChunkSize, map<const TestEntryData*, vector<vector<uint8_t>>>& encryptedBlobs,
+              bool expectSuccess) {
+    Status result;
+    vector<vector<uint8_t>> chunks = support::chunkVector(entry.valueCbor, dataChunkSize);
+
+    result = writableCredential->beginAddEntry(entry.profileIds, entry.nameSpace, entry.name,
+                                               entry.valueCbor.size());
+
+    if (expectSuccess) {
+        EXPECT_TRUE(result.isOk())
+                << result.exceptionCode() << "; " << result.exceptionMessage() << endl
+                << "entry name = " << entry.name << ", name space=" << entry.nameSpace << endl;
+    }
+
+    if (!result.isOk()) {
+        return false;
+    }
+
+    vector<vector<uint8_t>> encryptedChunks;
+    for (const auto& chunk : chunks) {
+        vector<uint8_t> encryptedContent;
+        result = writableCredential->addEntryValue(chunk, &encryptedContent);
+        if (expectSuccess) {
+            EXPECT_TRUE(result.isOk())
+                    << result.exceptionCode() << "; " << result.exceptionMessage() << endl
+                    << "entry name = " << entry.name << ", name space = " << entry.nameSpace
+                    << endl;
+
+            EXPECT_GT(encryptedContent.size(), 0u) << "entry name = " << entry.name
+                                                   << ", name space = " << entry.nameSpace << endl;
+        }
+
+        if (!result.isOk() || encryptedContent.size() <= 0u) {
+            return false;
+        }
+
+        encryptedChunks.push_back(encryptedContent);
+    }
+
+    encryptedBlobs[&entry] = encryptedChunks;
+    return true;
+}
+
+bool ValidateAttestationCertificate(vector<Certificate>& inputCertificates) {
+    return (inputCertificates.size() >= 2);
+    // TODO: add parsing of the certificate and make sure it is genuine.
+}
+
+void SetImageData(vector<uint8_t>& image) {
+    image.resize(256 * 1024 - 10);
+    for (size_t n = 0; n < image.size(); n++) {
+        image[n] = (uint8_t)n;
+    }
+}
+
+vector<RequestNamespace> buildRequestNamespaces(const vector<TestEntryData> entries) {
+    vector<RequestNamespace> ret;
+    RequestNamespace curNs;
+    for (const TestEntryData& testEntry : entries) {
+        if (testEntry.nameSpace != curNs.namespaceName) {
+            if (curNs.namespaceName.size() > 0) {
+                ret.push_back(curNs);
+            }
+            curNs.namespaceName = testEntry.nameSpace;
+            curNs.items.clear();
+        }
+
+        RequestDataItem item;
+        item.name = testEntry.name;
+        item.size = testEntry.valueCbor.size();
+        item.accessControlProfileIds = testEntry.profileIds;
+        curNs.items.push_back(item);
+    }
+    if (curNs.namespaceName.size() > 0) {
+        ret.push_back(curNs);
+    }
+    return ret;
+}
+
+}  // namespace android::hardware::identity::test_utils
diff --git a/identity/aidl/vts/VtsIdentityTestUtils.h b/identity/aidl/vts/VtsIdentityTestUtils.h
new file mode 100644
index 0000000..9e1f352
--- /dev/null
+++ b/identity/aidl/vts/VtsIdentityTestUtils.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef VTS_IDENTITY_TEST_UTILS_H
+#define VTS_IDENTITY_TEST_UTILS_H
+
+#include <android/hardware/identity/IIdentityCredentialStore.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+namespace android::hardware::identity::test_utils {
+
+using ::std::map;
+using ::std::optional;
+using ::std::string;
+using ::std::vector;
+
+using ::android::sp;
+using ::android::binder::Status;
+
+struct AttestationData {
+    AttestationData(sp<IWritableIdentityCredential>& writableCredential, string challenge,
+                    vector<uint8_t> applicationId)
+        : attestationApplicationId(applicationId) {
+        // ASSERT_NE(writableCredential, nullptr);
+
+        if (!challenge.empty()) {
+            attestationChallenge.assign(challenge.begin(), challenge.end());
+        }
+
+        result = writableCredential->getAttestationCertificate(
+                attestationApplicationId, attestationChallenge, &attestationCertificate);
+    }
+
+    AttestationData() {}
+
+    vector<uint8_t> attestationChallenge;
+    vector<uint8_t> attestationApplicationId;
+    vector<Certificate> attestationCertificate;
+    Status result;
+};
+
+struct TestEntryData {
+    TestEntryData(string nameSpace, string name, vector<int32_t> profileIds)
+        : nameSpace(nameSpace), name(name), profileIds(profileIds) {}
+
+    TestEntryData(string nameSpace, string name, const string& value, vector<int32_t> profileIds)
+        : TestEntryData(nameSpace, name, profileIds) {
+        valueCbor = cppbor::Tstr(((const char*)value.data())).encode();
+    }
+    TestEntryData(string nameSpace, string name, const vector<uint8_t>& value,
+                  vector<int32_t> profileIds)
+        : TestEntryData(nameSpace, name, profileIds) {
+        valueCbor = cppbor::Bstr(value).encode();
+    }
+    TestEntryData(string nameSpace, string name, bool value, vector<int32_t> profileIds)
+        : TestEntryData(nameSpace, name, profileIds) {
+        valueCbor = cppbor::Bool(value).encode();
+    }
+    TestEntryData(string nameSpace, string name, int64_t value, vector<int32_t> profileIds)
+        : TestEntryData(nameSpace, name, profileIds) {
+        if (value >= 0) {
+            valueCbor = cppbor::Uint(value).encode();
+        } else {
+            valueCbor = cppbor::Nint(-value).encode();
+        }
+    }
+
+    string nameSpace;
+    string name;
+    vector<uint8_t> valueCbor;
+    vector<int32_t> profileIds;
+};
+
+struct TestProfile {
+    uint16_t id;
+    vector<uint8_t> readerCertificate;
+    bool userAuthenticationRequired;
+    uint64_t timeoutMillis;
+};
+
+bool SetupWritableCredential(sp<IWritableIdentityCredential>& writableCredential,
+                             sp<IIdentityCredentialStore>& credentialStore);
+
+optional<vector<uint8_t>> GenerateReaderCertificate(string serialDecimal);
+
+optional<vector<uint8_t>> GenerateReaderCertificate(string serialDecimal,
+                                                    vector<uint8_t>& readerPrivateKey);
+
+optional<vector<SecureAccessControlProfile>> AddAccessControlProfiles(
+        sp<IWritableIdentityCredential>& writableCredential,
+        const vector<TestProfile>& testProfiles);
+
+bool AddEntry(sp<IWritableIdentityCredential>& writableCredential, const TestEntryData& entry,
+              int dataChunkSize, map<const TestEntryData*, vector<vector<uint8_t>>>& encryptedBlobs,
+              bool expectSuccess);
+
+bool ValidateAttestationCertificate(vector<Certificate>& inputCertificates);
+
+void SetImageData(vector<uint8_t>& image);
+
+vector<RequestNamespace> buildRequestNamespaces(const vector<TestEntryData> entries);
+
+}  // namespace android::hardware::identity::test_utils
+
+#endif  // VTS_IDENTITY_TEST_UTILS_H
diff --git a/identity/support/src/IdentityCredentialSupport.cpp b/identity/support/src/IdentityCredentialSupport.cpp
index bf6a5c3..dc49ddc 100644
--- a/identity/support/src/IdentityCredentialSupport.cpp
+++ b/identity/support/src/IdentityCredentialSupport.cpp
@@ -958,12 +958,17 @@
 optional<vector<uint8_t>> createEcKeyPair() {
     auto ec_key = EC_KEY_Ptr(EC_KEY_new());
     auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new());
-    auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
     if (ec_key.get() == nullptr || pkey.get() == nullptr) {
         LOG(ERROR) << "Memory allocation failed";
         return {};
     }
 
+    auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+    if (group.get() == nullptr) {
+        LOG(ERROR) << "Error creating EC group by curve name";
+        return {};
+    }
+
     if (EC_KEY_set_group(ec_key.get(), group.get()) != 1 ||
         EC_KEY_generate_key(ec_key.get()) != 1 || EC_KEY_check_key(ec_key.get()) < 0) {
         LOG(ERROR) << "Error generating key";
diff --git a/keymaster/4.0/support/include/keymasterV4_0/keymaster_tags.h b/keymaster/4.0/support/include/keymasterV4_0/keymaster_tags.h
index bc7f311..8d6e74a 100644
--- a/keymaster/4.0/support/include/keymasterV4_0/keymaster_tags.h
+++ b/keymaster/4.0/support/include/keymasterV4_0/keymaster_tags.h
@@ -112,6 +112,11 @@
 DECLARE_TYPED_TAG(ASSOCIATED_DATA);
 DECLARE_TYPED_TAG(ATTESTATION_APPLICATION_ID);
 DECLARE_TYPED_TAG(ATTESTATION_CHALLENGE);
+DECLARE_TYPED_TAG(ATTESTATION_ID_BRAND);
+DECLARE_TYPED_TAG(ATTESTATION_ID_DEVICE);
+DECLARE_TYPED_TAG(ATTESTATION_ID_PRODUCT);
+DECLARE_TYPED_TAG(ATTESTATION_ID_MANUFACTURER);
+DECLARE_TYPED_TAG(ATTESTATION_ID_MODEL);
 DECLARE_TYPED_TAG(AUTH_TIMEOUT);
 DECLARE_TYPED_TAG(BLOB_USAGE_REQUIREMENTS);
 DECLARE_TYPED_TAG(BLOCK_MODE);
@@ -155,21 +160,22 @@
 template <typename... Elems>
 struct MetaList {};
 
-using all_tags_t =
-    MetaList<TAG_INVALID_t, TAG_KEY_SIZE_t, TAG_MAC_LENGTH_t, TAG_CALLER_NONCE_t,
-             TAG_MIN_MAC_LENGTH_t, TAG_RSA_PUBLIC_EXPONENT_t, TAG_INCLUDE_UNIQUE_ID_t,
-             TAG_ACTIVE_DATETIME_t, TAG_ORIGINATION_EXPIRE_DATETIME_t, TAG_USAGE_EXPIRE_DATETIME_t,
-             TAG_MIN_SECONDS_BETWEEN_OPS_t, TAG_MAX_USES_PER_BOOT_t, TAG_USER_ID_t,
-             TAG_USER_SECURE_ID_t, TAG_NO_AUTH_REQUIRED_t, TAG_AUTH_TIMEOUT_t,
-             TAG_ALLOW_WHILE_ON_BODY_t, TAG_UNLOCKED_DEVICE_REQUIRED_t, TAG_APPLICATION_ID_t,
-             TAG_APPLICATION_DATA_t, TAG_CREATION_DATETIME_t, TAG_ROLLBACK_RESISTANCE_t,
-             TAG_HARDWARE_TYPE_t, TAG_ROOT_OF_TRUST_t, TAG_ASSOCIATED_DATA_t, TAG_NONCE_t,
-             TAG_BOOTLOADER_ONLY_t, TAG_OS_VERSION_t, TAG_OS_PATCHLEVEL_t, TAG_UNIQUE_ID_t,
-             TAG_ATTESTATION_CHALLENGE_t, TAG_ATTESTATION_APPLICATION_ID_t,
-             TAG_RESET_SINCE_ID_ROTATION_t, TAG_PURPOSE_t, TAG_ALGORITHM_t, TAG_BLOCK_MODE_t,
-             TAG_DIGEST_t, TAG_PADDING_t, TAG_BLOB_USAGE_REQUIREMENTS_t, TAG_ORIGIN_t,
-             TAG_USER_AUTH_TYPE_t, TAG_EC_CURVE_t, TAG_BOOT_PATCHLEVEL_t, TAG_VENDOR_PATCHLEVEL_t,
-             TAG_TRUSTED_CONFIRMATION_REQUIRED_t, TAG_TRUSTED_USER_PRESENCE_REQUIRED_t>;
+using all_tags_t = MetaList<
+        TAG_INVALID_t, TAG_KEY_SIZE_t, TAG_MAC_LENGTH_t, TAG_CALLER_NONCE_t, TAG_MIN_MAC_LENGTH_t,
+        TAG_RSA_PUBLIC_EXPONENT_t, TAG_INCLUDE_UNIQUE_ID_t, TAG_ACTIVE_DATETIME_t,
+        TAG_ORIGINATION_EXPIRE_DATETIME_t, TAG_USAGE_EXPIRE_DATETIME_t,
+        TAG_MIN_SECONDS_BETWEEN_OPS_t, TAG_MAX_USES_PER_BOOT_t, TAG_USER_ID_t, TAG_USER_SECURE_ID_t,
+        TAG_NO_AUTH_REQUIRED_t, TAG_AUTH_TIMEOUT_t, TAG_ALLOW_WHILE_ON_BODY_t,
+        TAG_UNLOCKED_DEVICE_REQUIRED_t, TAG_APPLICATION_ID_t, TAG_APPLICATION_DATA_t,
+        TAG_CREATION_DATETIME_t, TAG_ROLLBACK_RESISTANCE_t, TAG_HARDWARE_TYPE_t,
+        TAG_ROOT_OF_TRUST_t, TAG_ASSOCIATED_DATA_t, TAG_NONCE_t, TAG_BOOTLOADER_ONLY_t,
+        TAG_OS_VERSION_t, TAG_OS_PATCHLEVEL_t, TAG_UNIQUE_ID_t, TAG_ATTESTATION_CHALLENGE_t,
+        TAG_ATTESTATION_APPLICATION_ID_t, TAG_ATTESTATION_ID_BRAND_t, TAG_ATTESTATION_ID_DEVICE_t,
+        TAG_ATTESTATION_ID_PRODUCT_t, TAG_ATTESTATION_ID_MANUFACTURER_t, TAG_ATTESTATION_ID_MODEL_t,
+        TAG_RESET_SINCE_ID_ROTATION_t, TAG_PURPOSE_t, TAG_ALGORITHM_t, TAG_BLOCK_MODE_t,
+        TAG_DIGEST_t, TAG_PADDING_t, TAG_BLOB_USAGE_REQUIREMENTS_t, TAG_ORIGIN_t,
+        TAG_USER_AUTH_TYPE_t, TAG_EC_CURVE_t, TAG_BOOT_PATCHLEVEL_t, TAG_VENDOR_PATCHLEVEL_t,
+        TAG_TRUSTED_CONFIRMATION_REQUIRED_t, TAG_TRUSTED_USER_PRESENCE_REQUIRED_t>;
 
 template <typename TypedTagType>
 struct TypedTag2ValueType;
diff --git a/media/omx/1.0/vts/functional/common/Android.bp b/media/omx/1.0/vts/functional/common/Android.bp
index 2c024a0..7e7463a 100644
--- a/media/omx/1.0/vts/functional/common/Android.bp
+++ b/media/omx/1.0/vts/functional/common/Android.bp
@@ -68,11 +68,11 @@
         "android.hidl.allocator@1.0",
         "android.hidl.memory@1.0",
         "libhidlmemory",
-        "libnativehelper",
     ],
 
     // TODO(b/64437680): Assume these libs are always available on the device.
     shared_libs: [
+        "libnativehelper",
         "libstagefright_foundation",
     ],
 }
diff --git a/neuralnetworks/1.0/vts/functional/BasicTests.cpp b/neuralnetworks/1.0/vts/functional/BasicTests.cpp
index cc44c9e..bda43b1 100644
--- a/neuralnetworks/1.0/vts/functional/BasicTests.cpp
+++ b/neuralnetworks/1.0/vts/functional/BasicTests.cpp
@@ -18,8 +18,12 @@
 
 #include "VtsHalNeuralnetworks.h"
 
+#include "1.0/Callbacks.h"
+
 namespace android::hardware::neuralnetworks::V1_0::vts::functional {
 
+using implementation::PreparedModelCallback;
+
 // create device test
 TEST_P(NeuralnetworksHidlTest, CreateDevice) {}
 
@@ -43,4 +47,136 @@
     EXPECT_TRUE(ret.isOk());
 }
 
+// detect cycle
+TEST_P(NeuralnetworksHidlTest, CycleTest) {
+    // opnd0 = TENSOR_FLOAT32            // model input
+    // opnd1 = TENSOR_FLOAT32            // model input
+    // opnd2 = INT32                     // model input
+    // opnd3 = ADD(opnd0, opnd4, opnd2)
+    // opnd4 = ADD(opnd1, opnd3, opnd2)
+    // opnd5 = ADD(opnd4, opnd0, opnd2)  // model output
+    //
+    //            +-----+
+    //            |     |
+    //            v     |
+    // 3 = ADD(0, 4, 2) |
+    // |                |
+    // +----------+     |
+    //            |     |
+    //            v     |
+    // 4 = ADD(1, 3, 2) |
+    // |                |
+    // +----------------+
+    // |
+    // |
+    // +-------+
+    //         |
+    //         v
+    // 5 = ADD(4, 0, 2)
+
+    const std::vector<Operand> operands = {
+            {
+                    // operands[0]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .numberOfConsumers = 2,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::MODEL_INPUT,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[1]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .numberOfConsumers = 1,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::MODEL_INPUT,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[2]
+                    .type = OperandType::INT32,
+                    .dimensions = {},
+                    .numberOfConsumers = 3,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::MODEL_INPUT,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[3]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .numberOfConsumers = 1,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[4]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .numberOfConsumers = 2,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[5]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .numberOfConsumers = 0,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::MODEL_OUTPUT,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+    };
+
+    const std::vector<Operation> operations = {
+            {.type = OperationType::ADD, .inputs = {0, 4, 2}, .outputs = {3}},
+            {.type = OperationType::ADD, .inputs = {1, 3, 2}, .outputs = {4}},
+            {.type = OperationType::ADD, .inputs = {4, 0, 2}, .outputs = {5}},
+    };
+
+    const Model model = {
+            .operands = operands,
+            .operations = operations,
+            .inputIndexes = {0, 1, 2},
+            .outputIndexes = {5},
+            .operandValues = {},
+            .pools = {},
+    };
+
+    // ensure that getSupportedOperations() checks model validity
+    ErrorStatus supportedOpsErrorStatus = ErrorStatus::GENERAL_FAILURE;
+    Return<void> supportedOpsReturn = kDevice->getSupportedOperations(
+            model, [&model, &supportedOpsErrorStatus](ErrorStatus status,
+                                                      const hidl_vec<bool>& supported) {
+                supportedOpsErrorStatus = status;
+                if (status == ErrorStatus::NONE) {
+                    ASSERT_EQ(supported.size(), model.operations.size());
+                }
+            });
+    ASSERT_TRUE(supportedOpsReturn.isOk());
+    ASSERT_EQ(supportedOpsErrorStatus, ErrorStatus::INVALID_ARGUMENT);
+
+    // ensure that prepareModel() checks model validity
+    sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback;
+    Return<ErrorStatus> prepareLaunchReturn = kDevice->prepareModel(model, preparedModelCallback);
+    ASSERT_TRUE(prepareLaunchReturn.isOk());
+    //     Note that preparation can fail for reasons other than an
+    //     invalid model (invalid model should result in
+    //     INVALID_ARGUMENT) -- for example, perhaps not all
+    //     operations are supported, or perhaps the device hit some
+    //     kind of capacity limit.
+    EXPECT_NE(prepareLaunchReturn, ErrorStatus::NONE);
+    EXPECT_NE(preparedModelCallback->getStatus(), ErrorStatus::NONE);
+    EXPECT_EQ(preparedModelCallback->getPreparedModel(), nullptr);
+}
+
 }  // namespace android::hardware::neuralnetworks::V1_0::vts::functional
diff --git a/neuralnetworks/1.0/vts/functional/Utils.cpp b/neuralnetworks/1.0/vts/functional/Utils.cpp
index 3613e69..32850b0 100644
--- a/neuralnetworks/1.0/vts/functional/Utils.cpp
+++ b/neuralnetworks/1.0/vts/functional/Utils.cpp
@@ -29,7 +29,11 @@
 
 #include <gtest/gtest.h>
 #include <algorithm>
+#include <cstring>
+#include <functional>
 #include <iostream>
+#include <map>
+#include <numeric>
 #include <vector>
 
 namespace android::hardware::neuralnetworks {
@@ -172,6 +176,45 @@
     return outputBuffers;
 }
 
+uint32_t sizeOfData(V1_0::OperandType type) {
+    switch (type) {
+        case V1_0::OperandType::FLOAT32:
+        case V1_0::OperandType::INT32:
+        case V1_0::OperandType::UINT32:
+        case V1_0::OperandType::TENSOR_FLOAT32:
+        case V1_0::OperandType::TENSOR_INT32:
+            return 4;
+        case V1_0::OperandType::TENSOR_QUANT8_ASYMM:
+            return 1;
+        default:
+            CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+            return 0;
+    }
+}
+
+static bool isTensor(V1_0::OperandType type) {
+    switch (type) {
+        case V1_0::OperandType::FLOAT32:
+        case V1_0::OperandType::INT32:
+        case V1_0::OperandType::UINT32:
+            return false;
+        case V1_0::OperandType::TENSOR_FLOAT32:
+        case V1_0::OperandType::TENSOR_INT32:
+        case V1_0::OperandType::TENSOR_QUANT8_ASYMM:
+            return true;
+        default:
+            CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+            return false;
+    }
+}
+
+uint32_t sizeOfData(const V1_0::Operand& operand) {
+    const uint32_t dataSize = sizeOfData(operand.type);
+    if (isTensor(operand.type) && operand.dimensions.size() == 0) return 0;
+    return std::accumulate(operand.dimensions.begin(), operand.dimensions.end(), dataSize,
+                           std::multiplies<>{});
+}
+
 std::string gtestCompliantName(std::string name) {
     // gtest test names must only contain alphanumeric characters
     std::replace_if(
diff --git a/neuralnetworks/1.0/vts/functional/ValidateModel.cpp b/neuralnetworks/1.0/vts/functional/ValidateModel.cpp
index 79d8594..5ffbd43 100644
--- a/neuralnetworks/1.0/vts/functional/ValidateModel.cpp
+++ b/neuralnetworks/1.0/vts/functional/ValidateModel.cpp
@@ -17,9 +17,14 @@
 #define LOG_TAG "neuralnetworks_hidl_hal_test"
 
 #include "1.0/Callbacks.h"
+#include "1.0/Utils.h"
 #include "GeneratedTestHarness.h"
 #include "VtsHalNeuralnetworks.h"
 
+#include <optional>
+#include <type_traits>
+#include <utility>
+
 namespace android::hardware::neuralnetworks::V1_0::vts::functional {
 
 using implementation::PreparedModelCallback;
@@ -67,26 +72,6 @@
     validatePrepareModel(device, message, model);
 }
 
-// Delete element from hidl_vec. hidl_vec doesn't support a "remove" operation,
-// so this is efficiently accomplished by moving the element to the end and
-// resizing the hidl_vec to one less.
-template <typename Type>
-static void hidl_vec_removeAt(hidl_vec<Type>* vec, uint32_t index) {
-    if (vec) {
-        std::rotate(vec->begin() + index, vec->begin() + index + 1, vec->end());
-        vec->resize(vec->size() - 1);
-    }
-}
-
-template <typename Type>
-static uint32_t hidl_vec_push_back(hidl_vec<Type>* vec, const Type& value) {
-    // assume vec is valid
-    const uint32_t index = vec->size();
-    vec->resize(index + 1);
-    (*vec)[index] = value;
-    return index;
-}
-
 static uint32_t addOperand(Model* model) {
     return hidl_vec_push_back(&model->operands,
                               {
@@ -107,6 +92,211 @@
     return index;
 }
 
+// If we introduce a CONSTANT_COPY for an operand of size operandSize,
+// how much will this increase the size of the model?  This assumes
+// that we can (re)use all of model.operandValues for the operand
+// value.
+static size_t constantCopyExtraSize(const Model& model, size_t operandSize) {
+    const size_t operandValuesSize = model.operandValues.size();
+    return (operandValuesSize < operandSize) ? (operandSize - operandValuesSize) : 0;
+}
+
+// Highly specialized utility routine for converting an operand to
+// CONSTANT_COPY lifetime.
+//
+// Expects that:
+// - operand has a known size
+// - operand->lifetime has already been set to CONSTANT_COPY
+// - operand->location has been zeroed out
+//
+// Does the following:
+// - initializes operand->location to point to the beginning of model->operandValues
+// - resizes model->operandValues (if necessary) to be large enough for the operand
+//   value, padding it with zeroes on the end
+//
+// Potential problem:
+// By changing the operand to CONSTANT_COPY lifetime, this function is effectively initializing the
+// operand with unspecified (but deterministic) data. This means that the model may be invalidated
+// in two ways: not only is the lifetime of CONSTANT_COPY invalid, but the operand's value in the
+// graph may also be invalid (e.g., if the operand is used as an activation code and has an invalid
+// value). For now, this should be fine because it just means we're not testing what we think we're
+// testing in certain cases; but we can handwave this and assume we're probabilistically likely to
+// exercise the validation code over the span of the entire test set and operand space.
+//
+// Aborts if the specified operand type is an extension type or OEM type.
+static void becomeConstantCopy(Model* model, Operand* operand) {
+    // sizeOfData will abort if the specified type is an extension type or OEM type.
+    const size_t sizeOfOperand = sizeOfData(*operand);
+    EXPECT_NE(sizeOfOperand, size_t(0));
+    operand->location.poolIndex = 0;
+    operand->location.offset = 0;
+    operand->location.length = sizeOfOperand;
+    if (model->operandValues.size() < sizeOfOperand) {
+        model->operandValues.resize(sizeOfOperand);
+    }
+}
+
+// The sizeForBinder() functions estimate the size of the
+// representation of a value when sent to binder.  It's probably a bit
+// of an under-estimate, because we don't know the size of the
+// metadata in the binder format (e.g., representation of the size of
+// a vector); but at least it adds up "big" things like vector
+// contents.  However, it doesn't treat inter-field or end-of-struct
+// padding in a methodical way -- there's no attempt to be consistent
+// in whether or not padding in the native (C++) representation
+// contributes to the estimated size for the binder representation;
+// and there's no attempt to understand what padding (if any) is
+// needed in the binder representation.
+//
+// This assumes that non-metadata uses a fixed length encoding (e.g.,
+// a uint32_t is always encoded in sizeof(uint32_t) bytes, rather than
+// using an encoding whose length is related to the magnitude of the
+// encoded value).
+
+template <typename Type>
+static size_t sizeForBinder(const Type& val) {
+    static_assert(std::is_trivially_copyable_v<std::remove_reference_t<Type>>,
+                  "expected a trivially copyable type");
+    return sizeof(val);
+}
+
+template <typename Type>
+static size_t sizeForBinder(const hidl_vec<Type>& vec) {
+    return std::accumulate(vec.begin(), vec.end(), 0,
+                           [](size_t acc, const Type& x) { return acc + sizeForBinder(x); });
+}
+
+template <>
+size_t sizeForBinder(const Operand& operand) {
+    size_t size = 0;
+
+    size += sizeForBinder(operand.type);
+    size += sizeForBinder(operand.dimensions);
+    size += sizeForBinder(operand.numberOfConsumers);
+    size += sizeForBinder(operand.scale);
+    size += sizeForBinder(operand.zeroPoint);
+    size += sizeForBinder(operand.lifetime);
+    size += sizeForBinder(operand.location);
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const Operation& operation) {
+    size_t size = 0;
+
+    size += sizeForBinder(operation.type);
+    size += sizeForBinder(operation.inputs);
+    size += sizeForBinder(operation.outputs);
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const hidl_string& name) {
+    return name.size();
+}
+
+template <>
+size_t sizeForBinder(const hidl_memory& memory) {
+    // This is just a guess.
+
+    size_t size = 0;
+
+    if (const native_handle_t* handle = memory.handle()) {
+        size += sizeof(*handle);
+        size += sizeof(handle->data[0] * (handle->numFds + handle->numInts));
+    }
+    size += sizeForBinder(memory.name());
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const Model& model) {
+    size_t size = 0;
+
+    size += sizeForBinder(model.operands);
+    size += sizeForBinder(model.operations);
+    size += sizeForBinder(model.inputIndexes);
+    size += sizeForBinder(model.outputIndexes);
+    size += sizeForBinder(model.operandValues);
+    size += sizeForBinder(model.pools);
+
+    return size;
+}
+
+// https://developer.android.com/reference/android/os/TransactionTooLargeException.html
+//
+//     "The Binder transaction buffer has a limited fixed size,
+//     currently 1Mb, which is shared by all transactions in progress
+//     for the process."
+//
+// Will our representation fit under this limit?  There are two complications:
+// - Our representation size is just approximate (see sizeForBinder()).
+// - This object may not be the only occupant of the Binder transaction buffer.
+// So we'll be very conservative: We want the representation size to be no
+// larger than half the transaction buffer size.
+//
+// If our representation grows large enough that it still fits within
+// the transaction buffer but combined with other transactions may
+// exceed the buffer size, then we may see intermittent HAL transport
+// errors.
+static bool exceedsBinderSizeLimit(size_t representationSize) {
+    // Instead of using this fixed buffer size, we might instead be able to use
+    // ProcessState::self()->getMmapSize(). However, this has a potential
+    // problem: The binder/mmap size of the current process does not necessarily
+    // indicate the binder/mmap size of the service (i.e., the other process).
+    // The only way it would be a good indication is if both the current process
+    // and the service use the default size.
+    static const size_t kHalfBufferSize = 1024 * 1024 / 2;
+
+    return representationSize > kHalfBufferSize;
+}
+
+///////////////////////// VALIDATE EXECUTION ORDER ////////////////////////////
+
+static void mutateExecutionOrderTest(const sp<IDevice>& device, const V1_0::Model& model) {
+    for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+        const Operation& operationObj = model.operations[operation];
+        for (uint32_t input : operationObj.inputs) {
+            if (model.operands[input].lifetime == OperandLifeTime::TEMPORARY_VARIABLE ||
+                model.operands[input].lifetime == OperandLifeTime::MODEL_OUTPUT) {
+                // This operation reads an operand written by some
+                // other operation.  Move this operation to the
+                // beginning of the sequence, ensuring that it reads
+                // the operand before that operand is written, thereby
+                // violating execution order rules.
+                const std::string message = "mutateExecutionOrderTest: operation " +
+                                            std::to_string(operation) + " is a reader";
+                validate(device, message, model, [operation](Model* model) {
+                    auto& operations = model->operations;
+                    std::rotate(operations.begin(), operations.begin() + operation,
+                                operations.begin() + operation + 1);
+                });
+                break;  // only need to do this once per operation
+            }
+        }
+        for (uint32_t output : operationObj.outputs) {
+            if (model.operands[output].numberOfConsumers > 0) {
+                // This operation writes an operand read by some other
+                // operation.  Move this operation to the end of the
+                // sequence, ensuring that it writes the operand after
+                // that operand is read, thereby violating execution
+                // order rules.
+                const std::string message = "mutateExecutionOrderTest: operation " +
+                                            std::to_string(operation) + " is a writer";
+                validate(device, message, model, [operation](Model* model) {
+                    auto& operations = model->operations;
+                    std::rotate(operations.begin() + operation, operations.begin() + operation + 1,
+                                operations.end());
+                });
+                break;  // only need to do this once per operation
+            }
+        }
+    }
+}
+
 ///////////////////////// VALIDATE MODEL OPERAND TYPE /////////////////////////
 
 static const int32_t invalidOperandTypes[] = {
@@ -218,9 +408,233 @@
     }
 }
 
+///////////////////////// VALIDATE OPERAND LIFETIME /////////////////////////////////////////////
+
+static std::vector<OperandLifeTime> getInvalidLifeTimes(const Model& model, size_t modelSize,
+                                                        const Operand& operand) {
+    // TODO: Support OperandLifeTime::CONSTANT_REFERENCE as an invalid lifetime
+    // TODO: Support OperandLifeTime::NO_VALUE as an invalid lifetime
+
+    // Ways to get an invalid lifetime:
+    // - change whether a lifetime means an operand should have a writer
+    std::vector<OperandLifeTime> ret;
+    switch (operand.lifetime) {
+        case OperandLifeTime::MODEL_OUTPUT:
+        case OperandLifeTime::TEMPORARY_VARIABLE:
+            ret = {
+                    OperandLifeTime::MODEL_INPUT,
+                    OperandLifeTime::CONSTANT_COPY,
+            };
+            break;
+        case OperandLifeTime::CONSTANT_COPY:
+        case OperandLifeTime::CONSTANT_REFERENCE:
+        case OperandLifeTime::MODEL_INPUT:
+            ret = {
+                    OperandLifeTime::TEMPORARY_VARIABLE,
+                    OperandLifeTime::MODEL_OUTPUT,
+            };
+            break;
+        case OperandLifeTime::NO_VALUE:
+            // Not enough information to know whether
+            // TEMPORARY_VARIABLE or CONSTANT_COPY would be invalid --
+            // is this operand written (then CONSTANT_COPY would be
+            // invalid) or not (then TEMPORARY_VARIABLE would be
+            // invalid)?
+            break;
+        default:
+            ADD_FAILURE();
+            break;
+    }
+
+    const size_t operandSize = sizeOfData(operand);  // will be zero if shape is unknown
+    if (!operandSize ||
+        exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+        // Unknown size or too-large size
+        ret.erase(std::remove(ret.begin(), ret.end(), OperandLifeTime::CONSTANT_COPY), ret.end());
+    }
+
+    return ret;
+}
+
+static void mutateOperandLifeTimeTest(const sp<IDevice>& device, const V1_0::Model& model) {
+    const size_t modelSize = sizeForBinder(model);
+    for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+        const std::vector<OperandLifeTime> invalidLifeTimes =
+                getInvalidLifeTimes(model, modelSize, model.operands[operand]);
+        for (OperandLifeTime invalidLifeTime : invalidLifeTimes) {
+            const std::string message = "mutateOperandLifetimeTest: operand " +
+                                        std::to_string(operand) + " has lifetime " +
+                                        toString(invalidLifeTime) + " instead of lifetime " +
+                                        toString(model.operands[operand].lifetime);
+            validate(device, message, model, [operand, invalidLifeTime](Model* model) {
+                static const DataLocation kZeroDataLocation = {};
+                Operand& operandObj = model->operands[operand];
+                switch (operandObj.lifetime) {
+                    case OperandLifeTime::MODEL_INPUT: {
+                        hidl_vec_remove(&model->inputIndexes, uint32_t(operand));
+                        break;
+                    }
+                    case OperandLifeTime::MODEL_OUTPUT: {
+                        hidl_vec_remove(&model->outputIndexes, uint32_t(operand));
+                        break;
+                    }
+                    default:
+                        break;
+                }
+                operandObj.lifetime = invalidLifeTime;
+                operandObj.location = kZeroDataLocation;
+                switch (invalidLifeTime) {
+                    case OperandLifeTime::CONSTANT_COPY: {
+                        becomeConstantCopy(model, &operandObj);
+                        break;
+                    }
+                    case OperandLifeTime::MODEL_INPUT:
+                        hidl_vec_push_back(&model->inputIndexes, uint32_t(operand));
+                        break;
+                    case OperandLifeTime::MODEL_OUTPUT:
+                        hidl_vec_push_back(&model->outputIndexes, uint32_t(operand));
+                        break;
+                    default:
+                        break;
+                }
+            });
+        }
+    }
+}
+
+///////////////////////// VALIDATE OPERAND INPUT-or-OUTPUT //////////////////////////////////////
+
+static std::optional<OperandLifeTime> getInputOutputLifeTime(const Model& model, size_t modelSize,
+                                                             const Operand& operand) {
+    // Ways to get an invalid lifetime (with respect to model inputIndexes and outputIndexes):
+    // - change whether a lifetime means an operand is a model input, a model output, or neither
+    // - preserve whether or not a lifetime means an operand should have a writer
+    switch (operand.lifetime) {
+        case OperandLifeTime::CONSTANT_COPY:
+        case OperandLifeTime::CONSTANT_REFERENCE:
+            return OperandLifeTime::MODEL_INPUT;
+        case OperandLifeTime::MODEL_INPUT: {
+            const size_t operandSize = sizeOfData(operand);  // will be zero if shape is unknown
+            if (!operandSize ||
+                exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+                // Unknown size or too-large size
+                break;
+            }
+            return OperandLifeTime::CONSTANT_COPY;
+        }
+        case OperandLifeTime::MODEL_OUTPUT:
+            return OperandLifeTime::TEMPORARY_VARIABLE;
+        case OperandLifeTime::TEMPORARY_VARIABLE:
+            return OperandLifeTime::MODEL_OUTPUT;
+        case OperandLifeTime::NO_VALUE:
+            // Not enough information to know whether
+            // TEMPORARY_VARIABLE or CONSTANT_COPY would be an
+            // appropriate choice -- is this operand written (then
+            // TEMPORARY_VARIABLE would be appropriate) or not (then
+            // CONSTANT_COPY would be appropriate)?
+            break;
+        default:
+            ADD_FAILURE();
+            break;
+    }
+
+    return std::nullopt;
+}
+
+static void mutateOperandInputOutputTest(const sp<IDevice>& device, const V1_0::Model& model) {
+    const size_t modelSize = sizeForBinder(model);
+    for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+        const std::optional<OperandLifeTime> changedLifeTime =
+                getInputOutputLifeTime(model, modelSize, model.operands[operand]);
+        if (changedLifeTime) {
+            const std::string message = "mutateOperandInputOutputTest: operand " +
+                                        std::to_string(operand) + " has lifetime " +
+                                        toString(*changedLifeTime) + " instead of lifetime " +
+                                        toString(model.operands[operand].lifetime);
+            validate(device, message, model, [operand, changedLifeTime](Model* model) {
+                static const DataLocation kZeroDataLocation = {};
+                Operand& operandObj = model->operands[operand];
+                operandObj.lifetime = *changedLifeTime;
+                operandObj.location = kZeroDataLocation;
+                if (*changedLifeTime == OperandLifeTime::CONSTANT_COPY) {
+                    becomeConstantCopy(model, &operandObj);
+                }
+            });
+        }
+    }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF CONSUMERS //////////////////////////////////
+
+static std::vector<uint32_t> getInvalidNumberOfConsumers(uint32_t numberOfConsumers) {
+    if (numberOfConsumers == 0) {
+        return {1};
+    } else {
+        return {numberOfConsumers - 1, numberOfConsumers + 1};
+    }
+}
+
+static void mutateOperandNumberOfConsumersTest(const sp<IDevice>& device,
+                                               const V1_0::Model& model) {
+    for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+        const std::vector<uint32_t> invalidNumberOfConsumersVec =
+                getInvalidNumberOfConsumers(model.operands[operand].numberOfConsumers);
+        for (uint32_t invalidNumberOfConsumers : invalidNumberOfConsumersVec) {
+            const std::string message =
+                    "mutateOperandNumberOfConsumersTest: operand " + std::to_string(operand) +
+                    " numberOfConsumers = " + std::to_string(invalidNumberOfConsumers);
+            validate(device, message, model, [operand, invalidNumberOfConsumers](Model* model) {
+                model->operands[operand].numberOfConsumers = invalidNumberOfConsumers;
+            });
+        }
+    }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF WRITERS ////////////////////////////////////
+
+static void mutateOperandAddWriterTest(const sp<IDevice>& device, const V1_0::Model& model) {
+    for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+        for (size_t badOutputNum = 0; badOutputNum < model.operations[operation].outputs.size();
+             ++badOutputNum) {
+            const uint32_t outputOperandIndex = model.operations[operation].outputs[badOutputNum];
+            const std::string message = "mutateOperandAddWriterTest: operation " +
+                                        std::to_string(operation) + " writes to " +
+                                        std::to_string(outputOperandIndex);
+            // We'll insert a copy of the operation, all of whose
+            // OTHER output operands are newly-created -- i.e.,
+            // there'll only be a duplicate write of ONE of that
+            // operation's output operands.
+            validate(device, message, model, [operation, badOutputNum](Model* model) {
+                Operation newOperation = model->operations[operation];
+                for (uint32_t input : newOperation.inputs) {
+                    ++model->operands[input].numberOfConsumers;
+                }
+                for (size_t outputNum = 0; outputNum < newOperation.outputs.size(); ++outputNum) {
+                    if (outputNum == badOutputNum) continue;
+
+                    Operand operandValue = model->operands[newOperation.outputs[outputNum]];
+                    operandValue.numberOfConsumers = 0;
+                    if (operandValue.lifetime == OperandLifeTime::MODEL_OUTPUT) {
+                        operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+                    } else {
+                        ASSERT_EQ(operandValue.lifetime, OperandLifeTime::TEMPORARY_VARIABLE);
+                    }
+                    newOperation.outputs[outputNum] =
+                            hidl_vec_push_back(&model->operands, operandValue);
+                }
+                // Where do we insert the extra writer (a new
+                // operation)?  It has to be later than all the
+                // writers of its inputs.  The easiest thing to do
+                // is to insert it at the end of the operation
+                // sequence.
+                hidl_vec_push_back(&model->operations, newOperation);
+            });
+        }
+    }
+}
+
 ///////////////////////// VALIDATE EXTRA ??? /////////////////////////
 
-// TODO: Operand::lifetime
 // TODO: Operand::location
 
 ///////////////////////// VALIDATE OPERATION OPERAND TYPE /////////////////////////
@@ -351,6 +765,33 @@
     }
 }
 
+///////////////////////// VALIDATE MODEL OPERANDS WRITTEN ///////////////////////////////////////
+
+static void mutateOperationRemoveWriteTest(const sp<IDevice>& device, const V1_0::Model& model) {
+    for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+        for (size_t outputNum = 0; outputNum < model.operations[operation].outputs.size();
+             ++outputNum) {
+            const uint32_t outputOperandIndex = model.operations[operation].outputs[outputNum];
+            if (model.operands[outputOperandIndex].numberOfConsumers > 0) {
+                const std::string message = "mutateOperationRemoveWriteTest: operation " +
+                                            std::to_string(operation) + " writes to " +
+                                            std::to_string(outputOperandIndex);
+                validate(device, message, model, [operation, outputNum](Model* model) {
+                    uint32_t& outputOperandIndex = model->operations[operation].outputs[outputNum];
+                    Operand operandValue = model->operands[outputOperandIndex];
+                    operandValue.numberOfConsumers = 0;
+                    if (operandValue.lifetime == OperandLifeTime::MODEL_OUTPUT) {
+                        operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+                    } else {
+                        ASSERT_EQ(operandValue.lifetime, OperandLifeTime::TEMPORARY_VARIABLE);
+                    }
+                    outputOperandIndex = hidl_vec_push_back(&model->operands, operandValue);
+                });
+            }
+        }
+    }
+}
+
 ///////////////////////// REMOVE OPERAND FROM EVERYTHING /////////////////////////
 
 static void removeValueAndDecrementGreaterValues(hidl_vec<uint32_t>* vec, uint32_t value) {
@@ -476,14 +917,20 @@
 ////////////////////////// ENTRY POINT //////////////////////////////
 
 void validateModel(const sp<IDevice>& device, const Model& model) {
+    mutateExecutionOrderTest(device, model);
     mutateOperandTypeTest(device, model);
     mutateOperandRankTest(device, model);
     mutateOperandScaleTest(device, model);
     mutateOperandZeroPointTest(device, model);
+    mutateOperandLifeTimeTest(device, model);
+    mutateOperandInputOutputTest(device, model);
+    mutateOperandNumberOfConsumersTest(device, model);
+    mutateOperandAddWriterTest(device, model);
     mutateOperationOperandTypeTest(device, model);
     mutateOperationTypeTest(device, model);
     mutateOperationInputOperandIndexTest(device, model);
     mutateOperationOutputOperandIndexTest(device, model);
+    mutateOperationRemoveWriteTest(device, model);
     removeOperandTest(device, model);
     removeOperationTest(device, model);
     removeOperationInputTest(device, model);
diff --git a/neuralnetworks/1.0/vts/functional/include/1.0/Utils.h b/neuralnetworks/1.0/vts/functional/include/1.0/Utils.h
index 3292f79..7bd0460 100644
--- a/neuralnetworks/1.0/vts/functional/include/1.0/Utils.h
+++ b/neuralnetworks/1.0/vts/functional/include/1.0/Utils.h
@@ -21,6 +21,7 @@
 #include <android/hardware/neuralnetworks/1.0/types.h>
 #include <android/hardware_buffer.h>
 #include <android/hidl/memory/1.0/IMemory.h>
+#include <gtest/gtest.h>
 #include <algorithm>
 #include <iosfwd>
 #include <string>
@@ -108,6 +109,15 @@
     vec->resize(vec->size() - 1);
 }
 
+// Assumes there is exactly one instance of the value in the vector.
+template <typename Type>
+inline void hidl_vec_remove(hidl_vec<Type>* vec, const Type& val) {
+    CHECK(vec != nullptr);
+    auto where = std::find(vec->begin(), vec->end(), val);
+    ASSERT_NE(where, vec->end());
+    hidl_vec_removeAt(vec, where - vec->begin());
+}
+
 template <typename Type>
 inline uint32_t hidl_vec_push_back(hidl_vec<Type>* vec, const Type& value) {
     CHECK(vec != nullptr);
@@ -117,6 +127,18 @@
     return index;
 }
 
+// Returns the amount of space needed to store a value of the specified type.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(V1_0::OperandType type);
+
+// Returns the amount of space needed to store a value of the dimensions and
+// type of this operand. For a non-extension, non-OEM tensor with unspecified
+// rank or at least one unspecified dimension, returns zero.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(const V1_0::Operand& operand);
+
 template <typename Type>
 using Named = std::pair<std::string, Type>;
 
diff --git a/neuralnetworks/1.1/vts/functional/BasicTests.cpp b/neuralnetworks/1.1/vts/functional/BasicTests.cpp
index 44836f0..baadd1b 100644
--- a/neuralnetworks/1.1/vts/functional/BasicTests.cpp
+++ b/neuralnetworks/1.1/vts/functional/BasicTests.cpp
@@ -18,10 +18,16 @@
 
 #include "VtsHalNeuralnetworks.h"
 
+#include "1.0/Callbacks.h"
+
 namespace android::hardware::neuralnetworks::V1_1::vts::functional {
 
 using V1_0::DeviceStatus;
 using V1_0::ErrorStatus;
+using V1_0::Operand;
+using V1_0::OperandLifeTime;
+using V1_0::OperandType;
+using V1_0::implementation::PreparedModelCallback;
 
 // create device test
 TEST_P(NeuralnetworksHidlTest, CreateDevice) {}
@@ -48,4 +54,137 @@
     EXPECT_TRUE(ret.isOk());
 }
 
+// detect cycle
+TEST_P(NeuralnetworksHidlTest, CycleTest) {
+    // opnd0 = TENSOR_FLOAT32            // model input
+    // opnd1 = TENSOR_FLOAT32            // model input
+    // opnd2 = INT32                     // model input
+    // opnd3 = ADD(opnd0, opnd4, opnd2)
+    // opnd4 = ADD(opnd1, opnd3, opnd2)
+    // opnd5 = ADD(opnd4, opnd0, opnd2)  // model output
+    //
+    //            +-----+
+    //            |     |
+    //            v     |
+    // 3 = ADD(0, 4, 2) |
+    // |                |
+    // +----------+     |
+    //            |     |
+    //            v     |
+    // 4 = ADD(1, 3, 2) |
+    // |                |
+    // +----------------+
+    // |
+    // |
+    // +-------+
+    //         |
+    //         v
+    // 5 = ADD(4, 0, 2)
+
+    const std::vector<Operand> operands = {
+            {
+                    // operands[0]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .numberOfConsumers = 2,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::MODEL_INPUT,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[1]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .numberOfConsumers = 1,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::MODEL_INPUT,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[2]
+                    .type = OperandType::INT32,
+                    .dimensions = {},
+                    .numberOfConsumers = 3,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::MODEL_INPUT,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[3]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .numberOfConsumers = 1,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[4]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .numberOfConsumers = 2,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[5]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .numberOfConsumers = 0,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::MODEL_OUTPUT,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+    };
+
+    const std::vector<Operation> operations = {
+            {.type = OperationType::ADD, .inputs = {0, 4, 2}, .outputs = {3}},
+            {.type = OperationType::ADD, .inputs = {1, 3, 2}, .outputs = {4}},
+            {.type = OperationType::ADD, .inputs = {4, 0, 2}, .outputs = {5}},
+    };
+
+    const Model model = {
+            .operands = operands,
+            .operations = operations,
+            .inputIndexes = {0, 1, 2},
+            .outputIndexes = {5},
+            .operandValues = {},
+            .pools = {},
+    };
+
+    // ensure that getSupportedOperations_1_1() checks model validity
+    ErrorStatus supportedOpsErrorStatus = ErrorStatus::GENERAL_FAILURE;
+    Return<void> supportedOpsReturn = kDevice->getSupportedOperations_1_1(
+            model, [&model, &supportedOpsErrorStatus](ErrorStatus status,
+                                                      const hidl_vec<bool>& supported) {
+                supportedOpsErrorStatus = status;
+                if (status == ErrorStatus::NONE) {
+                    ASSERT_EQ(supported.size(), model.operations.size());
+                }
+            });
+    ASSERT_TRUE(supportedOpsReturn.isOk());
+    ASSERT_EQ(supportedOpsErrorStatus, ErrorStatus::INVALID_ARGUMENT);
+
+    // ensure that prepareModel_1_1() checks model validity
+    sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback;
+    Return<ErrorStatus> prepareLaunchReturn = kDevice->prepareModel_1_1(
+            model, ExecutionPreference::FAST_SINGLE_ANSWER, preparedModelCallback);
+    ASSERT_TRUE(prepareLaunchReturn.isOk());
+    //     Note that preparation can fail for reasons other than an
+    //     invalid model (invalid model should result in
+    //     INVALID_ARGUMENT) -- for example, perhaps not all
+    //     operations are supported, or perhaps the device hit some
+    //     kind of capacity limit.
+    EXPECT_NE(prepareLaunchReturn, ErrorStatus::NONE);
+    EXPECT_NE(preparedModelCallback->getStatus(), ErrorStatus::NONE);
+    EXPECT_EQ(preparedModelCallback->getPreparedModel(), nullptr);
+}
+
 }  // namespace android::hardware::neuralnetworks::V1_1::vts::functional
diff --git a/neuralnetworks/1.1/vts/functional/ValidateModel.cpp b/neuralnetworks/1.1/vts/functional/ValidateModel.cpp
index 3b6f0f8..1f4e4ed 100644
--- a/neuralnetworks/1.1/vts/functional/ValidateModel.cpp
+++ b/neuralnetworks/1.1/vts/functional/ValidateModel.cpp
@@ -16,13 +16,19 @@
 
 #define LOG_TAG "neuralnetworks_hidl_hal_test"
 
+#include <android/hardware/neuralnetworks/1.1/types.h>
 #include "1.0/Callbacks.h"
 #include "1.0/Utils.h"
 #include "GeneratedTestHarness.h"
 #include "VtsHalNeuralnetworks.h"
 
+#include <optional>
+#include <type_traits>
+#include <utility>
+
 namespace android::hardware::neuralnetworks::V1_1::vts::functional {
 
+using V1_0::DataLocation;
 using V1_0::ErrorStatus;
 using V1_0::IPreparedModel;
 using V1_0::Operand;
@@ -105,6 +111,212 @@
     return index;
 }
 
+// If we introduce a CONSTANT_COPY for an operand of size operandSize,
+// how much will this increase the size of the model?  This assumes
+// that we can (re)use all of model.operandValues for the operand
+// value.
+static size_t constantCopyExtraSize(const Model& model, size_t operandSize) {
+    const size_t operandValuesSize = model.operandValues.size();
+    return (operandValuesSize < operandSize) ? (operandSize - operandValuesSize) : 0;
+}
+
+// Highly specialized utility routine for converting an operand to
+// CONSTANT_COPY lifetime.
+//
+// Expects that:
+// - operand has a known size
+// - operand->lifetime has already been set to CONSTANT_COPY
+// - operand->location has been zeroed out
+//
+// Does the following:
+// - initializes operand->location to point to the beginning of model->operandValues
+// - resizes model->operandValues (if necessary) to be large enough for the operand
+//   value, padding it with zeroes on the end
+//
+// Potential problem:
+// By changing the operand to CONSTANT_COPY lifetime, this function is effectively initializing the
+// operand with unspecified (but deterministic) data. This means that the model may be invalidated
+// in two ways: not only is the lifetime of CONSTANT_COPY invalid, but the operand's value in the
+// graph may also be invalid (e.g., if the operand is used as an activation code and has an invalid
+// value). For now, this should be fine because it just means we're not testing what we think we're
+// testing in certain cases; but we can handwave this and assume we're probabilistically likely to
+// exercise the validation code over the span of the entire test set and operand space.
+//
+// Aborts if the specified operand type is an extension type or OEM type.
+static void becomeConstantCopy(Model* model, Operand* operand) {
+    // sizeOfData will abort if the specified type is an extension type or OEM type.
+    const size_t sizeOfOperand = sizeOfData(*operand);
+    EXPECT_NE(sizeOfOperand, size_t(0));
+    operand->location.poolIndex = 0;
+    operand->location.offset = 0;
+    operand->location.length = sizeOfOperand;
+    if (model->operandValues.size() < sizeOfOperand) {
+        model->operandValues.resize(sizeOfOperand);
+    }
+}
+
+// The sizeForBinder() functions estimate the size of the
+// representation of a value when sent to binder.  It's probably a bit
+// of an under-estimate, because we don't know the size of the
+// metadata in the binder format (e.g., representation of the size of
+// a vector); but at least it adds up "big" things like vector
+// contents.  However, it doesn't treat inter-field or end-of-struct
+// padding in a methodical way -- there's no attempt to be consistent
+// in whether or not padding in the native (C++) representation
+// contributes to the estimated size for the binder representation;
+// and there's no attempt to understand what padding (if any) is
+// needed in the binder representation.
+//
+// This assumes that non-metadata uses a fixed length encoding (e.g.,
+// a uint32_t is always encoded in sizeof(uint32_t) bytes, rather than
+// using an encoding whose length is related to the magnitude of the
+// encoded value).
+
+template <typename Type>
+static size_t sizeForBinder(const Type& val) {
+    static_assert(std::is_trivially_copyable_v<std::remove_reference_t<Type>>,
+                  "expected a trivially copyable type");
+    return sizeof(val);
+}
+
+template <typename Type>
+static size_t sizeForBinder(const hidl_vec<Type>& vec) {
+    return std::accumulate(vec.begin(), vec.end(), 0,
+                           [](size_t acc, const Type& x) { return acc + sizeForBinder(x); });
+}
+
+template <>
+size_t sizeForBinder(const Operand& operand) {
+    size_t size = 0;
+
+    size += sizeForBinder(operand.type);
+    size += sizeForBinder(operand.dimensions);
+    size += sizeForBinder(operand.numberOfConsumers);
+    size += sizeForBinder(operand.scale);
+    size += sizeForBinder(operand.zeroPoint);
+    size += sizeForBinder(operand.lifetime);
+    size += sizeForBinder(operand.location);
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const Operation& operation) {
+    size_t size = 0;
+
+    size += sizeForBinder(operation.type);
+    size += sizeForBinder(operation.inputs);
+    size += sizeForBinder(operation.outputs);
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const hidl_string& name) {
+    return name.size();
+}
+
+template <>
+size_t sizeForBinder(const hidl_memory& memory) {
+    // This is just a guess.
+
+    size_t size = 0;
+
+    if (const native_handle_t* handle = memory.handle()) {
+        size += sizeof(*handle);
+        size += sizeof(handle->data[0] * (handle->numFds + handle->numInts));
+    }
+    size += sizeForBinder(memory.name());
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const Model& model) {
+    size_t size = 0;
+
+    size += sizeForBinder(model.operands);
+    size += sizeForBinder(model.operations);
+    size += sizeForBinder(model.inputIndexes);
+    size += sizeForBinder(model.outputIndexes);
+    size += sizeForBinder(model.operandValues);
+    size += sizeForBinder(model.pools);
+    size += sizeForBinder(model.relaxComputationFloat32toFloat16);
+
+    return size;
+}
+
+// https://developer.android.com/reference/android/os/TransactionTooLargeException.html
+//
+//     "The Binder transaction buffer has a limited fixed size,
+//     currently 1Mb, which is shared by all transactions in progress
+//     for the process."
+//
+// Will our representation fit under this limit?  There are two complications:
+// - Our representation size is just approximate (see sizeForBinder()).
+// - This object may not be the only occupant of the Binder transaction buffer.
+// So we'll be very conservative: We want the representation size to be no
+// larger than half the transaction buffer size.
+//
+// If our representation grows large enough that it still fits within
+// the transaction buffer but combined with other transactions may
+// exceed the buffer size, then we may see intermittent HAL transport
+// errors.
+static bool exceedsBinderSizeLimit(size_t representationSize) {
+    // Instead of using this fixed buffer size, we might instead be able to use
+    // ProcessState::self()->getMmapSize(). However, this has a potential
+    // problem: The binder/mmap size of the current process does not necessarily
+    // indicate the binder/mmap size of the service (i.e., the other process).
+    // The only way it would be a good indication is if both the current process
+    // and the service use the default size.
+    static const size_t kHalfBufferSize = 1024 * 1024 / 2;
+
+    return representationSize > kHalfBufferSize;
+}
+
+///////////////////////// VALIDATE EXECUTION ORDER ////////////////////////////
+
+static void mutateExecutionOrderTest(const sp<IDevice>& device, const V1_1::Model& model) {
+    for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+        const Operation& operationObj = model.operations[operation];
+        for (uint32_t input : operationObj.inputs) {
+            if (model.operands[input].lifetime == OperandLifeTime::TEMPORARY_VARIABLE ||
+                model.operands[input].lifetime == OperandLifeTime::MODEL_OUTPUT) {
+                // This operation reads an operand written by some
+                // other operation.  Move this operation to the
+                // beginning of the sequence, ensuring that it reads
+                // the operand before that operand is written, thereby
+                // violating execution order rules.
+                const std::string message = "mutateExecutionOrderTest: operation " +
+                                            std::to_string(operation) + " is a reader";
+                validate(device, message, model, [operation](Model* model, ExecutionPreference*) {
+                    auto& operations = model->operations;
+                    std::rotate(operations.begin(), operations.begin() + operation,
+                                operations.begin() + operation + 1);
+                });
+                break;  // only need to do this once per operation
+            }
+        }
+        for (uint32_t output : operationObj.outputs) {
+            if (model.operands[output].numberOfConsumers > 0) {
+                // This operation writes an operand read by some other
+                // operation.  Move this operation to the end of the
+                // sequence, ensuring that it writes the operand after
+                // that operand is read, thereby violating execution
+                // order rules.
+                const std::string message = "mutateExecutionOrderTest: operation " +
+                                            std::to_string(operation) + " is a writer";
+                validate(device, message, model, [operation](Model* model, ExecutionPreference*) {
+                    auto& operations = model->operations;
+                    std::rotate(operations.begin() + operation, operations.begin() + operation + 1,
+                                operations.end());
+                });
+                break;  // only need to do this once per operation
+            }
+        }
+    }
+}
+
 ///////////////////////// VALIDATE MODEL OPERAND TYPE /////////////////////////
 
 static const int32_t invalidOperandTypes[] = {
@@ -221,9 +433,240 @@
     }
 }
 
+///////////////////////// VALIDATE OPERAND LIFETIME /////////////////////////////////////////////
+
+static std::vector<OperandLifeTime> getInvalidLifeTimes(const Model& model, size_t modelSize,
+                                                        const Operand& operand) {
+    // TODO: Support OperandLifeTime::CONSTANT_REFERENCE as an invalid lifetime
+    // TODO: Support OperandLifeTime::NO_VALUE as an invalid lifetime
+
+    // Ways to get an invalid lifetime:
+    // - change whether a lifetime means an operand should have a writer
+    std::vector<OperandLifeTime> ret;
+    switch (operand.lifetime) {
+        case OperandLifeTime::MODEL_OUTPUT:
+        case OperandLifeTime::TEMPORARY_VARIABLE:
+            ret = {
+                    OperandLifeTime::MODEL_INPUT,
+                    OperandLifeTime::CONSTANT_COPY,
+            };
+            break;
+        case OperandLifeTime::CONSTANT_COPY:
+        case OperandLifeTime::CONSTANT_REFERENCE:
+        case OperandLifeTime::MODEL_INPUT:
+            ret = {
+                    OperandLifeTime::TEMPORARY_VARIABLE,
+                    OperandLifeTime::MODEL_OUTPUT,
+            };
+            break;
+        case OperandLifeTime::NO_VALUE:
+            // Not enough information to know whether
+            // TEMPORARY_VARIABLE or CONSTANT_COPY would be invalid --
+            // is this operand written (then CONSTANT_COPY would be
+            // invalid) or not (then TEMPORARY_VARIABLE would be
+            // invalid)?
+            break;
+        default:
+            ADD_FAILURE();
+            break;
+    }
+
+    const size_t operandSize = sizeOfData(operand);  // will be zero if shape is unknown
+    if (!operandSize ||
+        exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+        // Unknown size or too-large size
+        ret.erase(std::remove(ret.begin(), ret.end(), OperandLifeTime::CONSTANT_COPY), ret.end());
+    }
+
+    return ret;
+}
+
+static void mutateOperandLifeTimeTest(const sp<IDevice>& device, const V1_1::Model& model) {
+    const size_t modelSize = sizeForBinder(model);
+    for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+        const std::vector<OperandLifeTime> invalidLifeTimes =
+                getInvalidLifeTimes(model, modelSize, model.operands[operand]);
+        for (OperandLifeTime invalidLifeTime : invalidLifeTimes) {
+            const std::string message = "mutateOperandLifetimeTest: operand " +
+                                        std::to_string(operand) + " has lifetime " +
+                                        toString(invalidLifeTime) + " instead of lifetime " +
+                                        toString(model.operands[operand].lifetime);
+            validate(device, message, model,
+                     [operand, invalidLifeTime](Model* model, ExecutionPreference*) {
+                         static const DataLocation kZeroDataLocation = {};
+                         Operand& operandObj = model->operands[operand];
+                         switch (operandObj.lifetime) {
+                             case OperandLifeTime::MODEL_INPUT: {
+                                 hidl_vec_remove(&model->inputIndexes, uint32_t(operand));
+                                 break;
+                             }
+                             case OperandLifeTime::MODEL_OUTPUT: {
+                                 hidl_vec_remove(&model->outputIndexes, uint32_t(operand));
+                                 break;
+                             }
+                             default:
+                                 break;
+                         }
+                         operandObj.lifetime = invalidLifeTime;
+                         operandObj.location = kZeroDataLocation;
+                         switch (invalidLifeTime) {
+                             case OperandLifeTime::CONSTANT_COPY: {
+                                 becomeConstantCopy(model, &operandObj);
+                                 break;
+                             }
+                             case OperandLifeTime::MODEL_INPUT:
+                                 hidl_vec_push_back(&model->inputIndexes, uint32_t(operand));
+                                 break;
+                             case OperandLifeTime::MODEL_OUTPUT:
+                                 hidl_vec_push_back(&model->outputIndexes, uint32_t(operand));
+                                 break;
+                             default:
+                                 break;
+                         }
+                     });
+        }
+    }
+}
+
+///////////////////////// VALIDATE OPERAND INPUT-or-OUTPUT //////////////////////////////////////
+
+static std::optional<OperandLifeTime> getInputOutputLifeTime(const Model& model, size_t modelSize,
+                                                             const Operand& operand) {
+    // Ways to get an invalid lifetime (with respect to model inputIndexes and outputIndexes):
+    // - change whether a lifetime means an operand is a model input, a model output, or neither
+    // - preserve whether or not a lifetime means an operand should have a writer
+    switch (operand.lifetime) {
+        case OperandLifeTime::CONSTANT_COPY:
+        case OperandLifeTime::CONSTANT_REFERENCE:
+            return OperandLifeTime::MODEL_INPUT;
+        case OperandLifeTime::MODEL_INPUT: {
+            const size_t operandSize = sizeOfData(operand);  // will be zero if shape is unknown
+            if (!operandSize ||
+                exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+                // Unknown size or too-large size
+                break;
+            }
+            return OperandLifeTime::CONSTANT_COPY;
+        }
+        case OperandLifeTime::MODEL_OUTPUT:
+            return OperandLifeTime::TEMPORARY_VARIABLE;
+        case OperandLifeTime::TEMPORARY_VARIABLE:
+            return OperandLifeTime::MODEL_OUTPUT;
+        case OperandLifeTime::NO_VALUE:
+            // Not enough information to know whether
+            // TEMPORARY_VARIABLE or CONSTANT_COPY would be an
+            // appropriate choice -- is this operand written (then
+            // TEMPORARY_VARIABLE would be appropriate) or not (then
+            // CONSTANT_COPY would be appropriate)?
+            break;
+        default:
+            ADD_FAILURE();
+            break;
+    }
+
+    return std::nullopt;
+}
+
+static void mutateOperandInputOutputTest(const sp<IDevice>& device, const V1_1::Model& model) {
+    const size_t modelSize = sizeForBinder(model);
+    for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+        const std::optional<OperandLifeTime> changedLifeTime =
+                getInputOutputLifeTime(model, modelSize, model.operands[operand]);
+        if (changedLifeTime) {
+            const std::string message = "mutateOperandInputOutputTest: operand " +
+                                        std::to_string(operand) + " has lifetime " +
+                                        toString(*changedLifeTime) + " instead of lifetime " +
+                                        toString(model.operands[operand].lifetime);
+            validate(device, message, model,
+                     [operand, changedLifeTime](Model* model, ExecutionPreference*) {
+                         static const DataLocation kZeroDataLocation = {};
+                         Operand& operandObj = model->operands[operand];
+                         operandObj.lifetime = *changedLifeTime;
+                         operandObj.location = kZeroDataLocation;
+                         if (*changedLifeTime == OperandLifeTime::CONSTANT_COPY) {
+                             becomeConstantCopy(model, &operandObj);
+                         }
+                     });
+        }
+    }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF CONSUMERS //////////////////////////////////
+
+static std::vector<uint32_t> getInvalidNumberOfConsumers(uint32_t numberOfConsumers) {
+    if (numberOfConsumers == 0) {
+        return {1};
+    } else {
+        return {numberOfConsumers - 1, numberOfConsumers + 1};
+    }
+}
+
+static void mutateOperandNumberOfConsumersTest(const sp<IDevice>& device,
+                                               const V1_1::Model& model) {
+    for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+        const std::vector<uint32_t> invalidNumberOfConsumersVec =
+                getInvalidNumberOfConsumers(model.operands[operand].numberOfConsumers);
+        for (uint32_t invalidNumberOfConsumers : invalidNumberOfConsumersVec) {
+            const std::string message =
+                    "mutateOperandNumberOfConsumersTest: operand " + std::to_string(operand) +
+                    " numberOfConsumers = " + std::to_string(invalidNumberOfConsumers);
+            validate(device, message, model,
+                     [operand, invalidNumberOfConsumers](Model* model, ExecutionPreference*) {
+                         model->operands[operand].numberOfConsumers = invalidNumberOfConsumers;
+                     });
+        }
+    }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF WRITERS ////////////////////////////////////
+
+static void mutateOperandAddWriterTest(const sp<IDevice>& device, const V1_1::Model& model) {
+    for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+        for (size_t badOutputNum = 0; badOutputNum < model.operations[operation].outputs.size();
+             ++badOutputNum) {
+            const uint32_t outputOperandIndex = model.operations[operation].outputs[badOutputNum];
+            const std::string message = "mutateOperandAddWriterTest: operation " +
+                                        std::to_string(operation) + " writes to " +
+                                        std::to_string(outputOperandIndex);
+            // We'll insert a copy of the operation, all of whose
+            // OTHER output operands are newly-created -- i.e.,
+            // there'll only be a duplicate write of ONE of that
+            // operation's output operands.
+            validate(device, message, model,
+                     [operation, badOutputNum](Model* model, ExecutionPreference*) {
+                         Operation newOperation = model->operations[operation];
+                         for (uint32_t input : newOperation.inputs) {
+                             ++model->operands[input].numberOfConsumers;
+                         }
+                         for (size_t outputNum = 0; outputNum < newOperation.outputs.size();
+                              ++outputNum) {
+                             if (outputNum == badOutputNum) continue;
+
+                             Operand operandValue =
+                                     model->operands[newOperation.outputs[outputNum]];
+                             operandValue.numberOfConsumers = 0;
+                             if (operandValue.lifetime == OperandLifeTime::MODEL_OUTPUT) {
+                                 operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+                             } else {
+                                 ASSERT_EQ(operandValue.lifetime,
+                                           OperandLifeTime::TEMPORARY_VARIABLE);
+                             }
+                             newOperation.outputs[outputNum] =
+                                     hidl_vec_push_back(&model->operands, operandValue);
+                         }
+                         // Where do we insert the extra writer (a new
+                         // operation)?  It has to be later than all the
+                         // writers of its inputs.  The easiest thing to do
+                         // is to insert it at the end of the operation
+                         // sequence.
+                         hidl_vec_push_back(&model->operations, newOperation);
+                     });
+        }
+    }
+}
+
 ///////////////////////// VALIDATE EXTRA ??? /////////////////////////
 
-// TODO: Operand::lifetime
 // TODO: Operand::location
 
 ///////////////////////// VALIDATE OPERATION OPERAND TYPE /////////////////////////
@@ -358,6 +801,37 @@
     }
 }
 
+///////////////////////// VALIDATE MODEL OPERANDS WRITTEN ///////////////////////////////////////
+
+static void mutateOperationRemoveWriteTest(const sp<IDevice>& device, const V1_1::Model& model) {
+    for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+        for (size_t outputNum = 0; outputNum < model.operations[operation].outputs.size();
+             ++outputNum) {
+            const uint32_t outputOperandIndex = model.operations[operation].outputs[outputNum];
+            if (model.operands[outputOperandIndex].numberOfConsumers > 0) {
+                const std::string message = "mutateOperationRemoveWriteTest: operation " +
+                                            std::to_string(operation) + " writes to " +
+                                            std::to_string(outputOperandIndex);
+                validate(device, message, model,
+                         [operation, outputNum](Model* model, ExecutionPreference*) {
+                             uint32_t& outputOperandIndex =
+                                     model->operations[operation].outputs[outputNum];
+                             Operand operandValue = model->operands[outputOperandIndex];
+                             operandValue.numberOfConsumers = 0;
+                             if (operandValue.lifetime == OperandLifeTime::MODEL_OUTPUT) {
+                                 operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+                             } else {
+                                 ASSERT_EQ(operandValue.lifetime,
+                                           OperandLifeTime::TEMPORARY_VARIABLE);
+                             }
+                             outputOperandIndex =
+                                     hidl_vec_push_back(&model->operands, operandValue);
+                         });
+            }
+        }
+    }
+}
+
 ///////////////////////// REMOVE OPERAND FROM EVERYTHING /////////////////////////
 
 static void removeValueAndDecrementGreaterValues(hidl_vec<uint32_t>* vec, uint32_t value) {
@@ -504,14 +978,20 @@
 ////////////////////////// ENTRY POINT //////////////////////////////
 
 void validateModel(const sp<IDevice>& device, const Model& model) {
+    mutateExecutionOrderTest(device, model);
     mutateOperandTypeTest(device, model);
     mutateOperandRankTest(device, model);
     mutateOperandScaleTest(device, model);
     mutateOperandZeroPointTest(device, model);
+    mutateOperandLifeTimeTest(device, model);
+    mutateOperandInputOutputTest(device, model);
+    mutateOperandNumberOfConsumersTest(device, model);
+    mutateOperandAddWriterTest(device, model);
     mutateOperationOperandTypeTest(device, model);
     mutateOperationTypeTest(device, model);
     mutateOperationInputOperandIndexTest(device, model);
     mutateOperationOutputOperandIndexTest(device, model);
+    mutateOperationRemoveWriteTest(device, model);
     removeOperandTest(device, model);
     removeOperationTest(device, model);
     removeOperationInputTest(device, model);
diff --git a/neuralnetworks/1.2/vts/functional/Android.bp b/neuralnetworks/1.2/vts/functional/Android.bp
index 481eb80..182f716 100644
--- a/neuralnetworks/1.2/vts/functional/Android.bp
+++ b/neuralnetworks/1.2/vts/functional/Android.bp
@@ -15,11 +15,12 @@
 //
 
 cc_library_static {
-    name: "VtsHalNeuralNetworksV1_2Callbacks",
+    name: "VtsHalNeuralNetworksV1_2_utils",
     defaults: ["neuralnetworks_vts_functional_defaults"],
     export_include_dirs: ["include"],
     srcs: [
         "Callbacks.cpp",
+        "Utils.cpp",
     ],
     static_libs: [
         "android.hardware.neuralnetworks@1.0",
@@ -51,7 +52,7 @@
     ],
     static_libs: [
         "VtsHalNeuralNetworksV1_0_utils",
-        "VtsHalNeuralNetworksV1_2Callbacks",
+        "VtsHalNeuralNetworksV1_2_utils",
         "android.hardware.neuralnetworks@1.0",
         "android.hardware.neuralnetworks@1.1",
         "android.hardware.neuralnetworks@1.2",
diff --git a/neuralnetworks/1.2/vts/functional/BasicTests.cpp b/neuralnetworks/1.2/vts/functional/BasicTests.cpp
index 58d3c4a..77340e7 100644
--- a/neuralnetworks/1.2/vts/functional/BasicTests.cpp
+++ b/neuralnetworks/1.2/vts/functional/BasicTests.cpp
@@ -20,9 +20,13 @@
 
 namespace android::hardware::neuralnetworks::V1_2::vts::functional {
 
+using implementation::PreparedModelCallback;
 using V1_0::DeviceStatus;
 using V1_0::ErrorStatus;
+using V1_0::OperandLifeTime;
 using V1_0::PerformanceInfo;
+using V1_1::ExecutionPreference;
+using HidlToken = hidl_array<uint8_t, static_cast<uint32_t>(Constant::BYTE_SIZE_OF_CACHE_TOKEN)>;
 
 // create device test
 TEST_P(NeuralnetworksHidlTest, CreateDevice) {}
@@ -123,4 +127,139 @@
             });
     EXPECT_TRUE(ret.isOk());
 }
+
+// detect cycle
+TEST_P(NeuralnetworksHidlTest, CycleTest) {
+    // opnd0 = TENSOR_FLOAT32            // model input
+    // opnd1 = TENSOR_FLOAT32            // model input
+    // opnd2 = INT32                     // model input
+    // opnd3 = ADD(opnd0, opnd4, opnd2)
+    // opnd4 = ADD(opnd1, opnd3, opnd2)
+    // opnd5 = ADD(opnd4, opnd0, opnd2)  // model output
+    //
+    //            +-----+
+    //            |     |
+    //            v     |
+    // 3 = ADD(0, 4, 2) |
+    // |                |
+    // +----------+     |
+    //            |     |
+    //            v     |
+    // 4 = ADD(1, 3, 2) |
+    // |                |
+    // +----------------+
+    // |
+    // |
+    // +-------+
+    //         |
+    //         v
+    // 5 = ADD(4, 0, 2)
+
+    const std::vector<Operand> operands = {
+            {
+                    // operands[0]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .numberOfConsumers = 2,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::MODEL_INPUT,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[1]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .numberOfConsumers = 1,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::MODEL_INPUT,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[2]
+                    .type = OperandType::INT32,
+                    .dimensions = {},
+                    .numberOfConsumers = 3,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::MODEL_INPUT,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[3]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .numberOfConsumers = 1,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[4]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .numberOfConsumers = 2,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[5]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .numberOfConsumers = 0,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::MODEL_OUTPUT,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+    };
+
+    const std::vector<Operation> operations = {
+            {.type = OperationType::ADD, .inputs = {0, 4, 2}, .outputs = {3}},
+            {.type = OperationType::ADD, .inputs = {1, 3, 2}, .outputs = {4}},
+            {.type = OperationType::ADD, .inputs = {4, 0, 2}, .outputs = {5}},
+    };
+
+    const Model model = {
+            .operands = operands,
+            .operations = operations,
+            .inputIndexes = {0, 1, 2},
+            .outputIndexes = {5},
+            .operandValues = {},
+            .pools = {},
+    };
+
+    // ensure that getSupportedOperations_1_2() checks model validity
+    ErrorStatus supportedOpsErrorStatus = ErrorStatus::GENERAL_FAILURE;
+    Return<void> supportedOpsReturn = kDevice->getSupportedOperations_1_2(
+            model, [&model, &supportedOpsErrorStatus](ErrorStatus status,
+                                                      const hidl_vec<bool>& supported) {
+                supportedOpsErrorStatus = status;
+                if (status == ErrorStatus::NONE) {
+                    ASSERT_EQ(supported.size(), model.operations.size());
+                }
+            });
+    ASSERT_TRUE(supportedOpsReturn.isOk());
+    ASSERT_EQ(supportedOpsErrorStatus, ErrorStatus::INVALID_ARGUMENT);
+
+    // ensure that prepareModel_1_2() checks model validity
+    sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback;
+    Return<ErrorStatus> prepareLaunchReturn = kDevice->prepareModel_1_2(
+            model, ExecutionPreference::FAST_SINGLE_ANSWER, hidl_vec<hidl_handle>(),
+            hidl_vec<hidl_handle>(), HidlToken(), preparedModelCallback);
+    ASSERT_TRUE(prepareLaunchReturn.isOk());
+    //     Note that preparation can fail for reasons other than an
+    //     invalid model (invalid model should result in
+    //     INVALID_ARGUMENT) -- for example, perhaps not all
+    //     operations are supported, or perhaps the device hit some
+    //     kind of capacity limit.
+    EXPECT_NE(prepareLaunchReturn, ErrorStatus::NONE);
+    EXPECT_NE(preparedModelCallback->getStatus(), ErrorStatus::NONE);
+    EXPECT_EQ(preparedModelCallback->getPreparedModel(), nullptr);
+}
+
 }  // namespace android::hardware::neuralnetworks::V1_2::vts::functional
diff --git a/neuralnetworks/1.2/vts/functional/Utils.cpp b/neuralnetworks/1.2/vts/functional/Utils.cpp
new file mode 100644
index 0000000..cc654f2
--- /dev/null
+++ b/neuralnetworks/1.2/vts/functional/Utils.cpp
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+#include <android-base/logging.h>
+#include <android/hardware/neuralnetworks/1.2/types.h>
+
+#include <functional>
+#include <numeric>
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+
+uint32_t sizeOfData(V1_2::OperandType type) {
+    switch (type) {
+        case V1_2::OperandType::FLOAT32:
+        case V1_2::OperandType::INT32:
+        case V1_2::OperandType::UINT32:
+        case V1_2::OperandType::TENSOR_FLOAT32:
+        case V1_2::OperandType::TENSOR_INT32:
+            return 4;
+        case V1_2::OperandType::TENSOR_QUANT16_SYMM:
+        case V1_2::OperandType::TENSOR_FLOAT16:
+        case V1_2::OperandType::FLOAT16:
+        case V1_2::OperandType::TENSOR_QUANT16_ASYMM:
+            return 2;
+        case V1_2::OperandType::TENSOR_QUANT8_ASYMM:
+        case V1_2::OperandType::BOOL:
+        case V1_2::OperandType::TENSOR_BOOL8:
+        case V1_2::OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+        case V1_2::OperandType::TENSOR_QUANT8_SYMM:
+            return 1;
+        default:
+            CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+            return 0;
+    }
+}
+
+static bool isTensor(V1_2::OperandType type) {
+    switch (type) {
+        case V1_2::OperandType::FLOAT32:
+        case V1_2::OperandType::INT32:
+        case V1_2::OperandType::UINT32:
+        case V1_2::OperandType::FLOAT16:
+        case V1_2::OperandType::BOOL:
+            return false;
+        case V1_2::OperandType::TENSOR_FLOAT32:
+        case V1_2::OperandType::TENSOR_INT32:
+        case V1_2::OperandType::TENSOR_QUANT16_SYMM:
+        case V1_2::OperandType::TENSOR_FLOAT16:
+        case V1_2::OperandType::TENSOR_QUANT16_ASYMM:
+        case V1_2::OperandType::TENSOR_QUANT8_ASYMM:
+        case V1_2::OperandType::TENSOR_BOOL8:
+        case V1_2::OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+        case V1_2::OperandType::TENSOR_QUANT8_SYMM:
+            return true;
+        default:
+            CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+            return false;
+    }
+}
+
+uint32_t sizeOfData(const V1_2::Operand& operand) {
+    const uint32_t dataSize = sizeOfData(operand.type);
+    if (isTensor(operand.type) && operand.dimensions.size() == 0) return 0;
+    return std::accumulate(operand.dimensions.begin(), operand.dimensions.end(), dataSize,
+                           std::multiplies<>{});
+}
+
+}  // namespace neuralnetworks
+}  // namespace hardware
+}  // namespace android
diff --git a/neuralnetworks/1.2/vts/functional/ValidateModel.cpp b/neuralnetworks/1.2/vts/functional/ValidateModel.cpp
index e9fc6e9..6583dfe 100644
--- a/neuralnetworks/1.2/vts/functional/ValidateModel.cpp
+++ b/neuralnetworks/1.2/vts/functional/ValidateModel.cpp
@@ -16,14 +16,21 @@
 
 #define LOG_TAG "neuralnetworks_hidl_hal_test"
 
+#include <android/hardware/neuralnetworks/1.1/types.h>
 #include "1.0/Utils.h"
 #include "1.2/Callbacks.h"
+#include "1.2/Utils.h"
 #include "GeneratedTestHarness.h"
 #include "VtsHalNeuralnetworks.h"
 
+#include <optional>
+#include <type_traits>
+#include <utility>
+
 namespace android::hardware::neuralnetworks::V1_2::vts::functional {
 
 using implementation::PreparedModelCallback;
+using V1_0::DataLocation;
 using V1_0::ErrorStatus;
 using V1_0::OperandLifeTime;
 using V1_1::ExecutionPreference;
@@ -105,6 +112,250 @@
     return index;
 }
 
+// If we introduce a CONSTANT_COPY for an operand of size operandSize,
+// how much will this increase the size of the model?  This assumes
+// that we can (re)use all of model.operandValues for the operand
+// value.
+static size_t constantCopyExtraSize(const Model& model, size_t operandSize) {
+    const size_t operandValuesSize = model.operandValues.size();
+    return (operandValuesSize < operandSize) ? (operandSize - operandValuesSize) : 0;
+}
+
+// Highly specialized utility routine for converting an operand to
+// CONSTANT_COPY lifetime.
+//
+// Expects that:
+// - operand has a known size
+// - operand->lifetime has already been set to CONSTANT_COPY
+// - operand->location has been zeroed out
+//
+// Does the following:
+// - initializes operand->location to point to the beginning of model->operandValues
+// - resizes model->operandValues (if necessary) to be large enough for the operand
+//   value, padding it with zeroes on the end
+//
+// Potential problem:
+// By changing the operand to CONSTANT_COPY lifetime, this function is effectively initializing the
+// operand with unspecified (but deterministic) data. This means that the model may be invalidated
+// in two ways: not only is the lifetime of CONSTANT_COPY invalid, but the operand's value in the
+// graph may also be invalid (e.g., if the operand is used as an activation code and has an invalid
+// value). For now, this should be fine because it just means we're not testing what we think we're
+// testing in certain cases; but we can handwave this and assume we're probabilistically likely to
+// exercise the validation code over the span of the entire test set and operand space.
+//
+// Aborts if the specified operand type is an extension type or OEM type.
+static void becomeConstantCopy(Model* model, Operand* operand) {
+    // sizeOfData will abort if the specified type is an extension type or OEM type.
+    const size_t sizeOfOperand = sizeOfData(*operand);
+    EXPECT_NE(sizeOfOperand, size_t(0));
+    operand->location.poolIndex = 0;
+    operand->location.offset = 0;
+    operand->location.length = sizeOfOperand;
+    if (model->operandValues.size() < sizeOfOperand) {
+        model->operandValues.resize(sizeOfOperand);
+    }
+}
+
+// The sizeForBinder() functions estimate the size of the
+// representation of a value when sent to binder.  It's probably a bit
+// of an under-estimate, because we don't know the size of the
+// metadata in the binder format (e.g., representation of the size of
+// a vector); but at least it adds up "big" things like vector
+// contents.  However, it doesn't treat inter-field or end-of-struct
+// padding in a methodical way -- there's no attempt to be consistent
+// in whether or not padding in the native (C++) representation
+// contributes to the estimated size for the binder representation;
+// and there's no attempt to understand what padding (if any) is
+// needed in the binder representation.
+//
+// This assumes that non-metadata uses a fixed length encoding (e.g.,
+// a uint32_t is always encoded in sizeof(uint32_t) bytes, rather than
+// using an encoding whose length is related to the magnitude of the
+// encoded value).
+
+template <typename Type>
+static size_t sizeForBinder(const Type& val) {
+    static_assert(std::is_trivially_copyable_v<std::remove_reference_t<Type>>,
+                  "expected a trivially copyable type");
+    return sizeof(val);
+}
+
+template <typename Type>
+static size_t sizeForBinder(const hidl_vec<Type>& vec) {
+    return std::accumulate(vec.begin(), vec.end(), 0,
+                           [](size_t acc, const Type& x) { return acc + sizeForBinder(x); });
+}
+
+template <>
+size_t sizeForBinder(const SymmPerChannelQuantParams& symmPerChannelQuantParams) {
+    size_t size = 0;
+
+    size += sizeForBinder(symmPerChannelQuantParams.scales);
+    size += sizeForBinder(symmPerChannelQuantParams.channelDim);
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const Operand::ExtraParams& extraParams) {
+    using Discriminator = Operand::ExtraParams::hidl_discriminator;
+    switch (extraParams.getDiscriminator()) {
+        case Discriminator::none:
+            return 0;
+        case Discriminator::channelQuant:
+            return sizeForBinder(extraParams.channelQuant());
+        case Discriminator::extension:
+            return sizeForBinder(extraParams.extension());
+    }
+    LOG(FATAL) << "Unrecognized extraParams enum: "
+               << static_cast<int>(extraParams.getDiscriminator());
+    return 0;
+}
+
+template <>
+size_t sizeForBinder(const Operand& operand) {
+    size_t size = 0;
+
+    size += sizeForBinder(operand.type);
+    size += sizeForBinder(operand.dimensions);
+    size += sizeForBinder(operand.numberOfConsumers);
+    size += sizeForBinder(operand.scale);
+    size += sizeForBinder(operand.zeroPoint);
+    size += sizeForBinder(operand.lifetime);
+    size += sizeForBinder(operand.location);
+    size += sizeForBinder(operand.extraParams);
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const Operation& operation) {
+    size_t size = 0;
+
+    size += sizeForBinder(operation.type);
+    size += sizeForBinder(operation.inputs);
+    size += sizeForBinder(operation.outputs);
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const hidl_string& name) {
+    return name.size();
+}
+
+template <>
+size_t sizeForBinder(const hidl_memory& memory) {
+    // This is just a guess.
+
+    size_t size = 0;
+
+    if (const native_handle_t* handle = memory.handle()) {
+        size += sizeof(*handle);
+        size += sizeof(handle->data[0] * (handle->numFds + handle->numInts));
+    }
+    size += sizeForBinder(memory.name());
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const Model::ExtensionNameAndPrefix& extensionNameToPrefix) {
+    size_t size = 0;
+
+    size += sizeForBinder(extensionNameToPrefix.name);
+    size += sizeForBinder(extensionNameToPrefix.prefix);
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const Model& model) {
+    size_t size = 0;
+
+    size += sizeForBinder(model.operands);
+    size += sizeForBinder(model.operations);
+    size += sizeForBinder(model.inputIndexes);
+    size += sizeForBinder(model.outputIndexes);
+    size += sizeForBinder(model.operandValues);
+    size += sizeForBinder(model.pools);
+    size += sizeForBinder(model.relaxComputationFloat32toFloat16);
+    size += sizeForBinder(model.extensionNameToPrefix);
+
+    return size;
+}
+
+// https://developer.android.com/reference/android/os/TransactionTooLargeException.html
+//
+//     "The Binder transaction buffer has a limited fixed size,
+//     currently 1Mb, which is shared by all transactions in progress
+//     for the process."
+//
+// Will our representation fit under this limit?  There are two complications:
+// - Our representation size is just approximate (see sizeForBinder()).
+// - This object may not be the only occupant of the Binder transaction buffer.
+// So we'll be very conservative: We want the representation size to be no
+// larger than half the transaction buffer size.
+//
+// If our representation grows large enough that it still fits within
+// the transaction buffer but combined with other transactions may
+// exceed the buffer size, then we may see intermittent HAL transport
+// errors.
+static bool exceedsBinderSizeLimit(size_t representationSize) {
+    // Instead of using this fixed buffer size, we might instead be able to use
+    // ProcessState::self()->getMmapSize(). However, this has a potential
+    // problem: The binder/mmap size of the current process does not necessarily
+    // indicate the binder/mmap size of the service (i.e., the other process).
+    // The only way it would be a good indication is if both the current process
+    // and the service use the default size.
+    static const size_t kHalfBufferSize = 1024 * 1024 / 2;
+
+    return representationSize > kHalfBufferSize;
+}
+
+///////////////////////// VALIDATE EXECUTION ORDER ////////////////////////////
+
+static void mutateExecutionOrderTest(const sp<IDevice>& device, const Model& model) {
+    for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+        const Operation& operationObj = model.operations[operation];
+        for (uint32_t input : operationObj.inputs) {
+            if (model.operands[input].lifetime == OperandLifeTime::TEMPORARY_VARIABLE ||
+                model.operands[input].lifetime == OperandLifeTime::MODEL_OUTPUT) {
+                // This operation reads an operand written by some
+                // other operation.  Move this operation to the
+                // beginning of the sequence, ensuring that it reads
+                // the operand before that operand is written, thereby
+                // violating execution order rules.
+                const std::string message = "mutateExecutionOrderTest: operation " +
+                                            std::to_string(operation) + " is a reader";
+                validate(device, message, model, [operation](Model* model, ExecutionPreference*) {
+                    auto& operations = model->operations;
+                    std::rotate(operations.begin(), operations.begin() + operation,
+                                operations.begin() + operation + 1);
+                });
+                break;  // only need to do this once per operation
+            }
+        }
+        for (uint32_t output : operationObj.outputs) {
+            if (model.operands[output].numberOfConsumers > 0) {
+                // This operation writes an operand read by some other
+                // operation.  Move this operation to the end of the
+                // sequence, ensuring that it writes the operand after
+                // that operand is read, thereby violating execution
+                // order rules.
+                const std::string message = "mutateExecutionOrderTest: operation " +
+                                            std::to_string(operation) + " is a writer";
+                validate(device, message, model, [operation](Model* model, ExecutionPreference*) {
+                    auto& operations = model->operations;
+                    std::rotate(operations.begin() + operation, operations.begin() + operation + 1,
+                                operations.end());
+                });
+                break;  // only need to do this once per operation
+            }
+        }
+    }
+}
+
 ///////////////////////// VALIDATE MODEL OPERAND TYPE /////////////////////////
 
 static const uint32_t invalidOperandTypes[] = {
@@ -251,9 +502,239 @@
     }
 }
 
+///////////////////////// VALIDATE OPERAND LIFETIME /////////////////////////////////////////////
+
+static std::vector<OperandLifeTime> getInvalidLifeTimes(const Model& model, size_t modelSize,
+                                                        const Operand& operand) {
+    // TODO: Support OperandLifeTime::CONSTANT_REFERENCE as an invalid lifetime
+    // TODO: Support OperandLifeTime::NO_VALUE as an invalid lifetime
+
+    // Ways to get an invalid lifetime:
+    // - change whether a lifetime means an operand should have a writer
+    std::vector<OperandLifeTime> ret;
+    switch (operand.lifetime) {
+        case OperandLifeTime::MODEL_OUTPUT:
+        case OperandLifeTime::TEMPORARY_VARIABLE:
+            ret = {
+                    OperandLifeTime::MODEL_INPUT,
+                    OperandLifeTime::CONSTANT_COPY,
+            };
+            break;
+        case OperandLifeTime::CONSTANT_COPY:
+        case OperandLifeTime::CONSTANT_REFERENCE:
+        case OperandLifeTime::MODEL_INPUT:
+            ret = {
+                    OperandLifeTime::TEMPORARY_VARIABLE,
+                    OperandLifeTime::MODEL_OUTPUT,
+            };
+            break;
+        case OperandLifeTime::NO_VALUE:
+            // Not enough information to know whether
+            // TEMPORARY_VARIABLE or CONSTANT_COPY would be invalid --
+            // is this operand written (then CONSTANT_COPY would be
+            // invalid) or not (then TEMPORARY_VARIABLE would be
+            // invalid)?
+            break;
+        default:
+            ADD_FAILURE();
+            break;
+    }
+
+    const size_t operandSize = sizeOfData(operand);  // will be zero if shape is unknown
+    if (!operandSize ||
+        exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+        // Unknown size or too-large size
+        ret.erase(std::remove(ret.begin(), ret.end(), OperandLifeTime::CONSTANT_COPY), ret.end());
+    }
+
+    return ret;
+}
+
+static void mutateOperandLifeTimeTest(const sp<IDevice>& device, const Model& model) {
+    const size_t modelSize = sizeForBinder(model);
+    for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+        const std::vector<OperandLifeTime> invalidLifeTimes =
+                getInvalidLifeTimes(model, modelSize, model.operands[operand]);
+        for (OperandLifeTime invalidLifeTime : invalidLifeTimes) {
+            const std::string message = "mutateOperandLifetimeTest: operand " +
+                                        std::to_string(operand) + " has lifetime " +
+                                        toString(invalidLifeTime) + " instead of lifetime " +
+                                        toString(model.operands[operand].lifetime);
+            validate(device, message, model,
+                     [operand, invalidLifeTime](Model* model, ExecutionPreference*) {
+                         static const DataLocation kZeroDataLocation = {};
+                         Operand& operandObj = model->operands[operand];
+                         switch (operandObj.lifetime) {
+                             case OperandLifeTime::MODEL_INPUT: {
+                                 hidl_vec_remove(&model->inputIndexes, uint32_t(operand));
+                                 break;
+                             }
+                             case OperandLifeTime::MODEL_OUTPUT: {
+                                 hidl_vec_remove(&model->outputIndexes, uint32_t(operand));
+                                 break;
+                             }
+                             default:
+                                 break;
+                         }
+                         operandObj.lifetime = invalidLifeTime;
+                         operandObj.location = kZeroDataLocation;
+                         switch (invalidLifeTime) {
+                             case OperandLifeTime::CONSTANT_COPY: {
+                                 becomeConstantCopy(model, &operandObj);
+                                 break;
+                             }
+                             case OperandLifeTime::MODEL_INPUT:
+                                 hidl_vec_push_back(&model->inputIndexes, uint32_t(operand));
+                                 break;
+                             case OperandLifeTime::MODEL_OUTPUT:
+                                 hidl_vec_push_back(&model->outputIndexes, uint32_t(operand));
+                                 break;
+                             default:
+                                 break;
+                         }
+                     });
+        }
+    }
+}
+
+///////////////////////// VALIDATE OPERAND INPUT-or-OUTPUT //////////////////////////////////////
+
+static std::optional<OperandLifeTime> getInputOutputLifeTime(const Model& model, size_t modelSize,
+                                                             const Operand& operand) {
+    // Ways to get an invalid lifetime (with respect to model inputIndexes and outputIndexes):
+    // - change whether a lifetime means an operand is a model input, a model output, or neither
+    // - preserve whether or not a lifetime means an operand should have a writer
+    switch (operand.lifetime) {
+        case OperandLifeTime::CONSTANT_COPY:
+        case OperandLifeTime::CONSTANT_REFERENCE:
+            return OperandLifeTime::MODEL_INPUT;
+        case OperandLifeTime::MODEL_INPUT: {
+            const size_t operandSize = sizeOfData(operand);  // will be zero if shape is unknown
+            if (!operandSize ||
+                exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+                // Unknown size or too-large size
+                break;
+            }
+            return OperandLifeTime::CONSTANT_COPY;
+        }
+        case OperandLifeTime::MODEL_OUTPUT:
+            return OperandLifeTime::TEMPORARY_VARIABLE;
+        case OperandLifeTime::TEMPORARY_VARIABLE:
+            return OperandLifeTime::MODEL_OUTPUT;
+        case OperandLifeTime::NO_VALUE:
+            // Not enough information to know whether
+            // TEMPORARY_VARIABLE or CONSTANT_COPY would be an
+            // appropriate choice -- is this operand written (then
+            // TEMPORARY_VARIABLE would be appropriate) or not (then
+            // CONSTANT_COPY would be appropriate)?
+            break;
+        default:
+            ADD_FAILURE();
+            break;
+    }
+
+    return std::nullopt;
+}
+
+static void mutateOperandInputOutputTest(const sp<IDevice>& device, const Model& model) {
+    const size_t modelSize = sizeForBinder(model);
+    for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+        const std::optional<OperandLifeTime> changedLifeTime =
+                getInputOutputLifeTime(model, modelSize, model.operands[operand]);
+        if (changedLifeTime) {
+            const std::string message = "mutateOperandInputOutputTest: operand " +
+                                        std::to_string(operand) + " has lifetime " +
+                                        toString(*changedLifeTime) + " instead of lifetime " +
+                                        toString(model.operands[operand].lifetime);
+            validate(device, message, model,
+                     [operand, changedLifeTime](Model* model, ExecutionPreference*) {
+                         static const DataLocation kZeroDataLocation = {};
+                         Operand& operandObj = model->operands[operand];
+                         operandObj.lifetime = *changedLifeTime;
+                         operandObj.location = kZeroDataLocation;
+                         if (*changedLifeTime == OperandLifeTime::CONSTANT_COPY) {
+                             becomeConstantCopy(model, &operandObj);
+                         }
+                     });
+        }
+    }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF CONSUMERS //////////////////////////////////
+
+static std::vector<uint32_t> getInvalidNumberOfConsumers(uint32_t numberOfConsumers) {
+    if (numberOfConsumers == 0) {
+        return {1};
+    } else {
+        return {numberOfConsumers - 1, numberOfConsumers + 1};
+    }
+}
+
+static void mutateOperandNumberOfConsumersTest(const sp<IDevice>& device, const Model& model) {
+    for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+        const std::vector<uint32_t> invalidNumberOfConsumersVec =
+                getInvalidNumberOfConsumers(model.operands[operand].numberOfConsumers);
+        for (uint32_t invalidNumberOfConsumers : invalidNumberOfConsumersVec) {
+            const std::string message =
+                    "mutateOperandNumberOfConsumersTest: operand " + std::to_string(operand) +
+                    " numberOfConsumers = " + std::to_string(invalidNumberOfConsumers);
+            validate(device, message, model,
+                     [operand, invalidNumberOfConsumers](Model* model, ExecutionPreference*) {
+                         model->operands[operand].numberOfConsumers = invalidNumberOfConsumers;
+                     });
+        }
+    }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF WRITERS ////////////////////////////////////
+
+static void mutateOperandAddWriterTest(const sp<IDevice>& device, const Model& model) {
+    for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+        for (size_t badOutputNum = 0; badOutputNum < model.operations[operation].outputs.size();
+             ++badOutputNum) {
+            const uint32_t outputOperandIndex = model.operations[operation].outputs[badOutputNum];
+            const std::string message = "mutateOperandAddWriterTest: operation " +
+                                        std::to_string(operation) + " writes to " +
+                                        std::to_string(outputOperandIndex);
+            // We'll insert a copy of the operation, all of whose
+            // OTHER output operands are newly-created -- i.e.,
+            // there'll only be a duplicate write of ONE of that
+            // operation's output operands.
+            validate(device, message, model,
+                     [operation, badOutputNum](Model* model, ExecutionPreference*) {
+                         Operation newOperation = model->operations[operation];
+                         for (uint32_t input : newOperation.inputs) {
+                             ++model->operands[input].numberOfConsumers;
+                         }
+                         for (size_t outputNum = 0; outputNum < newOperation.outputs.size();
+                              ++outputNum) {
+                             if (outputNum == badOutputNum) continue;
+
+                             Operand operandValue =
+                                     model->operands[newOperation.outputs[outputNum]];
+                             operandValue.numberOfConsumers = 0;
+                             if (operandValue.lifetime == OperandLifeTime::MODEL_OUTPUT) {
+                                 operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+                             } else {
+                                 ASSERT_EQ(operandValue.lifetime,
+                                           OperandLifeTime::TEMPORARY_VARIABLE);
+                             }
+                             newOperation.outputs[outputNum] =
+                                     hidl_vec_push_back(&model->operands, operandValue);
+                         }
+                         // Where do we insert the extra writer (a new
+                         // operation)?  It has to be later than all the
+                         // writers of its inputs.  The easiest thing to do
+                         // is to insert it at the end of the operation
+                         // sequence.
+                         hidl_vec_push_back(&model->operations, newOperation);
+                     });
+        }
+    }
+}
+
 ///////////////////////// VALIDATE EXTRA ??? /////////////////////////
 
-// TODO: Operand::lifetime
 // TODO: Operand::location
 
 ///////////////////////// VALIDATE OPERATION OPERAND TYPE /////////////////////////
@@ -461,6 +942,37 @@
     }
 }
 
+///////////////////////// VALIDATE MODEL OPERANDS WRITTEN ///////////////////////////////////////
+
+static void mutateOperationRemoveWriteTest(const sp<IDevice>& device, const Model& model) {
+    for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+        for (size_t outputNum = 0; outputNum < model.operations[operation].outputs.size();
+             ++outputNum) {
+            const uint32_t outputOperandIndex = model.operations[operation].outputs[outputNum];
+            if (model.operands[outputOperandIndex].numberOfConsumers > 0) {
+                const std::string message = "mutateOperationRemoveWriteTest: operation " +
+                                            std::to_string(operation) + " writes to " +
+                                            std::to_string(outputOperandIndex);
+                validate(device, message, model,
+                         [operation, outputNum](Model* model, ExecutionPreference*) {
+                             uint32_t& outputOperandIndex =
+                                     model->operations[operation].outputs[outputNum];
+                             Operand operandValue = model->operands[outputOperandIndex];
+                             operandValue.numberOfConsumers = 0;
+                             if (operandValue.lifetime == OperandLifeTime::MODEL_OUTPUT) {
+                                 operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+                             } else {
+                                 ASSERT_EQ(operandValue.lifetime,
+                                           OperandLifeTime::TEMPORARY_VARIABLE);
+                             }
+                             outputOperandIndex =
+                                     hidl_vec_push_back(&model->operands, operandValue);
+                         });
+            }
+        }
+    }
+}
+
 ///////////////////////// REMOVE OPERAND FROM EVERYTHING /////////////////////////
 
 static void removeValueAndDecrementGreaterValues(hidl_vec<uint32_t>* vec, uint32_t value) {
@@ -711,14 +1223,20 @@
 ////////////////////////// ENTRY POINT //////////////////////////////
 
 void validateModel(const sp<IDevice>& device, const Model& model) {
+    mutateExecutionOrderTest(device, model);
     mutateOperandTypeTest(device, model);
     mutateOperandRankTest(device, model);
     mutateOperandScaleTest(device, model);
     mutateOperandZeroPointTest(device, model);
+    mutateOperandLifeTimeTest(device, model);
+    mutateOperandInputOutputTest(device, model);
+    mutateOperandNumberOfConsumersTest(device, model);
+    mutateOperandAddWriterTest(device, model);
     mutateOperationOperandTypeTest(device, model);
     mutateOperationTypeTest(device, model);
     mutateOperationInputOperandIndexTest(device, model);
     mutateOperationOutputOperandIndexTest(device, model);
+    mutateOperationRemoveWriteTest(device, model);
     removeOperandTest(device, model);
     removeOperationTest(device, model);
     removeOperationInputTest(device, model);
diff --git a/neuralnetworks/1.2/vts/functional/include/1.2/Utils.h b/neuralnetworks/1.2/vts/functional/include/1.2/Utils.h
new file mode 100644
index 0000000..61a8d74
--- /dev/null
+++ b/neuralnetworks/1.2/vts/functional/include/1.2/Utils.h
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_NEURALNETWORKS_V1_2_UTILS_H
+#define ANDROID_HARDWARE_NEURALNETWORKS_V1_2_UTILS_H
+
+#include <android/hardware/neuralnetworks/1.2/types.h>
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+
+// Returns the amount of space needed to store a value of the specified type.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(V1_2::OperandType type);
+
+// Returns the amount of space needed to store a value of the dimensions and
+// type of this operand. For a non-extension, non-OEM tensor with unspecified
+// rank or at least one unspecified dimension, returns zero.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(const V1_2::Operand& operand);
+
+}  // namespace neuralnetworks
+}  // namespace hardware
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_NEURALNETWORKS_V1_2_UTILS_H
diff --git a/neuralnetworks/1.3/vts/functional/Android.bp b/neuralnetworks/1.3/vts/functional/Android.bp
index 545a5be..457e36e 100644
--- a/neuralnetworks/1.3/vts/functional/Android.bp
+++ b/neuralnetworks/1.3/vts/functional/Android.bp
@@ -54,7 +54,7 @@
     ],
     static_libs: [
         "VtsHalNeuralNetworksV1_0_utils",
-        "VtsHalNeuralNetworksV1_2Callbacks",
+        "VtsHalNeuralNetworksV1_2_utils",
         "VtsHalNeuralNetworksV1_3_utils",
         "android.hardware.neuralnetworks@1.0",
         "android.hardware.neuralnetworks@1.1",
diff --git a/neuralnetworks/1.3/vts/functional/BasicTests.cpp b/neuralnetworks/1.3/vts/functional/BasicTests.cpp
index 1c25369..6fcfc34 100644
--- a/neuralnetworks/1.3/vts/functional/BasicTests.cpp
+++ b/neuralnetworks/1.3/vts/functional/BasicTests.cpp
@@ -20,11 +20,14 @@
 
 namespace android::hardware::neuralnetworks::V1_3::vts::functional {
 
+using implementation::PreparedModelCallback;
 using V1_0::DeviceStatus;
 using V1_0::PerformanceInfo;
+using V1_1::ExecutionPreference;
 using V1_2::Constant;
 using V1_2::DeviceType;
 using V1_2::Extension;
+using HidlToken = hidl_array<uint8_t, static_cast<uint32_t>(Constant::BYTE_SIZE_OF_CACHE_TOKEN)>;
 
 // create device test
 TEST_P(NeuralnetworksHidlTest, CreateDevice) {}
@@ -65,4 +68,143 @@
     });
     EXPECT_TRUE(ret.isOk());
 }
+
+// detect cycle
+TEST_P(NeuralnetworksHidlTest, CycleTest) {
+    // opnd0 = TENSOR_FLOAT32            // model input
+    // opnd1 = TENSOR_FLOAT32            // model input
+    // opnd2 = INT32                     // model input
+    // opnd3 = ADD(opnd0, opnd4, opnd2)
+    // opnd4 = ADD(opnd1, opnd3, opnd2)
+    // opnd5 = ADD(opnd4, opnd0, opnd2)  // model output
+    //
+    //            +-----+
+    //            |     |
+    //            v     |
+    // 3 = ADD(0, 4, 2) |
+    // |                |
+    // +----------+     |
+    //            |     |
+    //            v     |
+    // 4 = ADD(1, 3, 2) |
+    // |                |
+    // +----------------+
+    // |
+    // |
+    // +-------+
+    //         |
+    //         v
+    // 5 = ADD(4, 0, 2)
+
+    const std::vector<Operand> operands = {
+            {
+                    // operands[0]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .numberOfConsumers = 2,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::SUBGRAPH_INPUT,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[1]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .numberOfConsumers = 1,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::SUBGRAPH_INPUT,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[2]
+                    .type = OperandType::INT32,
+                    .dimensions = {},
+                    .numberOfConsumers = 3,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::SUBGRAPH_INPUT,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[3]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .numberOfConsumers = 1,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[4]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .numberOfConsumers = 2,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+            {
+                    // operands[5]
+                    .type = OperandType::TENSOR_FLOAT32,
+                    .dimensions = {1},
+                    .numberOfConsumers = 0,
+                    .scale = 0.0f,
+                    .zeroPoint = 0,
+                    .lifetime = OperandLifeTime::SUBGRAPH_OUTPUT,
+                    .location = {.poolIndex = 0, .offset = 0, .length = 0},
+            },
+    };
+
+    const std::vector<Operation> operations = {
+            {.type = OperationType::ADD, .inputs = {0, 4, 2}, .outputs = {3}},
+            {.type = OperationType::ADD, .inputs = {1, 3, 2}, .outputs = {4}},
+            {.type = OperationType::ADD, .inputs = {4, 0, 2}, .outputs = {5}},
+    };
+
+    Subgraph subgraph = {
+            .operands = operands,
+            .operations = operations,
+            .inputIndexes = {0, 1, 2},
+            .outputIndexes = {5},
+    };
+    const Model model = {
+            .main = std::move(subgraph),
+            .referenced = {},
+            .operandValues = {},
+            .pools = {},
+    };
+
+    // ensure that getSupportedOperations_1_2() checks model validity
+    ErrorStatus supportedOpsErrorStatus = ErrorStatus::GENERAL_FAILURE;
+    Return<void> supportedOpsReturn = kDevice->getSupportedOperations_1_3(
+            model, [&model, &supportedOpsErrorStatus](ErrorStatus status,
+                                                      const hidl_vec<bool>& supported) {
+                supportedOpsErrorStatus = status;
+                if (status == ErrorStatus::NONE) {
+                    ASSERT_EQ(supported.size(), model.main.operations.size());
+                }
+            });
+    ASSERT_TRUE(supportedOpsReturn.isOk());
+    ASSERT_EQ(supportedOpsErrorStatus, ErrorStatus::INVALID_ARGUMENT);
+
+    // ensure that prepareModel_1_3() checks model validity
+    sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback;
+    Return<ErrorStatus> prepareLaunchReturn = kDevice->prepareModel_1_3(
+            model, ExecutionPreference::FAST_SINGLE_ANSWER, Priority::MEDIUM, {},
+            hidl_vec<hidl_handle>(), hidl_vec<hidl_handle>(), HidlToken(), preparedModelCallback);
+    ASSERT_TRUE(prepareLaunchReturn.isOk());
+    //     Note that preparation can fail for reasons other than an
+    //     invalid model (invalid model should result in
+    //     INVALID_ARGUMENT) -- for example, perhaps not all
+    //     operations are supported, or perhaps the device hit some
+    //     kind of capacity limit.
+    EXPECT_NE(prepareLaunchReturn, ErrorStatus::NONE);
+    EXPECT_NE(preparedModelCallback->getStatus(), ErrorStatus::NONE);
+    EXPECT_EQ(preparedModelCallback->getPreparedModel(), nullptr);
+}
+
 }  // namespace android::hardware::neuralnetworks::V1_3::vts::functional
diff --git a/neuralnetworks/1.3/vts/functional/Utils.cpp b/neuralnetworks/1.3/vts/functional/Utils.cpp
index 23e2af8..c460e11 100644
--- a/neuralnetworks/1.3/vts/functional/Utils.cpp
+++ b/neuralnetworks/1.3/vts/functional/Utils.cpp
@@ -17,11 +17,78 @@
 #include "1.3/Utils.h"
 
 #include <iostream>
+#include <numeric>
+#include "android-base/logging.h"
+#include "android/hardware/neuralnetworks/1.3/types.h"
 
-namespace android::hardware::neuralnetworks::V1_3 {
+namespace android::hardware::neuralnetworks {
+
+uint32_t sizeOfData(V1_3::OperandType type) {
+    switch (type) {
+        case V1_3::OperandType::FLOAT32:
+        case V1_3::OperandType::INT32:
+        case V1_3::OperandType::UINT32:
+        case V1_3::OperandType::TENSOR_FLOAT32:
+        case V1_3::OperandType::TENSOR_INT32:
+            return 4;
+        case V1_3::OperandType::TENSOR_QUANT16_SYMM:
+        case V1_3::OperandType::TENSOR_FLOAT16:
+        case V1_3::OperandType::FLOAT16:
+        case V1_3::OperandType::TENSOR_QUANT16_ASYMM:
+            return 2;
+        case V1_3::OperandType::TENSOR_QUANT8_ASYMM:
+        case V1_3::OperandType::BOOL:
+        case V1_3::OperandType::TENSOR_BOOL8:
+        case V1_3::OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+        case V1_3::OperandType::TENSOR_QUANT8_SYMM:
+        case V1_3::OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
+            return 1;
+        case V1_3::OperandType::SUBGRAPH:
+            return 0;
+        default:
+            CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+            return 0;
+    }
+}
+
+static bool isTensor(V1_3::OperandType type) {
+    switch (type) {
+        case V1_3::OperandType::FLOAT32:
+        case V1_3::OperandType::INT32:
+        case V1_3::OperandType::UINT32:
+        case V1_3::OperandType::FLOAT16:
+        case V1_3::OperandType::BOOL:
+        case V1_3::OperandType::SUBGRAPH:
+            return false;
+        case V1_3::OperandType::TENSOR_FLOAT32:
+        case V1_3::OperandType::TENSOR_INT32:
+        case V1_3::OperandType::TENSOR_QUANT16_SYMM:
+        case V1_3::OperandType::TENSOR_FLOAT16:
+        case V1_3::OperandType::TENSOR_QUANT16_ASYMM:
+        case V1_3::OperandType::TENSOR_QUANT8_ASYMM:
+        case V1_3::OperandType::TENSOR_BOOL8:
+        case V1_3::OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+        case V1_3::OperandType::TENSOR_QUANT8_SYMM:
+        case V1_3::OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
+            return true;
+        default:
+            CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+            return false;
+    }
+}
+
+uint32_t sizeOfData(const V1_3::Operand& operand) {
+    const uint32_t dataSize = sizeOfData(operand.type);
+    if (isTensor(operand.type) && operand.dimensions.size() == 0) return 0;
+    return std::accumulate(operand.dimensions.begin(), operand.dimensions.end(), dataSize,
+                           std::multiplies<>{});
+}
+
+namespace V1_3 {
 
 ::std::ostream& operator<<(::std::ostream& os, ErrorStatus errorStatus) {
     return os << toString(errorStatus);
 }
 
-}  // namespace android::hardware::neuralnetworks::V1_3
+}  // namespace V1_3
+}  // namespace android::hardware::neuralnetworks
diff --git a/neuralnetworks/1.3/vts/functional/ValidateModel.cpp b/neuralnetworks/1.3/vts/functional/ValidateModel.cpp
index e590fda..849ef7b 100644
--- a/neuralnetworks/1.3/vts/functional/ValidateModel.cpp
+++ b/neuralnetworks/1.3/vts/functional/ValidateModel.cpp
@@ -16,15 +16,22 @@
 
 #define LOG_TAG "neuralnetworks_hidl_hal_test"
 
+#include <android/hardware/neuralnetworks/1.1/types.h>
+#include <android/hardware/neuralnetworks/1.3/types.h>
 #include "1.0/Utils.h"
 #include "1.3/Callbacks.h"
 #include "1.3/Utils.h"
 #include "GeneratedTestHarness.h"
 #include "VtsHalNeuralnetworks.h"
 
+#include <optional>
+#include <type_traits>
+#include <utility>
+
 namespace android::hardware::neuralnetworks::V1_3::vts::functional {
 
 using implementation::PreparedModelCallback;
+using V1_0::DataLocation;
 using V1_1::ExecutionPreference;
 using V1_2::SymmPerChannelQuantParams;
 using HidlToken =
@@ -112,6 +119,262 @@
     return index;
 }
 
+// If we introduce a CONSTANT_COPY for an operand of size operandSize,
+// how much will this increase the size of the model?  This assumes
+// that we can (re)use all of model.operandValues for the operand
+// value.
+static size_t constantCopyExtraSize(const Model& model, size_t operandSize) {
+    const size_t operandValuesSize = model.operandValues.size();
+    return (operandValuesSize < operandSize) ? (operandSize - operandValuesSize) : 0;
+}
+
+// Highly specialized utility routine for converting an operand to
+// CONSTANT_COPY lifetime.
+//
+// Expects that:
+// - operand has a known size
+// - operand->lifetime has already been set to CONSTANT_COPY
+// - operand->location has been zeroed out
+//
+// Does the following:
+// - initializes operand->location to point to the beginning of model->operandValues
+// - resizes model->operandValues (if necessary) to be large enough for the operand
+//   value, padding it with zeroes on the end
+//
+// Potential problem:
+// By changing the operand to CONSTANT_COPY lifetime, this function is effectively initializing the
+// operand with unspecified (but deterministic) data. This means that the model may be invalidated
+// in two ways: not only is the lifetime of CONSTANT_COPY invalid, but the operand's value in the
+// graph may also be invalid (e.g., if the operand is used as an activation code and has an invalid
+// value). For now, this should be fine because it just means we're not testing what we think we're
+// testing in certain cases; but we can handwave this and assume we're probabilistically likely to
+// exercise the validation code over the span of the entire test set and operand space.
+//
+// Aborts if the specified operand type is an extension type or OEM type.
+static void becomeConstantCopy(Model* model, Operand* operand) {
+    // sizeOfData will abort if the specified type is an extension type or OEM type.
+    const size_t sizeOfOperand = sizeOfData(*operand);
+    EXPECT_NE(sizeOfOperand, size_t(0));
+    operand->location.poolIndex = 0;
+    operand->location.offset = 0;
+    operand->location.length = sizeOfOperand;
+    if (model->operandValues.size() < sizeOfOperand) {
+        model->operandValues.resize(sizeOfOperand);
+    }
+}
+
+// The sizeForBinder() functions estimate the size of the
+// representation of a value when sent to binder.  It's probably a bit
+// of an under-estimate, because we don't know the size of the
+// metadata in the binder format (e.g., representation of the size of
+// a vector); but at least it adds up "big" things like vector
+// contents.  However, it doesn't treat inter-field or end-of-struct
+// padding in a methodical way -- there's no attempt to be consistent
+// in whether or not padding in the native (C++) representation
+// contributes to the estimated size for the binder representation;
+// and there's no attempt to understand what padding (if any) is
+// needed in the binder representation.
+//
+// This assumes that non-metadata uses a fixed length encoding (e.g.,
+// a uint32_t is always encoded in sizeof(uint32_t) bytes, rather than
+// using an encoding whose length is related to the magnitude of the
+// encoded value).
+
+template <typename Type>
+static size_t sizeForBinder(const Type& val) {
+    static_assert(std::is_trivially_copyable_v<std::remove_reference_t<Type>>,
+                  "expected a trivially copyable type");
+    return sizeof(val);
+}
+
+template <typename Type>
+static size_t sizeForBinder(const hidl_vec<Type>& vec) {
+    return std::accumulate(vec.begin(), vec.end(), 0,
+                           [](size_t acc, const Type& x) { return acc + sizeForBinder(x); });
+}
+
+template <>
+size_t sizeForBinder(const SymmPerChannelQuantParams& symmPerChannelQuantParams) {
+    size_t size = 0;
+
+    size += sizeForBinder(symmPerChannelQuantParams.scales);
+    size += sizeForBinder(symmPerChannelQuantParams.channelDim);
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const V1_2::Operand::ExtraParams& extraParams) {
+    using Discriminator = V1_2::Operand::ExtraParams::hidl_discriminator;
+    switch (extraParams.getDiscriminator()) {
+        case Discriminator::none:
+            return 0;
+        case Discriminator::channelQuant:
+            return sizeForBinder(extraParams.channelQuant());
+        case Discriminator::extension:
+            return sizeForBinder(extraParams.extension());
+    }
+    LOG(FATAL) << "Unrecognized extraParams enum: "
+               << static_cast<int>(extraParams.getDiscriminator());
+    return 0;
+}
+
+template <>
+size_t sizeForBinder(const Operand& operand) {
+    size_t size = 0;
+
+    size += sizeForBinder(operand.type);
+    size += sizeForBinder(operand.dimensions);
+    size += sizeForBinder(operand.numberOfConsumers);
+    size += sizeForBinder(operand.scale);
+    size += sizeForBinder(operand.zeroPoint);
+    size += sizeForBinder(operand.lifetime);
+    size += sizeForBinder(operand.location);
+    size += sizeForBinder(operand.extraParams);
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const Operation& operation) {
+    size_t size = 0;
+
+    size += sizeForBinder(operation.type);
+    size += sizeForBinder(operation.inputs);
+    size += sizeForBinder(operation.outputs);
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const hidl_string& name) {
+    return name.size();
+}
+
+template <>
+size_t sizeForBinder(const hidl_memory& memory) {
+    // This is just a guess.
+
+    size_t size = 0;
+
+    if (const native_handle_t* handle = memory.handle()) {
+        size += sizeof(*handle);
+        size += sizeof(handle->data[0] * (handle->numFds + handle->numInts));
+    }
+    size += sizeForBinder(memory.name());
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const Subgraph& subgraph) {
+    size_t size = 0;
+
+    size += sizeForBinder(subgraph.operands);
+    size += sizeForBinder(subgraph.operations);
+    size += sizeForBinder(subgraph.inputIndexes);
+    size += sizeForBinder(subgraph.outputIndexes);
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const V1_2::Model::ExtensionNameAndPrefix& extensionNameToPrefix) {
+    size_t size = 0;
+
+    size += sizeForBinder(extensionNameToPrefix.name);
+    size += sizeForBinder(extensionNameToPrefix.prefix);
+
+    return size;
+}
+
+template <>
+size_t sizeForBinder(const Model& model) {
+    size_t size = 0;
+
+    size += sizeForBinder(model.main);
+    size += sizeForBinder(model.referenced);
+    size += sizeForBinder(model.operandValues);
+    size += sizeForBinder(model.pools);
+    size += sizeForBinder(model.relaxComputationFloat32toFloat16);
+    size += sizeForBinder(model.extensionNameToPrefix);
+
+    return size;
+}
+
+// https://developer.android.com/reference/android/os/TransactionTooLargeException.html
+//
+//     "The Binder transaction buffer has a limited fixed size,
+//     currently 1Mb, which is shared by all transactions in progress
+//     for the process."
+//
+// Will our representation fit under this limit?  There are two complications:
+// - Our representation size is just approximate (see sizeForBinder()).
+// - This object may not be the only occupant of the Binder transaction buffer.
+// So we'll be very conservative: We want the representation size to be no
+// larger than half the transaction buffer size.
+//
+// If our representation grows large enough that it still fits within
+// the transaction buffer but combined with other transactions may
+// exceed the buffer size, then we may see intermittent HAL transport
+// errors.
+static bool exceedsBinderSizeLimit(size_t representationSize) {
+    // Instead of using this fixed buffer size, we might instead be able to use
+    // ProcessState::self()->getMmapSize(). However, this has a potential
+    // problem: The binder/mmap size of the current process does not necessarily
+    // indicate the binder/mmap size of the service (i.e., the other process).
+    // The only way it would be a good indication is if both the current process
+    // and the service use the default size.
+    static const size_t kHalfBufferSize = 1024 * 1024 / 2;
+
+    return representationSize > kHalfBufferSize;
+}
+
+///////////////////////// VALIDATE EXECUTION ORDER ////////////////////////////
+
+static void mutateExecutionOrderTest(const sp<IDevice>& device, const Model& model) {
+    for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+        const Operation& operationObj = model.main.operations[operation];
+        for (uint32_t input : operationObj.inputs) {
+            if (model.main.operands[input].lifetime == OperandLifeTime::TEMPORARY_VARIABLE ||
+                model.main.operands[input].lifetime == OperandLifeTime::SUBGRAPH_OUTPUT) {
+                // This operation reads an operand written by some
+                // other operation.  Move this operation to the
+                // beginning of the sequence, ensuring that it reads
+                // the operand before that operand is written, thereby
+                // violating execution order rules.
+                const std::string message = "mutateExecutionOrderTest: operation " +
+                                            std::to_string(operation) + " is a reader";
+                validate(device, message, model,
+                         [operation](Model* model, ExecutionPreference*, Priority*) {
+                             auto& operations = model->main.operations;
+                             std::rotate(operations.begin(), operations.begin() + operation,
+                                         operations.begin() + operation + 1);
+                         });
+                break;  // only need to do this once per operation
+            }
+        }
+        for (uint32_t output : operationObj.outputs) {
+            if (model.main.operands[output].numberOfConsumers > 0) {
+                // This operation writes an operand read by some other
+                // operation.  Move this operation to the end of the
+                // sequence, ensuring that it writes the operand after
+                // that operand is read, thereby violating execution
+                // order rules.
+                const std::string message = "mutateExecutionOrderTest: operation " +
+                                            std::to_string(operation) + " is a writer";
+                validate(device, message, model,
+                         [operation](Model* model, ExecutionPreference*, Priority*) {
+                             auto& operations = model->main.operations;
+                             std::rotate(operations.begin() + operation,
+                                         operations.begin() + operation + 1, operations.end());
+                         });
+                break;  // only need to do this once per operation
+            }
+        }
+    }
+}
+
 ///////////////////////// VALIDATE MODEL OPERAND TYPE /////////////////////////
 
 static const uint32_t invalidOperandTypes[] = {
@@ -261,9 +524,245 @@
     }
 }
 
+///////////////////////// VALIDATE OPERAND LIFETIME /////////////////////////////////////////////
+
+static std::vector<OperandLifeTime> getInvalidLifeTimes(const Model& model, size_t modelSize,
+                                                        const Operand& operand) {
+    // TODO: Support OperandLifeTime::CONSTANT_REFERENCE as an invalid lifetime
+    // TODO: Support OperandLifeTime::NO_VALUE as an invalid lifetime
+
+    // Ways to get an invalid lifetime:
+    // - change whether a lifetime means an operand should have a writer
+    std::vector<OperandLifeTime> ret;
+    switch (operand.lifetime) {
+        case OperandLifeTime::SUBGRAPH_OUTPUT:
+        case OperandLifeTime::TEMPORARY_VARIABLE:
+            ret = {
+                    OperandLifeTime::SUBGRAPH_INPUT,
+                    OperandLifeTime::CONSTANT_COPY,
+            };
+            break;
+        case OperandLifeTime::CONSTANT_COPY:
+        case OperandLifeTime::CONSTANT_REFERENCE:
+        case OperandLifeTime::SUBGRAPH_INPUT:
+            ret = {
+                    OperandLifeTime::TEMPORARY_VARIABLE,
+                    OperandLifeTime::SUBGRAPH_OUTPUT,
+            };
+            break;
+        case OperandLifeTime::NO_VALUE:
+            // Not enough information to know whether
+            // TEMPORARY_VARIABLE or CONSTANT_COPY would be invalid --
+            // is this operand written (then CONSTANT_COPY would be
+            // invalid) or not (then TEMPORARY_VARIABLE would be
+            // invalid)?
+            break;
+        case OperandLifeTime::SUBGRAPH:
+            break;
+        default:
+            ADD_FAILURE();
+            break;
+    }
+
+    const size_t operandSize = sizeOfData(operand);  // will be zero if shape is unknown
+    if (!operandSize ||
+        exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+        // Unknown size or too-large size
+        ret.erase(std::remove(ret.begin(), ret.end(), OperandLifeTime::CONSTANT_COPY), ret.end());
+    }
+
+    return ret;
+}
+
+static void mutateOperandLifeTimeTest(const sp<IDevice>& device, const Model& model) {
+    const size_t modelSize = sizeForBinder(model);
+    for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+        const std::vector<OperandLifeTime> invalidLifeTimes =
+                getInvalidLifeTimes(model, modelSize, model.main.operands[operand]);
+        for (OperandLifeTime invalidLifeTime : invalidLifeTimes) {
+            const std::string message = "mutateOperandLifetimeTest: operand " +
+                                        std::to_string(operand) + " has lifetime " +
+                                        toString(invalidLifeTime) + " instead of lifetime " +
+                                        toString(model.main.operands[operand].lifetime);
+            validate(device, message, model,
+                     [operand, invalidLifeTime](Model* model, ExecutionPreference*, Priority*) {
+                         static const DataLocation kZeroDataLocation = {};
+                         Operand& operandObj = model->main.operands[operand];
+                         switch (operandObj.lifetime) {
+                             case OperandLifeTime::SUBGRAPH_INPUT: {
+                                 hidl_vec_remove(&model->main.inputIndexes, uint32_t(operand));
+                                 break;
+                             }
+                             case OperandLifeTime::SUBGRAPH_OUTPUT: {
+                                 hidl_vec_remove(&model->main.outputIndexes, uint32_t(operand));
+                                 break;
+                             }
+                             default:
+                                 break;
+                         }
+                         operandObj.lifetime = invalidLifeTime;
+                         operandObj.location = kZeroDataLocation;
+                         switch (invalidLifeTime) {
+                             case OperandLifeTime::CONSTANT_COPY: {
+                                 becomeConstantCopy(model, &operandObj);
+                                 break;
+                             }
+                             case OperandLifeTime::SUBGRAPH_INPUT:
+                                 hidl_vec_push_back(&model->main.inputIndexes, uint32_t(operand));
+                                 break;
+                             case OperandLifeTime::SUBGRAPH_OUTPUT:
+                                 hidl_vec_push_back(&model->main.outputIndexes, uint32_t(operand));
+                                 break;
+                             default:
+                                 break;
+                         }
+                     });
+        }
+    }
+}
+
+///////////////////////// VALIDATE OPERAND INPUT-or-OUTPUT //////////////////////////////////////
+
+static std::optional<OperandLifeTime> getInputOutputLifeTime(const Model& model, size_t modelSize,
+                                                             const Operand& operand) {
+    // Ways to get an invalid lifetime (with respect to model inputIndexes and outputIndexes):
+    // - change whether a lifetime means an operand is a model input, a model output, or neither
+    // - preserve whether or not a lifetime means an operand should have a writer
+    switch (operand.lifetime) {
+        case OperandLifeTime::CONSTANT_COPY:
+        case OperandLifeTime::CONSTANT_REFERENCE:
+            return OperandLifeTime::SUBGRAPH_INPUT;
+        case OperandLifeTime::SUBGRAPH_INPUT: {
+            const size_t operandSize = sizeOfData(operand);  // will be zero if shape is unknown
+            if (!operandSize ||
+                exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+                // Unknown size or too-large size
+                break;
+            }
+            return OperandLifeTime::CONSTANT_COPY;
+        }
+        case OperandLifeTime::SUBGRAPH_OUTPUT:
+            return OperandLifeTime::TEMPORARY_VARIABLE;
+        case OperandLifeTime::TEMPORARY_VARIABLE:
+            return OperandLifeTime::SUBGRAPH_OUTPUT;
+        case OperandLifeTime::NO_VALUE:
+            // Not enough information to know whether
+            // TEMPORARY_VARIABLE or CONSTANT_COPY would be an
+            // appropriate choice -- is this operand written (then
+            // TEMPORARY_VARIABLE would be appropriate) or not (then
+            // CONSTANT_COPY would be appropriate)?
+            break;
+        case OperandLifeTime::SUBGRAPH:
+            break;
+        default:
+            ADD_FAILURE();
+            break;
+    }
+
+    return std::nullopt;
+}
+
+static void mutateOperandInputOutputTest(const sp<IDevice>& device, const Model& model) {
+    const size_t modelSize = sizeForBinder(model);
+    for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+        const std::optional<OperandLifeTime> changedLifeTime =
+                getInputOutputLifeTime(model, modelSize, model.main.operands[operand]);
+        if (changedLifeTime) {
+            const std::string message = "mutateOperandInputOutputTest: operand " +
+                                        std::to_string(operand) + " has lifetime " +
+                                        toString(*changedLifeTime) + " instead of lifetime " +
+                                        toString(model.main.operands[operand].lifetime);
+            validate(device, message, model,
+                     [operand, changedLifeTime](Model* model, ExecutionPreference*, Priority*) {
+                         static const DataLocation kZeroDataLocation = {};
+                         Operand& operandObj = model->main.operands[operand];
+                         operandObj.lifetime = *changedLifeTime;
+                         operandObj.location = kZeroDataLocation;
+                         if (*changedLifeTime == OperandLifeTime::CONSTANT_COPY) {
+                             becomeConstantCopy(model, &operandObj);
+                         }
+                     });
+        }
+    }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF CONSUMERS //////////////////////////////////
+
+static std::vector<uint32_t> getInvalidNumberOfConsumers(uint32_t numberOfConsumers) {
+    if (numberOfConsumers == 0) {
+        return {1};
+    } else {
+        return {numberOfConsumers - 1, numberOfConsumers + 1};
+    }
+}
+
+static void mutateOperandNumberOfConsumersTest(const sp<IDevice>& device, const Model& model) {
+    for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+        const std::vector<uint32_t> invalidNumberOfConsumersVec =
+                getInvalidNumberOfConsumers(model.main.operands[operand].numberOfConsumers);
+        for (uint32_t invalidNumberOfConsumers : invalidNumberOfConsumersVec) {
+            const std::string message =
+                    "mutateOperandNumberOfConsumersTest: operand " + std::to_string(operand) +
+                    " numberOfConsumers = " + std::to_string(invalidNumberOfConsumers);
+            validate(device, message, model,
+                     [operand, invalidNumberOfConsumers](Model* model, ExecutionPreference*,
+                                                         Priority*) {
+                         model->main.operands[operand].numberOfConsumers = invalidNumberOfConsumers;
+                     });
+        }
+    }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF WRITERS ////////////////////////////////////
+
+static void mutateOperandAddWriterTest(const sp<IDevice>& device, const Model& model) {
+    for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+        for (size_t badOutputNum = 0;
+             badOutputNum < model.main.operations[operation].outputs.size(); ++badOutputNum) {
+            const uint32_t outputOperandIndex =
+                    model.main.operations[operation].outputs[badOutputNum];
+            const std::string message = "mutateOperandAddWriterTest: operation " +
+                                        std::to_string(operation) + " writes to " +
+                                        std::to_string(outputOperandIndex);
+            // We'll insert a copy of the operation, all of whose
+            // OTHER output operands are newly-created -- i.e.,
+            // there'll only be a duplicate write of ONE of that
+            // operation's output operands.
+            validate(device, message, model,
+                     [operation, badOutputNum](Model* model, ExecutionPreference*, Priority*) {
+                         Operation newOperation = model->main.operations[operation];
+                         for (uint32_t input : newOperation.inputs) {
+                             ++model->main.operands[input].numberOfConsumers;
+                         }
+                         for (size_t outputNum = 0; outputNum < newOperation.outputs.size();
+                              ++outputNum) {
+                             if (outputNum == badOutputNum) continue;
+
+                             Operand operandValue =
+                                     model->main.operands[newOperation.outputs[outputNum]];
+                             operandValue.numberOfConsumers = 0;
+                             if (operandValue.lifetime == OperandLifeTime::SUBGRAPH_OUTPUT) {
+                                 operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+                             } else {
+                                 ASSERT_EQ(operandValue.lifetime,
+                                           OperandLifeTime::TEMPORARY_VARIABLE);
+                             }
+                             newOperation.outputs[outputNum] =
+                                     hidl_vec_push_back(&model->main.operands, operandValue);
+                         }
+                         // Where do we insert the extra writer (a new
+                         // operation)?  It has to be later than all the
+                         // writers of its inputs.  The easiest thing to do
+                         // is to insert it at the end of the operation
+                         // sequence.
+                         hidl_vec_push_back(&model->main.operations, newOperation);
+                     });
+        }
+    }
+}
+
 ///////////////////////// VALIDATE EXTRA ??? /////////////////////////
 
-// TODO: Operand::lifetime
 // TODO: Operand::location
 
 ///////////////////////// VALIDATE OPERATION OPERAND TYPE /////////////////////////
@@ -511,6 +1010,37 @@
     }
 }
 
+///////////////////////// VALIDATE MODEL OPERANDS WRITTEN ///////////////////////////////////////
+
+static void mutateOperationRemoveWriteTest(const sp<IDevice>& device, const Model& model) {
+    for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+        for (size_t outputNum = 0; outputNum < model.main.operations[operation].outputs.size();
+             ++outputNum) {
+            const uint32_t outputOperandIndex = model.main.operations[operation].outputs[outputNum];
+            if (model.main.operands[outputOperandIndex].numberOfConsumers > 0) {
+                const std::string message = "mutateOperationRemoveWriteTest: operation " +
+                                            std::to_string(operation) + " writes to " +
+                                            std::to_string(outputOperandIndex);
+                validate(device, message, model,
+                         [operation, outputNum](Model* model, ExecutionPreference*, Priority*) {
+                             uint32_t& outputOperandIndex =
+                                     model->main.operations[operation].outputs[outputNum];
+                             Operand operandValue = model->main.operands[outputOperandIndex];
+                             operandValue.numberOfConsumers = 0;
+                             if (operandValue.lifetime == OperandLifeTime::SUBGRAPH_OUTPUT) {
+                                 operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+                             } else {
+                                 ASSERT_EQ(operandValue.lifetime,
+                                           OperandLifeTime::TEMPORARY_VARIABLE);
+                             }
+                             outputOperandIndex =
+                                     hidl_vec_push_back(&model->main.operands, operandValue);
+                         });
+            }
+        }
+    }
+}
+
 ///////////////////////// REMOVE OPERAND FROM EVERYTHING /////////////////////////
 
 static void removeValueAndDecrementGreaterValues(hidl_vec<uint32_t>* vec, uint32_t value) {
@@ -804,14 +1334,20 @@
 ////////////////////////// ENTRY POINT //////////////////////////////
 
 void validateModel(const sp<IDevice>& device, const Model& model) {
+    mutateExecutionOrderTest(device, model);
     mutateOperandTypeTest(device, model);
     mutateOperandRankTest(device, model);
     mutateOperandScaleTest(device, model);
     mutateOperandZeroPointTest(device, model);
+    mutateOperandLifeTimeTest(device, model);
+    mutateOperandInputOutputTest(device, model);
+    mutateOperandNumberOfConsumersTest(device, model);
+    mutateOperandAddWriterTest(device, model);
     mutateOperationOperandTypeTest(device, model);
     mutateOperationTypeTest(device, model);
     mutateOperationInputOperandIndexTest(device, model);
     mutateOperationOutputOperandIndexTest(device, model);
+    mutateOperationRemoveWriteTest(device, model);
     removeOperandTest(device, model);
     removeOperationTest(device, model);
     removeOperationInputTest(device, model);
diff --git a/neuralnetworks/1.3/vts/functional/include/1.3/Utils.h b/neuralnetworks/1.3/vts/functional/include/1.3/Utils.h
index 3661b66..e07e73b 100644
--- a/neuralnetworks/1.3/vts/functional/include/1.3/Utils.h
+++ b/neuralnetworks/1.3/vts/functional/include/1.3/Utils.h
@@ -24,6 +24,18 @@
 
 inline constexpr V1_3::Priority kDefaultPriority = V1_3::Priority::MEDIUM;
 
+// Returns the amount of space needed to store a value of the specified type.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(V1_3::OperandType type);
+
+// Returns the amount of space needed to store a value of the dimensions and
+// type of this operand. For a non-extension, non-OEM tensor with unspecified
+// rank or at least one unspecified dimension, returns zero.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(const V1_3::Operand& operand);
+
 }  // namespace android::hardware::neuralnetworks
 
 namespace android::hardware::neuralnetworks::V1_3 {
diff --git a/sensors/2.0/multihal/HalProxy.cpp b/sensors/2.0/multihal/HalProxy.cpp
index ac6f17a..fbff24c 100644
--- a/sensors/2.0/multihal/HalProxy.cpp
+++ b/sensors/2.0/multihal/HalProxy.cpp
@@ -360,7 +360,7 @@
     } else {
         std::string subHalLibraryFile;
         while (subHalConfigStream >> subHalLibraryFile) {
-            void* handle = dlopen(subHalLibraryFile.c_str(), RTLD_NOW);
+            void* handle = getHandleForSubHalSharedObject(subHalLibraryFile);
             if (handle == nullptr) {
                 ALOGE("dlopen failed for library: %s", subHalLibraryFile.c_str());
             } else {
@@ -415,6 +415,25 @@
     }
 }
 
+void* HalProxy::getHandleForSubHalSharedObject(const std::string& filename) {
+    static const std::string kSubHalShareObjectLocations[] = {
+            "",  // Default locations will be searched
+#ifdef __LP64__
+            "/vendor/lib64/hw/", "/odm/lib64/", "/odm/lib64/hw/"
+#else
+            "/vendor/lib/hw/", "/odm/lib/", "/odm/lib/hw/"
+#endif
+    };
+
+    for (const std::string& dir : kSubHalShareObjectLocations) {
+        void* handle = dlopen((dir + filename).c_str(), RTLD_NOW);
+        if (handle != nullptr) {
+            return handle;
+        }
+    }
+    return nullptr;
+}
+
 void HalProxy::init() {
     initializeSubHalCallbacks();
     initializeSensorList();
diff --git a/sensors/2.0/multihal/include/HalProxy.h b/sensors/2.0/multihal/include/HalProxy.h
index 978f7cf..10bba55 100644
--- a/sensors/2.0/multihal/include/HalProxy.h
+++ b/sensors/2.0/multihal/include/HalProxy.h
@@ -262,6 +262,16 @@
     void initializeSensorList();
 
     /**
+     * Try using the default include directories as well as the directories defined in
+     * kSubHalShareObjectLocations to get a handle for dlsym for a subhal.
+     *
+     * @param filename The file name to search for.
+     *
+     * @return The handle or nullptr if search failed.
+     */
+    void* getHandleForSubHalSharedObject(const std::string& filename);
+
+    /**
      * Calls the helper methods that all ctors use.
      */
     void init();
diff --git a/sensors/2.0/vts/functional/Android.bp b/sensors/2.0/vts/functional/Android.bp
index 4765fa2..7755ef5 100644
--- a/sensors/2.0/vts/functional/Android.bp
+++ b/sensors/2.0/vts/functional/Android.bp
@@ -33,5 +33,9 @@
         "libfmq",
         "VtsHalSensorsTargetTestUtils",
     ],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
 }
 
diff --git a/sensors/2.0/vts/functional/VtsHalSensorsV2_0TargetTest.cpp b/sensors/2.0/vts/functional/VtsHalSensorsV2_0TargetTest.cpp
index d0ebe4d..e46f034 100644
--- a/sensors/2.0/vts/functional/VtsHalSensorsV2_0TargetTest.cpp
+++ b/sensors/2.0/vts/functional/VtsHalSensorsV2_0TargetTest.cpp
@@ -320,11 +320,13 @@
                          << s.sensorHandle << std::dec << " type=" << static_cast<int>(s.type)
                          << " name=" << s.name);
 
-            // Test non-empty type string
-            EXPECT_FALSE(s.typeAsString.empty());
-
-            // Test defined type matches defined string type
-            EXPECT_NO_FATAL_FAILURE(assertTypeMatchStringType(s.type, s.typeAsString));
+            // Test type string non-empty only for private sensor types.
+            if (s.type >= SensorType::DEVICE_PRIVATE_BASE) {
+                EXPECT_FALSE(s.typeAsString.empty());
+            } else if (!s.typeAsString.empty()) {
+                // Test type string matches framework string if specified for non-private types.
+                EXPECT_NO_FATAL_FAILURE(assertTypeMatchStringType(s.type, s.typeAsString));
+            }
 
             // Test if all sensor has name and vendor
             EXPECT_FALSE(s.name.empty());