Skip to content

Commit 16dcd0b

Browse files
authored
Add iOS-equivalent manifest-building features (#32)
1 parent 594552e commit 16dcd0b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+3771
-2
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Gradle
22
.gradle/
3+
.kotlin/
34
build/
45
*/build/
56
downloads/
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
This file is licensed to you under the Apache License, Version 2.0
3+
(http://www.apache.org/licenses/LICENSE-2.0) or the MIT license
4+
(http://opensource.org/licenses/MIT), at your option.
5+
6+
Unless required by applicable law or agreed to in writing, this software is
7+
distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF
8+
ANY KIND, either express or implied. See the LICENSE-MIT and LICENSE-APACHE
9+
files for the specific language governing permissions and limitations under
10+
each license.
11+
*/
12+
package org.contentauth.c2pa
13+
14+
import android.content.Context
15+
import androidx.test.ext.junit.runners.AndroidJUnit4
16+
import androidx.test.platform.app.InstrumentationRegistry
17+
import kotlinx.coroutines.runBlocking
18+
import org.contentauth.c2pa.test.shared.ManifestTests
19+
import org.junit.Test
20+
import org.junit.runner.RunWith
21+
import java.io.File
22+
import kotlin.test.assertTrue
23+
24+
/** Android instrumented tests for Manifest definition types. */
25+
@RunWith(AndroidJUnit4::class)
26+
class AndroidManifestTests : ManifestTests() {
27+
28+
private val targetContext = InstrumentationRegistry.getInstrumentation().targetContext
29+
30+
override fun getContext(): Context = targetContext
31+
32+
override fun loadResourceAsBytes(resourceName: String): ByteArray =
33+
ResourceTestHelper.loadResourceAsBytes(resourceName)
34+
35+
override fun loadResourceAsString(resourceName: String): String =
36+
ResourceTestHelper.loadResourceAsString(resourceName)
37+
38+
override fun copyResourceToFile(resourceName: String, fileName: String): File =
39+
ResourceTestHelper.copyResourceToFile(targetContext, resourceName, fileName)
40+
41+
@Test
42+
fun runTestMinimal() = runBlocking {
43+
val result = testMinimal()
44+
assertTrue(result.success, "Manifest Minimal test failed: ${result.message}")
45+
}
46+
47+
@Test
48+
fun runTestCreated() = runBlocking {
49+
val result = testCreated()
50+
assertTrue(result.success, "Manifest Created test failed: ${result.message}")
51+
}
52+
53+
@Test
54+
fun runTestEnumRendering() = runBlocking {
55+
val result = testEnumRendering()
56+
assertTrue(result.success, "Enum Rendering test failed: ${result.message}")
57+
}
58+
59+
@Test
60+
fun runTestRegionOfInterest() = runBlocking {
61+
val result = testRegionOfInterest()
62+
assertTrue(result.success, "Region of Interest test failed: ${result.message}")
63+
}
64+
65+
@Test
66+
fun runTestResourceRef() = runBlocking {
67+
val result = testResourceRef()
68+
assertTrue(result.success, "ResourceRef test failed: ${result.message}")
69+
}
70+
71+
@Test
72+
fun runTestHashedUri() = runBlocking {
73+
val result = testHashedUri()
74+
assertTrue(result.success, "HashedUri test failed: ${result.message}")
75+
}
76+
77+
@Test
78+
fun runTestUriOrResource() = runBlocking {
79+
val result = testUriOrResource()
80+
assertTrue(result.success, "UriOrResource test failed: ${result.message}")
81+
}
82+
83+
@Test
84+
fun runTestMassInit() = runBlocking {
85+
val result = testMassInit()
86+
assertTrue(result.success, "Mass Init test failed: ${result.message}")
87+
}
88+
89+
@Test
90+
fun runTestManifestToJson() = runBlocking {
91+
val result = testManifestToJson()
92+
assertTrue(result.success, "Manifest to JSON test failed: ${result.message}")
93+
}
94+
95+
@Test
96+
fun runTestShapeFactoryMethods() = runBlocking {
97+
val result = testShapeFactoryMethods()
98+
assertTrue(result.success, "Shape Factory Methods test failed: ${result.message}")
99+
}
100+
101+
@Test
102+
fun runTestManifestWithBuilder() = runBlocking {
103+
val result = testManifestWithBuilder()
104+
assertTrue(result.success, "Manifest with Builder test failed: ${result.message}")
105+
}
106+
107+
@Test
108+
fun runTestAllAssertionTypes() = runBlocking {
109+
val result = testAllAssertionTypes()
110+
assertTrue(result.success, "All Assertion Types test failed: ${result.message}")
111+
}
112+
113+
@Test
114+
fun runTestIngredients() = runBlocking {
115+
val result = testIngredients()
116+
assertTrue(result.success, "Ingredients test failed: ${result.message}")
117+
}
118+
119+
@Test
120+
fun runTestActionWithRegions() = runBlocking {
121+
val result = testActionWithRegions()
122+
assertTrue(result.success, "Action with Regions test failed: ${result.message}")
123+
}
124+
125+
@Test
126+
fun runTestMalformedJson() = runBlocking {
127+
val result = testMalformedJson()
128+
assertTrue(result.success, "Malformed JSON test failed: ${result.message}")
129+
}
130+
131+
@Test
132+
fun runTestSpecialCharacters() = runBlocking {
133+
val result = testSpecialCharacters()
134+
assertTrue(result.success, "Special Characters test failed: ${result.message}")
135+
}
136+
137+
@Test
138+
fun runTestCreatedFactory() = runBlocking {
139+
val result = testCreatedFactory()
140+
assertTrue(result.success, "Created Factory test failed: ${result.message}")
141+
}
142+
143+
@Test
144+
fun runTestAllValidationStatusCodes() = runBlocking {
145+
val result = testAllValidationStatusCodes()
146+
assertTrue(result.success, "All Validation Status Codes test failed: ${result.message}")
147+
}
148+
149+
@Test
150+
fun runTestAllDigitalSourceTypes() = runBlocking {
151+
val result = testAllDigitalSourceTypes()
152+
assertTrue(result.success, "All Digital Source Types test failed: ${result.message}")
153+
}
154+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
This file is licensed to you under the Apache License, Version 2.0
3+
(http://www.apache.org/licenses/LICENSE-2.0) or the MIT license
4+
(http://opensource.org/licenses/MIT), at your option.
5+
6+
Unless required by applicable law or agreed to in writing, this software is
7+
distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF
8+
ANY KIND, either express or implied. See the LICENSE-MIT and LICENSE-APACHE
9+
files for the specific language governing permissions and limitations under
10+
each license.
11+
*/
12+
13+
package org.contentauth.c2pa.manifest
14+
15+
import kotlinx.serialization.SerialName
16+
import kotlinx.serialization.Serializable
17+
import org.contentauth.c2pa.DigitalSourceType
18+
import org.contentauth.c2pa.PredefinedAction
19+
20+
/**
21+
* Represents an action performed on the asset for use in manifest assertions.
22+
*
23+
* This is the serializable version of Action for use within AssertionDefinition.
24+
*
25+
* @property action The action name (use [PredefinedAction.value] or a custom action string).
26+
* @property digitalSourceType A URL identifying an IPTC digital source type.
27+
* @property softwareAgent The software or hardware used to perform the action.
28+
* @property parameters Additional information describing the action.
29+
* @property when The timestamp when the action was performed (ISO 8601 format).
30+
* @property changes Regions of interest describing what changed.
31+
* @property related Related ingredient labels.
32+
* @property reason The reason for performing the action.
33+
* @see AssertionDefinition
34+
* @see PredefinedAction
35+
*/
36+
@Serializable
37+
data class ActionAssertion(
38+
val action: String,
39+
val digitalSourceType: String? = null,
40+
val softwareAgent: String? = null,
41+
val parameters: Map<String, String>? = null,
42+
@SerialName("when")
43+
val whenPerformed: String? = null,
44+
val changes: List<RegionOfInterest>? = null,
45+
val related: List<String>? = null,
46+
val reason: String? = null,
47+
) {
48+
/**
49+
* Creates an action using a [PredefinedAction] and [DigitalSourceType].
50+
*/
51+
constructor(
52+
action: PredefinedAction,
53+
digitalSourceType: DigitalSourceType? = null,
54+
softwareAgent: String? = null,
55+
parameters: Map<String, String>? = null,
56+
whenPerformed: String? = null,
57+
changes: List<RegionOfInterest>? = null,
58+
related: List<String>? = null,
59+
reason: String? = null,
60+
) : this(
61+
action = action.value,
62+
digitalSourceType = digitalSourceType?.toIptcUrl(),
63+
softwareAgent = softwareAgent,
64+
parameters = parameters,
65+
whenPerformed = whenPerformed,
66+
changes = changes,
67+
related = related,
68+
reason = reason,
69+
)
70+
71+
companion object {
72+
/**
73+
* Creates a "created" action with the specified digital source type.
74+
*/
75+
fun created(
76+
digitalSourceType: DigitalSourceType,
77+
softwareAgent: String? = null,
78+
) = ActionAssertion(
79+
action = PredefinedAction.CREATED,
80+
digitalSourceType = digitalSourceType,
81+
softwareAgent = softwareAgent,
82+
)
83+
84+
/**
85+
* Creates an "edited" action.
86+
*/
87+
fun edited(
88+
softwareAgent: String? = null,
89+
changes: List<RegionOfInterest>? = null,
90+
) = ActionAssertion(
91+
action = PredefinedAction.EDITED,
92+
softwareAgent = softwareAgent,
93+
changes = changes,
94+
)
95+
}
96+
}
97+
98+
private fun DigitalSourceType.toIptcUrl(): String =
99+
when (this) {
100+
DigitalSourceType.EMPTY -> "http://c2pa.org/digitalsourcetype/empty"
101+
DigitalSourceType.TRAINED_ALGORITHMIC_DATA -> "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicData"
102+
DigitalSourceType.DIGITAL_CAPTURE -> "http://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture"
103+
DigitalSourceType.COMPUTATIONAL_CAPTURE -> "http://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture"
104+
DigitalSourceType.NEGATIVE_FILM -> "http://cv.iptc.org/newscodes/digitalsourcetype/negativeFilm"
105+
DigitalSourceType.POSITIVE_FILM -> "http://cv.iptc.org/newscodes/digitalsourcetype/positiveFilm"
106+
DigitalSourceType.PRINT -> "http://cv.iptc.org/newscodes/digitalsourcetype/print"
107+
DigitalSourceType.HUMAN_EDITS -> "http://cv.iptc.org/newscodes/digitalsourcetype/humanEdits"
108+
DigitalSourceType.COMPOSITE_WITH_TRAINED_ALGORITHMIC_MEDIA -> "http://cv.iptc.org/newscodes/digitalsourcetype/compositeWithTrainedAlgorithmicMedia"
109+
DigitalSourceType.ALGORITHMICALLY_ENHANCED -> "http://cv.iptc.org/newscodes/digitalsourcetype/algorithmicallyEnhanced"
110+
DigitalSourceType.DIGITAL_CREATION -> "http://cv.iptc.org/newscodes/digitalsourcetype/digitalCreation"
111+
DigitalSourceType.DATA_DRIVEN_MEDIA -> "http://cv.iptc.org/newscodes/digitalsourcetype/dataDrivenMedia"
112+
DigitalSourceType.TRAINED_ALGORITHMIC_MEDIA -> "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia"
113+
DigitalSourceType.ALGORITHMIC_MEDIA -> "http://cv.iptc.org/newscodes/digitalsourcetype/algorithmicMedia"
114+
DigitalSourceType.SCREEN_CAPTURE -> "http://cv.iptc.org/newscodes/digitalsourcetype/screenCapture"
115+
DigitalSourceType.VIRTUAL_RECORDING -> "http://cv.iptc.org/newscodes/digitalsourcetype/virtualRecording"
116+
DigitalSourceType.COMPOSITE -> "http://cv.iptc.org/newscodes/digitalsourcetype/composite"
117+
DigitalSourceType.COMPOSITE_CAPTURE -> "http://cv.iptc.org/newscodes/digitalsourcetype/compositeCapture"
118+
DigitalSourceType.COMPOSITE_SYNTHETIC -> "http://cv.iptc.org/newscodes/digitalsourcetype/compositeSynthetic"
119+
}

0 commit comments

Comments
 (0)