Skip to content
Open
Show file tree
Hide file tree
Changes from 95 commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
79e735e
@W-21146662: [Android] App attestation integration testing (Initial P…
JohnsonEricAtSalesforce Mar 12, 2026
e912590
@W-21933885: [MSDK Android] App Attestation Implementation (Adapt To …
JohnsonEricAtSalesforce Apr 9, 2026
00a2924
@W-21933885: [MSDK Android] App Attestation Implementation (Extract T…
JohnsonEricAtSalesforce Apr 10, 2026
9c62dbe
@W-21933885: [MSDK Android] App Attestation Implementation (Replace A…
JohnsonEricAtSalesforce Apr 15, 2026
d1376e5
@W-21933885: [MSDK Android] App Attestation Implementation (Improve C…
JohnsonEricAtSalesforce Apr 16, 2026
e7f94f6
@W-21933885: [MSDK Android] App Attestation Implementation (Temporari…
JohnsonEricAtSalesforce Apr 16, 2026
763515c
@W-21933885: [MSDK Android] App Attestation Implementation (Improve T…
JohnsonEricAtSalesforce Apr 16, 2026
91e6079
@W-21933885: [MSDK Android] App Attestation Implementation (Extract C…
JohnsonEricAtSalesforce Apr 16, 2026
62660cf
@W-21933885: [MSDK Android] App Attestation Implementation (Ignore Fl…
JohnsonEricAtSalesforce Apr 16, 2026
ba416fd
@W-21933885: [MSDK Android] App Attestation Implementation (In-Line R…
JohnsonEricAtSalesforce Apr 16, 2026
4a40216
@W-21933885: [MSDK Android] App Attestation Implementation (Return Nu…
JohnsonEricAtSalesforce Apr 16, 2026
2d2ca63
@W-21933885: [MSDK Android] App Attestation Implementation (Resolve T…
JohnsonEricAtSalesforce Apr 16, 2026
80771f6
@W-21933885: [MSDK Android] App Attestation Implementation (Implement…
JohnsonEricAtSalesforce Apr 16, 2026
7842f19
@W-21933885: [MSDK Android] App Attestation Implementation (Ignore Fl…
JohnsonEricAtSalesforce Apr 16, 2026
3ebedd4
@W-21933885: [MSDK Android] App Attestation Implementation (Increase …
JohnsonEricAtSalesforce Apr 17, 2026
5219194
@W-21933885: [MSDK Android] App Attestation Implementation (Increase …
JohnsonEricAtSalesforce Apr 17, 2026
09651ad
@W-21933885: [MSDK Android] App Attestation Implementation (Increase …
JohnsonEricAtSalesforce Apr 17, 2026
1ab0d06
@W-21933885: [MSDK Android] App Attestation Implementation (First Att…
JohnsonEricAtSalesforce Apr 17, 2026
44849dc
@W-21933885: [MSDK Android] App Attestation Implementation (Finalize …
JohnsonEricAtSalesforce Apr 17, 2026
fb5c845
@W-21933885: [MSDK Android] App Attestation Implementation (Improve C…
JohnsonEricAtSalesforce Apr 17, 2026
86377f5
@W-21933885: [MSDK Android] App Attestation Implementation (Test Cove…
JohnsonEricAtSalesforce Apr 17, 2026
a2a451f
@W-21933885: [MSDK Android] App Attestation Implementation (Update Te…
JohnsonEricAtSalesforce Apr 17, 2026
0b158b5
@W-21933885: [MSDK Android] App Attestation Implementation (Light Cle…
JohnsonEricAtSalesforce Apr 17, 2026
4076ab2
@W-21933885: [MSDK Android] App Attestation Implementation (Correct S…
JohnsonEricAtSalesforce Apr 17, 2026
b0b7370
@W-21933885: [MSDK Android] App Attestation Implementation (Replace T…
JohnsonEricAtSalesforce Apr 17, 2026
e374d58
@W-21933885: [MSDK Android] App Attestation Implementation (New OAuth…
JohnsonEricAtSalesforce Apr 20, 2026
b1e5bad
@W-21933885: [MSDK Android] App Attestation Implementation (Temporary…
JohnsonEricAtSalesforce Apr 20, 2026
d488ff0
@W-21933885: [MSDK Android] App Attestation Implementation (Light Sel…
JohnsonEricAtSalesforce Apr 20, 2026
1325829
@W-21933885: [MSDK Android] App Attestation Implementation (Address U…
JohnsonEricAtSalesforce Apr 20, 2026
1ebcfe2
@W-21933885: [MSDK Android] App Attestation Implementation (Correct s…
JohnsonEricAtSalesforce Apr 20, 2026
9bf16d1
@W-21933885: [MSDK Android] App Attestation Implementation (Correct C…
JohnsonEricAtSalesforce Apr 20, 2026
e3398ad
@W-21933885: [MSDK Android] App Attestation Implementation (Automated…
JohnsonEricAtSalesforce Apr 20, 2026
6e06e9e
@W-21933885: [MSDK Android] App Attestation Implementation (Automated…
JohnsonEricAtSalesforce Apr 20, 2026
3a0e417
@W-21933885: [MSDK Android] App Attestation Implementation (Automated…
JohnsonEricAtSalesforce Apr 20, 2026
91a434c
@W-21933885: [MSDK Android] App Attestation Implementation (Automated…
JohnsonEricAtSalesforce Apr 20, 2026
466b058
@W-21933885: [MSDK Android] App Attestation Implementation (Automated…
JohnsonEricAtSalesforce Apr 20, 2026
5845735
@W-21933885: [MSDK Android] App Attestation Implementation (Resolve T…
JohnsonEricAtSalesforce Apr 21, 2026
9a2aca8
@W-21933885: [MSDK Android] App Attestation Implementation (Resolve T…
JohnsonEricAtSalesforce Apr 21, 2026
5035087
@W-21933885: [MSDK Android] App Attestation Implementation (Light Aut…
JohnsonEricAtSalesforce Apr 22, 2026
b8f1790
@W-21933885: [MSDK Android] App Attestation Implementation (Updated T…
JohnsonEricAtSalesforce Apr 22, 2026
7e5c233
@W-21933885: [MSDK Android] App Attestation Implementation (Automated…
JohnsonEricAtSalesforce Apr 22, 2026
f984d45
@W-21933885: [MSDK Android] App Attestation Implementation (Automated…
JohnsonEricAtSalesforce Apr 22, 2026
1304062
@W-21933885: [MSDK Android] App Attestation Implementation (Automated…
JohnsonEricAtSalesforce Apr 22, 2026
ab2af33
@W-21933885: [MSDK Android] App Attestation Implementation (Automated…
JohnsonEricAtSalesforce Apr 22, 2026
4029772
@W-21933885: [MSDK Android] App Attestation Implementation (Revert Li…
JohnsonEricAtSalesforce Apr 22, 2026
f3fad52
@W-21933885: [MSDK Android] App Attestation Implementation (Restore P…
JohnsonEricAtSalesforce Apr 23, 2026
48f98cc
@W-21933885: [MSDK Android] App Attestation Implementation (Ignore sc…
JohnsonEricAtSalesforce Apr 23, 2026
484e620
@W-21933885: [MSDK Android] App Attestation Implementation (Limit Int…
JohnsonEricAtSalesforce Apr 23, 2026
b6c4354
@W-21933885: [MSDK Android] App Attestation Implementation (Add Code …
JohnsonEricAtSalesforce Apr 23, 2026
86f4b77
@W-21933885: [MSDK Android] App Attestation Implementation (Ignore Sc…
JohnsonEricAtSalesforce Apr 23, 2026
0f15179
@W-21933885: [MSDK Android] App Attestation Implementation (Add Idlin…
JohnsonEricAtSalesforce Apr 23, 2026
9ddfa9e
@W-21933885: [MSDK Android] App Attestation Implementation (Add expli…
JohnsonEricAtSalesforce Apr 23, 2026
aeb7405
@W-21933885: [MSDK Android] App Attestation Implementation (Refactor …
JohnsonEricAtSalesforce Apr 23, 2026
e9d4890
@W-21933885: [MSDK Android] App Attestation Implementation (Ignore Sc…
JohnsonEricAtSalesforce Apr 23, 2026
6a36d44
@W-21933885: [MSDK Android] App Attestation Implementation (Revert Ch…
JohnsonEricAtSalesforce Apr 23, 2026
6cdb0d1
@W-21933885: [MSDK Android] App Attestation Implementation (Ignore To…
JohnsonEricAtSalesforce Apr 23, 2026
ffce923
@W-21933885: [MSDK Android] App Attestation Implementation (Fix Token…
JohnsonEricAtSalesforce Apr 23, 2026
1b994fb
@W-21933885: [MSDK Android] App Attestation Implementation (Fix Cross…
JohnsonEricAtSalesforce Apr 23, 2026
e9b32a4
@W-21933885: [MSDK Android] App Attestation Implementation (Fix AuthC…
JohnsonEricAtSalesforce Apr 23, 2026
97228a7
@W-21933885: [MSDK Android] App Attestation Implementation (Remove Du…
JohnsonEricAtSalesforce Apr 23, 2026
f93c9e5
@W-21933885: [MSDK Android] App Attestation Implementation (Re-Enable…
JohnsonEricAtSalesforce Apr 23, 2026
8975f32
@W-21933885: [MSDK Android] App Attestation Implementation (Fix Token…
JohnsonEricAtSalesforce Apr 23, 2026
732a127
@W-21933885: [MSDK Android] App Attestation Implementation (Fix Token…
JohnsonEricAtSalesforce Apr 23, 2026
9a39ef0
@W-21933885: [MSDK Android] App Attestation Implementation (Fix Token…
JohnsonEricAtSalesforce Apr 23, 2026
7131666
@W-21933885: [MSDK Android] App Attestation Implementation (Add unreg…
JohnsonEricAtSalesforce Apr 23, 2026
15a9b04
@W-21933885: [MSDK Android] App Attestation Implementation (Fix onRes…
JohnsonEricAtSalesforce Apr 23, 2026
d2998a3
@W-21933885: [MSDK Android] App Attestation Implementation (Add addit…
JohnsonEricAtSalesforce Apr 23, 2026
9994387
@W-21933885: [MSDK Android] App Attestation Implementation (Fix addit…
JohnsonEricAtSalesforce Apr 23, 2026
29beb71
@W-21933885: [MSDK Android] App Attestation Implementation (Increase …
JohnsonEricAtSalesforce Apr 23, 2026
2c44b99
@W-21933885: [MSDK Android] App Attestation Implementation (Add regis…
JohnsonEricAtSalesforce Apr 23, 2026
966c13a
@W-21933885: [MSDK Android] App Attestation Implementation (Updated T…
JohnsonEricAtSalesforce Apr 23, 2026
c724a1a
@W-21933885: [MSDK Android] App Attestation Implementation (Add Test …
JohnsonEricAtSalesforce Apr 24, 2026
2ef4eca
@W-21933885: [MSDK Android] App Attestation Implementation (Add Test …
JohnsonEricAtSalesforce Apr 24, 2026
3673200
@W-21933885: [MSDK Android] App Attestation Implementation (Fix Nativ…
JohnsonEricAtSalesforce Apr 24, 2026
f674131
@W-21933885: [MSDK Android] App Attestation Implementation (Reorder I…
JohnsonEricAtSalesforce Apr 24, 2026
a44d1f0
@W-21933885: [MSDK Android] App Attestation Implementation (Disable t…
JohnsonEricAtSalesforce Apr 24, 2026
30d3ca8
@W-21933885: [MSDK Android] App Attestation Implementation (Add Compr…
JohnsonEricAtSalesforce Apr 24, 2026
ecdd9d2
@W-21933885: [MSDK Android] App Attestation Implementation (Fix Login…
JohnsonEricAtSalesforce Apr 24, 2026
e2070f0
@W-21933885: [MSDK Android] App Attestation Implementation (Add Compr…
JohnsonEricAtSalesforce Apr 24, 2026
3e822b6
@W-21933885: [MSDK Android] App Attestation Implementation (Update Na…
JohnsonEricAtSalesforce Apr 24, 2026
98efcc6
@W-21933885: [MSDK Android] App Attestation Implementation (Clean Up …
JohnsonEricAtSalesforce Apr 24, 2026
d154ec6
@W-21933885: [MSDK Android] App Attestation Implementation (Updated T…
JohnsonEricAtSalesforce Apr 24, 2026
55d370f
@W-21933885: [MSDK Android] App Attestation Implementation (NativeLog…
JohnsonEricAtSalesforce Apr 24, 2026
89400d3
@W-21933885: [MSDK Android] App Attestation Implementation (Add Depen…
JohnsonEricAtSalesforce Apr 24, 2026
8a60818
@W-21933885: [MSDK Android] App Attestation Implementation (Ignore ID…
JohnsonEricAtSalesforce Apr 24, 2026
261f874
@W-21933885: [MSDK Android] App Attestation Implementation (Fix IDPAu…
JohnsonEricAtSalesforce Apr 24, 2026
63172a1
@W-21933885: [MSDK Android] App Attestation Implementation (Add Debug…
JohnsonEricAtSalesforce Apr 24, 2026
6946375
@W-21933885: [MSDK Android] App Attestation Implementation (Updated T…
JohnsonEricAtSalesforce Apr 24, 2026
2ba67fb
@W-21933885: [MSDK Android] App Attestation Implementation (Updated I…
JohnsonEricAtSalesforce Apr 24, 2026
5f968fa
@W-21933885: [MSDK Android] App Attestation Implementation (Updated I…
JohnsonEricAtSalesforce Apr 25, 2026
d526e06
@W-21933885: [MSDK Android] App Attestation Implementation (Significa…
JohnsonEricAtSalesforce Apr 25, 2026
06c3719
@W-21933885: [MSDK Android] App Attestation Implementation (Initializ…
JohnsonEricAtSalesforce Apr 26, 2026
beb685d
@W-21933885: [MSDK Android] App Attestation Implementation (Ignore Un…
JohnsonEricAtSalesforce Apr 26, 2026
8d48205
@W-21933885: [MSDK Android] App Attestation Implementation (Correct U…
JohnsonEricAtSalesforce Apr 26, 2026
da8bce9
@W-21933885: [MSDK Android] App Attestation Implementation (Update Un…
JohnsonEricAtSalesforce Apr 26, 2026
76c91d8
Fix JaCoCo source directory configuration for code coverage reporting
JohnsonEricAtSalesforce Apr 27, 2026
b1e380e
@W-21933885: [MSDK Android] App Attestation Implementation (Update Un…
JohnsonEricAtSalesforce Apr 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion .github/workflows/reusable-lib-workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ jobs:
- name: Install Dependencies
env:
TEST_CREDENTIALS: ${{ secrets.TEST_CREDENTIALS }}
# On PR runs, only SalesforceReact consumes the bundled index.android.bundle,
# so skip the yarn install + react-native bundle step for every other lib to
# save ~3-5 min per matrix job. Nightly runs still produce the bundle.
SKIP_REACT_NATIVE_BUNDLE: ${{ (inputs.is_pr && inputs.lib != 'SalesforceReact') && '1' || '0' }}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unrelated to the feature, but could be useful to consider. While spending a lot of time watching ~25m CI runs, I asked our tools where a quick win would be in trimming that time. The analysis found that we're re-running yarn from scratch for all the modules instead of just for SalesforceReact. I believe this brought my run down to ~12m! I need to verify that over a few more runs, but if that's the case this could be a nice optimization. @brandonpage?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice find!

run: |
./install.sh
echo $TEST_CREDENTIALS > ./shared/test/test_credentials.json
Expand Down Expand Up @@ -68,6 +72,42 @@ jobs:
run: |
./gradlew libs:${{ inputs.lib }}:assembleAndroidTest
./gradlew native:NativeSampleApps:RestExplorer:assembleDebug
- name: Run Unit Tests
if: success() || failure()
continue-on-error: true
run: ./gradlew libs:${{ inputs.lib }}:testDebugUnitTest --continue
- name: Publish Unit Test Results
uses: EnricoMi/publish-unit-test-result-action@v2
if: success() || failure()
with:
check_name: ${{ inputs.lib }} Unit Test Results
files: |
libs/${{ inputs.lib }}/build/test-results/testDebugUnitTest/*.xml
comment_mode: off
fail_on: nothing
- name: Archive Unit Test Results
uses: actions/upload-artifact@v4
if: success() || failure()
with:
name: unit-test-results-${{ inputs.lib }}
path: libs/${{ inputs.lib }}/build/test-results/testDebugUnitTest/*.xml
- name: Generate Unit Test Coverage Report
if: success() || failure()
run: ./gradlew libs:${{ inputs.lib }}:unitTestCoverageReport
- uses: codecov/codecov-action@v5
if: success() || failure()
with:
files: libs/${{ inputs.lib }}/build/reports/jacoco/unitTestCoverageReport/unitTestCoverageReport.xml
flags: ${{ inputs.lib }}-unit-tests
name: ${{ inputs.lib }}-unit-test-coverage
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- name: Archive Unit Test Coverage Report
uses: actions/upload-artifact@v4
if: success() || failure()
with:
name: unit-test-coverage-${{ inputs.lib }}
path: libs/${{ inputs.lib }}/build/reports/jacoco/unitTestCoverageReport/
- uses: 'google-github-actions/auth@v2'
if: success() || failure()
with:
Expand Down Expand Up @@ -109,7 +149,6 @@ jobs:

if $IS_PR ; then
LEVELS_TO_TEST=$PR_API_VERSION
RETRIES=1
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brandonpage, a little later you'll see a fix in the RestClientTest.java when I had generated by our tools to resolve unreliable behavior I saw in that test. I've been marking it @Ignore a lot since it's so unpredictable. The fix looks solid - It's so solid that our tools believe we don't need retry anymore. I'd love to see a "faster fail" on pull request runs since they can take a very long time. Thoughts?

fi

# Build test-targets-for-shard arguments from config file
Expand Down
46 changes: 46 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,52 @@ See [README.md](README.md) for basic setup. Commands below are for contributors
- **Test data cleanup**: Every test must clean up created soups, user accounts, and cached data. Use `@Before`/`@After` rigorously.
- **Test credentials**: Tests requiring authentication need `test_credentials.json` in `shared/test/`.

### Firebase Test Lab Considerations

**CRITICAL: MockK `mockkStatic()` Does Not Work on Firebase Test Lab**

Firebase Test Lab silently disables MockK's static mocking, causing tests to execute real implementations. Tests pass locally but timeout (60s) on Firebase with `UncompletedCoroutinesError` as real network/Google Play Services calls execute.

**Root Cause:** Firebase's SELinux policies block MockK bytecode instrumentation, APK re-signing breaks inline mocking agent, and real Google Play Services execute unexpected calls.

**DO NOT USE:**
```kotlin
mockkStatic(OAuth2::class) // ❌ Fails silently on Firebase
```

**REQUIRED ALTERNATIVES:**

1. **Parameter Injection (Simplest):**
```kotlin
// Add parameter with default value pointing to static method
fun myFunction(
helper: (String) -> Int = { StaticClass.staticMethod(it) }
) {
val result = helper("input")
}

// Test injects mock lambda
myFunction(helper = { "mock result" })
```

2. **TypeAlias for Complex Static Methods:**
```kotlin
// Define function type
typealias GetAuthUrl = (Boolean, URI, String, String) -> URI?

// Use as parameter with default calling static method
suspend fun authorize(
getAuthUrl: GetAuthUrl = { a, b, c, d -> OAuth2.getAuthUrl(a, b, c, d) }
) { /* ... */ }

// Test injects simple mock
authorize(getAuthUrl = { _, _, _, _ -> URI("mock://url") })
```

**Rule:** Never use `mockkStatic()` in instrumented tests. Always use parameter injection to enable mocking.

---

### What to Test for Each Library

**SalesforceSDK (Core)**:
Expand Down
2 changes: 1 addition & 1 deletion hybrid/HybridSampleApps/AccountEditor/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ android {

packaging {
resources {
excludes += setOf("META-INF/LICENSE", "META-INF/LICENSE.txt", "META-INF/DEPENDENCIES", "META-INF/NOTICE")
excludes += setOf("META-INF/LICENSE", "META-INF/LICENSE.txt", "META-INF/DEPENDENCIES", "META-INF/NOTICE", "AndroidManifest.xml")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ android {

packaging {
resources {
excludes += setOf("META-INF/LICENSE", "META-INF/LICENSE.txt", "META-INF/DEPENDENCIES", "META-INF/NOTICE")
excludes += setOf("META-INF/LICENSE", "META-INF/LICENSE.txt", "META-INF/DEPENDENCIES", "META-INF/NOTICE", "AndroidManifest.xml")
}
}

Expand Down
17 changes: 11 additions & 6 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,17 @@ git submodule update
git -C external/shared checkout -- samples/mobilesyncexplorer/bootconfig.json samples/accounteditor/bootconfig.json 2>/dev/null || true

# get react native
pushd "libs/SalesforceReact"
rm -rf node_modules
rm yarn.lock
yarn install
./node_modules/.bin/react-native bundle --platform android --dev true --entry-file node_modules/react-native-force/test/alltests.js --bundle-output ../test/SalesforceReactTest/assets/index.android.bundle --assets-dest ../test/SalesforceReactTest/assets/
popd
# Set SKIP_REACT_NATIVE_BUNDLE=1 to skip the yarn install and bundle step for
# jobs that do not consume libs/test/SalesforceReactTest/assets/index.android.bundle.
# Default behavior is unchanged (the bundle is produced).
if [ "${SKIP_REACT_NATIVE_BUNDLE:-0}" != "1" ]; then
pushd "libs/SalesforceReact"
rm -rf node_modules
rm yarn.lock
yarn install
./node_modules/.bin/react-native bundle --platform android --dev true --entry-file node_modules/react-native-force/test/alltests.js --bundle-output ../test/SalesforceReactTest/assets/index.android.bundle --assets-dest ../test/SalesforceReactTest/assets/
popd
fi

# Apply bootconfig placeholder substitution. Usage:
# apply_bootconfig_paths [sample_file] path1 path2 ...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,18 @@ public FileLogger(Context context, String componentName) throws IOException {
this.context = context;
this.componentName = componentName;
readFileLoggerPrefs();
final File filename = new File(context.getFilesDir(), componentName + LOG_SUFFIX);
file = new QueueFile(filename);
try {
final File filesDir = context.getFilesDir();
if (filesDir == null) {
// This can happen in test environments (e.g., Robolectric) where file system is not available
throw new IOException("Context.getFilesDir() returned null - file system not available");
}
final File filename = new File(filesDir, componentName + LOG_SUFFIX);
file = new QueueFile(filename);
} catch (NullPointerException e) {
// File constructor can throw NPE in test environments even if filesDir is not null
throw new IOException("Failed to create log file - file system not available", e);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,12 @@ private void readLoggerPrefs() {
storeLoggerPrefs(level);
}
final String logLevelString = sp.getString(componentName, level.toString());
logLevel = Level.valueOf(logLevelString);
try {
logLevel = Level.valueOf(logLevelString);
} catch (IllegalArgumentException e) {
// This can happen in test environments (e.g., Robolectric)
logLevel = level;
}
}

/**
Expand Down
57 changes: 51 additions & 6 deletions libs/SalesforceSDK/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ dependencies {
api("androidx.browser:browser:1.8.0") // Update requires API 36 compileSdk
Copy link
Copy Markdown

@github-actions github-actions Bot Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ A newer version of androidx.browser:browser than 1.8.0 is available: 1.10.0

api("androidx.work:work-runtime-ktx:2.10.3")
Copy link
Copy Markdown

@github-actions github-actions Bot Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ A newer version of androidx.work:work-runtime-ktx than 2.10.3 is available: 2.11.2


implementation("com.google.android.play:integrity:1.6.0")
implementation("com.google.accompanist:accompanist-drawablepainter:0.37.3")
implementation("com.google.android.material:material:1.13.0") // remove this when all xml is gone
implementation("androidx.appcompat:appcompat:1.7.1")
Expand Down Expand Up @@ -55,6 +56,14 @@ dependencies {
androidTestImplementation("androidx.arch.core:core-testing:2.2.0")
androidTestImplementation("androidx.compose.ui:ui-test-junit4:$composeVersion")
Copy link
Copy Markdown

@github-actions github-actions Bot Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ A newer version of androidx.compose.ui:ui-test-junit4 than 1.8.2 is available: 1.11.0

androidTestImplementation("io.mockk:mockk-android:1.14.0") // Update requires Kotlin 2
Copy link
Copy Markdown

@github-actions github-actions Bot Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ A newer version of io.mockk:mockk-android than 1.14.0 is available: 1.14.9


testImplementation("junit:junit:4.13.2")
testImplementation("io.mockk:mockk:1.14.0") // Update requires Kotlin 2
Copy link
Copy Markdown

@github-actions github-actions Bot Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ A newer version of io.mockk:mockk than 1.14.0 is available: 1.14.9

testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1") // Update requires Kotlin 2
Copy link
Copy Markdown

@github-actions github-actions Bot Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ A newer version of org.jetbrains.kotlinx:kotlinx-coroutines-test than 1.8.1 is available: 1.10.2

testImplementation("androidx.test:core:1.7.0")
testImplementation("androidx.test.ext:junit:1.3.0")
testImplementation("androidx.arch.core:core-testing:2.2.0")
testImplementation("org.robolectric:robolectric:4.14.1")
Copy link
Copy Markdown

@github-actions github-actions Bot Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ A newer version of org.robolectric:robolectric than 4.14.1 is available: 4.16.1

}

android {
Expand All @@ -78,27 +87,35 @@ android {
sourceSets {
getByName("main") {
manifest.srcFile("AndroidManifest.xml")
java.srcDirs(arrayOf("src"))
resources.srcDirs(arrayOf("src"))
aidl.srcDirs(arrayOf("src"))
renderscript.srcDirs(arrayOf("src"))
// Non-standard layout: production code is in src/com and src/debug (excludes src/test and src/androidTest)
java.srcDirs(arrayOf("src/com", "src/debug"))
resources.srcDirs(arrayOf("src/com", "src/debug"))
aidl.srcDirs(arrayOf("src/com", "src/debug"))
renderscript.srcDirs(arrayOf("src/com", "src/debug"))
res.srcDirs(arrayOf("res"))
assets.srcDirs(arrayOf("assets"))
}

getByName("androidTest") {
setRoot("../test/SalesforceSDKTest")
// Manifest is in standard location: src/androidTest/AndroidManifest.xml
// Test code remains in external ../test/SalesforceSDKTest directory
java.srcDirs(arrayOf("../test/SalesforceSDKTest/src"))
resources.srcDirs(arrayOf("../test/SalesforceSDKTest/src"))
res.srcDirs(arrayOf("../test/SalesforceSDKTest/res"))
@Suppress("UnstableApiUsage")
assets.directories.add("../../shared/test")
}

getByName("test") {
// Standard layout for JVM unit tests: src/test/kotlin
java.srcDirs(arrayOf("src/test/kotlin"))
resources.srcDirs(arrayOf("src/test/resources"))
}
}

packaging {
resources {
excludes += setOf("META-INF/LICENSE*", "META-INF/LICENSE.txt", "META-INF/DEPENDENCIES", "META-INF/NOTICE")
excludes += setOf("META-INF/LICENSE*", "META-INF/LICENSE.txt", "META-INF/DEPENDENCIES", "META-INF/NOTICE", "AndroidManifest.xml")
}
}

Expand Down Expand Up @@ -142,6 +159,34 @@ android {
classDirectories.setFrom(javaTree, kotlinTree)
executionData.setFrom(fileTree("$rootDir/firebase") { setIncludes(arrayListOf("**/coverage.ec")) })
}

val unitTestCoverage: TaskProvider<JacocoReport> = tasks.register<JacocoReport>("unitTestCoverageReport") {
group = "Coverage"
description = "Generate JaCoCo coverage report for unit tests."
dependsOn("testDebugUnitTest")
}

unitTestCoverage {
reports {
xml.required = true
html.required = true
}

sourceDirectories.setFrom(files("${project.projectDir}/src", "${project.projectDir}/src/debug"))
val fileFilter = arrayListOf("**/R.class", "**/R$*.class", "**/BuildConfig.*", "**/Manifest*.*", "**/*Test*.*", "android/**/*.*")
val javaTree = fileTree("${project.projectDir}/build/intermediates/javac/debug") { setExcludes(fileFilter) }
val kotlinTree = fileTree("${project.projectDir}/build/tmp/kotlin-classes/debug") { setExcludes(fileFilter) }
classDirectories.setFrom(javaTree, kotlinTree)
executionData.setFrom(fileTree("${project.projectDir}/build/jacoco") { setIncludes(arrayListOf("testDebugUnitTest.exec")) })
}
}

// Configure JaCoCo for unit tests
tasks.withType<Test> {
configure<JacocoTaskExtension> {
isIncludeNoLocationClasses = true
excludes = listOf("jdk.internal.*")
}
}

kotlin {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import com.salesforce.androidsdk.app.Features.FEATURE_BROWSER_LOGIN
import com.salesforce.androidsdk.app.Features.FEATURE_NATIVE_LOGIN
import com.salesforce.androidsdk.app.SalesforceSDKManager.Theme.DARK
import com.salesforce.androidsdk.app.SalesforceSDKManager.Theme.SYSTEM_DEFAULT
import com.salesforce.androidsdk.auth.AppAttestationClient
import com.salesforce.androidsdk.auth.AuthenticatorService.KEY_INSTANCE_URL
import com.salesforce.androidsdk.auth.HttpAccess
import com.salesforce.androidsdk.auth.HttpAccess.DEFAULT
Expand Down Expand Up @@ -226,6 +227,54 @@ open class SalesforceSDKManager protected constructor(
*/
val loginActivityClass: Class<out Activity> = nativeLoginActivity ?: webViewLoginActivityClass

/**
* The client side implementation of the Salesforce App Attestation External
* Client App (ECA) Plugin or null when app attestation is disabled.
*
* This property is not intended for public use outside of Salesforce Mobile
* SDK
*
* TODO: Make this Kotlin-internal once it is no longer referenced by Java. ECJ20260420
*/
@Volatile
var appAttestationClient: AppAttestationClient? = null
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A crux change is creating a new object to encapsulate all the things for the new Salesforce "Challenge" API, the Integrity Token Provider, the Token and providing that in the "Attestation" format the auth and token refresh endpoints now expect. That's here.

Our tools had some great suggestion around making this property thread safe, so I added @volatile, the private setter and a dedicated lock object based on tool feedback.

@VisibleForTesting
internal set

/** Lock object for synchronized access to the app Attestation Client */
private val appAttestationClientLock = Any()

/**
* Updates the Salesforce App Attestation ECA Plugin Client for the selected
* login server and matching Google Cloud Project ID. When using App
* Attestation, this value must match the linked Google Cloud Project ID
* for the app in Google Play Console's Play Integrity API and provided to
* the Salesforce App Attestation External Client App Plugin.
*
* @param apiHostName The Salesforce App Attestation External Client App
* (ECA) Plugin Challenge API Host Name. This usually matches the selected
* login server
* @param googleCloudProjectId The Google Cloud Project ID or null to
* disable Salesforce App Attestation
*/
fun updateAppAttestationClient(
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If one reads the description of this pull request, this is the entry point for an app to actually enable App Attestation.

apiHostName: String,
googleCloudProjectId: Long? = null
) {
synchronized(appAttestationClientLock) {
appAttestationClient = googleCloudProjectId?.let { appAttestationGoogleCloudProjectId ->
AppAttestationClient(
context = appContext,
apiHostName = apiHostName,
deviceId = deviceId,
googleCloudProjectId = appAttestationGoogleCloudProjectId,
remoteAccessConsumerKey = getBootConfig(appContext).remoteAccessConsumerKey,
restClient = clientManager.peekUnauthenticatedRestClient()
)
}
}
}

/**
* ViewModel Factory the SDK will use in LoginActivity and composable functions. Setting this will allow for
* visual customization without overriding LoginActivity.
Expand Down
Loading
Loading