From f90b7cd60c057b0e0475d5e861d141a01e0ffa5f Mon Sep 17 00:00:00 2001 From: JacobLinCool Date: Thu, 6 Aug 2026 22:07:07 +0800 Subject: [PATCH] Rename the project to Particeps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the Android Data Collector / ADC identity everywhere it is maintained: the product name, the Gradle root project, the Android applicationId and Kotlin root package, the Protocol v1 wire names, the artifact extensions, the join URI, the receiver contract, the offline analysis package, Web authoring, researcher tooling, CI and release artifacts, and the documentation. One commit, so no revision of main claims to be Particeps while emitting ADC artifacts. Protocol v1 keeps schema_version 1 and gains no second dialect. PTCCFG01 and PTCEXP01 replace ADCCFG01 and ADCEXP01 at the same eight bytes, so no binary offset moves; particeps-research-bundle-v1 replaces research-bundle-v1 and is ten bytes longer, and because it is authenticated as HPKE info and AES-GCM associated data the whole conformance corpus was regenerated rather than edited. The retired identity now has executable fail-closed coverage in Kotlin, TypeScript, and Python rather than merely being absent: retired magics, the retired bundle format, the retired join scheme, retired routing headers, and the retired media type each have a hostile vector or rejection test, and every surviving old literal carries a comment saying so. The implementation surfaced ADC-derived identifiers the issue's contract did not list, and they are renamed here rather than silently retained: the on-device frame magics and file suffixes, the corpus and dataset format identities, the Parquet column metadata keys, the key-ID derivation domains, the Keystore aliases, the WorkManager work names and tags, the Gradle system properties, the browser locale key, the Kotlin conformance package, and the receiver's npm package name. Moving applicationId to cool.linc.particeps makes this a different Android application. There is no upgrade, no migration, and no shared Keystore namespace; a pre-rename install is removed separately and its studies, keys, encrypted segments, and staged upload bodies go with it. That boundary is what made renaming the on-device names safe, and it is recorded in the README, the participant guide, and invariant 8 of the implementation contract so a later reviewer does not lift one of those renames out of context. The release signing key and certificate are unchanged. Two guards keep the rename from decaying. tools/retired_identity_audit.py fails the build on any retired spelling outside a reviewed, reasoned allow-list, and pins the applicationId, so the fresh-install boundary is tested and not only documented. The upload request vocabulary — media type, bundle format, and the seven routing header names — is now a shared corpus fixture that the Kotlin emitter and the TypeScript receiver each assert against, because it was the one part of the wire contract whose producer and only reader could be renamed independently without any test noticing. Regenerated rather than edited: the conformance corpus, the collector-catalog Kotlin contract, the demo signed envelope (the researcher name it carries is inside the Ed25519-signed canonical bytes), and the analysis lockfile. Closes #23. --- .github/workflows/analysis.yml | 10 +- .github/workflows/ci.yml | 5 +- .github/workflows/release.yml | 11 +- .gitignore | 2 +- CITATION.cff | 24 +-- README.md | 50 +++--- app/build.gradle.kts | 8 +- .../AndroidConfigurationImportTest.kt | 40 ----- .../AndroidConfigurationImportTest.kt | 78 +++++++++ .../CoreFlowTest.kt | 6 +- .../P2CollectorEmulatorTest.kt | 44 ++--- .../DemoStudy.kt | 2 +- app/src/debug/res/raw/demo_study_envelope.txt | 2 +- app/src/main/AndroidManifest.xml | 4 +- .../AppLocale.kt | 2 +- .../BootRecoveryReceiver.kt | 4 +- .../CollectionService.kt | 4 +- .../CollectorApplication.kt | 76 ++++----- .../CollectorDashboard.kt | 16 +- .../CollectorGlyphs.kt | 2 +- .../CollectorSummary.kt | 30 ++-- .../ExperimentDeadlineWorker.kt | 6 +- .../MainActivity.kt | 12 +- .../StudyViewModel.kt | 22 +-- .../SurveyActivity.kt | 24 +-- .../UploadWorker.kt | 6 +- .../platform/AndroidResearchClocks.kt | 6 +- .../platform/AndroidStudyPlatform.kt | 53 +++--- .../platform/FileUploadOutbox.kt | 12 +- .../platform/JoinArtifactDownloader.kt | 8 +- .../platform/OkHttpStudyUploader.kt | 40 ++--- app/src/main/res/values-zh-rTW/strings.xml | 2 +- app/src/main/res/values/strings.xml | 17 +- app/src/main/res/values/styles.xml | 2 +- .../DemoStudy.kt | 2 +- .../CollectorSummaryTest.kt | 2 +- .../DemoStudyAssetTest.kt | 6 +- .../SurveySubmissionStateTest.kt | 6 +- .../platform/FileUploadOutboxTest.kt | 8 +- .../platform/InterventionWorkPolicyTest.kt | 16 +- .../platform/JoinArtifactDownloaderTest.kt | 8 +- .../platform/OkHttpStudyUploaderTest.kt | 96 ++++++++--- .../platform/UploadIdentityTest.kt | 18 +- assurance/collector-policy.json | 16 +- collector/accelerometer/build.gradle.kts | 2 +- .../accelerometer/AccelerometerCollector.kt | 28 ++-- collector/ambient-light/build.gradle.kts | 2 +- .../ambientlight/AmbientLightCollector.kt | 34 ++-- .../ambientlight/AmbientLightCollectorTest.kt | 12 +- collector/app-lifecycle/build.gradle.kts | 2 +- .../applifecycle/AppLifecycleCollector.kt | 28 ++-- collector/battery-state/build.gradle.kts | 2 +- .../batterystate/BatteryStateCollector.kt | 38 ++--- .../batterystate/BatteryStateCollectorTest.kt | 12 +- collector/gyroscope/build.gradle.kts | 2 +- .../collector/gyroscope/GyroscopeCollector.kt | 30 ++-- .../gyroscope/GyroscopeCollectorTest.kt | 8 +- collector/keyboard-ime/build.gradle.kts | 2 +- .../keyboardime/ImeObservationBridge.kt | 4 +- .../keyboardime/KeyboardTouchCollector.kt | 30 ++-- .../keyboardime/ResearchInputMethodService.kt | 2 +- .../keyboardime/ResearchKeyboardView.kt | 2 +- .../main/res/xml/research_input_method.xml | 2 +- .../keyboardime/ImeObservationBridgeTest.kt | 2 +- collector/location/build.gradle.kts | 2 +- .../collector/location/LocationCollector.kt | 40 ++--- collector/network-state/build.gradle.kts | 2 +- .../networkstate/NetworkStateCollector.kt | 34 ++-- collector/network-usage/build.gradle.kts | 2 +- .../networkusage/NetworkUsageCollector.kt | 32 ++-- collector/proximity/build.gradle.kts | 2 +- .../collector/proximity/ProximityCollector.kt | 34 ++-- .../proximity/ProximityCollectorTest.kt | 12 +- collector/sensor-common/build.gradle.kts | 2 +- .../sensorcommon/AndroidSensorCollector.kt | 18 +- .../sensorcommon/SensorSourceLifecycleTest.kt | 10 +- collector/temporal-context/build.gradle.kts | 2 +- .../TemporalContextCollector.kt | 38 ++--- .../TemporalContextCollectorTest.kt | 12 +- collector/usage-events/build.gradle.kts | 2 +- .../usageevents/UsageEventsCollector.kt | 30 ++-- core/access/build.gradle.kts | 2 +- .../core/access/AccessManager.kt | 10 +- .../core/collector/CollectorContracts.kt | 10 +- .../core/collector/LatestValueRateGate.kt | 2 +- .../core/collector/ProtocolEventContracts.kt | 2 +- .../collector/SerializedCallbackCollector.kt | 4 +- .../core/collector/SourceLifecycle.kt | 2 +- .../core/collector/EventFieldContractTest.kt | 2 +- .../core/collector/LatestValueRateGateTest.kt | 2 +- .../core/collector/ProtocolEventSizeTest.kt | 6 +- .../SerializedCallbackCollectorTest.kt | 8 +- .../core/collector/SourceLifecycleTest.kt | 2 +- .../core/crypto/Ed25519Crypto.kt | 2 +- .../core/crypto/HpkeCrypto.kt | 6 +- .../core/crypto/Ed25519CryptoTest.kt | 2 +- .../core/crypto/HpkeCryptoTest.kt | 4 +- .../core/runtime/EventAdmissionGate.kt | 4 +- .../core/runtime/ExperimentRuntime.kt | 74 ++++----- .../core/runtime/EventAdmissionGateTest.kt | 2 +- .../core/runtime/ExperimentRuntimeTest.kt | 88 +++++----- .../core/export/CanonicalJsonWriter.kt | 2 +- .../core/export/ResearchBundleVerifier.kt | 30 ++-- .../core/export/ResearchExport.kt | 28 ++-- .../core/export/UploadReceiptCodec.kt | 2 +- .../core/export/ResearchExportTest.kt | 38 ++--- .../core/model/ExperimentModels.kt | 2 +- .../core/model/ExperimentStateMachine.kt | 2 +- .../core/model/StudyData.kt | 2 +- .../core/model/ExperimentStateMachineTest.kt | 2 +- .../core/protocol/JoinLink.kt | 22 +-- .../core/protocol/SignedConfiguration.kt | 12 +- .../protocol/ConfigurationProtocolTest.kt | 26 +-- .../core/protocol/JoinLinkTest.kt | 22 +-- core/storage/build.gradle.kts | 2 +- .../storage/EncryptedExperimentStoreTest.kt | 16 +- .../core/storage/AppendTransactionRecovery.kt | 6 +- .../core/storage/EncryptedActiveStudyStore.kt | 12 +- .../core/storage/EncryptedExperimentStore.kt | 28 ++-- .../core/storage/EvictionPlanner.kt | 2 +- .../core/storage/StudyDataJsonCodec.kt | 18 +- .../storage/AppendTransactionRecoveryTest.kt | 12 +- .../core/storage/EvictionPlannerTest.kt | 2 +- .../storage/StudyDataReconciliationTest.kt | 8 +- .../InterventionSchedulePlanner.kt | 28 ++-- .../core/application/StudyApplication.kt | 56 +++---- .../InterventionSchedulePlannerTest.kt | 44 ++--- .../application/StudySessionManagerTest.kt | 104 ++++++------ .../core/definition/ProtocolBase64Url.kt | 2 +- .../core/definition/ProtocolCanonicalJson.kt | 2 +- .../core/definition/StudyConfiguration.kt | 2 +- .../definition/StudyConfigurationCodec.kt | 2 +- .../NetworkUsageConfigurationTest.kt | 2 +- .../core/definition/P2ConfigurationTest.kt | 2 +- docs/component-boundaries.md | 6 +- docs/data-collector-implementation-guide.md | 50 +++--- docs/data-dictionary.md | 8 +- docs/maintainers/release.md | 39 ++++- docs/p0-p2-implementation-contract.md | 27 ++- docs/participant-guide.md | 34 ++-- docs/researcher-guide.md | 95 ++++++----- docs/system-design.md | 36 ++-- docs/threat-model.md | 10 +- .../.gitignore | 0 .../README.md | 22 +-- .../pyproject.toml | 8 +- .../src/particeps_analysis}/__init__.py | 2 +- .../src/particeps_analysis}/bundle.py | 10 +- .../src/particeps_analysis}/catalog.py | 2 +- .../src/particeps_analysis}/cli.py | 6 +- .../src/particeps_analysis}/configuration.py | 0 .../src/particeps_analysis}/crypto.py | 0 .../src/particeps_analysis}/encoding.py | 0 .../src/particeps_analysis}/errors.py | 0 .../src/particeps_analysis}/event_store.py | 2 +- .../src/particeps_analysis}/filesystem.py | 0 .../src/particeps_analysis}/inventory.py | 10 +- .../src/particeps_analysis}/jcs.py | 0 .../src/particeps_analysis}/limits.py | 0 .../src/particeps_analysis}/models.py | 0 .../src/particeps_analysis}/pipeline.py | 8 +- .../src/particeps_analysis}/py.typed | 0 .../src/particeps_analysis}/reassembly.py | 2 +- .../src/particeps_analysis}/sink.py | 16 +- .../src/particeps_analysis}/sources.py | 2 +- .../src/particeps_analysis}/streaming_json.py | 0 .../src/particeps_analysis}/summary.py | 0 .../tests/__init__.py | 0 .../tests/fakes.py | 0 .../tests/test_cli.py | 16 +- .../tests/test_configuration_catalog.py | 8 +- .../tests/test_conformance.py | 22 +-- .../tests/test_inventory.py | 26 +-- .../tests/test_pipeline.py | 30 ++-- .../tests/test_reassembly.py | 10 +- .../tests/test_sink.py | 16 +- .../tests/test_streaming_filesystem.py | 6 +- {adc-analysis => particeps-analysis}/uv.lock | 2 +- protocol/v1/README.md | 97 +++++++---- protocol/v1/collector-catalog.json | 2 +- protocol/v1/conformance-vectors.json | 146 ++++++++++------- protocol/v1/join-link-vectors.json | 26 +-- receiver/README.md | 18 +- receiver/package.json | 2 +- receiver/src/contract.ts | 47 +++--- receiver/tests/receiver.test.ts | 92 ++++++++--- receiver/tests/receiver.workerd.test.ts | 18 +- receiver/wrangler.example.jsonc | 4 +- researcher-tools/build.gradle.kts | 4 +- researcher-tools/examples/demo-study.json | 2 +- .../researcher/Main.kt | 24 +-- .../researcher/DecryptCommandTest.kt | 8 +- settings.gradle.kts | 2 +- tools/catalog.py | 10 +- tools/catalog_parity.py | 12 +- .../kotlin/ProtocolConformanceTest.kt | 24 +-- .../typescript/catalog-parity.spec.ts | 6 +- .../typescript/protocol-vectors.spec.ts | 10 +- tools/generate_protocol_vectors.mjs | 54 ++++-- tools/protocol-conformance.init.gradle | 2 +- tools/retired_identity_audit.py | 155 ++++++++++++++++++ tools/tests/test_retired_identity_audit.py | 123 ++++++++++++++ tools/validate_protocol_vectors.py | 29 +++- web/CONTRACT.md | 22 +-- web/e2e/one-line.mjs | 4 +- web/e2e/researcher-flow.mjs | 28 ++-- web/e2e/units.mjs | 6 +- web/package.json | 2 +- web/src/app.html | 2 +- web/src/lib/i18n/en.ts | 8 +- web/src/lib/i18n/messages.ts | 2 +- web/src/lib/i18n/types.ts | 2 +- web/src/lib/i18n/zh-TW.ts | 12 +- web/src/lib/{adc => particeps}/bundle.ts | 8 +- web/src/lib/{adc => particeps}/canonical.ts | 2 +- web/src/lib/{adc => particeps}/crypto.ts | 0 web/src/lib/{adc => particeps}/envelope.ts | 6 +- web/src/lib/{adc => particeps}/ids.ts | 6 +- web/src/lib/{adc => particeps}/join.ts | 2 +- web/src/lib/{adc => particeps}/schema.ts | 2 +- web/src/lib/{adc => particeps}/types.ts | 2 +- web/src/lib/participant/Hero.svelte | 4 + web/src/lib/participant/content.ts | 4 +- web/src/lib/participant/copy.ts | 21 ++- web/src/lib/ui/BytePane.svelte | 2 +- web/src/lib/ui/DropTarget.svelte | 2 +- web/src/lib/ui/IdField.svelte | 2 +- web/src/lib/ui/field-context.ts | 2 +- web/src/lib/ui/i18n.svelte.ts | 2 +- web/src/lib/ui/types.ts | 2 +- web/src/routes/+page.svelte | 2 +- web/src/routes/researcher/+page.svelte | 4 +- .../routes/researcher/CollectorCard.svelte | 2 +- .../researcher/InterventionEditor.svelte | 4 +- .../routes/researcher/JoinLinkPanel.svelte | 6 +- .../routes/researcher/SignatureReceipt.svelte | 2 +- web/src/routes/researcher/StepFiles.svelte | 12 +- web/src/routes/researcher/StepRead.svelte | 14 +- web/src/routes/researcher/StepSign.svelte | 4 +- web/src/routes/researcher/StepStudy.svelte | 4 +- web/src/routes/researcher/WindowStrip.svelte | 2 +- web/src/routes/researcher/artifacts.ts | 8 +- web/src/routes/researcher/draft.svelte.ts | 30 ++-- web/src/routes/researcher/estimate.ts | 2 +- web/src/routes/researcher/keys.ts | 2 +- web/src/routes/researcher/parse.ts | 18 +- web/src/routes/researcher/presets.ts | 2 +- web/src/routes/researcher/random-window.ts | 2 +- web/src/routes/researcher/scales.ts | 2 +- web/tests/bundle.spec.ts | 32 ++-- web/tests/canonical.spec.ts | 2 +- web/tests/compat.spec.ts | 26 ++- web/tests/crypto.spec.ts | 8 +- web/tests/fixture.ts | 13 +- web/tests/hostile.spec.ts | 12 +- web/tests/ids.spec.ts | 6 +- web/tests/join.spec.ts | 36 ++-- web/tests/p2.spec.ts | 6 +- web/tests/participant-copy.spec.ts | 12 ++ web/tests/researcher-draft.spec.ts | 4 +- web/tests/researcher.spec.ts | 8 +- web/tests/seal.ts | 10 +- 262 files changed, 2358 insertions(+), 1644 deletions(-) delete mode 100644 app/src/androidTest/kotlin/cool/linc/androiddatacollector/AndroidConfigurationImportTest.kt create mode 100644 app/src/androidTest/kotlin/cool/linc/particeps/AndroidConfigurationImportTest.kt rename app/src/androidTest/kotlin/cool/linc/{androiddatacollector => particeps}/CoreFlowTest.kt (97%) rename app/src/androidTest/kotlin/cool/linc/{androiddatacollector => particeps}/P2CollectorEmulatorTest.kt (86%) rename app/src/debug/kotlin/cool/linc/{androiddatacollector => particeps}/DemoStudy.kt (96%) rename app/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/AppLocale.kt (98%) rename app/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/BootRecoveryReceiver.kt (91%) rename app/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/CollectionService.kt (95%) rename app/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/CollectorApplication.kt (65%) rename app/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/CollectorDashboard.kt (98%) rename app/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/CollectorGlyphs.kt (99%) rename app/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/CollectorSummary.kt (87%) rename app/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/ExperimentDeadlineWorker.kt (86%) rename app/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/MainActivity.kt (94%) rename app/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/StudyViewModel.kt (88%) rename app/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/SurveyActivity.kt (95%) rename app/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/UploadWorker.kt (93%) rename app/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/platform/AndroidResearchClocks.kt (82%) rename app/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/platform/AndroidStudyPlatform.kt (89%) rename app/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/platform/FileUploadOutbox.kt (96%) rename app/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/platform/JoinArtifactDownloader.kt (93%) rename app/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/platform/OkHttpStudyUploader.kt (89%) rename app/src/release/kotlin/cool/linc/{androiddatacollector => particeps}/DemoStudy.kt (93%) rename app/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/CollectorSummaryTest.kt (92%) rename app/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/DemoStudyAssetTest.kt (86%) rename app/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/SurveySubmissionStateTest.kt (87%) rename app/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/platform/FileUploadOutboxTest.kt (96%) rename app/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/platform/InterventionWorkPolicyTest.kt (91%) rename app/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/platform/JoinArtifactDownloaderTest.kt (92%) rename app/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/platform/OkHttpStudyUploaderTest.kt (81%) rename app/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/platform/UploadIdentityTest.kt (81%) rename collector/accelerometer/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/collector/accelerometer/AccelerometerCollector.kt (74%) rename collector/ambient-light/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/collector/ambientlight/AmbientLightCollector.kt (82%) rename collector/ambient-light/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/collector/ambientlight/AmbientLightCollectorTest.kt (88%) rename collector/app-lifecycle/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/collector/applifecycle/AppLifecycleCollector.kt (76%) rename collector/battery-state/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/collector/batterystate/BatteryStateCollector.kt (86%) rename collector/battery-state/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/collector/batterystate/BatteryStateCollectorTest.kt (87%) rename collector/gyroscope/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/collector/gyroscope/GyroscopeCollector.kt (72%) rename collector/gyroscope/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/collector/gyroscope/GyroscopeCollectorTest.kt (82%) rename collector/keyboard-ime/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/collector/keyboardime/ImeObservationBridge.kt (95%) rename collector/keyboard-ime/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/collector/keyboardime/KeyboardTouchCollector.kt (74%) rename collector/keyboard-ime/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/collector/keyboardime/ResearchInputMethodService.kt (97%) rename collector/keyboard-ime/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/collector/keyboardime/ResearchKeyboardView.kt (99%) rename collector/keyboard-ime/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/collector/keyboardime/ImeObservationBridgeTest.kt (97%) rename collector/location/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/collector/location/LocationCollector.kt (81%) rename collector/network-state/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/collector/networkstate/NetworkStateCollector.kt (80%) rename collector/network-usage/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/collector/networkusage/NetworkUsageCollector.kt (86%) rename collector/proximity/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/collector/proximity/ProximityCollector.kt (84%) rename collector/proximity/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/collector/proximity/ProximityCollectorTest.kt (87%) rename collector/sensor-common/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/collector/sensorcommon/AndroidSensorCollector.kt (85%) rename collector/sensor-common/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/collector/sensorcommon/SensorSourceLifecycleTest.kt (91%) rename collector/temporal-context/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/collector/temporalcontext/TemporalContextCollector.kt (85%) rename collector/temporal-context/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/collector/temporalcontext/TemporalContextCollectorTest.kt (89%) rename collector/usage-events/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/collector/usageevents/UsageEventsCollector.kt (87%) rename core/access/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/access/AccessManager.kt (93%) rename core/collector-api/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/collector/CollectorContracts.kt (97%) rename core/collector-api/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/collector/LatestValueRateGate.kt (98%) rename core/collector-api/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/collector/ProtocolEventContracts.kt (99%) rename core/collector-api/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/collector/SerializedCallbackCollector.kt (98%) rename core/collector-api/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/collector/SourceLifecycle.kt (98%) rename core/collector-api/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/core/collector/EventFieldContractTest.kt (94%) rename core/collector-api/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/core/collector/LatestValueRateGateTest.kt (98%) rename core/collector-api/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/core/collector/ProtocolEventSizeTest.kt (88%) rename core/collector-api/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/core/collector/SerializedCallbackCollectorTest.kt (97%) rename core/collector-api/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/core/collector/SourceLifecycleTest.kt (97%) rename core/crypto/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/crypto/Ed25519Crypto.kt (93%) rename core/crypto/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/crypto/HpkeCrypto.kt (91%) rename core/crypto/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/core/crypto/Ed25519CryptoTest.kt (96%) rename core/crypto/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/core/crypto/HpkeCryptoTest.kt (95%) rename core/experiment-runtime/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/runtime/EventAdmissionGate.kt (94%) rename core/experiment-runtime/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/runtime/ExperimentRuntime.kt (93%) rename core/experiment-runtime/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/core/runtime/EventAdmissionGateTest.kt (94%) rename core/experiment-runtime/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/core/runtime/ExperimentRuntimeTest.kt (92%) rename core/export/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/export/CanonicalJsonWriter.kt (99%) rename core/export/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/export/ResearchBundleVerifier.kt (95%) rename core/export/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/export/ResearchExport.kt (95%) rename core/export/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/export/UploadReceiptCodec.kt (98%) rename core/export/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/core/export/ResearchExportTest.kt (91%) rename core/model/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/model/ExperimentModels.kt (98%) rename core/model/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/model/ExperimentStateMachine.kt (97%) rename core/model/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/model/StudyData.kt (99%) rename core/model/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/core/model/ExperimentStateMachineTest.kt (98%) rename core/protocol/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/protocol/JoinLink.kt (90%) rename core/protocol/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/protocol/SignedConfiguration.kt (94%) rename core/protocol/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/core/protocol/ConfigurationProtocolTest.kt (91%) rename core/protocol/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/core/protocol/JoinLinkTest.kt (79%) rename core/storage/src/androidTest/kotlin/cool/linc/{androiddatacollector => particeps}/core/storage/EncryptedExperimentStoreTest.kt (95%) rename core/storage/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/storage/AppendTransactionRecovery.kt (96%) rename core/storage/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/storage/EncryptedActiveStudyStore.kt (94%) rename core/storage/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/storage/EncryptedExperimentStore.kt (97%) rename core/storage/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/storage/EvictionPlanner.kt (98%) rename core/storage/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/storage/StudyDataJsonCodec.kt (94%) rename core/storage/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/core/storage/AppendTransactionRecoveryTest.kt (91%) rename core/storage/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/core/storage/EvictionPlannerTest.kt (98%) rename core/storage/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/core/storage/StudyDataReconciliationTest.kt (88%) rename core/study-application/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/application/InterventionSchedulePlanner.kt (93%) rename core/study-application/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/application/StudyApplication.kt (95%) rename core/study-application/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/core/application/InterventionSchedulePlannerTest.kt (92%) rename core/study-application/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/core/application/StudySessionManagerTest.kt (92%) rename core/study-definition/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/definition/ProtocolBase64Url.kt (94%) rename core/study-definition/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/definition/ProtocolCanonicalJson.kt (98%) rename core/study-definition/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/definition/StudyConfiguration.kt (99%) rename core/study-definition/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/core/definition/StudyConfigurationCodec.kt (99%) rename core/study-definition/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/core/definition/NetworkUsageConfigurationTest.kt (95%) rename core/study-definition/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/core/definition/P2ConfigurationTest.kt (99%) rename {adc-analysis => particeps-analysis}/.gitignore (100%) rename {adc-analysis => particeps-analysis}/README.md (91%) rename {adc-analysis => particeps-analysis}/pyproject.toml (79%) rename {adc-analysis/src/adc_analysis => particeps-analysis/src/particeps_analysis}/__init__.py (65%) rename {adc-analysis/src/adc_analysis => particeps-analysis/src/particeps_analysis}/bundle.py (99%) rename {adc-analysis/src/adc_analysis => particeps-analysis/src/particeps_analysis}/catalog.py (99%) rename {adc-analysis/src/adc_analysis => particeps-analysis/src/particeps_analysis}/cli.py (93%) rename {adc-analysis/src/adc_analysis => particeps-analysis/src/particeps_analysis}/configuration.py (100%) rename {adc-analysis/src/adc_analysis => particeps-analysis/src/particeps_analysis}/crypto.py (100%) rename {adc-analysis/src/adc_analysis => particeps-analysis/src/particeps_analysis}/encoding.py (100%) rename {adc-analysis/src/adc_analysis => particeps-analysis/src/particeps_analysis}/errors.py (100%) rename {adc-analysis/src/adc_analysis => particeps-analysis/src/particeps_analysis}/event_store.py (99%) rename {adc-analysis/src/adc_analysis => particeps-analysis/src/particeps_analysis}/filesystem.py (100%) rename {adc-analysis/src/adc_analysis => particeps-analysis/src/particeps_analysis}/inventory.py (96%) rename {adc-analysis/src/adc_analysis => particeps-analysis/src/particeps_analysis}/jcs.py (100%) rename {adc-analysis/src/adc_analysis => particeps-analysis/src/particeps_analysis}/limits.py (100%) rename {adc-analysis/src/adc_analysis => particeps-analysis/src/particeps_analysis}/models.py (100%) rename {adc-analysis/src/adc_analysis => particeps-analysis/src/particeps_analysis}/pipeline.py (95%) rename {adc-analysis/src/adc_analysis => particeps-analysis/src/particeps_analysis}/py.typed (100%) rename {adc-analysis/src/adc_analysis => particeps-analysis/src/particeps_analysis}/reassembly.py (99%) rename {adc-analysis/src/adc_analysis => particeps-analysis/src/particeps_analysis}/sink.py (96%) rename {adc-analysis/src/adc_analysis => particeps-analysis/src/particeps_analysis}/sources.py (97%) rename {adc-analysis/src/adc_analysis => particeps-analysis/src/particeps_analysis}/streaming_json.py (100%) rename {adc-analysis/src/adc_analysis => particeps-analysis/src/particeps_analysis}/summary.py (100%) rename {adc-analysis => particeps-analysis}/tests/__init__.py (100%) rename {adc-analysis => particeps-analysis}/tests/fakes.py (100%) rename {adc-analysis => particeps-analysis}/tests/test_cli.py (89%) rename {adc-analysis => particeps-analysis}/tests/test_configuration_catalog.py (98%) rename {adc-analysis => particeps-analysis}/tests/test_conformance.py (93%) rename {adc-analysis => particeps-analysis}/tests/test_inventory.py (86%) rename {adc-analysis => particeps-analysis}/tests/test_pipeline.py (89%) rename {adc-analysis => particeps-analysis}/tests/test_reassembly.py (96%) rename {adc-analysis => particeps-analysis}/tests/test_sink.py (96%) rename {adc-analysis => particeps-analysis}/tests/test_streaming_filesystem.py (92%) rename {adc-analysis => particeps-analysis}/uv.lock (99%) rename researcher-tools/src/main/kotlin/cool/linc/{androiddatacollector => particeps}/researcher/Main.kt (93%) rename researcher-tools/src/test/kotlin/cool/linc/{androiddatacollector => particeps}/researcher/DecryptCommandTest.kt (94%) create mode 100644 tools/retired_identity_audit.py create mode 100644 tools/tests/test_retired_identity_audit.py rename web/src/lib/{adc => particeps}/bundle.ts (99%) rename web/src/lib/{adc => particeps}/canonical.ts (99%) rename web/src/lib/{adc => particeps}/crypto.ts (100%) rename web/src/lib/{adc => particeps}/envelope.ts (95%) rename web/src/lib/{adc => particeps}/ids.ts (98%) rename web/src/lib/{adc => particeps}/join.ts (99%) rename web/src/lib/{adc => particeps}/schema.ts (99%) rename web/src/lib/{adc => particeps}/types.ts (98%) diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index 7151556..124985a 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -5,14 +5,14 @@ on: branches: - main paths: - - "adc-analysis/**" + - "particeps-analysis/**" - "protocol/v1/**" - "tools/generate_protocol_vectors.mjs" - "tools/validate_protocol_vectors.py" - ".github/workflows/analysis.yml" pull_request: paths: - - "adc-analysis/**" + - "particeps-analysis/**" - "protocol/v1/**" - "tools/generate_protocol_vectors.mjs" - "tools/validate_protocol_vectors.py" @@ -33,7 +33,7 @@ jobs: timeout-minutes: 20 defaults: run: - working-directory: adc-analysis + working-directory: particeps-analysis steps: - name: Check out source @@ -46,9 +46,9 @@ jobs: with: version: "0.11.26" python-version: "3.13" - working-directory: adc-analysis + working-directory: particeps-analysis enable-cache: true - cache-dependency-glob: adc-analysis/uv.lock + cache-dependency-glob: particeps-analysis/uv.lock - name: Install locked dependencies run: uv sync --locked diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88ec2ac..9f99a5d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,6 +57,7 @@ jobs: python3 tools/catalog.py check python3 tools/catalog_parity.py python3 tools/validate_protocol_vectors.py + python3 tools/retired_identity_audit.py python3 -m unittest discover -s tools/tests -v # The wrapper JAR is a binary in the tree; this checks it against Gradle's published @@ -100,7 +101,7 @@ jobs: ./gradlew --no-daemon -I tools/protocol-conformance.init.gradle :core:export:test - --tests adc.conformance.ProtocolConformanceTest + --tests particeps.conformance.ProtocolConformanceTest - name: Enforce collector capability boundary run: python3 tools/collector_assurance.py @@ -108,7 +109,7 @@ jobs: - name: Upload debug APK uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: android-data-collector-debug-${{ github.sha }} + name: particeps-debug-${{ github.sha }} path: app/build/outputs/apk/debug/app-debug.apk if-no-files-found: error retention-days: 14 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0d9f35f..142bb80 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -114,9 +114,9 @@ jobs: run: | umask 077 mkdir -p .signing - printf '%s' "$KEYSTORE_BASE64" | base64 --decode > .signing/android-data-collector-release.jks + printf '%s' "$KEYSTORE_BASE64" | base64 --decode > .signing/particeps-release.jks { - printf 'storeFile=.signing/android-data-collector-release.jks\n' + printf 'storeFile=.signing/particeps-release.jks\n' printf 'storePassword=%s\n' "$KEYSTORE_PASSWORD" printf 'keyAlias=%s\n' "$KEY_ALIAS" printf 'keyPassword=%s\n' "$KEY_PASSWORD" @@ -134,11 +134,12 @@ jobs: python3 tools/catalog.py check python3 tools/catalog_parity.py python3 tools/validate_protocol_vectors.py + python3 tools/retired_identity_audit.py python3 tools/collector_assurance.py ./gradlew --no-daemon \ -I tools/protocol-conformance.init.gradle \ :core:export:test \ - --tests adc.conformance.ProtocolConformanceTest + --tests particeps.conformance.ProtocolConformanceTest - name: Verify and package signed APK id: signed_apk @@ -147,7 +148,7 @@ jobs: build_tools="$ANDROID_SDK_ROOT/build-tools/$ANDROID_BUILD_TOOLS_VERSION" signed_apk="app/build/outputs/apk/release/app-release.apk" release_dir="$RUNNER_TEMP/android-release" - release_apk="$release_dir/android-data-collector-$GITHUB_REF_NAME.apk" + release_apk="$release_dir/particeps-$GITHUB_REF_NAME.apk" test -f "$signed_apk" mkdir -p "$release_dir" @@ -185,4 +186,4 @@ jobs: - name: Remove materialized signing files if: always() - run: rm -f .signing/android-data-collector-release.jks .signing/release-signing.properties + run: rm -f .signing/particeps-release.jks .signing/release-signing.properties diff --git a/.gitignore b/.gitignore index ce4c05b..c2a82de 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,6 @@ bin/ *-private.json *.jks *.keystore -*.adcexp +*.partexp !researcher-tools/examples/INSECURE-demo-signing-private.key !researcher-tools/examples/INSECURE-demo-hpke-private.key diff --git a/CITATION.cff b/CITATION.cff index fff8d28..c20bac0 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -1,5 +1,5 @@ cff-version: 1.2.0 -title: Android Data Collector +title: Particeps message: If you use this software in published work, please cite it. type: software authors: @@ -8,13 +8,13 @@ authors: family-names: Lin alias: JacobLinCool email: jacoblincool@gmail.com -repository-code: https://github.com/JacobLinCool/android-data-collector -url: https://github.com/JacobLinCool/android-data-collector +repository-code: https://github.com/JacobLinCool/particeps +url: https://github.com/JacobLinCool/particeps abstract: >- - A local-first, participant-controlled Android platform for research data - collection. Study content is fixed by an Ed25519-signed configuration that can - only select collectors already compiled into the application. Events are - encrypted on device with a non-exportable Android Keystore key. Data reaches + Particeps is a local-first, participant-first Android platform for research + data collection. Study content is fixed by an Ed25519-signed configuration + that can only select collectors already compiled into the application. Events + are encrypted on device with a non-exportable Android Keystore key. Data reaches the research team as a bundle encrypted to the researcher's HPKE public key, either through a participant-initiated export or, when the signed configuration declares an endpoint and the consent screen discloses it, @@ -28,6 +28,10 @@ keywords: - participant control - android license: MIT -# TODO: update version and date-released at each tagged release. -version: 1.0.0-rc.3 -date-released: "2026-08-05" +# No `version` or `date-released` here, on purpose. Every tag this repository has published +# (v1.0.0-rc.1 through v1.0.0-rc.3) predates the rename: those builds carry the pre-rename +# identity, a different applicationId, and different file formats, and the project does not +# support them. Naming one of them as a version of Particeps would cite a release that does +# not exist, so this file cites the repository instead and a citation of it carries no version. +# TODO: when the first post-rename tag ships, add `version` and `date-released` back as part of +# cutting it, and keep updating both at each tagged release thereafter. diff --git a/README.md b/README.md index 94633fb..998d279 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,27 @@ -# Android Data Collector +# Particeps -Run a mobile data collection study without building an app. A study is a signed configuration file: choose which collectors to run, set their parameters and the study duration, sign it, and hand it to participants. Data is collected on the device and encrypted as it is written; it reaches you as an encrypted export the participant sends, or on a schedule if the study names an upload endpoint. +**Participant-first sensing for research.** Run a mobile data collection study without building an app. A study is a signed configuration file: choose which collectors to run, set their parameters and the study duration, sign it, and hand it to participants. Data is collected on the device and encrypted as it is written; it reaches you as an encrypted export the participant sends, or on a schedule if the study names an upload endpoint. -[![Android CI](https://github.com/JacobLinCool/android-data-collector/actions/workflows/ci.yml/badge.svg)](https://github.com/JacobLinCool/android-data-collector/actions/workflows/ci.yml) +[![Android CI](https://github.com/JacobLinCool/particeps/actions/workflows/ci.yml/badge.svg)](https://github.com/JacobLinCool/particeps/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![Android 14–17](https://img.shields.io/badge/Android-14%E2%80%9317%20(API%2034%E2%80%9337)-3DDC84.svg)](#requirements) Standing up a mobile sensing study normally means writing an Android app, getting permissions right, handling background execution, and building a data pipeline — before collecting a single sample. This platform does that part once. Designing a new study means writing a JSON file and signing it. +### The name + +*Particeps* is Latin for one who takes part or shares in something, and it is the root of *participant*. Say it PAR-ti-keps. + +The name describes where the design puts the participant, and it is worth being exact about what that does and does not mean. Events are written and encrypted on the participant's own phone; every collector a study enables is shown to them, with what it records and what it cannot establish, before they are asked to consent; and nothing is collected until they press Start. Those are defaults the implementation actually provides. What the name does not grant is authorship of the study: the collector set, the duration, and whether the study uploads on a schedule are fixed in the signed configuration, and a participant cannot change them, add to them, or recall a bundle once it has been delivered. Their leverage over a running study is bounded and real — decline it outright, withhold the Android access an *optional* collector needs so that collector stays off, pause, finish early, withdraw, and delete the local data. A collector the configuration marks required is not optional in that sense: withholding its access stops the study rather than trimming it. + ## How a study works 1. **Generate your keys.** One Ed25519 pair to sign study configurations, one X25519 HPKE pair to decrypt bundles. `researcher-tools` writes raw 32-byte keys as unpadded base64url. 2. **Write the study.** A strict Protocol v1 RFC 8785 JSON file naming collectors, reusable surveys, scheduled interventions, anonymous or assigned-code identity mode, duration, storage quota, consent text, and signing/export public keys. -3. **Sign it.** `researcher-tools sign` produces a `.adccfg` file. Because the signing public key travels inside the signed bytes, any build of the app can verify it. -4. **Distribute.** Participants install the app and import your `.adccfg`. Setup is five steps, one screen each — the study details, what each enabled collector records and does not record, the consent text with the signer's key fingerprint, the Android access your collectors need, and the start button — and collection begins only when they press it. +3. **Sign it.** `researcher-tools sign` produces a `.partcfg` file. Because the signing public key travels inside the signed bytes, any build of the app can verify it. +4. **Distribute.** Participants install the app and import your `.partcfg`. Setup is five steps, one screen each — the study details, what each enabled collector records and does not record, the consent text with the signer's key fingerprint, the Android access your collectors need, and the start button — and collection begins only when they press it. 5. **Collect.** Events are written to encrypted on-device storage. Participants can pause, resume, finish early, or withdraw. -6. **Export and analyse.** The participant exports an encrypted bundle and sends it to you. If the study declares an upload endpoint, the app also delivers immutable ciphertext bundles to an R2 receiver on a schedule. `adc-analysis` inventories, verifies, decrypts, reassembles, and writes typed Parquet offline. +6. **Export and analyse.** The participant exports an encrypted bundle and sends it to you. If the study declares an upload endpoint, the app also delivers immutable ciphertext bundles to an R2 receiver on a schedule. `particeps-analysis` inventories, verifies, decrypts, reassembles, and writes typed Parquet offline. The full procedure, including key handling and study design guidance, is in the [researcher guide](docs/researcher-guide.md). @@ -91,10 +97,10 @@ With an emulator or device attached: ``` The app suite separates the Android signed-configuration regression -([`AndroidConfigurationImportTest`](app/src/androidTest/kotlin/cool/linc/androiddatacollector/AndroidConfigurationImportTest.kt)), -the full participant UI flow ([`CoreFlowTest`](app/src/androidTest/kotlin/cool/linc/androiddatacollector/CoreFlowTest.kt)), +([`AndroidConfigurationImportTest`](app/src/androidTest/kotlin/cool/linc/particeps/AndroidConfigurationImportTest.kt)), +the full participant UI flow ([`CoreFlowTest`](app/src/androidTest/kotlin/cool/linc/particeps/CoreFlowTest.kt)), and the five-collector Android integration -([`P2CollectorEmulatorTest`](app/src/androidTest/kotlin/cool/linc/androiddatacollector/P2CollectorEmulatorTest.kt)). +([`P2CollectorEmulatorTest`](app/src/androidTest/kotlin/cool/linc/particeps/P2CollectorEmulatorTest.kt)). The last test skips when gyro, light, or proximity hardware is absent. Its optional exact-value mode expects a sensor-capable emulator that the host has already configured; the test does not fake Android's sensor APIs: @@ -107,7 +113,7 @@ adb -s emulator-5554 emu sensor set gyroscope 1.25:-2.5:0.5 adb -s emulator-5554 emu sensor set light 123 adb -s emulator-5554 emu sensor set proximity 1 ./gradlew :app:connectedDebugAndroidTest \ - -Pandroid.testInstrumentationRunnerArguments.class=cool.linc.androiddatacollector.P2CollectorEmulatorTest \ + -Pandroid.testInstrumentationRunnerArguments.class=cool.linc.particeps.P2CollectorEmulatorTest \ -Pandroid.testInstrumentationRunnerArguments.p2SyntheticInputs=true ``` @@ -125,9 +131,9 @@ For that reason a **release build ships no demonstration study** — the signed signing-keygen generate an Ed25519 signing pair hpke-keygen generate a raw X25519 HPKE key pair canonicalize strictly parse and emit a canonical configuration -sign sign a canonical configuration into .adccfg +sign sign a canonical configuration into .partcfg check-config verify envelope, signature, platform, validity window, and client build; optionally pin the signer -decrypt decrypt an .adcexp into research-bundle-v1 JSON +decrypt decrypt a .partexp into particeps-research-bundle-v1 JSON ``` ## Architecture @@ -172,17 +178,17 @@ flowchart LR Platform-independent modules contain no `android.*` imports, which keeps the domain logic testable on the JVM. [Component boundaries](docs/component-boundaries.md) documents the contracts. -New contributors should treat [`protocol/v1`](protocol/v1/README.md) as the normative wire contract, the [collector catalog](protocol/v1/collector-catalog.json) as the schema source, and [`docs/p0-p2-implementation-contract.md`](docs/p0-p2-implementation-contract.md) as the implementation decision record. Trace one path through the [configuration codec](core/study-definition/src/main/kotlin/cool/linc/androiddatacollector/core/definition/StudyConfigurationCodec.kt), [signed envelope](core/protocol/src/main/kotlin/cool/linc/androiddatacollector/core/protocol/SignedConfiguration.kt), [bundle exporter](core/export/src/main/kotlin/cool/linc/androiddatacollector/core/export/ResearchExport.kt), [bundle verifier](core/export/src/main/kotlin/cool/linc/androiddatacollector/core/export/ResearchBundleVerifier.kt), [single-entry outbox](app/src/main/kotlin/cool/linc/androiddatacollector/platform/FileUploadOutbox.kt), [HTTP adapter](app/src/main/kotlin/cool/linc/androiddatacollector/platform/OkHttpStudyUploader.kt), [receiver handler](receiver/src/index.ts), and the offline [`adc-analysis`](adc-analysis/README.md) pipeline. The join path is similarly short: [Web authoring](web/src/lib/adc/join.ts), [shared parser](core/protocol/src/main/kotlin/cool/linc/androiddatacollector/core/protocol/JoinLink.kt), [Android staging](app/src/main/kotlin/cool/linc/androiddatacollector/platform/JoinArtifactDownloader.kt), [intent entry](app/src/main/kotlin/cool/linc/androiddatacollector/MainActivity.kt), then the existing [session import](core/study-application/src/main/kotlin/cool/linc/androiddatacollector/core/application/StudyApplication.kt). The [outbox](app/src/test/kotlin/cool/linc/androiddatacollector/platform/FileUploadOutboxTest.kt), [uploader](app/src/test/kotlin/cool/linc/androiddatacollector/platform/OkHttpStudyUploaderTest.kt), and [receiver](receiver/tests/receiver.test.ts) tests make crash/replay and receipt semantics executable. Receiver deployment and R2 operations start at [`receiver/README.md`](receiver/README.md), and the Collector capability policy lives under [`assurance`](assurance/README.md). +New contributors should treat [`protocol/v1`](protocol/v1/README.md) as the normative wire contract, the [collector catalog](protocol/v1/collector-catalog.json) as the schema source, and [`docs/p0-p2-implementation-contract.md`](docs/p0-p2-implementation-contract.md) as the implementation decision record. Trace one path through the [configuration codec](core/study-definition/src/main/kotlin/cool/linc/particeps/core/definition/StudyConfigurationCodec.kt), [signed envelope](core/protocol/src/main/kotlin/cool/linc/particeps/core/protocol/SignedConfiguration.kt), [bundle exporter](core/export/src/main/kotlin/cool/linc/particeps/core/export/ResearchExport.kt), [bundle verifier](core/export/src/main/kotlin/cool/linc/particeps/core/export/ResearchBundleVerifier.kt), [single-entry outbox](app/src/main/kotlin/cool/linc/particeps/platform/FileUploadOutbox.kt), [HTTP adapter](app/src/main/kotlin/cool/linc/particeps/platform/OkHttpStudyUploader.kt), [receiver handler](receiver/src/index.ts), and the offline [`particeps-analysis`](particeps-analysis/README.md) pipeline. The join path is similarly short: [Web authoring](web/src/lib/particeps/join.ts), [shared parser](core/protocol/src/main/kotlin/cool/linc/particeps/core/protocol/JoinLink.kt), [Android staging](app/src/main/kotlin/cool/linc/particeps/platform/JoinArtifactDownloader.kt), [intent entry](app/src/main/kotlin/cool/linc/particeps/MainActivity.kt), then the existing [session import](core/study-application/src/main/kotlin/cool/linc/particeps/core/application/StudyApplication.kt). The [outbox](app/src/test/kotlin/cool/linc/particeps/platform/FileUploadOutboxTest.kt), [uploader](app/src/test/kotlin/cool/linc/particeps/platform/OkHttpStudyUploaderTest.kt), and [receiver](receiver/tests/receiver.test.ts) tests make crash/replay and receipt semantics executable. Receiver deployment and R2 operations start at [`receiver/README.md`](receiver/README.md), and the Collector capability policy lives under [`assurance`](assurance/README.md). For `random_window`, trace the signed model and bounds in -[`StudyConfiguration.kt`](core/study-definition/src/main/kotlin/cool/linc/androiddatacollector/core/definition/StudyConfiguration.kt), +[`StudyConfiguration.kt`](core/study-definition/src/main/kotlin/cool/linc/particeps/core/definition/StudyConfiguration.kt), its codec and [Web editor](web/src/routes/researcher/InterventionEditor.svelte), then the CSPRNG materialization in -[`InterventionSchedulePlanner.kt`](core/study-application/src/main/kotlin/cool/linc/androiddatacollector/core/application/InterventionSchedulePlanner.kt). -The [session](core/study-application/src/main/kotlin/cool/linc/androiddatacollector/core/application/StudyApplication.kt) +[`InterventionSchedulePlanner.kt`](core/study-application/src/main/kotlin/cool/linc/particeps/core/application/InterventionSchedulePlanner.kt). +The [session](core/study-application/src/main/kotlin/cool/linc/particeps/core/application/StudyApplication.kt) persists the occurrence before scheduling; the Android delivery/expiry workers in -[`AndroidStudyPlatform.kt`](app/src/main/kotlin/cool/linc/androiddatacollector/platform/AndroidStudyPlatform.kt) -and [`BootRecoveryReceiver`](app/src/main/kotlin/cool/linc/androiddatacollector/BootRecoveryReceiver.kt) +[`AndroidStudyPlatform.kt`](app/src/main/kotlin/cool/linc/particeps/platform/AndroidStudyPlatform.kt) +and [`BootRecoveryReceiver`](app/src/main/kotlin/cool/linc/particeps/BootRecoveryReceiver.kt) reconcile the same ID after retries, reboot, clock, or time-zone changes. The adjacent planner, runtime, session, and app policy tests make each boundary executable. @@ -201,13 +207,19 @@ runtime, session, and app policy tests make each boundary executable. | [P0–P2 implementation contract](docs/p0-p2-implementation-contract.md) | Locked architectural decisions and scope | | [Collector capability policy](assurance/README.md) | Static source, bytecode, and dependency boundaries for collectors | | [Ciphertext receiver](receiver/README.md) | R2-only Worker contract, verification commands, deployment, and operations | -| [Offline analysis](adc-analysis/README.md) | Ciphertext inventory, verification, reassembly, and typed Parquet materialization | +| [Offline analysis](particeps-analysis/README.md) | Ciphertext inventory, verification, reassembly, and typed Parquet materialization | | [Release process](docs/maintainers/release.md) | Maintainers | ## Contributing New collectors are the main contribution path — see [CONTRIBUTING.md](CONTRIBUTING.md) and the [implementation guide](docs/data-collector-implementation-guide.md). To report a security or privacy issue, see [SECURITY.md](SECURITY.md) rather than opening a public issue. +## Coming from a pre-rename release candidate + +This project was called Android Data Collector through its 1.0 release candidates; every tag published so far carries that identity. The rename to Particeps moved the Android `applicationId` from `cool.linc.androiddatacollector` to `cool.linc.particeps`, and Android treats those as two different applications. There is no upgrade and no migration: installing Particeps does not see, move, or convert anything belonging to an installed pre-rename build, which keeps running under its own name until it is removed. Uninstalling it takes its Keystore keys with it, and every study, encrypted event segment, undelivered outbox bundle, and imported configuration on that install becomes unrecoverable — cloud backup and device transfer were already disabled for this app, so nothing is held anywhere else. Export whatever is still wanted before uninstalling, and re-enable the research keyboard under the new app if a study uses it. + +Artifacts produced before the rename are unsupported for final Protocol v1. A `.adccfg` configuration, a `.adcexp` export, an `ADCCFG01` or `ADCEXP01` container, a `research-bundle-v1` document, an `adc://join/v1` link, and an upload carrying `application/vnd.adc.research-bundle` or any `X-ADC-*` header are invalid input to every current implementation and are rejected exactly as random bytes are. Re-sign configurations with the current tooling and re-run any pilot; there is no converter, and none will be added. + ## Status This repository implements and tests the full local participant flow on Android 14–17. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8d2623e..c499b24 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,11 +16,11 @@ fun Properties.requireSigningValue(name: String): String = ?: error("Missing $name in ${releaseSigningPropertiesFile.path}") android { - namespace = "cool.linc.androiddatacollector" + namespace = "cool.linc.particeps" compileSdk = 37 defaultConfig { - applicationId = "cool.linc.androiddatacollector" + applicationId = "cool.linc.particeps" minSdk = 34 targetSdk = 37 versionCode = providers.gradleProperty("releaseVersionCode").map(String::toInt).getOrElse(1) @@ -76,7 +76,7 @@ kotlin { } tasks.withType().configureEach { - systemProperty("adc.appProjectDir", projectDir.absolutePath) + systemProperty("particeps.appProjectDir", projectDir.absolutePath) } dependencies { @@ -116,6 +116,8 @@ dependencies { debugImplementation(libs.compose.ui.test.manifest) testImplementation(libs.junit4) + // Only to read the shared Protocol v1 corpus in a test; no production code parses JSON this way. + testImplementation(libs.gson) androidTestImplementation(platform(libs.compose.bom)) androidTestImplementation(libs.androidx.test.junit) diff --git a/app/src/androidTest/kotlin/cool/linc/androiddatacollector/AndroidConfigurationImportTest.kt b/app/src/androidTest/kotlin/cool/linc/androiddatacollector/AndroidConfigurationImportTest.kt deleted file mode 100644 index 157c05d..0000000 --- a/app/src/androidTest/kotlin/cool/linc/androiddatacollector/AndroidConfigurationImportTest.kt +++ /dev/null @@ -1,40 +0,0 @@ -package cool.linc.androiddatacollector - -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import cool.linc.androiddatacollector.core.model.ExperimentState -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeout -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNull -import org.junit.Test -import org.junit.runner.RunWith - -/** Regression for Protocol v1 raw-key signature verification on Android's provider set. */ -@RunWith(AndroidJUnit4::class) -class AndroidConfigurationImportTest { - @Test - fun debugDemoImportsIntoTheAndroidSession() = runBlocking { - val application = ApplicationProvider.getApplicationContext() - val session = application.session - withTimeout(TIMEOUT_MILLIS) { session.snapshot.first { it.initialized } } - assertNull("test requires a clean study session", session.snapshot.value.configuration) - - try { - val loadDemo = requireNotNull(DemoStudy.load) - session.importSignedConfiguration(loadDemo(application.resources)) - - assertEquals(ExperimentState.IMPORTED, session.snapshot.value.runtime.metadata?.state) - } finally { - if (session.snapshot.value.configuration != null) { - session.withdraw() - session.deleteLocalData() - } - } - } - - private companion object { - const val TIMEOUT_MILLIS = 20_000L - } -} diff --git a/app/src/androidTest/kotlin/cool/linc/particeps/AndroidConfigurationImportTest.kt b/app/src/androidTest/kotlin/cool/linc/particeps/AndroidConfigurationImportTest.kt new file mode 100644 index 0000000..880a1d2 --- /dev/null +++ b/app/src/androidTest/kotlin/cool/linc/particeps/AndroidConfigurationImportTest.kt @@ -0,0 +1,78 @@ +package cool.linc.particeps + +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import cool.linc.particeps.core.model.ExperimentState +import cool.linc.particeps.core.storage.EncryptedActiveStudyStore +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Protocol v1 configuration import on Android's own provider set: the raw-key signature + * verification the app accepts, and the retired envelope identity it must refuse. + */ +@RunWith(AndroidJUnit4::class) +class AndroidConfigurationImportTest { + @Test + fun debugDemoImportsIntoTheAndroidSession() = runBlocking { + val application = ApplicationProvider.getApplicationContext() + val session = application.session + withTimeout(TIMEOUT_MILLIS) { session.snapshot.first { it.initialized } } + assertNull("test requires a clean study session", session.snapshot.value.configuration) + + try { + val loadDemo = requireNotNull(DemoStudy.load) + session.importSignedConfiguration(loadDemo(application.resources)) + + assertEquals(ExperimentState.IMPORTED, session.snapshot.value.runtime.metadata?.state) + } finally { + if (session.snapshot.value.configuration != null) { + session.withdraw() + session.deleteLocalData() + } + } + } + + /** + * The import picker asks for `application/octet-stream` and the wildcard MIME type, so a file + * left over from the retired identity stays selectable however it is named; the picker is not + * and cannot be the gate. The parser is. An envelope that is well formed apart from its magic + * must fail the import outright, leaving no study in the session and nothing on disk. + */ + @Test + fun theRetiredConfigurationMagicFailsClosedInTheParser() = runBlocking { + val application = ApplicationProvider.getApplicationContext() + val session = application.session + withTimeout(TIMEOUT_MILLIS) { session.snapshot.first { it.initialized } } + assertNull("test requires a clean study session", session.snapshot.value.configuration) + + val loadDemo = requireNotNull(DemoStudy.load) + val envelope = loadDemo(application.resources).also { RETIRED_CONFIGURATION_MAGIC.copyInto(it) } + + val failure = runCatching { session.importSignedConfiguration(envelope) }.exceptionOrNull() + + assertNotNull("the retired magic must not import", failure) + assertNull(session.snapshot.value.configuration) + assertNull(session.snapshot.value.runtime.metadata) + assertNull( + "a refused import must persist no active study", + EncryptedActiveStudyStore(application).load(), + ) + } + + private companion object { + const val TIMEOUT_MILLIS = 20_000L + + /** + * Retired-identity rejection fixture: the pre-Particeps signed-configuration magic, kept + * here only as bytes the import path has to refuse. Nothing in this repository writes it. + */ + val RETIRED_CONFIGURATION_MAGIC = "ADCCFG01".toByteArray(Charsets.US_ASCII) + } +} diff --git a/app/src/androidTest/kotlin/cool/linc/androiddatacollector/CoreFlowTest.kt b/app/src/androidTest/kotlin/cool/linc/particeps/CoreFlowTest.kt similarity index 97% rename from app/src/androidTest/kotlin/cool/linc/androiddatacollector/CoreFlowTest.kt rename to app/src/androidTest/kotlin/cool/linc/particeps/CoreFlowTest.kt index 8b03bd3..84861dd 100644 --- a/app/src/androidTest/kotlin/cool/linc/androiddatacollector/CoreFlowTest.kt +++ b/app/src/androidTest/kotlin/cool/linc/particeps/CoreFlowTest.kt @@ -1,4 +1,4 @@ -package cool.linc.androiddatacollector +package cool.linc.particeps import android.Manifest import androidx.compose.ui.test.assertTextEquals @@ -10,8 +10,8 @@ import androidx.compose.ui.test.performScrollTo import androidx.lifecycle.Lifecycle import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry -import cool.linc.androiddatacollector.core.collector.CollectorStatus -import cool.linc.androiddatacollector.core.model.ExperimentState +import cool.linc.particeps.core.collector.CollectorStatus +import cool.linc.particeps.core.model.ExperimentState import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue diff --git a/app/src/androidTest/kotlin/cool/linc/androiddatacollector/P2CollectorEmulatorTest.kt b/app/src/androidTest/kotlin/cool/linc/particeps/P2CollectorEmulatorTest.kt similarity index 86% rename from app/src/androidTest/kotlin/cool/linc/androiddatacollector/P2CollectorEmulatorTest.kt rename to app/src/androidTest/kotlin/cool/linc/particeps/P2CollectorEmulatorTest.kt index ef98f88..25e5c23 100644 --- a/app/src/androidTest/kotlin/cool/linc/androiddatacollector/P2CollectorEmulatorTest.kt +++ b/app/src/androidTest/kotlin/cool/linc/particeps/P2CollectorEmulatorTest.kt @@ -1,4 +1,4 @@ -package cool.linc.androiddatacollector +package cool.linc.particeps import android.content.Context import android.hardware.Sensor @@ -6,27 +6,27 @@ import android.hardware.SensorManager import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry -import cool.linc.androiddatacollector.collector.ambientlight.AmbientLightCollectorPlugin -import cool.linc.androiddatacollector.collector.batterystate.BatteryStateCollectorPlugin -import cool.linc.androiddatacollector.collector.gyroscope.GyroscopeCollectorPlugin -import cool.linc.androiddatacollector.collector.proximity.ProximityCollectorPlugin -import cool.linc.androiddatacollector.collector.temporalcontext.TemporalContextCollectorPlugin -import cool.linc.androiddatacollector.core.collector.AdmissionToken -import cool.linc.androiddatacollector.core.collector.Collector -import cool.linc.androiddatacollector.core.collector.CollectorContext -import cool.linc.androiddatacollector.core.collector.CollectorPlugin -import cool.linc.androiddatacollector.core.collector.CollectorStatus -import cool.linc.androiddatacollector.core.collector.EmitResult -import cool.linc.androiddatacollector.core.collector.EventSink -import cool.linc.androiddatacollector.core.definition.AmbientLightConfiguration -import cool.linc.androiddatacollector.core.definition.BatteryStateConfiguration -import cool.linc.androiddatacollector.core.definition.CollectorConfiguration -import cool.linc.androiddatacollector.core.definition.GyroscopeConfiguration -import cool.linc.androiddatacollector.core.definition.ProximityConfiguration -import cool.linc.androiddatacollector.core.definition.TemporalContextConfiguration -import cool.linc.androiddatacollector.core.model.EventDraft -import cool.linc.androiddatacollector.core.model.RecordedEvent -import cool.linc.androiddatacollector.platform.AndroidResearchClocks +import cool.linc.particeps.collector.ambientlight.AmbientLightCollectorPlugin +import cool.linc.particeps.collector.batterystate.BatteryStateCollectorPlugin +import cool.linc.particeps.collector.gyroscope.GyroscopeCollectorPlugin +import cool.linc.particeps.collector.proximity.ProximityCollectorPlugin +import cool.linc.particeps.collector.temporalcontext.TemporalContextCollectorPlugin +import cool.linc.particeps.core.collector.AdmissionToken +import cool.linc.particeps.core.collector.Collector +import cool.linc.particeps.core.collector.CollectorContext +import cool.linc.particeps.core.collector.CollectorPlugin +import cool.linc.particeps.core.collector.CollectorStatus +import cool.linc.particeps.core.collector.EmitResult +import cool.linc.particeps.core.collector.EventSink +import cool.linc.particeps.core.definition.AmbientLightConfiguration +import cool.linc.particeps.core.definition.BatteryStateConfiguration +import cool.linc.particeps.core.definition.CollectorConfiguration +import cool.linc.particeps.core.definition.GyroscopeConfiguration +import cool.linc.particeps.core.definition.ProximityConfiguration +import cool.linc.particeps.core.definition.TemporalContextConfiguration +import cool.linc.particeps.core.model.EventDraft +import cool.linc.particeps.core.model.RecordedEvent +import cool.linc.particeps.platform.AndroidResearchClocks import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob diff --git a/app/src/debug/kotlin/cool/linc/androiddatacollector/DemoStudy.kt b/app/src/debug/kotlin/cool/linc/particeps/DemoStudy.kt similarity index 96% rename from app/src/debug/kotlin/cool/linc/androiddatacollector/DemoStudy.kt rename to app/src/debug/kotlin/cool/linc/particeps/DemoStudy.kt index c017de8..e3c27f0 100644 --- a/app/src/debug/kotlin/cool/linc/androiddatacollector/DemoStudy.kt +++ b/app/src/debug/kotlin/cool/linc/particeps/DemoStudy.kt @@ -1,4 +1,4 @@ -package cool.linc.androiddatacollector +package cool.linc.particeps import android.content.res.Resources import java.util.Base64 diff --git a/app/src/debug/res/raw/demo_study_envelope.txt b/app/src/debug/res/raw/demo_study_envelope.txt index c1549ec..484a69a 100644 --- a/app/src/debug/res/raw/demo_study_envelope.txt +++ b/app/src/debug/res/raw/demo_study_envelope.txt @@ -1 +1 @@ -QURDQ0ZHMDEAEAAACkFkZW1vLXNpZ25lci0yMDI2eyJhc3NpZ25lZF9wYXJ0aWNpcGFudF9pZCI6bnVsbCwiY29sbGVjdG9ycyI6W3siY29uZmlnIjp7fSwiaWQiOiJhcHBfbGlmZWN5Y2xlLnYxIiwicmVxdWlyZWQiOnRydWV9LHsiY29uZmlnIjp7Im1heGltdW1fcmVwb3J0X2xhdGVuY3lfdXMiOjEwMDAwMDAsInNhbXBsaW5nX3BlcmlvZF91cyI6MTAwMDAwfSwiaWQiOiJhY2NlbGVyb21ldGVyLnYxIiwicmVxdWlyZWQiOnRydWV9LHsiY29uZmlnIjp7ImluY2x1ZGVfYmFuZHdpZHRoX2VzdGltYXRlcyI6dHJ1ZX0sImlkIjoibmV0d29ya19zdGF0ZS52MSIsInJlcXVpcmVkIjp0cnVlfSx7ImNvbmZpZyI6eyJwb2xsX2ludGVydmFsX21pbnV0ZXMiOjUsInRyYW5zcG9ydHMiOlsibW9iaWxlIiwid2lmaSJdfSwiaWQiOiJuZXR3b3JrX3VzYWdlLnYxIiwicmVxdWlyZWQiOmZhbHNlfSx7ImNvbmZpZyI6eyJwb2xsX2ludGVydmFsX21pbnV0ZXMiOjE1fSwiaWQiOiJ1c2FnZV9ldmVudHMudjEiLCJyZXF1aXJlZCI6ZmFsc2V9LHsiY29uZmlnIjp7ImludGVydmFsX21pbGxpcyI6MTAwMDAsIm1heGltdW1fYmF0Y2hfZGVsYXlfbWlsbGlzIjozMDAwMCwibWluaW11bV9kaXNwbGFjZW1lbnRfbWlsbGltZXRlcnMiOjUwMDAsIm1pbmltdW1faW50ZXJ2YWxfbWlsbGlzIjo1MDAwLCJwcmlvcml0eSI6IkJBTEFOQ0VEIn0sImlkIjoibG9jYXRpb24udjEiLCJyZXF1aXJlZCI6ZmFsc2V9LHsiY29uZmlnIjp7InRyYWplY3Rvcnlfc2FtcGxpbmdfaHoiOjYwfSwiaWQiOiJrZXlib2FyZF90b3VjaC52MSIsInJlcXVpcmVkIjpmYWxzZX1dLCJjb25maWd1cmF0aW9uX2lkIjoiZGVtby1jb25maWctMjAyNiIsImNvbnNlbnQiOnsiZG9jdW1lbnRfdmVyc2lvbiI6ImRlbW8tMSIsInN1bW1hcnkiOiJUaGlzIGRlbW9uc3RyYXRpb24gY2FuIGNvbGxlY3QgcHJlY2lzZSBsb2NhdGlvbiwgbW90aW9uLCBuZXR3b3JrIHN0YXRlLCBhZ2dyZWdhdGUgV2ktRmkvbW9iaWxlIHVzYWdlLCBhcHAgYW5kIHNjcmVlbiB1c2FnZSBldmVudHMsIHRoaXMgYXBwJ3MgbGlmZWN5Y2xlLCBhbmQgdG91Y2ggZHluYW1pY3MgbWFkZSBpbnNpZGUgdGhlIG9wdGlvbmFsIHJlc2VhcmNoIGtleWJvYXJkLiBJdCBuZXZlciByZWNvcmRzIGtleWJvYXJkIHRleHQuIERhdGEgc3RheXMgZW5jcnlwdGVkIG9uIHRoaXMgZGV2aWNlIHVudGlsIHlvdSBjaG9vc2UgRXhwb3J0LiBZb3UgY2FuIHBhdXNlLCB3aXRoZHJhdywgZXhwb3J0IHJlcGVhdGVkbHksIG9yIGRlbGV0ZSBsb2NhbCBkYXRhLiJ9LCJkdXJhdGlvbl9ob3VycyI6MjQsImV4cGVyaW1lbnRfaWQiOiJtb2R1bGFyLXNlbnNpbmctZGVtbyIsImV4cGlyZXNfYXQiOiIyMDM1LTAxLTAxVDAwOjAwOjAwWiIsImV4cG9ydCI6eyJocGtlX3B1YmxpY19rZXkiOiJHbkpEZkRoOFhIMWVBUlRIbVRMTmxvZzljdXJ0cGhUcEVuN0wzNnNZMFFBIiwicmVzZWFyY2hlcl9rZXlfaWQiOiJkZW1vLWhwa2UtMjAyNiJ9LCJpbnRlcnZlbnRpb25zIjpbeyJhY3Rpb24iOnsibm90aWZpY2F0aW9uX21lc3NhZ2UiOiJQbGVhc2UgY29tcGxldGUgdGhlIHN0dWR5IGNoZWNrLWluLiIsIm5vdGlmaWNhdGlvbl90aXRsZSI6IlN0dWR5IGNoZWNrLWluIiwic3VydmV5X2lkIjoiZGVtby1zdXJ2ZXkiLCJ0eXBlIjoic3VydmV5In0sImlkIjoiZGVtby1jaGVjay1pbiIsInRyaWdnZXJzIjpbeyJhdmFpbGFiaWxpdHlfbWludXRlcyI6MTQ0MCwiaWQiOiJhZnRlci1vbmUtaG91ciIsInNjaGVkdWxlIjp7ImNsb2NrIjoiQUNUSVZFX1JVTk5JTkdfVElNRSIsIm9mZnNldF9taW51dGVzIjo2MCwidHlwZSI6Im9uZV90aW1lIn19XX1dLCJpc3N1ZWRfYXQiOiIyMDI2LTAxLTAxVDAwOjAwOjAwWiIsIm1pbmltdW1fY2xpZW50X3ZlcnNpb24iOiIxIiwicGxhdGZvcm0iOiJhbmRyb2lkIiwicHVycG9zZSI6IlZlcmlmeSB0aGUgY29tcGxldGUgb24tZGV2aWNlIGNvbGxlY3Rpb24sIHBhdXNlLCBleHBvcnQsIGFuZCByZXNlYXJjaGVyLWRlY3J5cHRpb24gbG9vcC4iLCJyZXNlYXJjaGVyIjp7ImNvbnRhY3QiOiJyZXNlYXJjaEBleGFtcGxlLmludmFsaWQiLCJuYW1lIjoiQW5kcm9pZCBEYXRhIENvbGxlY3RvciBtYWludGFpbmVycyJ9LCJzY2hlbWFfdmVyc2lvbiI6MSwic2lnbmVyIjp7ImtleV9pZCI6ImRlbW8tc2lnbmVyLTIwMjYiLCJwdWJsaWNfa2V5Ijoic1JTYVRwWm1UU0JMN2VONm5TX0hCc05tTE04bjFoZFJtSXQxdnRMWnNDMCJ9LCJzdG9yYWdlIjp7Im1heGltdW1fbG9jYWxfYnl0ZXMiOjE2Nzc3MjE2fSwic3VydmV5cyI6W3siZGVzY3JpcHRpb24iOnsiZGVmYXVsdCI6IlRlbGwgdXMgaG93IHRoZSBzdHVkeSBpcyBnb2luZy4iLCJ0cmFuc2xhdGlvbnMiOnt9fSwiaWQiOiJkZW1vLXN1cnZleSIsInF1ZXN0aW9ucyI6W3siaWQiOiJzdGF0dXMtbm90ZSIsIm1heGltdW1fbGVuZ3RoIjo1MDAsInByb21wdCI6eyJkZWZhdWx0IjoiSG93IGlzIGl0IGdvaW5nPyIsInRyYW5zbGF0aW9ucyI6e319LCJyZXF1aXJlZCI6ZmFsc2UsInR5cGUiOiJzaG9ydF90ZXh0In1dLCJ0aXRsZSI6eyJkZWZhdWx0IjoiU3R1ZHkgY2hlY2staW4iLCJ0cmFuc2xhdGlvbnMiOnsiemgtVFciOiLnoJTnqbbnorroqo0ifX19XSwidGl0bGUiOiJNb2R1bGFyIHNlbnNpbmcgZGVtb25zdHJhdGlvbiIsInVwbG9hZCI6e319UmqpwSmoyhlVTYn/JzgrNbEBiCTEQhDUD6j/6iTkZLDj21a0YnS6JAFQaKSyvS3N/xZDfJnjsNdas/z0oLZ3DQ== +UFRDQ0ZHMDEAEAAACjRkZW1vLXNpZ25lci0yMDI2eyJhc3NpZ25lZF9wYXJ0aWNpcGFudF9pZCI6bnVsbCwiY29sbGVjdG9ycyI6W3siY29uZmlnIjp7fSwiaWQiOiJhcHBfbGlmZWN5Y2xlLnYxIiwicmVxdWlyZWQiOnRydWV9LHsiY29uZmlnIjp7Im1heGltdW1fcmVwb3J0X2xhdGVuY3lfdXMiOjEwMDAwMDAsInNhbXBsaW5nX3BlcmlvZF91cyI6MTAwMDAwfSwiaWQiOiJhY2NlbGVyb21ldGVyLnYxIiwicmVxdWlyZWQiOnRydWV9LHsiY29uZmlnIjp7ImluY2x1ZGVfYmFuZHdpZHRoX2VzdGltYXRlcyI6dHJ1ZX0sImlkIjoibmV0d29ya19zdGF0ZS52MSIsInJlcXVpcmVkIjp0cnVlfSx7ImNvbmZpZyI6eyJwb2xsX2ludGVydmFsX21pbnV0ZXMiOjUsInRyYW5zcG9ydHMiOlsibW9iaWxlIiwid2lmaSJdfSwiaWQiOiJuZXR3b3JrX3VzYWdlLnYxIiwicmVxdWlyZWQiOmZhbHNlfSx7ImNvbmZpZyI6eyJwb2xsX2ludGVydmFsX21pbnV0ZXMiOjE1fSwiaWQiOiJ1c2FnZV9ldmVudHMudjEiLCJyZXF1aXJlZCI6ZmFsc2V9LHsiY29uZmlnIjp7ImludGVydmFsX21pbGxpcyI6MTAwMDAsIm1heGltdW1fYmF0Y2hfZGVsYXlfbWlsbGlzIjozMDAwMCwibWluaW11bV9kaXNwbGFjZW1lbnRfbWlsbGltZXRlcnMiOjUwMDAsIm1pbmltdW1faW50ZXJ2YWxfbWlsbGlzIjo1MDAwLCJwcmlvcml0eSI6IkJBTEFOQ0VEIn0sImlkIjoibG9jYXRpb24udjEiLCJyZXF1aXJlZCI6ZmFsc2V9LHsiY29uZmlnIjp7InRyYWplY3Rvcnlfc2FtcGxpbmdfaHoiOjYwfSwiaWQiOiJrZXlib2FyZF90b3VjaC52MSIsInJlcXVpcmVkIjpmYWxzZX1dLCJjb25maWd1cmF0aW9uX2lkIjoiZGVtby1jb25maWctMjAyNiIsImNvbnNlbnQiOnsiZG9jdW1lbnRfdmVyc2lvbiI6ImRlbW8tMSIsInN1bW1hcnkiOiJUaGlzIGRlbW9uc3RyYXRpb24gY2FuIGNvbGxlY3QgcHJlY2lzZSBsb2NhdGlvbiwgbW90aW9uLCBuZXR3b3JrIHN0YXRlLCBhZ2dyZWdhdGUgV2ktRmkvbW9iaWxlIHVzYWdlLCBhcHAgYW5kIHNjcmVlbiB1c2FnZSBldmVudHMsIHRoaXMgYXBwJ3MgbGlmZWN5Y2xlLCBhbmQgdG91Y2ggZHluYW1pY3MgbWFkZSBpbnNpZGUgdGhlIG9wdGlvbmFsIHJlc2VhcmNoIGtleWJvYXJkLiBJdCBuZXZlciByZWNvcmRzIGtleWJvYXJkIHRleHQuIERhdGEgc3RheXMgZW5jcnlwdGVkIG9uIHRoaXMgZGV2aWNlIHVudGlsIHlvdSBjaG9vc2UgRXhwb3J0LiBZb3UgY2FuIHBhdXNlLCB3aXRoZHJhdywgZXhwb3J0IHJlcGVhdGVkbHksIG9yIGRlbGV0ZSBsb2NhbCBkYXRhLiJ9LCJkdXJhdGlvbl9ob3VycyI6MjQsImV4cGVyaW1lbnRfaWQiOiJtb2R1bGFyLXNlbnNpbmctZGVtbyIsImV4cGlyZXNfYXQiOiIyMDM1LTAxLTAxVDAwOjAwOjAwWiIsImV4cG9ydCI6eyJocGtlX3B1YmxpY19rZXkiOiJHbkpEZkRoOFhIMWVBUlRIbVRMTmxvZzljdXJ0cGhUcEVuN0wzNnNZMFFBIiwicmVzZWFyY2hlcl9rZXlfaWQiOiJkZW1vLWhwa2UtMjAyNiJ9LCJpbnRlcnZlbnRpb25zIjpbeyJhY3Rpb24iOnsibm90aWZpY2F0aW9uX21lc3NhZ2UiOiJQbGVhc2UgY29tcGxldGUgdGhlIHN0dWR5IGNoZWNrLWluLiIsIm5vdGlmaWNhdGlvbl90aXRsZSI6IlN0dWR5IGNoZWNrLWluIiwic3VydmV5X2lkIjoiZGVtby1zdXJ2ZXkiLCJ0eXBlIjoic3VydmV5In0sImlkIjoiZGVtby1jaGVjay1pbiIsInRyaWdnZXJzIjpbeyJhdmFpbGFiaWxpdHlfbWludXRlcyI6MTQ0MCwiaWQiOiJhZnRlci1vbmUtaG91ciIsInNjaGVkdWxlIjp7ImNsb2NrIjoiQUNUSVZFX1JVTk5JTkdfVElNRSIsIm9mZnNldF9taW51dGVzIjo2MCwidHlwZSI6Im9uZV90aW1lIn19XX1dLCJpc3N1ZWRfYXQiOiIyMDI2LTAxLTAxVDAwOjAwOjAwWiIsIm1pbmltdW1fY2xpZW50X3ZlcnNpb24iOiIxIiwicGxhdGZvcm0iOiJhbmRyb2lkIiwicHVycG9zZSI6IlZlcmlmeSB0aGUgY29tcGxldGUgb24tZGV2aWNlIGNvbGxlY3Rpb24sIHBhdXNlLCBleHBvcnQsIGFuZCByZXNlYXJjaGVyLWRlY3J5cHRpb24gbG9vcC4iLCJyZXNlYXJjaGVyIjp7ImNvbnRhY3QiOiJyZXNlYXJjaEBleGFtcGxlLmludmFsaWQiLCJuYW1lIjoiUGFydGljZXBzIG1haW50YWluZXJzIn0sInNjaGVtYV92ZXJzaW9uIjoxLCJzaWduZXIiOnsia2V5X2lkIjoiZGVtby1zaWduZXItMjAyNiIsInB1YmxpY19rZXkiOiJzUlNhVHBabVRTQkw3ZU42blNfSEJzTm1MTThuMWhkUm1JdDF2dExac0MwIn0sInN0b3JhZ2UiOnsibWF4aW11bV9sb2NhbF9ieXRlcyI6MTY3NzcyMTZ9LCJzdXJ2ZXlzIjpbeyJkZXNjcmlwdGlvbiI6eyJkZWZhdWx0IjoiVGVsbCB1cyBob3cgdGhlIHN0dWR5IGlzIGdvaW5nLiIsInRyYW5zbGF0aW9ucyI6e319LCJpZCI6ImRlbW8tc3VydmV5IiwicXVlc3Rpb25zIjpbeyJpZCI6InN0YXR1cy1ub3RlIiwibWF4aW11bV9sZW5ndGgiOjUwMCwicHJvbXB0Ijp7ImRlZmF1bHQiOiJIb3cgaXMgaXQgZ29pbmc/IiwidHJhbnNsYXRpb25zIjp7fX0sInJlcXVpcmVkIjpmYWxzZSwidHlwZSI6InNob3J0X3RleHQifV0sInRpdGxlIjp7ImRlZmF1bHQiOiJTdHVkeSBjaGVjay1pbiIsInRyYW5zbGF0aW9ucyI6eyJ6aC1UVyI6IueglOeptueiuuiqjSJ9fX1dLCJ0aXRsZSI6Ik1vZHVsYXIgc2Vuc2luZyBkZW1vbnN0cmF0aW9uIiwidXBsb2FkIjp7fX3nB+WpAxyZ08hPu53ZM6DiixluueAXEbVTTGNDBpy3U9tvjUtlp68r34IUvD9yYOX4muw2nu7k9ICMASvvzvAM diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 195cce8..4da9e42 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,7 +27,7 @@ android:label="@string/app_name" android:localeConfig="@xml/locales_config" android:supportsRtl="true" - android:theme="@style/Theme.AndroidDataCollector" + android:theme="@style/Theme.Particeps" android:usesCleartextTraffic="false"> diff --git a/app/src/main/kotlin/cool/linc/androiddatacollector/AppLocale.kt b/app/src/main/kotlin/cool/linc/particeps/AppLocale.kt similarity index 98% rename from app/src/main/kotlin/cool/linc/androiddatacollector/AppLocale.kt rename to app/src/main/kotlin/cool/linc/particeps/AppLocale.kt index 8bade6d..6ea7ee5 100644 --- a/app/src/main/kotlin/cool/linc/androiddatacollector/AppLocale.kt +++ b/app/src/main/kotlin/cool/linc/particeps/AppLocale.kt @@ -1,4 +1,4 @@ -package cool.linc.androiddatacollector +package cool.linc.particeps import android.app.LocaleConfig import android.app.LocaleManager diff --git a/app/src/main/kotlin/cool/linc/androiddatacollector/BootRecoveryReceiver.kt b/app/src/main/kotlin/cool/linc/particeps/BootRecoveryReceiver.kt similarity index 91% rename from app/src/main/kotlin/cool/linc/androiddatacollector/BootRecoveryReceiver.kt rename to app/src/main/kotlin/cool/linc/particeps/BootRecoveryReceiver.kt index 5fcf697..281d47c 100644 --- a/app/src/main/kotlin/cool/linc/androiddatacollector/BootRecoveryReceiver.kt +++ b/app/src/main/kotlin/cool/linc/particeps/BootRecoveryReceiver.kt @@ -1,9 +1,9 @@ -package cool.linc.androiddatacollector +package cool.linc.particeps import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import cool.linc.androiddatacollector.platform.InterventionDeliveryCoordinator +import cool.linc.particeps.platform.InterventionDeliveryCoordinator import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch diff --git a/app/src/main/kotlin/cool/linc/androiddatacollector/CollectionService.kt b/app/src/main/kotlin/cool/linc/particeps/CollectionService.kt similarity index 95% rename from app/src/main/kotlin/cool/linc/androiddatacollector/CollectionService.kt rename to app/src/main/kotlin/cool/linc/particeps/CollectionService.kt index 2d7e364..f1ea1c5 100644 --- a/app/src/main/kotlin/cool/linc/androiddatacollector/CollectionService.kt +++ b/app/src/main/kotlin/cool/linc/particeps/CollectionService.kt @@ -1,4 +1,4 @@ -package cool.linc.androiddatacollector +package cool.linc.particeps import android.app.Notification import android.app.NotificationChannel @@ -58,7 +58,7 @@ class CollectionService : Service() { } companion object { - private const val ACTION_START = "cool.linc.androiddatacollector.START_COLLECTION" + private const val ACTION_START = "cool.linc.particeps.START_COLLECTION" private const val EXTRA_STUDY_TITLE = "study_title" private const val EXTRA_LOCATION = "location" private const val CHANNEL_ID = "active-research-collection" diff --git a/app/src/main/kotlin/cool/linc/androiddatacollector/CollectorApplication.kt b/app/src/main/kotlin/cool/linc/particeps/CollectorApplication.kt similarity index 65% rename from app/src/main/kotlin/cool/linc/androiddatacollector/CollectorApplication.kt rename to app/src/main/kotlin/cool/linc/particeps/CollectorApplication.kt index 9f25d8a..41e1f66 100644 --- a/app/src/main/kotlin/cool/linc/androiddatacollector/CollectorApplication.kt +++ b/app/src/main/kotlin/cool/linc/particeps/CollectorApplication.kt @@ -1,43 +1,43 @@ -package cool.linc.androiddatacollector +package cool.linc.particeps import android.app.Application -import cool.linc.androiddatacollector.collector.accelerometer.AccelerometerCollectorPlugin -import cool.linc.androiddatacollector.collector.ambientlight.AmbientLightCollectorPlugin -import cool.linc.androiddatacollector.collector.applifecycle.AppLifecycleCollectorPlugin -import cool.linc.androiddatacollector.collector.batterystate.BatteryStateCollectorPlugin -import cool.linc.androiddatacollector.collector.gyroscope.GyroscopeCollectorPlugin -import cool.linc.androiddatacollector.collector.keyboardime.KeyboardTouchCollectorPlugin -import cool.linc.androiddatacollector.collector.keyboardime.ResearchInputMethodService -import cool.linc.androiddatacollector.collector.location.LocationCollectorPlugin -import cool.linc.androiddatacollector.collector.networkstate.NetworkStateCollectorPlugin -import cool.linc.androiddatacollector.collector.networkusage.NetworkUsageCollectorPlugin -import cool.linc.androiddatacollector.collector.proximity.ProximityCollectorPlugin -import cool.linc.androiddatacollector.collector.temporalcontext.TemporalContextCollectorPlugin -import cool.linc.androiddatacollector.collector.usageevents.UsageEventsCollectorPlugin -import cool.linc.androiddatacollector.core.access.AccessManager -import cool.linc.androiddatacollector.core.application.ExperimentRuntimeFactory -import cool.linc.androiddatacollector.core.application.StudyAccessPolicy -import cool.linc.androiddatacollector.core.application.StudyExporter -import cool.linc.androiddatacollector.core.application.StudySessionManager -import cool.linc.androiddatacollector.core.application.StudyStoreFactory -import cool.linc.androiddatacollector.core.application.StudyVerifier -import cool.linc.androiddatacollector.core.collector.CollectorRegistry -import cool.linc.androiddatacollector.core.definition.StudyConfiguration -import cool.linc.androiddatacollector.core.export.BundleKind -import cool.linc.androiddatacollector.core.export.BundleProducer -import cool.linc.androiddatacollector.core.export.ExportSnapshot -import cool.linc.androiddatacollector.core.export.ResearchExport -import cool.linc.androiddatacollector.core.protocol.ConfigurationVerifier -import cool.linc.androiddatacollector.core.runtime.ExperimentRuntime -import cool.linc.androiddatacollector.core.storage.EncryptedActiveStudyStore -import cool.linc.androiddatacollector.core.storage.EncryptedExperimentStore -import cool.linc.androiddatacollector.platform.AndroidResearchClocks -import cool.linc.androiddatacollector.platform.AndroidStudyCollectionHost -import cool.linc.androiddatacollector.platform.AndroidStudyWorkScheduler -import cool.linc.androiddatacollector.platform.FileUploadOutbox -import cool.linc.androiddatacollector.platform.InterventionDeliveryCoordinator -import cool.linc.androiddatacollector.platform.JoinArtifactDownloader -import cool.linc.androiddatacollector.platform.OkHttpStudyUploader +import cool.linc.particeps.collector.accelerometer.AccelerometerCollectorPlugin +import cool.linc.particeps.collector.ambientlight.AmbientLightCollectorPlugin +import cool.linc.particeps.collector.applifecycle.AppLifecycleCollectorPlugin +import cool.linc.particeps.collector.batterystate.BatteryStateCollectorPlugin +import cool.linc.particeps.collector.gyroscope.GyroscopeCollectorPlugin +import cool.linc.particeps.collector.keyboardime.KeyboardTouchCollectorPlugin +import cool.linc.particeps.collector.keyboardime.ResearchInputMethodService +import cool.linc.particeps.collector.location.LocationCollectorPlugin +import cool.linc.particeps.collector.networkstate.NetworkStateCollectorPlugin +import cool.linc.particeps.collector.networkusage.NetworkUsageCollectorPlugin +import cool.linc.particeps.collector.proximity.ProximityCollectorPlugin +import cool.linc.particeps.collector.temporalcontext.TemporalContextCollectorPlugin +import cool.linc.particeps.collector.usageevents.UsageEventsCollectorPlugin +import cool.linc.particeps.core.access.AccessManager +import cool.linc.particeps.core.application.ExperimentRuntimeFactory +import cool.linc.particeps.core.application.StudyAccessPolicy +import cool.linc.particeps.core.application.StudyExporter +import cool.linc.particeps.core.application.StudySessionManager +import cool.linc.particeps.core.application.StudyStoreFactory +import cool.linc.particeps.core.application.StudyVerifier +import cool.linc.particeps.core.collector.CollectorRegistry +import cool.linc.particeps.core.definition.StudyConfiguration +import cool.linc.particeps.core.export.BundleKind +import cool.linc.particeps.core.export.BundleProducer +import cool.linc.particeps.core.export.ExportSnapshot +import cool.linc.particeps.core.export.ResearchExport +import cool.linc.particeps.core.protocol.ConfigurationVerifier +import cool.linc.particeps.core.runtime.ExperimentRuntime +import cool.linc.particeps.core.storage.EncryptedActiveStudyStore +import cool.linc.particeps.core.storage.EncryptedExperimentStore +import cool.linc.particeps.platform.AndroidResearchClocks +import cool.linc.particeps.platform.AndroidStudyCollectionHost +import cool.linc.particeps.platform.AndroidStudyWorkScheduler +import cool.linc.particeps.platform.FileUploadOutbox +import cool.linc.particeps.platform.InterventionDeliveryCoordinator +import cool.linc.particeps.platform.JoinArtifactDownloader +import cool.linc.particeps.platform.OkHttpStudyUploader import java.time.Instant import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers diff --git a/app/src/main/kotlin/cool/linc/androiddatacollector/CollectorDashboard.kt b/app/src/main/kotlin/cool/linc/particeps/CollectorDashboard.kt similarity index 98% rename from app/src/main/kotlin/cool/linc/androiddatacollector/CollectorDashboard.kt rename to app/src/main/kotlin/cool/linc/particeps/CollectorDashboard.kt index f877294..d7fa222 100644 --- a/app/src/main/kotlin/cool/linc/androiddatacollector/CollectorDashboard.kt +++ b/app/src/main/kotlin/cool/linc/particeps/CollectorDashboard.kt @@ -1,4 +1,4 @@ -package cool.linc.androiddatacollector +package cool.linc.particeps import androidx.compose.foundation.Canvas import androidx.compose.foundation.background @@ -52,13 +52,13 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import cool.linc.androiddatacollector.core.collector.AccessKind -import cool.linc.androiddatacollector.core.collector.AccessStatus -import cool.linc.androiddatacollector.core.collector.CollectorStatus -import cool.linc.androiddatacollector.core.definition.StudyConfiguration -import cool.linc.androiddatacollector.core.definition.UploadConfiguration -import cool.linc.androiddatacollector.core.model.ExperimentState -import cool.linc.androiddatacollector.core.model.StudyMetadata +import cool.linc.particeps.core.collector.AccessKind +import cool.linc.particeps.core.collector.AccessStatus +import cool.linc.particeps.core.collector.CollectorStatus +import cool.linc.particeps.core.definition.StudyConfiguration +import cool.linc.particeps.core.definition.UploadConfiguration +import cool.linc.particeps.core.model.ExperimentState +import cool.linc.particeps.core.model.StudyMetadata import java.text.NumberFormat import kotlinx.coroutines.delay diff --git a/app/src/main/kotlin/cool/linc/androiddatacollector/CollectorGlyphs.kt b/app/src/main/kotlin/cool/linc/particeps/CollectorGlyphs.kt similarity index 99% rename from app/src/main/kotlin/cool/linc/androiddatacollector/CollectorGlyphs.kt rename to app/src/main/kotlin/cool/linc/particeps/CollectorGlyphs.kt index 3b56243..bb8b64b 100644 --- a/app/src/main/kotlin/cool/linc/androiddatacollector/CollectorGlyphs.kt +++ b/app/src/main/kotlin/cool/linc/particeps/CollectorGlyphs.kt @@ -1,4 +1,4 @@ -package cool.linc.androiddatacollector +package cool.linc.particeps import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.size diff --git a/app/src/main/kotlin/cool/linc/androiddatacollector/CollectorSummary.kt b/app/src/main/kotlin/cool/linc/particeps/CollectorSummary.kt similarity index 87% rename from app/src/main/kotlin/cool/linc/androiddatacollector/CollectorSummary.kt rename to app/src/main/kotlin/cool/linc/particeps/CollectorSummary.kt index 7d6a82a..d175c12 100644 --- a/app/src/main/kotlin/cool/linc/androiddatacollector/CollectorSummary.kt +++ b/app/src/main/kotlin/cool/linc/particeps/CollectorSummary.kt @@ -1,22 +1,22 @@ -package cool.linc.androiddatacollector +package cool.linc.particeps import androidx.compose.runtime.Composable import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource -import cool.linc.androiddatacollector.core.definition.AccelerometerConfiguration -import cool.linc.androiddatacollector.core.definition.AmbientLightConfiguration -import cool.linc.androiddatacollector.core.definition.AppLifecycleConfiguration -import cool.linc.androiddatacollector.core.definition.BatteryStateConfiguration -import cool.linc.androiddatacollector.core.definition.CollectorConfiguration -import cool.linc.androiddatacollector.core.definition.KeyboardTouchConfiguration -import cool.linc.androiddatacollector.core.definition.GyroscopeConfiguration -import cool.linc.androiddatacollector.core.definition.LocationConfiguration -import cool.linc.androiddatacollector.core.definition.NetworkStateConfiguration -import cool.linc.androiddatacollector.core.definition.NetworkUsageConfiguration -import cool.linc.androiddatacollector.core.definition.ProximityConfiguration -import cool.linc.androiddatacollector.core.definition.TemporalContextConfiguration -import cool.linc.androiddatacollector.core.definition.UsageEventsConfiguration -import cool.linc.androiddatacollector.core.definition.UploadConfiguration +import cool.linc.particeps.core.definition.AccelerometerConfiguration +import cool.linc.particeps.core.definition.AmbientLightConfiguration +import cool.linc.particeps.core.definition.AppLifecycleConfiguration +import cool.linc.particeps.core.definition.BatteryStateConfiguration +import cool.linc.particeps.core.definition.CollectorConfiguration +import cool.linc.particeps.core.definition.KeyboardTouchConfiguration +import cool.linc.particeps.core.definition.GyroscopeConfiguration +import cool.linc.particeps.core.definition.LocationConfiguration +import cool.linc.particeps.core.definition.NetworkStateConfiguration +import cool.linc.particeps.core.definition.NetworkUsageConfiguration +import cool.linc.particeps.core.definition.ProximityConfiguration +import cool.linc.particeps.core.definition.TemporalContextConfiguration +import cool.linc.particeps.core.definition.UsageEventsConfiguration +import cool.linc.particeps.core.definition.UploadConfiguration /** * One collector, described to the participant in their own language. diff --git a/app/src/main/kotlin/cool/linc/androiddatacollector/ExperimentDeadlineWorker.kt b/app/src/main/kotlin/cool/linc/particeps/ExperimentDeadlineWorker.kt similarity index 86% rename from app/src/main/kotlin/cool/linc/androiddatacollector/ExperimentDeadlineWorker.kt rename to app/src/main/kotlin/cool/linc/particeps/ExperimentDeadlineWorker.kt index 340cf2f..072a447 100644 --- a/app/src/main/kotlin/cool/linc/androiddatacollector/ExperimentDeadlineWorker.kt +++ b/app/src/main/kotlin/cool/linc/particeps/ExperimentDeadlineWorker.kt @@ -1,10 +1,10 @@ -package cool.linc.androiddatacollector +package cool.linc.particeps import android.content.Context import androidx.work.CoroutineWorker import androidx.work.WorkerParameters -import cool.linc.androiddatacollector.core.model.ExperimentState -import cool.linc.androiddatacollector.core.runtime.CommandResult +import cool.linc.particeps.core.model.ExperimentState +import cool.linc.particeps.core.runtime.CommandResult import kotlinx.coroutines.flow.first class ExperimentDeadlineWorker( diff --git a/app/src/main/kotlin/cool/linc/androiddatacollector/MainActivity.kt b/app/src/main/kotlin/cool/linc/particeps/MainActivity.kt similarity index 94% rename from app/src/main/kotlin/cool/linc/androiddatacollector/MainActivity.kt rename to app/src/main/kotlin/cool/linc/particeps/MainActivity.kt index 4f372ba..0744132 100644 --- a/app/src/main/kotlin/cool/linc/androiddatacollector/MainActivity.kt +++ b/app/src/main/kotlin/cool/linc/particeps/MainActivity.kt @@ -1,4 +1,4 @@ -package cool.linc.androiddatacollector +package cool.linc.particeps import android.Manifest import android.content.Intent @@ -10,10 +10,10 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope -import cool.linc.androiddatacollector.platform.InterventionWorker -import cool.linc.androiddatacollector.core.collector.AccessKind -import cool.linc.androiddatacollector.core.protocol.JoinLink -import cool.linc.androiddatacollector.core.protocol.SignedConfigurationCodec +import cool.linc.particeps.platform.InterventionWorker +import cool.linc.particeps.core.collector.AccessKind +import cool.linc.particeps.core.protocol.JoinLink +import cool.linc.particeps.core.protocol.SignedConfigurationCodec import java.time.Instant import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -68,7 +68,7 @@ class MainActivity : ComponentActivity() { withdraw = viewModel::withdraw, export = { val id = (state as? StudyUiState.ActiveStudy)?.configuration?.experimentId ?: "research" - exportLauncher.launch("$id-${Instant.now().epochSecond}.adcexp") + exportLauncher.launch("$id-${Instant.now().epochSecond}.partexp") }, delete = viewModel::deleteLocalData, ), diff --git a/app/src/main/kotlin/cool/linc/androiddatacollector/StudyViewModel.kt b/app/src/main/kotlin/cool/linc/particeps/StudyViewModel.kt similarity index 88% rename from app/src/main/kotlin/cool/linc/androiddatacollector/StudyViewModel.kt rename to app/src/main/kotlin/cool/linc/particeps/StudyViewModel.kt index 61b66c8..5b39ddc 100644 --- a/app/src/main/kotlin/cool/linc/androiddatacollector/StudyViewModel.kt +++ b/app/src/main/kotlin/cool/linc/particeps/StudyViewModel.kt @@ -1,18 +1,18 @@ -package cool.linc.androiddatacollector +package cool.linc.particeps import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope -import cool.linc.androiddatacollector.core.application.StudySessionManager -import cool.linc.androiddatacollector.core.application.UploadStatus -import cool.linc.androiddatacollector.core.collector.AccessStatus -import cool.linc.androiddatacollector.core.collector.CollectorHealth -import cool.linc.androiddatacollector.core.definition.StudyConfiguration -import cool.linc.androiddatacollector.core.export.ExportReceipt -import cool.linc.androiddatacollector.core.model.StudyMetadata -import cool.linc.androiddatacollector.core.protocol.JoinLink -import cool.linc.androiddatacollector.core.protocol.SignedConfigurationCodec -import cool.linc.androiddatacollector.core.runtime.CommandResult +import cool.linc.particeps.core.application.StudySessionManager +import cool.linc.particeps.core.application.UploadStatus +import cool.linc.particeps.core.collector.AccessStatus +import cool.linc.particeps.core.collector.CollectorHealth +import cool.linc.particeps.core.definition.StudyConfiguration +import cool.linc.particeps.core.export.ExportReceipt +import cool.linc.particeps.core.model.StudyMetadata +import cool.linc.particeps.core.protocol.JoinLink +import cool.linc.particeps.core.protocol.SignedConfigurationCodec +import cool.linc.particeps.core.runtime.CommandResult import java.io.OutputStream import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers diff --git a/app/src/main/kotlin/cool/linc/androiddatacollector/SurveyActivity.kt b/app/src/main/kotlin/cool/linc/particeps/SurveyActivity.kt similarity index 95% rename from app/src/main/kotlin/cool/linc/androiddatacollector/SurveyActivity.kt rename to app/src/main/kotlin/cool/linc/particeps/SurveyActivity.kt index 54ee51e..6c327cd 100644 --- a/app/src/main/kotlin/cool/linc/androiddatacollector/SurveyActivity.kt +++ b/app/src/main/kotlin/cool/linc/particeps/SurveyActivity.kt @@ -1,4 +1,4 @@ -package cool.linc.androiddatacollector +package cool.linc.particeps import android.os.Bundle import androidx.activity.ComponentActivity @@ -47,17 +47,17 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewModelScope -import cool.linc.androiddatacollector.core.application.StudySessionManager -import cool.linc.androiddatacollector.core.definition.MultipleChoiceQuestion -import cool.linc.androiddatacollector.core.definition.ScaleQuestion -import cool.linc.androiddatacollector.core.definition.ShortTextQuestion -import cool.linc.androiddatacollector.core.definition.SingleChoiceQuestion -import cool.linc.androiddatacollector.core.definition.SurveyAction -import cool.linc.androiddatacollector.core.definition.SurveyDefinition -import cool.linc.androiddatacollector.core.definition.SurveyQuestion -import cool.linc.androiddatacollector.core.model.OccurrenceState -import cool.linc.androiddatacollector.core.runtime.SurveyAnswer -import cool.linc.androiddatacollector.core.runtime.SurveySubmissionResult +import cool.linc.particeps.core.application.StudySessionManager +import cool.linc.particeps.core.definition.MultipleChoiceQuestion +import cool.linc.particeps.core.definition.ScaleQuestion +import cool.linc.particeps.core.definition.ShortTextQuestion +import cool.linc.particeps.core.definition.SingleChoiceQuestion +import cool.linc.particeps.core.definition.SurveyAction +import cool.linc.particeps.core.definition.SurveyDefinition +import cool.linc.particeps.core.definition.SurveyQuestion +import cool.linc.particeps.core.model.OccurrenceState +import cool.linc.particeps.core.runtime.SurveyAnswer +import cool.linc.particeps.core.runtime.SurveySubmissionResult import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.first diff --git a/app/src/main/kotlin/cool/linc/androiddatacollector/UploadWorker.kt b/app/src/main/kotlin/cool/linc/particeps/UploadWorker.kt similarity index 93% rename from app/src/main/kotlin/cool/linc/androiddatacollector/UploadWorker.kt rename to app/src/main/kotlin/cool/linc/particeps/UploadWorker.kt index 6eb54bd..afd2ea3 100644 --- a/app/src/main/kotlin/cool/linc/androiddatacollector/UploadWorker.kt +++ b/app/src/main/kotlin/cool/linc/particeps/UploadWorker.kt @@ -1,11 +1,11 @@ -package cool.linc.androiddatacollector +package cool.linc.particeps import android.content.Context import androidx.work.CoroutineWorker import androidx.work.ExistingWorkPolicy import androidx.work.WorkerParameters -import cool.linc.androiddatacollector.core.application.UploadAttemptResult -import cool.linc.androiddatacollector.platform.AndroidStudyWorkScheduler +import cool.linc.particeps.core.application.UploadAttemptResult +import cool.linc.particeps.platform.AndroidStudyWorkScheduler import kotlinx.coroutines.flow.first /** diff --git a/app/src/main/kotlin/cool/linc/androiddatacollector/platform/AndroidResearchClocks.kt b/app/src/main/kotlin/cool/linc/particeps/platform/AndroidResearchClocks.kt similarity index 82% rename from app/src/main/kotlin/cool/linc/androiddatacollector/platform/AndroidResearchClocks.kt rename to app/src/main/kotlin/cool/linc/particeps/platform/AndroidResearchClocks.kt index 42560fb..e62719e 100644 --- a/app/src/main/kotlin/cool/linc/androiddatacollector/platform/AndroidResearchClocks.kt +++ b/app/src/main/kotlin/cool/linc/particeps/platform/AndroidResearchClocks.kt @@ -1,10 +1,10 @@ -package cool.linc.androiddatacollector.platform +package cool.linc.particeps.platform import android.content.Context import android.os.SystemClock import android.provider.Settings -import cool.linc.androiddatacollector.core.collector.ResearchClocks -import cool.linc.androiddatacollector.core.model.ResearchTime +import cool.linc.particeps.core.collector.ResearchClocks +import cool.linc.particeps.core.model.ResearchTime import java.security.MessageDigest class AndroidResearchClocks( diff --git a/app/src/main/kotlin/cool/linc/androiddatacollector/platform/AndroidStudyPlatform.kt b/app/src/main/kotlin/cool/linc/particeps/platform/AndroidStudyPlatform.kt similarity index 89% rename from app/src/main/kotlin/cool/linc/androiddatacollector/platform/AndroidStudyPlatform.kt rename to app/src/main/kotlin/cool/linc/particeps/platform/AndroidStudyPlatform.kt index a5d1662..60b3d36 100644 --- a/app/src/main/kotlin/cool/linc/androiddatacollector/platform/AndroidStudyPlatform.kt +++ b/app/src/main/kotlin/cool/linc/particeps/platform/AndroidStudyPlatform.kt @@ -1,4 +1,4 @@ -package cool.linc.androiddatacollector.platform +package cool.linc.particeps.platform import android.Manifest import android.app.NotificationChannel @@ -17,20 +17,20 @@ import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkManager import androidx.work.CoroutineWorker import androidx.work.WorkerParameters -import cool.linc.androiddatacollector.CollectionService -import cool.linc.androiddatacollector.ExperimentDeadlineWorker -import cool.linc.androiddatacollector.MainActivity -import cool.linc.androiddatacollector.R -import cool.linc.androiddatacollector.SurveyActivity -import cool.linc.androiddatacollector.UploadWorker -import cool.linc.androiddatacollector.core.application.StudyCollectionHost -import cool.linc.androiddatacollector.core.application.StudyWorkScheduler -import cool.linc.androiddatacollector.core.definition.StudyConfiguration -import cool.linc.androiddatacollector.core.definition.SurveyAction -import cool.linc.androiddatacollector.core.definition.UploadConfiguration -import cool.linc.androiddatacollector.core.model.InterventionOccurrence -import cool.linc.androiddatacollector.core.runtime.OccurrenceClaimResult -import cool.linc.androiddatacollector.core.runtime.OccurrenceExpiryResult +import cool.linc.particeps.CollectionService +import cool.linc.particeps.ExperimentDeadlineWorker +import cool.linc.particeps.MainActivity +import cool.linc.particeps.R +import cool.linc.particeps.SurveyActivity +import cool.linc.particeps.UploadWorker +import cool.linc.particeps.core.application.StudyCollectionHost +import cool.linc.particeps.core.application.StudyWorkScheduler +import cool.linc.particeps.core.definition.StudyConfiguration +import cool.linc.particeps.core.definition.SurveyAction +import cool.linc.particeps.core.definition.UploadConfiguration +import cool.linc.particeps.core.model.InterventionOccurrence +import cool.linc.particeps.core.runtime.OccurrenceClaimResult +import cool.linc.particeps.core.runtime.OccurrenceExpiryResult import java.util.concurrent.TimeUnit import kotlinx.coroutines.CancellationException import kotlinx.coroutines.flow.first @@ -214,18 +214,18 @@ class AndroidStudyWorkScheduler( workManager.cancelAllWorkByTag(uploadTag(experimentId)) } - private fun deadlineWorkName(experimentId: String) = "adc-deadline-$experimentId" - private fun uploadTag(experimentId: String) = "adc-upload-$experimentId" + private fun deadlineWorkName(experimentId: String) = "particeps-deadline-$experimentId" + private fun uploadTag(experimentId: String) = "particeps-upload-$experimentId" companion object { fun uploadWorkName(experimentId: String, configurationId: String) = - "adc-upload-$experimentId-$configurationId" + "particeps-upload-$experimentId-$configurationId" } } internal object InterventionWorkIdentity { - fun deliveryTag(experimentId: String) = "adc-intervention-delivery-$experimentId" - fun expiryTag(experimentId: String) = "adc-intervention-expiry-$experimentId" - fun deliveryName(experimentId: String, occurrenceId: String) = "adc-intervention-$experimentId-$occurrenceId" + fun deliveryTag(experimentId: String) = "particeps-intervention-delivery-$experimentId" + fun expiryTag(experimentId: String) = "particeps-intervention-expiry-$experimentId" + fun deliveryName(experimentId: String, occurrenceId: String) = "particeps-intervention-$experimentId-$occurrenceId" fun expiryName(experimentId: String, occurrenceId: String) = "${deliveryName(experimentId, occurrenceId)}-expiry" } @@ -305,7 +305,7 @@ class InterventionWorker( private suspend fun deliver(): Result { val occurrenceId = inputData.getString(KEY_OCCURRENCE_ID) ?: return Result.failure() - val application = applicationContext as cool.linc.androiddatacollector.CollectorApplication + val application = applicationContext as cool.linc.particeps.CollectorApplication if (application.session.snapshot.first { it.initialized }.configuration == null) return Result.success() val claim = application.session.claimOccurrenceIfDue(occurrenceId) when (deliveryWorkerDirective(claim)) { @@ -328,7 +328,10 @@ class InterventionWorker( val target = if (dispatch.action is SurveyAction) SurveyActivity::class.java else MainActivity::class.java val intent = Intent(applicationContext, target) .setAction(ACTION_OPEN_OCCURRENCE) - .setData(Uri.Builder().scheme("adc").authority("occurrence").appendPath(occurrenceId).build()) + // Not the join URI. This only makes each intervention's PendingIntent distinct, and it is + // deliberately not declared in any manifest intent filter: the join filter requires + // ACTION_VIEW with host "join" and path "/v1", which this intent has none of. + .setData(Uri.Builder().scheme("particeps").authority("occurrence").appendPath(occurrenceId).build()) .putExtra(KEY_OCCURRENCE_ID, occurrenceId) val pendingIntent = PendingIntent.getActivity( applicationContext, @@ -383,7 +386,7 @@ class InterventionWorker( companion object { const val KEY_OCCURRENCE_ID = "occurrence_id" - const val ACTION_OPEN_OCCURRENCE = "cool.linc.androiddatacollector.OPEN_OCCURRENCE" + const val ACTION_OPEN_OCCURRENCE = "cool.linc.particeps.OPEN_OCCURRENCE" private const val CHANNEL_ID = "research-interventions-v1" } } @@ -403,7 +406,7 @@ class InterventionExpiryWorker( private suspend fun expire(): Result { val occurrenceId = inputData.getString(InterventionWorker.KEY_OCCURRENCE_ID) ?: return Result.failure() - val application = applicationContext as cool.linc.androiddatacollector.CollectorApplication + val application = applicationContext as cool.linc.particeps.CollectorApplication if (application.session.snapshot.first { it.initialized }.configuration == null) return Result.success() return when (expiryWorkerDirective(application.session.expireOccurrenceIfDue(occurrenceId))) { ExpiryWorkerDirective.RETRY -> Result.retry() diff --git a/app/src/main/kotlin/cool/linc/androiddatacollector/platform/FileUploadOutbox.kt b/app/src/main/kotlin/cool/linc/particeps/platform/FileUploadOutbox.kt similarity index 96% rename from app/src/main/kotlin/cool/linc/androiddatacollector/platform/FileUploadOutbox.kt rename to app/src/main/kotlin/cool/linc/particeps/platform/FileUploadOutbox.kt index dc8fc87..bdfad50 100644 --- a/app/src/main/kotlin/cool/linc/androiddatacollector/platform/FileUploadOutbox.kt +++ b/app/src/main/kotlin/cool/linc/particeps/platform/FileUploadOutbox.kt @@ -1,9 +1,9 @@ -package cool.linc.androiddatacollector.platform +package cool.linc.particeps.platform import android.system.Os import android.system.OsConstants -import cool.linc.androiddatacollector.core.application.StudyUploadException -import cool.linc.androiddatacollector.core.export.ExportReceipt +import cool.linc.particeps.core.application.StudyUploadException +import cool.linc.particeps.core.export.ExportReceipt import java.io.BufferedInputStream import java.io.DataInputStream import java.io.DataOutputStream @@ -234,12 +234,12 @@ internal class FileUploadOutbox( } private val manifest get() = directory.resolve("stage.manifest") - private val body get() = directory.resolve("stage.adcexp") + private val body get() = directory.resolve("stage.partexp") private val temporaryManifest get() = directory.resolve("stage.manifest.tmp") - private val temporaryBody get() = directory.resolve("stage.adcexp.tmp") + private val temporaryBody get() = directory.resolve("stage.partexp.tmp") private companion object { - val MANIFEST_MAGIC = "ADCOUT01".toByteArray(Charsets.US_ASCII) + val MANIFEST_MAGIC = "PTCOUT01".toByteArray(Charsets.US_ASCII) val SHA256_HEX = Regex("[0-9a-f]{64}") const val SHA256_BYTES = 32 const val DIGEST_BUFFER_BYTES = 64 * 1024 diff --git a/app/src/main/kotlin/cool/linc/androiddatacollector/platform/JoinArtifactDownloader.kt b/app/src/main/kotlin/cool/linc/particeps/platform/JoinArtifactDownloader.kt similarity index 93% rename from app/src/main/kotlin/cool/linc/androiddatacollector/platform/JoinArtifactDownloader.kt rename to app/src/main/kotlin/cool/linc/particeps/platform/JoinArtifactDownloader.kt index 2e6e66b..b3d08e2 100644 --- a/app/src/main/kotlin/cool/linc/androiddatacollector/platform/JoinArtifactDownloader.kt +++ b/app/src/main/kotlin/cool/linc/particeps/platform/JoinArtifactDownloader.kt @@ -1,7 +1,7 @@ -package cool.linc.androiddatacollector.platform +package cool.linc.particeps.platform -import cool.linc.androiddatacollector.core.protocol.JoinLink -import cool.linc.androiddatacollector.core.protocol.SignedConfigurationCodec +import cool.linc.particeps.core.protocol.JoinLink +import cool.linc.particeps.core.protocol.SignedConfigurationCodec import java.io.File import java.io.FileOutputStream import java.io.IOException @@ -84,7 +84,7 @@ class JoinArtifactDownloader internal constructor( } companion object { - private const val STAGING_FILE = "artifact.adccfg.tmp" + private const val STAGING_FILE = "artifact.partcfg.tmp" private const val BUFFER_BYTES = 64 * 1024 fun defaultClient(): OkHttpClient = OkHttpClient.Builder() diff --git a/app/src/main/kotlin/cool/linc/androiddatacollector/platform/OkHttpStudyUploader.kt b/app/src/main/kotlin/cool/linc/particeps/platform/OkHttpStudyUploader.kt similarity index 89% rename from app/src/main/kotlin/cool/linc/androiddatacollector/platform/OkHttpStudyUploader.kt rename to app/src/main/kotlin/cool/linc/particeps/platform/OkHttpStudyUploader.kt index e3ef3a7..d056c50 100644 --- a/app/src/main/kotlin/cool/linc/androiddatacollector/platform/OkHttpStudyUploader.kt +++ b/app/src/main/kotlin/cool/linc/particeps/platform/OkHttpStudyUploader.kt @@ -1,16 +1,16 @@ -package cool.linc.androiddatacollector.platform +package cool.linc.particeps.platform -import cool.linc.androiddatacollector.core.application.StudyUploadException -import cool.linc.androiddatacollector.core.application.StudyUploader -import cool.linc.androiddatacollector.core.export.BundleKind -import cool.linc.androiddatacollector.core.export.BundleProducer -import cool.linc.androiddatacollector.core.export.ExportReceipt -import cool.linc.androiddatacollector.core.export.ExportSnapshot -import cool.linc.androiddatacollector.core.export.ResearchExport -import cool.linc.androiddatacollector.core.export.UploadReceiptCodec -import cool.linc.androiddatacollector.core.model.StudyMetadata -import cool.linc.androiddatacollector.core.model.StudyStore -import cool.linc.androiddatacollector.core.protocol.VerifiedConfiguration +import cool.linc.particeps.core.application.StudyUploadException +import cool.linc.particeps.core.application.StudyUploader +import cool.linc.particeps.core.export.BundleKind +import cool.linc.particeps.core.export.BundleProducer +import cool.linc.particeps.core.export.ExportReceipt +import cool.linc.particeps.core.export.ExportSnapshot +import cool.linc.particeps.core.export.ResearchExport +import cool.linc.particeps.core.export.UploadReceiptCodec +import cool.linc.particeps.core.model.StudyMetadata +import cool.linc.particeps.core.model.StudyStore +import cool.linc.particeps.core.protocol.VerifiedConfiguration import java.io.IOException import java.util.Base64 import java.util.concurrent.atomic.AtomicBoolean @@ -203,7 +203,7 @@ class OkHttpStudyUploader internal constructor( companion object { const val TARGET_PLAINTEXT_BYTES = 16L * 1024 * 1024 - const val MEDIA_TYPE = "application/vnd.adc.research-bundle" + const val MEDIA_TYPE = "application/vnd.particeps.research-bundle" private const val MAXIMUM_RECEIPT_BYTES = 2_048 private val ACCEPTED_STATUS_CODES = setOf(200, 201) private val BUNDLE_MEDIA_TYPE = MEDIA_TYPE.toMediaType() @@ -231,13 +231,13 @@ internal fun uploadHeaders( "Content-Type" to OkHttpStudyUploader.MEDIA_TYPE, "Content-Length" to receipt.byteCount.toString(), "Content-Digest" to "sha-256=:${receipt.sha256.hexDigestBase64()}:", - "X-ADC-Bundle-Id" to receipt.bundleId.toString(), - "X-ADC-Bundle-Format" to ResearchExport.BUNDLE_FORMAT, - "X-ADC-Configuration-SHA256" to receipt.configurationSha256, - "X-ADC-Researcher-Key-Id" to configuration.configuration.export.researcherKeyId, - "X-ADC-Sequence-From" to receipt.firstSequence.toString(), - "X-ADC-Sequence-To" to receipt.lastSequence.toString(), - "X-ADC-Event-Count" to receipt.eventCount.toString(), + "X-Particeps-Bundle-Id" to receipt.bundleId.toString(), + "X-Particeps-Bundle-Format" to ResearchExport.BUNDLE_FORMAT, + "X-Particeps-Configuration-SHA256" to receipt.configurationSha256, + "X-Particeps-Researcher-Key-Id" to configuration.configuration.export.researcherKeyId, + "X-Particeps-Sequence-From" to receipt.firstSequence.toString(), + "X-Particeps-Sequence-To" to receipt.lastSequence.toString(), + "X-Particeps-Event-Count" to receipt.eventCount.toString(), ) } diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 9c8c16d..4094591 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -18,7 +18,7 @@ 本次問卷已超過填寫期限,無法送出。 請完成必填題目,並檢查每一項回答。 目前無法開啟這份問卷。 - 研究資料收集器 + 準備中 %1$d 分 %1$d 小時 %2$d 分 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cd81cfb..ff352e9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,8 +5,14 @@ summary — is never translated: it is rendered exactly as it was signed. --> - - Android Data Collector + + Particeps Research activities Survey Opening survey… @@ -23,7 +29,12 @@ This survey occurrence is unavailable. - Research Data Collector + + @string/app_name Starting up %1$dm %1$dh %2$dm diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 629a87a..1ca4ebd 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,6 +1,6 @@ -