diff --git a/.circleci/config.yml b/.circleci/config.yml index 0ae19ca13f..4338da8381 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -389,7 +389,6 @@ workflows: # Friday 8 PM - API 29 # Friday 9 PM - API 27 # Friday 10 PM - API 25 - # Friday 11 PM - API 23 test-api-30: triggers: - schedule: @@ -739,46 +738,3 @@ workflows: test_apk: *salesforceReact-apk-path api_level: 25 - test-api-23: - triggers: - - schedule: - cron: "0 7 * * 6" - filters: - branches: - only: - - dev - jobs: - - run-tests: - name: "SalesforceAnalytics API 23" - api_level: 23 - - run-tests: - name: "SalesforceSDK API 23" - lib: "SalesforceSDK" - test_apk: *coresdk-apk-path - api_level: 23 - - run-tests: - name: "SmartStore API 23" - lib: "SmartStore" - test_apk: *smartStore-apk-path - api_level: 23 - - run-tests: - name: "MobileSync API 23" - lib: "MobileSync" - test_apk: *mobileSync-apk-path - api_level: 23 - - run-tests: - name: "SalesforceHybrid API 23" - lib: "SalesforceHybrid" - test_apk: *salesforceHybrid-apk-path - api_level: 23 - - run-tests: - name: "RestExplorer API 23" - lib: "RestExplorer" - test_apk: *restExplorer-apk-path - api_level: 23 - - run-tests: - name: "SalesforceReact API 23" - lib: "SalesforceReact" - test_apk: *salesforceReact-apk-path - api_level: 23 - diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000000..522fa4a0f7 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,2 @@ +# Comment line immediately above ownership line is reserved for related gus information. Please be careful while editing. +#ECCN:Open Source diff --git a/README.md b/README.md index ca24799bdb..284276baa8 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,8 @@ This pulls submodule dependencies from github. Introduction == -### What's New in 9.2.1 -See [release notes](https://github.com/forcedotcom/SalesforceMobileSDK-Android/releases/tag/v9.2.1). +### What's New in 10.0.0 +See [release notes](https://github.com/forcedotcom/SalesforceMobileSDK-Android/releases/tag/v10.0.0). ### Native Applications The Salesforce Mobile SDK provides essential libraries for quickly building native mobile apps that seamlessly integrate with the Salesforce cloud architecture. Out of the box, we provide an implementation of OAuth2, abstracting away the complexity of securely storing refresh tokens or fetching a new session ID when a session expires. The SDK also provides Java wrappers for the Salesforce REST API, making it easy to retrieve, store, and manipulate data. diff --git a/build.gradle b/build.gradle index 1254660249..f4a2c701e2 100644 --- a/build.gradle +++ b/build.gradle @@ -10,14 +10,14 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.0.2' + classpath 'com.android.tools.build:gradle:7.1.0' classpath 'io.github.gradle-nexus:publish-plugin:1.1.0' } } allprojects { group = 'com.salesforce.mobilesdk' - version = '9.2.1' + version = '10.0.0' repositories { mavenLocal() maven { diff --git a/external/shared b/external/shared index 250b53f9b9..ee84c0217b 160000 --- a/external/shared +++ b/external/shared @@ -1 +1 @@ -Subproject commit 250b53f9b98f03dfd09436168b045fce6a06f9bc +Subproject commit ee84c0217bb2a51a3230baf2e9d69fb0a93de274 diff --git a/gradle.properties b/gradle.properties index 5362072fd4..f1d0d100ff 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -cdvCompileSdkVersion=30 -cdvMinSdkVersion=23 +cdvCompileSdkVersion=32 +cdvMinSdkVersion=24 android.useAndroidX=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0f80bbf516..ffed3a254e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/hybrid/HybridSampleApps/AccountEditor/build.gradle b/hybrid/HybridSampleApps/AccountEditor/build.gradle index 033d6a1128..acf48cd0d7 100644 --- a/hybrid/HybridSampleApps/AccountEditor/build.gradle +++ b/hybrid/HybridSampleApps/AccountEditor/build.gradle @@ -5,11 +5,11 @@ dependencies { } android { - compileSdkVersion 30 + compileSdkVersion 32 defaultConfig { - targetSdkVersion 30 - minSdkVersion 23 + targetSdkVersion 32 + minSdkVersion 24 } sourceSets { @@ -23,15 +23,14 @@ android { assets.srcDirs = ['assets'] } } - packagingOptions { - exclude 'META-INF/LICENSE' - exclude 'META-INF/LICENSE.txt' - exclude 'META-INF/DEPENDENCIES' - exclude 'META-INF/NOTICE' - } + packagingOptions { + resources { + excludes += ['META-INF/LICENSE', 'META-INF/LICENSE.txt', 'META-INF/DEPENDENCIES', 'META-INF/NOTICE'] + } + } + lint { + abortOnError false + xmlReport true + } - lintOptions { - xmlReport true - abortOnError false - } } diff --git a/hybrid/HybridSampleApps/MobileSyncExplorerHybrid/build.gradle b/hybrid/HybridSampleApps/MobileSyncExplorerHybrid/build.gradle index f92033d5c8..206c2652b4 100644 --- a/hybrid/HybridSampleApps/MobileSyncExplorerHybrid/build.gradle +++ b/hybrid/HybridSampleApps/MobileSyncExplorerHybrid/build.gradle @@ -5,11 +5,11 @@ dependencies { } android { - compileSdkVersion 30 + compileSdkVersion 32 defaultConfig { - targetSdkVersion 30 - minSdkVersion 23 + targetSdkVersion 32 + minSdkVersion 24 } sourceSets { @@ -24,15 +24,14 @@ android { assets.srcDirs = ['assets'] } } - packagingOptions { - exclude 'META-INF/LICENSE' - exclude 'META-INF/LICENSE.txt' - exclude 'META-INF/DEPENDENCIES' - exclude 'META-INF/NOTICE' - } + packagingOptions { + resources { + excludes += ['META-INF/LICENSE', 'META-INF/LICENSE.txt', 'META-INF/DEPENDENCIES', 'META-INF/NOTICE'] + } + } + lint { + abortOnError false + xmlReport true + } - lintOptions { - xmlReport true - abortOnError false - } } diff --git a/hybrid/HybridSampleApps/NoteSync/build.gradle b/hybrid/HybridSampleApps/NoteSync/build.gradle index 033d6a1128..acf48cd0d7 100644 --- a/hybrid/HybridSampleApps/NoteSync/build.gradle +++ b/hybrid/HybridSampleApps/NoteSync/build.gradle @@ -5,11 +5,11 @@ dependencies { } android { - compileSdkVersion 30 + compileSdkVersion 32 defaultConfig { - targetSdkVersion 30 - minSdkVersion 23 + targetSdkVersion 32 + minSdkVersion 24 } sourceSets { @@ -23,15 +23,14 @@ android { assets.srcDirs = ['assets'] } } - packagingOptions { - exclude 'META-INF/LICENSE' - exclude 'META-INF/LICENSE.txt' - exclude 'META-INF/DEPENDENCIES' - exclude 'META-INF/NOTICE' - } + packagingOptions { + resources { + excludes += ['META-INF/LICENSE', 'META-INF/LICENSE.txt', 'META-INF/DEPENDENCIES', 'META-INF/NOTICE'] + } + } + lint { + abortOnError false + xmlReport true + } - lintOptions { - xmlReport true - abortOnError false - } } diff --git a/libs/MobileSync/AndroidManifest.xml b/libs/MobileSync/AndroidManifest.xml index 724d353d45..6553cd5e4f 100644 --- a/libs/MobileSync/AndroidManifest.xml +++ b/libs/MobileSync/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="76" + android:versionName="10.0.0.dev"> diff --git a/libs/MobileSync/build.gradle b/libs/MobileSync/build.gradle index 19dd25c46e..03345801de 100644 --- a/libs/MobileSync/build.gradle +++ b/libs/MobileSync/build.gradle @@ -20,11 +20,11 @@ dependencies { } android { - compileSdkVersion 30 + compileSdkVersion 32 defaultConfig { - targetSdkVersion 30 - minSdkVersion 23 + targetSdkVersion 32 + minSdkVersion 24 } buildTypes { @@ -51,26 +51,25 @@ android { res.srcDirs = ['../test/MobileSyncTest/res'] } } - defaultConfig { + packagingOptions { + resources { + excludes += ['META-INF/LICENSE', 'META-INF/LICENSE.txt', 'META-INF/DEPENDENCIES', 'META-INF/NOTICE'] + } + } + defaultConfig { testApplicationId "com.salesforce.androidsdk.mobilesync.tests" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } - packagingOptions { - exclude 'META-INF/LICENSE' - exclude 'META-INF/LICENSE.txt' - exclude 'META-INF/DEPENDENCIES' - exclude 'META-INF/NOTICE' - } + lint { + abortOnError false + xmlReport true + } - lintOptions { - xmlReport true - abortOnError false - } } ext { PUBLISH_GROUP_ID = 'com.salesforce.mobilesdk' - PUBLISH_VERSION = '9.2.1' + PUBLISH_VERSION = '10.0.0' PUBLISH_ARTIFACT_ID = 'MobileSync' } diff --git a/libs/MobileSync/src/com/salesforce/androidsdk/mobilesync/app/MobileSyncUpgradeManager.java b/libs/MobileSync/src/com/salesforce/androidsdk/mobilesync/app/MobileSyncUpgradeManager.java index c2f1a3b3f5..6c78ffb49c 100644 --- a/libs/MobileSync/src/com/salesforce/androidsdk/mobilesync/app/MobileSyncUpgradeManager.java +++ b/libs/MobileSync/src/com/salesforce/androidsdk/mobilesync/app/MobileSyncUpgradeManager.java @@ -26,8 +26,6 @@ */ package com.salesforce.androidsdk.mobilesync.app; -import com.salesforce.androidsdk.mobilesync.manager.LayoutSyncManager; -import com.salesforce.androidsdk.mobilesync.manager.MetadataSyncManager; import com.salesforce.androidsdk.mobilesync.util.MobileSyncLogger; import com.salesforce.androidsdk.smartstore.app.SmartStoreUpgradeManager; @@ -80,9 +78,6 @@ protected synchronized void upgradeSObject() { try { final String majorVersionNum = installedVersion.substring(0, 3); double installedVerDouble = Double.parseDouble(majorVersionNum); - if (installedVerDouble < 8.2) { - upgradeTo8Dot2(); - } } catch (Exception e) { MobileSyncLogger.e(TAG, "Failed to parse installed version."); } @@ -96,11 +91,4 @@ protected synchronized void upgradeSObject() { public String getInstalledSobjectVersion() { return getInstalledVersion(MOBILE_SYNC_KEY); } - - private void upgradeTo8Dot2() { - LayoutSyncManager.getInstance().getSmartStore().dropSoup("sfdcLayouts"); - LayoutSyncManager.reset(); - MetadataSyncManager.getInstance().getSmartStore().dropSoup("sfdcMetadata"); - MetadataSyncManager.reset(); - } } diff --git a/libs/SalesforceAnalytics/AndroidManifest.xml b/libs/SalesforceAnalytics/AndroidManifest.xml index c1a54d1203..39217f93ce 100644 --- a/libs/SalesforceAnalytics/AndroidManifest.xml +++ b/libs/SalesforceAnalytics/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="76" + android:versionName="10.0.0.dev"> diff --git a/libs/SalesforceAnalytics/build.gradle b/libs/SalesforceAnalytics/build.gradle index a1d80972c8..8c3bf2204d 100644 --- a/libs/SalesforceAnalytics/build.gradle +++ b/libs/SalesforceAnalytics/build.gradle @@ -9,18 +9,18 @@ apply plugin: 'com.android.library' dependencies { api 'com.squareup:tape:1.2.3' - api 'io.github.pilgr:paperdb:2.7.1' + api 'io.github.pilgr:paperdb:2.7.2' androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test:rules:1.4.0' androidTestImplementation 'androidx.test.ext:junit:1.1.3' } android { - compileSdkVersion 30 + compileSdkVersion 32 defaultConfig { - targetSdkVersion 30 - minSdkVersion 23 + targetSdkVersion 32 + minSdkVersion 24 } buildTypes { @@ -46,29 +46,28 @@ android { res.srcDirs = ['../test/SalesforceAnalyticsTest/res'] } } - defaultConfig { + packagingOptions { + resources { + excludes += ['META-INF/LICENSE', 'META-INF/LICENSE.txt', 'META-INF/DEPENDENCIES', 'META-INF/NOTICE'] + } + } + defaultConfig { testApplicationId "com.salesforce.androidsdk.analytics.tests" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } - packagingOptions { - exclude 'META-INF/LICENSE' - exclude 'META-INF/LICENSE.txt' - exclude 'META-INF/DEPENDENCIES' - exclude 'META-INF/NOTICE' - } + lint { + abortOnError false + xmlReport true + } - lintOptions { - xmlReport true - abortOnError false - } } ext { PUBLISH_GROUP_ID = 'com.salesforce.mobilesdk' - PUBLISH_VERSION = '9.2.1' + PUBLISH_VERSION = '10.0.0' PUBLISH_ARTIFACT_ID = 'SalesforceAnalytics' } if (rootProject.name == 'SalesforceMobileSDK-Android') { apply from: "${rootProject.projectDir}/publish/publish-module.gradle" -} \ No newline at end of file +} diff --git a/libs/SalesforceAnalytics/src/com/salesforce/androidsdk/analytics/model/InstrumentationEvent.java b/libs/SalesforceAnalytics/src/com/salesforce/androidsdk/analytics/model/InstrumentationEvent.java index 8c9ae2fdd2..4a50dcf16e 100644 --- a/libs/SalesforceAnalytics/src/com/salesforce/androidsdk/analytics/model/InstrumentationEvent.java +++ b/libs/SalesforceAnalytics/src/com/salesforce/androidsdk/analytics/model/InstrumentationEvent.java @@ -356,7 +356,9 @@ public JSONObject toJson() { if (errorType != null) { json.put(ERROR_TYPE_KEY, errorType.name()); } - json.put(DEVICE_APP_ATTRIBUTES_KEY, deviceAppAttributes.toJson()); + if (deviceAppAttributes != null) { + json.put(DEVICE_APP_ATTRIBUTES_KEY, deviceAppAttributes.toJson()); + } json.put(CONNECTION_TYPE_KEY, connectionType); json.put(SENDER_PARENT_ID_KEY, senderParentId); json.put(SESSION_START_TIME_KEY, sessionStartTime); diff --git a/libs/SalesforceAnalytics/src/com/salesforce/androidsdk/analytics/security/Encryptor.java b/libs/SalesforceAnalytics/src/com/salesforce/androidsdk/analytics/security/Encryptor.java index ec6b16f12a..814fe4a741 100644 --- a/libs/SalesforceAnalytics/src/com/salesforce/androidsdk/analytics/security/Encryptor.java +++ b/libs/SalesforceAnalytics/src/com/salesforce/androidsdk/analytics/security/Encryptor.java @@ -110,10 +110,7 @@ private static Cipher getDecryptingCipher(byte[] keyBytes, byte[] iv) return cipher; } - /* - * TODO: Remove this in Mobile SDK 10.0. - */ - private static Cipher getLegacyDecryptingCipher(byte[] keyBytes, byte[] iv) + private static Cipher getAESCBCDecryptingCipher(byte[] keyBytes, byte[] iv) throws InvalidAlgorithmParameterException, InvalidKeyException { final Cipher cipher = getBestCipher(AES_CBC_CIPHER); final SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, cipher.getAlgorithm()); @@ -122,19 +119,6 @@ private static Cipher getLegacyDecryptingCipher(byte[] keyBytes, byte[] iv) return cipher; } - /** - * Decrypts data with key using AES/CBC/PKCS5Padding. Should be used only for - * migration of encrypted data from an older version to Mobile SDK 8.2. - * - * @param data Data. - * @param key Base64 encoded 128 bit key or null (to leave data unchanged). - * @return Decrypted data. - * @deprecated Will be removed in Mobile SDK 10.0. - */ - public static String legacyDecrypt(String data, String key) { - return decrypt(data, key, new byte[16]); - } - /** * Decrypts data with key using AES/GCM/NoPadding. * @@ -146,19 +130,6 @@ public static String decrypt(String data, String key) { return decrypt(data, key, new byte[12]); } - /** - * Decrypts data with key using AES/CBC/PKCS5Padding. Should be used only for - * migration of encrypted data from an older version to Mobile SDK 8.2. - * - * @param data Data. - * @param key Key. - * @return Decrypted data. - * @deprecated Will be removed in Mobile SDK 10.0. - */ - public static String legacyDecrypt(byte[] data, String key) { - return decrypt(data, key, new byte[16]); - } - /** * Decrypts data with key using AES/GCM/NoPadding. * @@ -466,7 +437,7 @@ public static byte[] decryptWithRSABytes(PrivateKey privateKey, String data) { */ public static String decryptBytes(byte[] data, byte[] key, byte[] iv) { try { - final Cipher cipher = getLegacyDecryptingCipher(key, iv); + final Cipher cipher = getAESCBCDecryptingCipher(key, iv); byte[] result = cipher.doFinal(data, 0, data.length); return new String(result, 0, result.length, StandardCharsets.UTF_8); } catch (Exception e) { @@ -484,6 +455,19 @@ public static String decryptBytes(byte[] data, byte[] key, byte[] iv) { * about the operation this method was called for. */ public static String getStringFromStream(InputStream stream) throws IOException { + ByteArrayOutputStream output = getByteArrayStreamFromStream(stream); + return output.toString(StandardCharsets.UTF_8.name()); + } + + /** + * Retrieves data from an InputStream. Guaranteed to close the InputStream. + * + * @param stream InputStream data. + * @return Data from the InputStream as a ByteArrayOutputStream + * @throws IOException Provide log details of this exception in a catch with specifics + * about the operation this method was called for. + */ + public static ByteArrayOutputStream getByteArrayStreamFromStream(InputStream stream) throws IOException { ByteArrayOutputStream output = new ByteArrayOutputStream(); byte[] buffer = new byte[READ_BUFFER_LENGTH]; int length; @@ -495,7 +479,7 @@ public static String getStringFromStream(InputStream stream) throws IOException finally { stream.close(); } - return output.toString(StandardCharsets.UTF_8.name()); + return output; } /** @@ -584,7 +568,7 @@ private static byte[] decrypt(byte[] data, int length, byte[] key, byte[] iv) th if (iv.length == 12) { cipher = getDecryptingCipher(key, iv); } else { - cipher = getLegacyDecryptingCipher(key, iv); + cipher = getAESCBCDecryptingCipher(key, iv); } return cipher.doFinal(meat, 0, meatLen); } diff --git a/libs/SalesforceAnalytics/src/com/salesforce/androidsdk/analytics/store/EventStoreManager.java b/libs/SalesforceAnalytics/src/com/salesforce/androidsdk/analytics/store/EventStoreManager.java index a6154e41de..7ede858a01 100644 --- a/libs/SalesforceAnalytics/src/com/salesforce/androidsdk/analytics/store/EventStoreManager.java +++ b/libs/SalesforceAnalytics/src/com/salesforce/androidsdk/analytics/store/EventStoreManager.java @@ -37,6 +37,8 @@ import org.json.JSONObject; import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; import java.util.List; import io.paperdb.Book; @@ -109,7 +111,7 @@ public void storeEvents(List events) { } /** - * Returns a specific event stored on the filesystem. + * Returns a specific event stored on the filesystem for that unique identifier. * * @param eventId Unique identifier for the event. * @return Event. @@ -141,15 +143,13 @@ public InstrumentationEvent fetchEvent(String eventId) { } /** - * Returns all the events stored on the filesystem for that unique identifier. + * Returns all the events stored on the filesystem. * * @return List of events. */ public List fetchAllEvents() { - final List eventIds = book.getAllKeys(); final List events = new ArrayList<>(); - for (final String eventId : eventIds) { - final InstrumentationEvent event = fetchEvent(eventId); + for (InstrumentationEvent event : iterateAllEvents()) { if (event != null) { events.add(event); } @@ -157,6 +157,35 @@ public List fetchAllEvents() { return events; } + /** + * Streams all the events stored on the filesystem. Will load each + * event into memory one at a time to decrease memory impact. + * + * @return Iterable of events. + */ + public Iterable iterateAllEvents() { + return new Iterable() { + private final List eventIds = book.getAllKeys(); + + @Override + public Iterator iterator() { + return new Iterator() { + private final Iterator eventIdIterator = eventIds.iterator(); + + @Override + public boolean hasNext() { + return eventIdIterator.hasNext(); + } + + @Override + public InstrumentationEvent next() { + return fetchEvent(eventIdIterator.next()); + } + }; + } + }; + } + /** * Deletes a specific event stored on the filesystem. * @@ -171,7 +200,7 @@ public boolean deleteEvent(String eventId) { /** * Deletes the events stored on the filesystem for that unique identifier. */ - public void deleteEvents(List eventIds) { + public void deleteEvents(Collection eventIds) { if (eventIds == null || eventIds.size() == 0) { SalesforceAnalyticsLogger.d(context, TAG, "No events to delete"); return; diff --git a/libs/SalesforceHybrid/AndroidManifest.xml b/libs/SalesforceHybrid/AndroidManifest.xml index f011f42848..3937e8bed3 100644 --- a/libs/SalesforceHybrid/AndroidManifest.xml +++ b/libs/SalesforceHybrid/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="76" + android:versionName="10.0.0.dev"> diff --git a/libs/SalesforceHybrid/build.gradle b/libs/SalesforceHybrid/build.gradle index 1f99c32e03..8c30a681bc 100644 --- a/libs/SalesforceHybrid/build.gradle +++ b/libs/SalesforceHybrid/build.gradle @@ -10,8 +10,8 @@ apply plugin: 'com.android.library' dependencies { api project(':libs:MobileSync') api 'org.apache.cordova:framework:10.1.0' - api 'androidx.appcompat:appcompat:1.3.1' - api 'androidx.appcompat:appcompat-resources:1.3.1' + api 'androidx.appcompat:appcompat:1.4.0' + api 'androidx.appcompat:appcompat-resources:1.4.0' api 'androidx.webkit:webkit:1.4.0' androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test:rules:1.4.0' @@ -19,11 +19,11 @@ dependencies { } android { - compileSdkVersion 30 + compileSdkVersion 32 defaultConfig { - targetSdkVersion 30 - minSdkVersion 23 + targetSdkVersion 32 + minSdkVersion 24 } buildTypes { @@ -49,26 +49,25 @@ android { res.srcDirs = ['../test/SalesforceHybridTest/res'] } } - defaultConfig { + packagingOptions { + resources { + excludes += ['META-INF/LICENSE', 'META-INF/LICENSE.txt', 'META-INF/DEPENDENCIES', 'META-INF/NOTICE'] + } + } + defaultConfig { testApplicationId "com.salesforce.androidsdk.salesforcehybrid.tests" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } - packagingOptions { - exclude 'META-INF/LICENSE' - exclude 'META-INF/LICENSE.txt' - exclude 'META-INF/DEPENDENCIES' - exclude 'META-INF/NOTICE' - } - - lintOptions { - xmlReport true - abortOnError false - } + lint { + abortOnError false + xmlReport true + } + } ext { PUBLISH_GROUP_ID = 'com.salesforce.mobilesdk' - PUBLISH_VERSION = '9.2.1' + PUBLISH_VERSION = '10.0.0' PUBLISH_ARTIFACT_ID = 'SalesforceHybrid' } diff --git a/libs/SalesforceHybrid/res/xml/config.xml b/libs/SalesforceHybrid/res/xml/config.xml index aed8894629..fc1497dadd 100644 --- a/libs/SalesforceHybrid/res/xml/config.xml +++ b/libs/SalesforceHybrid/res/xml/config.xml @@ -1,7 +1,7 @@ + version = "10.0.0"> diff --git a/libs/SalesforceHybrid/src/com/salesforce/androidsdk/phonegap/ui/SalesforceDroidGapActivity.java b/libs/SalesforceHybrid/src/com/salesforce/androidsdk/phonegap/ui/SalesforceDroidGapActivity.java index 1e5108ffc7..db2ce1cf71 100644 --- a/libs/SalesforceHybrid/src/com/salesforce/androidsdk/phonegap/ui/SalesforceDroidGapActivity.java +++ b/libs/SalesforceHybrid/src/com/salesforce/androidsdk/phonegap/ui/SalesforceDroidGapActivity.java @@ -281,11 +281,6 @@ public void onDestroy() { super.onDestroy(); } - @Override - public void onUserInteraction() { - delegate.onUserInteraction(); - } - @Override public boolean onKeyUp(int keyCode, KeyEvent event) { return delegate.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event); diff --git a/libs/SalesforceHybrid/src/com/salesforce/androidsdk/phonegap/ui/SalesforceWebViewClient.java b/libs/SalesforceHybrid/src/com/salesforce/androidsdk/phonegap/ui/SalesforceWebViewClient.java index f2328e4ed4..d86c85477a 100644 --- a/libs/SalesforceHybrid/src/com/salesforce/androidsdk/phonegap/ui/SalesforceWebViewClient.java +++ b/libs/SalesforceHybrid/src/com/salesforce/androidsdk/phonegap/ui/SalesforceWebViewClient.java @@ -90,21 +90,14 @@ public SalesforceWebViewClient(SalesforceWebViewEngine parentEngine) { } } - /* - * TODO: Remove this method and move the logic into the method below once minApi >= 24. - */ - @Override - public boolean shouldOverrideUrlLoading(final WebView view, String url) { - if (SalesforceWebViewClientHelper.shouldOverrideUrlLoading(ctx, view, url)) { - return true; - } else { - return super.shouldOverrideUrlLoading(view, url); - } - } - @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { - return shouldOverrideUrlLoading(view, request.getUrl().toString()); + String url = request.getUrl().toString(); + if (SalesforceWebViewClientHelper.shouldOverrideUrlLoading(ctx, view, url)) { + return true; + } else { + return super.shouldOverrideUrlLoading(view, request); + } } @Override diff --git a/libs/SalesforceHybrid/src/com/salesforce/androidsdk/phonegap/ui/SalesforceWebViewClientHelper.java b/libs/SalesforceHybrid/src/com/salesforce/androidsdk/phonegap/ui/SalesforceWebViewClientHelper.java index 0b1f9e3f15..2129fec154 100644 --- a/libs/SalesforceHybrid/src/com/salesforce/androidsdk/phonegap/ui/SalesforceWebViewClientHelper.java +++ b/libs/SalesforceHybrid/src/com/salesforce/androidsdk/phonegap/ui/SalesforceWebViewClientHelper.java @@ -68,10 +68,10 @@ public class SalesforceWebViewClientHelper { /** * To be called from shouldOverrideUrlLoading. * - * @param ctx - * @param view - * @param url - * @return + * @param ctx Context. + * @param view The webview initiating the callback. + * @param url The url of the page. + * @return True if url loading should be overridden, false otherwise. */ public static boolean shouldOverrideUrlLoading(Context ctx, WebView view, String url) { @@ -116,8 +116,8 @@ public static String getAppHomeUrl(Context ctx) { } /** - * @param ctx - * @return true if there is a cached version of the app's home page + * @param ctx Context. + * @return true if there is a cached version of the app's home page */ public static boolean hasCachedAppHome(Context ctx) { String cachedAppHomeUrl = getAppHomeUrl(ctx); diff --git a/libs/SalesforceReact/AndroidManifest.xml b/libs/SalesforceReact/AndroidManifest.xml index 459030fc6c..3530e6b732 100644 --- a/libs/SalesforceReact/AndroidManifest.xml +++ b/libs/SalesforceReact/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="76" + android:versionName="10.0.0.dev"> diff --git a/libs/SalesforceReact/build.gradle b/libs/SalesforceReact/build.gradle index b56d10760f..a5b9a70d7b 100644 --- a/libs/SalesforceReact/build.gradle +++ b/libs/SalesforceReact/build.gradle @@ -20,7 +20,7 @@ apply plugin: 'com.android.library' dependencies { api project(':libs:MobileSync') - api 'com.facebook.react:react-native:0.66.0' + api 'com.facebook.react:react-native:0.67.1' androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test:rules:1.4.0' androidTestImplementation 'androidx.test.ext:junit:1.1.3' @@ -35,11 +35,11 @@ dependencies { } android { - compileSdkVersion 30 + compileSdkVersion 32 defaultConfig { - targetSdkVersion 30 - minSdkVersion 23 + targetSdkVersion 32 + minSdkVersion 24 } buildTypes { @@ -65,23 +65,22 @@ android { res.srcDirs = ['../test/SalesforceReactTest/res'] } } + packagingOptions { + resources { + excludes += ['META-INF/LICENSE', 'META-INF/LICENSE.txt', 'META-INF/DEPENDENCIES', 'META-INF/NOTICE'] + } + } - defaultConfig { + defaultConfig { testApplicationId "com.salesforce.androidsdk.salesforcereact.tests" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } - packagingOptions { - exclude 'META-INF/LICENSE' - exclude 'META-INF/LICENSE.txt' - exclude 'META-INF/DEPENDENCIES' - exclude 'META-INF/NOTICE' - } - - lintOptions { - xmlReport true - abortOnError false - } + lint { + abortOnError false + xmlReport true + } + } def assetsFolder = new File('libs/test/SalesforceReactTest/assets') @@ -113,14 +112,14 @@ afterEvaluate { // Generate react tests bundle first. preDebugAndroidTestBuild.dependsOn(buildReactTestBundleIfNotExists) - } catch(Throwable t) { + } catch(Throwable ignored) { println("The preDebugAndroidTestBuild task was not found.") } } ext { PUBLISH_GROUP_ID = 'com.salesforce.mobilesdk' - PUBLISH_VERSION = '9.2.1' + PUBLISH_VERSION = '10.0.0' PUBLISH_ARTIFACT_ID = 'SalesforceReact' } diff --git a/libs/SalesforceReact/package.json b/libs/SalesforceReact/package.json index 5cd14b6fa2..1e301fbe94 100644 --- a/libs/SalesforceReact/package.json +++ b/libs/SalesforceReact/package.json @@ -1,6 +1,6 @@ { "name": "SalesforceReact", - "version": "9.2.1", + "version": "10.0.0", "private": true, "scripts": { "start": "node node_modules/react-native/local-cli/cli.js start" @@ -8,8 +8,8 @@ "dependencies": { "create-react-class": "^15.7.0", "react": "17.0.2", - "react-native": "0.66.0", - "react-native-force": "git+https://github.com/forcedotcom/SalesforceMobileSDK-ReactNative.git#v9.2.1" + "react-native": "0.67.1", + "react-native-force": "git+https://github.com/forcedotcom/SalesforceMobileSDK-ReactNative.git#dev" }, "devDependencies": { "@babel/core": "^7.12.9", @@ -19,13 +19,13 @@ "@react-native-community/eslint-config": "^2.0.0", "@types/jest": "^26.0.18", "@types/react": "^17.0.19", - "@types/react-native": "^0.64.13", + "@types/react-native": "^0.66.12", "@types/react-test-renderer": "^17.0.1", "babel-jest": "^26.6.3", "chai": "4.2.0", "eslint": "^7.15.0", "jest": "^26.6.3", - "metro-react-native-babel-preset": "^0.66.0", + "metro-react-native-babel-preset": "^0.66.2", "react-native-codegen": "^0.0.7", "react-test-renderer": "17.0.2", "typescript": "^4.1.2" diff --git a/libs/SalesforceReact/src/com/salesforce/androidsdk/reactnative/ui/SalesforceReactActivity.java b/libs/SalesforceReact/src/com/salesforce/androidsdk/reactnative/ui/SalesforceReactActivity.java index 5cf8c2d237..01441ad102 100644 --- a/libs/SalesforceReact/src/com/salesforce/androidsdk/reactnative/ui/SalesforceReactActivity.java +++ b/libs/SalesforceReact/src/com/salesforce/androidsdk/reactnative/ui/SalesforceReactActivity.java @@ -152,11 +152,6 @@ public void onDestroy() { super.onDestroy(); } - @Override - public void onUserInteraction() { - delegate.onUserInteraction(); - } - @Override public boolean onKeyUp(int keyCode, KeyEvent event) { return delegate.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event); diff --git a/libs/SalesforceSDK/AndroidManifest.xml b/libs/SalesforceSDK/AndroidManifest.xml index 5162afa082..21fe1ec9d9 100644 --- a/libs/SalesforceSDK/AndroidManifest.xml +++ b/libs/SalesforceSDK/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="76" + android:versionName="10.0.0.dev"> @@ -111,7 +111,10 @@ - + diff --git a/libs/SalesforceSDK/build.gradle b/libs/SalesforceSDK/build.gradle index 9d20534fb9..5eddaab164 100644 --- a/libs/SalesforceSDK/build.gradle +++ b/libs/SalesforceSDK/build.gradle @@ -11,34 +11,22 @@ dependencies { api project(':libs:SalesforceAnalytics') api 'com.squareup.okhttp3:okhttp:3.12.12' api 'com.google.firebase:firebase-messaging:20.1.0' - api 'androidx.core:core:1.6.0' - - /* - * Don't upgrade browser library version until minApiVersion >= 24. - * This is because the latest version of this library has minVersion = 24. - */ - api 'androidx.browser:browser:1.0.0' - implementation 'com.google.android.material:material:1.4.0' - implementation 'androidx.biometric:biometric:1.1.0' + api 'androidx.core:core:1.7.0' + api 'androidx.browser:browser:1.4.0' + implementation 'com.google.android.material:material:1.5.0' + implementation 'androidx.biometric:biometric:1.2.0-alpha04' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test:rules:1.4.0' androidTestImplementation 'androidx.test.ext:junit:1.1.3' - - - /* - * Provides backport of CompletableFuture - * Remove once minApiVersion >= 24 - */ - androidTestImplementation 'net.sourceforge.streamsupport:android-retrofuture:1.7.3' } android { - compileSdkVersion 30 + compileSdkVersion 32 defaultConfig { - targetSdkVersion 30 - minSdkVersion 23 + targetSdkVersion 32 + minSdkVersion 24 } buildTypes { @@ -64,26 +52,25 @@ android { res.srcDirs = ['../test/SalesforceSDKTest/res'] } } - defaultConfig { + packagingOptions { + resources { + excludes += ['META-INF/LICENSE', 'META-INF/LICENSE.txt', 'META-INF/DEPENDENCIES', 'META-INF/NOTICE'] + } + } + defaultConfig { testApplicationId "com.salesforce.androidsdk.tests" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } - packagingOptions { - exclude 'META-INF/LICENSE' - exclude 'META-INF/LICENSE.txt' - exclude 'META-INF/DEPENDENCIES' - exclude 'META-INF/NOTICE' - } + lint { + abortOnError false + xmlReport true + } - lintOptions { - xmlReport true - abortOnError false - } } ext { PUBLISH_GROUP_ID = 'com.salesforce.mobilesdk' - PUBLISH_VERSION = '9.2.1' + PUBLISH_VERSION = '10.0.0' PUBLISH_ARTIFACT_ID = 'SalesforceSDK' } diff --git a/libs/SalesforceSDK/src/com/salesforce/androidsdk/analytics/SalesforceAnalyticsManager.java b/libs/SalesforceSDK/src/com/salesforce/androidsdk/analytics/SalesforceAnalyticsManager.java index 57b3b415b6..4f2f0def49 100644 --- a/libs/SalesforceSDK/src/com/salesforce/androidsdk/analytics/SalesforceAnalyticsManager.java +++ b/libs/SalesforceSDK/src/com/salesforce/androidsdk/analytics/SalesforceAnalyticsManager.java @@ -35,7 +35,6 @@ import com.salesforce.androidsdk.analytics.manager.AnalyticsManager; import com.salesforce.androidsdk.analytics.model.DeviceAppAttributes; import com.salesforce.androidsdk.analytics.model.InstrumentationEvent; -import com.salesforce.androidsdk.analytics.security.Encryptor; import com.salesforce.androidsdk.analytics.store.EventStoreManager; import com.salesforce.androidsdk.analytics.transform.AILTNTransform; import com.salesforce.androidsdk.analytics.transform.Transform; @@ -46,13 +45,11 @@ import com.salesforce.androidsdk.util.SalesforceSDKLogger; import org.json.JSONArray; -import org.json.JSONException; import org.json.JSONObject; -import java.io.File; -import java.io.FilenameFilter; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -60,6 +57,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; /** * This class contains APIs that can be used to interact with @@ -72,6 +70,7 @@ public class SalesforceAnalyticsManager { private static final String ANALYTICS_ON_OFF_KEY = "ailtn_enabled"; private static final String AILTN_POLICY_PREF = "ailtn_policy"; private static final int DEFAULT_PUBLISH_FREQUENCY_IN_HOURS = 8; + private static final int DEFAULT_BATCH_SIZE = 100; private static final String TAG = "AnalyticsManager"; private static final String UNAUTH_INSTANCE_KEY = "_no_user"; @@ -79,12 +78,13 @@ public class SalesforceAnalyticsManager { private static boolean sPublishHandlerActive; private static ScheduledFuture sScheduler; private static int sPublishFrequencyInHours = DEFAULT_PUBLISH_FREQUENCY_IN_HOURS; + private static int sEventPublishBatchSize = DEFAULT_BATCH_SIZE; - private AnalyticsManager analyticsManager; - private EventStoreManager eventStoreManager; - private UserAccount account; + private final AnalyticsManager analyticsManager; + private final EventStoreManager eventStoreManager; + private final UserAccount account; private boolean enabled; - private Map, Class> remotes; + private final Map, Class> remotes; /** * Returns the instance of this class associated with an unauthenticated user context. @@ -200,6 +200,20 @@ public static synchronized void setPublishFrequencyInHours(int publishFrequencyI } } + /** + * Set the batch size for publishing instrumentation events. Will limit the + * number of events sent in a single network request to the specified batch + * size. Will silently return if batch size is less than or equal to zero. + * + * @param batchSize Event batch size + */ + public static synchronized void setEventPublishBatchSize(int batchSize) { + if (batchSize <= 0) { + return; + } + sEventPublishBatchSize = batchSize; + } + /** * Returns the publish frequency currently set, in hours. * @@ -274,7 +288,7 @@ public boolean isLoggingEnabled() { * This method should NOT be called from the main thread. */ public synchronized void publishAllEvents() { - final List events = eventStoreManager.fetchAllEvents(); + final Iterable events = eventStoreManager.iterateAllEvents(); publishEvents(events); } @@ -284,50 +298,64 @@ public synchronized void publishAllEvents() { * deleted if publishing was successful for all registered endpoints. * This method should NOT be called from the main thread. * - * @param events List of events. + * @param events Iterable of events. */ - public synchronized void publishEvents(List events) { - if (events == null || events.size() == 0) { + public synchronized void publishEvents(Iterable events) { + if (events == null) { return; } - final List eventsIds = new ArrayList<>(); + final Set eventsIds = new HashSet<>(); boolean success = true; - final Set> remoteKeySet = remotes.keySet(); - for (final Class transformClass : remoteKeySet) { - Transform transformer = null; + final Set, Class>> remoteKeySet = remotes.entrySet(); + for (final Map.Entry, Class> remoteEntry : remoteKeySet) { + Transform transformer; try { - transformer = transformClass.newInstance(); + transformer = remoteEntry.getKey().newInstance(); } catch (Exception e) { SalesforceSDKLogger.e(TAG, "Exception thrown while instantiating class", e); + continue; } - if (transformer != null) { - final JSONArray eventsJSONArray = new JSONArray(); - for (final InstrumentationEvent event : events) { - eventsIds.add(event.getEventId()); - final JSONObject eventJSON = transformer.transform(event); - if (eventJSON != null) { - eventsJSONArray.put(eventJSON); - } + + AnalyticsPublisher networkPublisher; + try { + networkPublisher = remoteEntry.getValue().newInstance(); + } catch (Exception e) { + SalesforceSDKLogger.e(TAG, "Exception thrown while instantiating class", e); + continue; + } + + int eventCount = 0; + JSONArray eventsJSONArray = new JSONArray(); + for (final InstrumentationEvent event : events) { + if (event == null) { + continue; } - AnalyticsPublisher networkPublisher = null; - try { - networkPublisher = remotes.get(transformClass).newInstance(); - } catch (Exception e) { - SalesforceSDKLogger.e(TAG, "Exception thrown while instantiating class", e); + eventsIds.add(event.getEventId()); + final JSONObject eventJSON = transformer.transform(event); + if (eventJSON == null) { + continue; } - if (networkPublisher != null) { - boolean networkSuccess = networkPublisher.publish(eventsJSONArray); - - /* - * Updates the success flag only if all previous requests have been - * successful. This ensures that the operation is marked success only - * if all publishers are successful. - */ - if (success) { - success = networkSuccess; + eventsJSONArray.put(eventJSON); + + // Publish a batch if we've reached the batch size + eventCount++; + if (eventCount >= sEventPublishBatchSize) { + eventCount = 0; + + boolean batchSuccess = networkPublisher.publish(eventsJSONArray); + eventsJSONArray = new JSONArray(); + success &= batchSuccess; + if (!batchSuccess) { + // Don't bother trying this publisher after the first failure + break; } } } + + // Publish events that didn't get batched + if (eventCount > 0) { + success &= networkPublisher.publish(eventsJSONArray); + } } /* @@ -370,6 +398,19 @@ public void addRemotePublisher(Class transformer, remotes.put(transformer, publisher); } + /** + * Removes a remote publisher to publish events to. + * + * @param transformer Transformer class. + */ + void removeRemotePublisher(Class transformer) { + if (transformer == null) { + SalesforceSDKLogger.w(TAG, "Invalid transformer"); + return; + } + remotes.remove(transformer); + } + private SalesforceAnalyticsManager(UserAccount account) { this.account = account; final SalesforceSDKManager sdkManager = SalesforceSDKManager.getInstance(); @@ -453,89 +494,4 @@ public void run() { }; return scheduler.scheduleAtFixedRate(publishRunnable, 0, sPublishFrequencyInHours, TimeUnit.HOURS); } - - /** - * One time upgrade steps from older versions to Mobile SDK 8.2. Only for internal use! - * - * @param account User account. - * @param context Context. - * @deprecated Will be removed in Mobile SDK 10.0. - */ - public static synchronized void upgradeTo8Dot2(UserAccount account, Context context) { - final String filenameSuffix = (account != null) ? account.getCommunityLevelFilenameSuffix() - : UNAUTH_INSTANCE_KEY; - final File rootDir = context.getFilesDir(); - final List oldEventFiles = getAllEventFiles(rootDir, filenameSuffix); - final List oldEvents = new ArrayList<>(); - for (final File file : oldEventFiles) { - final InstrumentationEvent event = fetchEvent(file); - if (event != null) { - oldEvents.add(event); - file.delete(); - } - } - final SalesforceAnalyticsManager sfAnalyticsManager = new SalesforceAnalyticsManager(account); - sfAnalyticsManager.getEventStoreManager().storeEvents(oldEvents); - } - - /* - * TODO: Exists only for upgrade steps to 8.2. Remove this in Mobile SDK 10.0. - */ - private static InstrumentationEvent fetchEvent(File file) { - if (file == null || !file.exists()) { - return null; - } - InstrumentationEvent event = null; - String eventString = null; - try { - String json = Encryptor.getStringFromFile(file); - eventString = Encryptor.legacyDecrypt(json, - SalesforceSDKManager.getLegacyEncryptionKey()); - } catch (Exception ex) { - SalesforceSDKLogger.e(TAG, "Exception occurred while attempting to read file contents", ex); - } - if (!TextUtils.isEmpty(eventString)) { - try { - final JSONObject jsonObject = new JSONObject(eventString); - event = new InstrumentationEvent(jsonObject); - } catch (JSONException e) { - SalesforceSDKLogger.e(TAG, "Exception occurred while attempting to convert to JSON", e); - } - } - return event; - } - - /* - * TODO: Exists only for upgrade steps to 8.2. Remove this in Mobile SDK 10.0. - */ - private static List getAllEventFiles(File rootDir, String fileSuffix) { - final EventFileFilter fileFilter = new EventFileFilter(fileSuffix); - final List files = new ArrayList<>(); - final File[] listOfFiles = rootDir.listFiles(); - if (listOfFiles != null) { - for (final File file : listOfFiles) { - if (file != null && fileFilter.accept(rootDir, file.getName())) { - files.add(file); - } - } - } - return files; - } - - /* - * TODO: Exists only for upgrade steps to 8.2. Remove this in Mobile SDK 10.0. - */ - private static class EventFileFilter implements FilenameFilter { - - private String fileSuffix; - - EventFileFilter(String fileSuffix) { - this.fileSuffix = fileSuffix; - } - - @Override - public boolean accept(File dir, String filename) { - return (filename != null && filename.endsWith(fileSuffix)); - } - } } diff --git a/libs/SalesforceSDK/src/com/salesforce/androidsdk/app/SalesforceSDKManager.java b/libs/SalesforceSDK/src/com/salesforce/androidsdk/app/SalesforceSDKManager.java index e4e121d543..95709238b1 100644 --- a/libs/SalesforceSDK/src/com/salesforce/androidsdk/app/SalesforceSDKManager.java +++ b/libs/SalesforceSDK/src/com/salesforce/androidsdk/app/SalesforceSDKManager.java @@ -33,7 +33,6 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; @@ -44,6 +43,8 @@ import android.content.res.Resources; import android.os.AsyncTask; import android.os.Build; +import android.os.Handler; +import android.os.Looper; import android.provider.Settings; import android.text.TextUtils; import android.view.View; @@ -77,13 +78,11 @@ import com.salesforce.androidsdk.rest.ClientManager; import com.salesforce.androidsdk.rest.ClientManager.LoginOptions; import com.salesforce.androidsdk.rest.RestClient; -import com.salesforce.androidsdk.security.PasscodeManager; import com.salesforce.androidsdk.security.SalesforceKeyGenerator; import com.salesforce.androidsdk.security.ScreenLockManager; import com.salesforce.androidsdk.ui.AccountSwitcherActivity; import com.salesforce.androidsdk.ui.DevInfoActivity; import com.salesforce.androidsdk.ui.LoginActivity; -import com.salesforce.androidsdk.ui.PasscodeActivity; import com.salesforce.androidsdk.util.EventsObservable; import com.salesforce.androidsdk.util.EventsObservable.EventType; import com.salesforce.androidsdk.util.SalesforceSDKLogger; @@ -115,7 +114,7 @@ public class SalesforceSDKManager implements LifecycleObserver { /** * Current version of this SDK. */ - public static final String SDK_VERSION = "9.2.1"; + public static final String SDK_VERSION = "10.0.0.dev"; /** * Intent action meant for instances of SalesforceSDKManager residing in other processes @@ -271,7 +270,9 @@ protected SalesforceSDKManager(Context context, Class mainAc // If your app runs in multiple processes, all the SalesforceSDKManager need to run cleanup during a logout final CleanupReceiver cleanupReceiver = new CleanupReceiver(); context.registerReceiver(cleanupReceiver, new IntentFilter(SalesforceSDKManager.CLEANUP_INTENT_ACTION)); - ProcessLifecycleOwner.get().getLifecycle().addObserver(this); + new Handler(Looper.getMainLooper()).post(() -> { + ProcessLifecycleOwner.get().getLifecycle().addObserver(this); + }); } /** @@ -402,25 +403,6 @@ public static void initNative(Context context, Class mainAct SalesforceSDKManager.init(context, mainActivity, loginActivity); } - /** - * Sets a custom passcode activity class to be used instead of the default class. - * The custom class must subclass PasscodeActivity. - * - * @param activity Subclass of PasscodeActivity. - * - * @deprecated Will be removed in Mobile SDK 10.0. - */ - public void setPasscodeActivity(Class activity) { } - - /** - * Returns the descriptor of the passcode activity class that's currently in use. - * - * @return Passcode activity class descriptor. - * - * @deprecated Will be removed in Mobile SDK 10.0. - */ - public Class getPasscodeActivity() { return PasscodeActivity.class; } - /** * Indicates whether the SDK should automatically log out when the * access token is revoked. If you override this method to return @@ -516,15 +498,6 @@ public synchronized Class getPushServiceType() { return pushServiceType; } - /** - * Returns the descriptor of the passcode activity class that's currently in use. - * - * @return Passcode activity class descriptor. - * - * @deprecated Will be removed in Mobile SDK 10.0. - */ - public PasscodeManager getPasscodeManager() { return new PasscodeManager(context); } - /** * Returns the ScreenLock manager that's associated with SalesforceSDKManager. * @@ -1087,16 +1060,6 @@ public static String encrypt(String data, String key) { return Encryptor.encrypt(data, key); } - /** - * Returns the legacy encryption key. This should be called only as a means to migrate to the new key. - * - * @return Legacy encryption key. - * @deprecated Will be removed in Mobile SDK 10.0. - */ - public static String getLegacyEncryptionKey() { - return SalesforceKeyGenerator.getLegacyEncryptionKey(INTERNAL_ENTROPY); - } - /** * Returns the encryption key being used. * @@ -1124,8 +1087,8 @@ public static String decrypt(String data, String key) { */ private static class RevokeTokenTask extends AsyncTask { - private String refreshToken; - private String loginServer; + private final String refreshToken; + private final String loginServer; public RevokeTokenTask(String refreshToken, String loginServer) { this.refreshToken = refreshToken; @@ -1184,27 +1147,6 @@ public ClientManager getClientManager(String jwt, String url) { return new ClientManager(getAppContext(), getAccountType(), getLoginOptions(jwt, url), true); } - /** - * @deprecated Will be removed in Mobile SDK 10.0. - */ - public void removeAllCookies() { - CookieManager.getInstance().removeAllCookies(null); - } - - /** - * @deprecated Will be removed in Mobile SDK 10.0. - */ - public void removeSessionCookies() { - CookieManager.getInstance().removeSessionCookies(null); - } - - /** - * @deprecated Will be removed in Mobile SDK 10.0. - */ - public void syncCookies() { - CookieManager.getInstance().flush(); - } - /** * Show dev support dialog */ @@ -1223,19 +1165,11 @@ public void run() { new AlertDialog.Builder(frontActivity) .setItems( devActions.keySet().toArray(new String[0]), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - devActionHandlers[which].onSelected(); - devActionsDialog = null; - } + (dialog, which) -> { + devActionHandlers[which].onSelected(); + devActionsDialog = null; }) - .setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - devActionsDialog = null; - } - }) + .setOnCancelListener(dialog -> devActionsDialog = null) .setTitle(R.string.sf__dev_support_title) .create(); devActionsDialog.show(); diff --git a/libs/SalesforceSDK/src/com/salesforce/androidsdk/app/SalesforceSDKUpgradeManager.java b/libs/SalesforceSDK/src/com/salesforce/androidsdk/app/SalesforceSDKUpgradeManager.java index 4e8869b838..21523051ad 100644 --- a/libs/SalesforceSDK/src/com/salesforce/androidsdk/app/SalesforceSDKUpgradeManager.java +++ b/libs/SalesforceSDK/src/com/salesforce/androidsdk/app/SalesforceSDKUpgradeManager.java @@ -34,8 +34,6 @@ import com.salesforce.androidsdk.accounts.UserAccount; import com.salesforce.androidsdk.accounts.UserAccountManager; -import com.salesforce.androidsdk.analytics.SalesforceAnalyticsManager; -import com.salesforce.androidsdk.rest.ClientManager; import com.salesforce.androidsdk.util.SalesforceSDKLogger; import java.util.List; @@ -93,9 +91,6 @@ protected synchronized void upgradeAccMgr() { try { final String majorVersionNum = installedVersion.substring(0, 3); double installedVerDouble = Double.parseDouble(majorVersionNum); - if (installedVerDouble < 8.2) { - upgradeTo8Dot2(); - } if (installedVerDouble < 9.2) { upgradeTo9Dot2(); } @@ -135,28 +130,6 @@ protected String getInstalledVersion(String key) { return sp.getString(key, ""); } - private void upgradeTo8Dot2() { - ClientManager.upgradeTo8Dot2(); - migrateAnalyticsData(); - } - - private void migrateAnalyticsData() { - final List userAccounts = UserAccountManager.getInstance().getAuthenticatedUsers(); - - // Migrating an unauthenticated user's analytics data. - SalesforceAnalyticsManager.upgradeTo8Dot2(null, SalesforceSDKManager.getInstance().getAppContext()); - - // Migrating each individual user's analytics data. - if (userAccounts != null) { - for (final UserAccount userAccount : userAccounts) { - if (userAccount != null) { - SalesforceAnalyticsManager.upgradeTo8Dot2(userAccount, - SalesforceSDKManager.getInstance().getAppContext()); - } - } - } - } - // TODO: Remove upgrade step in Mobile SDK 11.0 private void upgradeTo9Dot2() { final String KEY_PASSCODE ="passcode"; diff --git a/libs/SalesforceSDK/src/com/salesforce/androidsdk/push/PushService.java b/libs/SalesforceSDK/src/com/salesforce/androidsdk/push/PushService.java index 58fb769037..b15285c664 100644 --- a/libs/SalesforceSDK/src/com/salesforce/androidsdk/push/PushService.java +++ b/libs/SalesforceSDK/src/com/salesforce/androidsdk/push/PushService.java @@ -188,7 +188,8 @@ private void scheduleSFDCRegistrationRetry(long when, UserAccount account) { retryIntent.putExtra(PushMessaging.ACCOUNT_BUNDLE_KEY, account.toBundle()); } final PendingIntent retryPIntent = PendingIntent.getBroadcast(SalesforceSDKManager.getInstance().getAppContext(), - 1, retryIntent, PendingIntent.FLAG_ONE_SHOT); + 1, retryIntent, + PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE); final AlarmManager am = (AlarmManager) SalesforceSDKManager.getInstance().getAppContext().getSystemService(Context.ALARM_SERVICE); am.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), retryPIntent); } diff --git a/libs/SalesforceSDK/src/com/salesforce/androidsdk/rest/ClientManager.java b/libs/SalesforceSDK/src/com/salesforce/androidsdk/rest/ClientManager.java index ec8468557d..8e87d04544 100644 --- a/libs/SalesforceSDK/src/com/salesforce/androidsdk/rest/ClientManager.java +++ b/libs/SalesforceSDK/src/com/salesforce/androidsdk/rest/ClientManager.java @@ -283,90 +283,6 @@ public RestClient peekRestClient(Account acc) { } } - /** - * One time upgrade steps from older versions to Mobile SDK 8.2. Only for internal use! - * - * @deprecated Will be removed in Mobile SDK 10.0. - */ - public static synchronized void upgradeTo8Dot2() { - final AccountManager accountManager = AccountManager.get(SalesforceSDKManager.getInstance().getAppContext()); - final Account[] accounts = accountManager.getAccountsByType(SalesforceSDKManager.getInstance().getAccountType()); - for (final Account account : accounts) { - if (account != null) { - final String legacyEncryptionKey = SalesforceSDKManager.getLegacyEncryptionKey(); - final String newEncryptionKey = SalesforceSDKManager.getEncryptionKey(); - final String authToken = Encryptor.legacyDecrypt(accountManager.getUserData(account, AccountManager.KEY_AUTHTOKEN), legacyEncryptionKey); - accountManager.setUserData(account, AccountManager.KEY_AUTHTOKEN, Encryptor.encrypt(authToken, newEncryptionKey)); - final String refreshToken = Encryptor.legacyDecrypt(accountManager.getPassword(account), legacyEncryptionKey); - accountManager.setPassword(account, Encryptor.encrypt(refreshToken, newEncryptionKey)); - final String loginServer = Encryptor.legacyDecrypt(accountManager.getUserData(account, AuthenticatorService.KEY_LOGIN_URL), legacyEncryptionKey); - accountManager.setUserData(account, AuthenticatorService.KEY_LOGIN_URL, Encryptor.encrypt(loginServer, newEncryptionKey)); - final String idUrl = Encryptor.legacyDecrypt(accountManager.getUserData(account, AuthenticatorService.KEY_ID_URL), legacyEncryptionKey); - accountManager.setUserData(account, AuthenticatorService.KEY_ID_URL, Encryptor.encrypt(idUrl, newEncryptionKey)); - final String clientId = Encryptor.legacyDecrypt(accountManager.getUserData(account, AuthenticatorService.KEY_CLIENT_ID), legacyEncryptionKey); - accountManager.setUserData(account, AuthenticatorService.KEY_CLIENT_ID, Encryptor.encrypt(clientId, newEncryptionKey)); - final String instanceServer = Encryptor.legacyDecrypt(accountManager.getUserData(account, AuthenticatorService.KEY_INSTANCE_URL), legacyEncryptionKey); - accountManager.setUserData(account, AuthenticatorService.KEY_INSTANCE_URL, Encryptor.encrypt(instanceServer, newEncryptionKey)); - final String orgId = Encryptor.legacyDecrypt(accountManager.getUserData(account, AuthenticatorService.KEY_ORG_ID), legacyEncryptionKey); - accountManager.setUserData(account, AuthenticatorService.KEY_ORG_ID, Encryptor.encrypt(orgId, newEncryptionKey)); - final String userId = Encryptor.legacyDecrypt(accountManager.getUserData(account, AuthenticatorService.KEY_USER_ID), legacyEncryptionKey); - accountManager.setUserData(account, AuthenticatorService.KEY_USER_ID, Encryptor.encrypt(userId, newEncryptionKey)); - final String username = Encryptor.legacyDecrypt(accountManager.getUserData(account, AuthenticatorService.KEY_USERNAME), legacyEncryptionKey); - accountManager.setUserData(account, AuthenticatorService.KEY_USERNAME, Encryptor.encrypt(username, newEncryptionKey)); - final String lastName = Encryptor.legacyDecrypt(accountManager.getUserData(account, AuthenticatorService.KEY_LAST_NAME), legacyEncryptionKey); - accountManager.setUserData(account, AuthenticatorService.KEY_LAST_NAME, Encryptor.encrypt(lastName, newEncryptionKey)); - final String email = Encryptor.legacyDecrypt(accountManager.getUserData(account, AuthenticatorService.KEY_EMAIL), legacyEncryptionKey); - accountManager.setUserData(account, AuthenticatorService.KEY_EMAIL, Encryptor.encrypt(email, newEncryptionKey)); - final String encFirstName = accountManager.getUserData(account, AuthenticatorService.KEY_FIRST_NAME); - String firstName; - if (encFirstName != null) { - firstName = Encryptor.legacyDecrypt(encFirstName, legacyEncryptionKey); - accountManager.setUserData(account, AuthenticatorService.KEY_FIRST_NAME, Encryptor.encrypt(firstName, newEncryptionKey)); - } - final String encDisplayName = accountManager.getUserData(account, AuthenticatorService.KEY_DISPLAY_NAME); - String displayName; - if (encDisplayName != null) { - displayName = Encryptor.legacyDecrypt(encDisplayName, legacyEncryptionKey); - accountManager.setUserData(account, AuthenticatorService.KEY_DISPLAY_NAME, Encryptor.encrypt(displayName, newEncryptionKey)); - } - final String encPhotoUrl = accountManager.getUserData(account, AuthenticatorService.KEY_PHOTO_URL); - String photoUrl; - if (encPhotoUrl != null) { - photoUrl = Encryptor.legacyDecrypt(encPhotoUrl, legacyEncryptionKey); - accountManager.setUserData(account, AuthenticatorService.KEY_PHOTO_URL, Encryptor.encrypt(photoUrl, newEncryptionKey)); - } - final String encThumbnailUrl = accountManager.getUserData(account, AuthenticatorService.KEY_THUMBNAIL_URL); - String thumbnailUrl; - if (encThumbnailUrl != null) { - thumbnailUrl = Encryptor.legacyDecrypt(encThumbnailUrl, legacyEncryptionKey); - accountManager.setUserData(account, AuthenticatorService.KEY_THUMBNAIL_URL, Encryptor.encrypt(thumbnailUrl, newEncryptionKey)); - } - final List additionalOauthKeys = SalesforceSDKManager.getInstance().getAdditionalOauthKeys(); - if (additionalOauthKeys != null && !additionalOauthKeys.isEmpty()) { - for (final String key : additionalOauthKeys) { - final String encValue = accountManager.getUserData(account, key); - if (encValue != null) { - final String value = Encryptor.legacyDecrypt(encValue, legacyEncryptionKey); - accountManager.setUserData(account, key, Encryptor.encrypt(value, newEncryptionKey)); - } - } - } - final String encCommunityId = accountManager.getUserData(account, AuthenticatorService.KEY_COMMUNITY_ID); - String communityId; - if (encCommunityId != null) { - communityId = Encryptor.legacyDecrypt(encCommunityId, legacyEncryptionKey); - accountManager.setUserData(account, AuthenticatorService.KEY_COMMUNITY_ID, Encryptor.encrypt(communityId, newEncryptionKey)); - } - final String encCommunityUrl = accountManager.getUserData(account, AuthenticatorService.KEY_COMMUNITY_URL); - String communityUrl; - if (encCommunityUrl != null) { - communityUrl = Encryptor.legacyDecrypt(encCommunityUrl, legacyEncryptionKey); - accountManager.setUserData(account, AuthenticatorService.KEY_COMMUNITY_URL, Encryptor.encrypt(communityUrl, newEncryptionKey)); - } - } - } - } - /** * Invalidate current auth token. The next call to {@link #getRestClient(Activity, RestClientCallback) getRestClient} will do a refresh. */ diff --git a/libs/SalesforceSDK/src/com/salesforce/androidsdk/security/PasscodeManager.java b/libs/SalesforceSDK/src/com/salesforce/androidsdk/security/PasscodeManager.java deleted file mode 100644 index 651bdf9f75..0000000000 --- a/libs/SalesforceSDK/src/com/salesforce/androidsdk/security/PasscodeManager.java +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright (c) 2014-present, salesforce.com, inc. - * All rights reserved. - * Redistribution and use of this software in source and binary forms, with or - * without modification, are permitted provided that the following conditions - * are met: - * - Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - Neither the name of salesforce.com, inc. nor the names of its contributors - * may be used to endorse or promote products derived from this software without - * specific prior written permission of salesforce.com, inc. - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package com.salesforce.androidsdk.security; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.os.Handler; - -import com.salesforce.androidsdk.accounts.UserAccount; -import com.salesforce.androidsdk.analytics.EventBuilderHelper; -import com.salesforce.androidsdk.analytics.security.Encryptor; -import com.salesforce.androidsdk.app.SalesforceSDKManager; -import com.salesforce.androidsdk.util.EventsObservable; -import com.salesforce.androidsdk.util.EventsObservable.EventType; - -import java.io.File; -import java.io.FilenameFilter; - -/** - * This class manages the inactivity timeout, and keeps track of if the UI should locked etc. - * - * @author wmathurin - * @author bhariharan - * - * @deprecated Will be removed in Mobile SDK 10.0. Use {@link ScreenLockManager} instead. - */ -public class PasscodeManager { - - // Default min passcode length - public static final int MIN_PASSCODE_LENGTH = 4; - - // Request code used to start passcode activity - public static final int PASSCODE_REQUEST_CODE = 777; - - /** - * Parameterized constructor. - * - * @param ctx Context. - */ - public PasscodeManager(Context ctx) { } - - public PasscodeManager(Context ctx, HashConfig verificationHashConfig) { } - - /** - * Returns true if a passcode change is required. - * - * @return true if passcode change required. - */ - public boolean isPasscodeChangeRequired() { return false; } - - - /** - * Set passcode change required flag to the passed value - * @param ctx Context. - * @param passcodeChangeRequired value to set passcode change required flag to - */ - public void setPasscodeChangeRequired(Context ctx, boolean passcodeChangeRequired) { } - - /** - * Returns the timeout value for the specified account. - * - * @param account UserAccount instance. - * @return Timeout value. - */ - public int getTimeoutMsForOrg(UserAccount account) { return 0; } - - /** - * Returns the minimum passcode length for the specified account. - * - * @param account UserAccount instance. - * @return Minimum passcode length. - */ - public int getPasscodeLengthForOrg(UserAccount account) { return 0; } - - /** - * Stores the mobile policy for the specified account. - * - * @param account UserAccount instance. - * @param timeout Timeout value, in ms. - * @param passLen Minimum passcode length. - * @param bioAllowed If biometric Unlock is Allowed by connected App - */ - @SuppressLint("ApplySharedPref") - public void storeMobilePolicyForOrg(UserAccount account, int timeout, int passLen, boolean bioAllowed) { } - - /** - * Reset this passcode manager: delete stored passcode and reset fields to their starting value - */ - @SuppressLint("ApplySharedPref") - public void reset(Context ctx) { } - - /** - * Resets the passcode policies for a particular org upon logout. - * - * @param context Context. - * @param account User account. - */ - @SuppressLint("ApplySharedPref") - public void reset(Context context, UserAccount account) { } - - /** - * Enable/disable passcode screen. - */ - public void setEnabled(boolean enabled) { } - - /** - * @return true if passcode manager is enabled. - */ - public boolean isEnabled() { return false; } - - /** - * @return the new failure count - */ - public int addFailedPasscodeAttempt() { return 0; } - - /** - * @param ctx Context. - * @param passcode Passcode. - * @return true if passcode matches the one stored (hashed) in private preference - */ - public boolean check(Context ctx, String passcode) { return true; } - - /** - * Store the given passcode (hashed) in private preference - * @param ctx Context. - * @param passcode Passcode. - */ - @SuppressLint("ApplySharedPref") - public void store(Context ctx, String passcode) { } - - /** - * @param ctx Context. - * @return true if passcode was already created - */ - public boolean hasStoredPasscode(Context ctx) { return false; } - - /** - * @return number of failed passcode attempts - */ - public int getFailedPasscodeAttempts() { return 0; } - - /** - * @return true if locked - */ - public boolean isLocked() { return false; } - - /** - * @param ctx Context. - */ - public void lock(Context ctx) { } - - /** - * @param frontActivity - * @param registerActivity - * @return - */ - public boolean lockIfNeeded(Activity frontActivity, boolean registerActivity) { return false; } - - /** - * To be called by passcode protected activity when being paused - */ - public void onPause(Activity ctx) { } - - /** - * To be called by passcode protected activity when being resumed - * When passcode screen is about to be shown, false is returned, the activity will be resumed once - * the user has successfully enter her passcode - * - * @return true if the resume should be allowed to continue and false otherwise - */ - public boolean onResume(Activity ctx) { return false; } - - /** - * To be called by passcode protected activity whenever there is a user interaction - */ - public void recordUserInteraction() { } - - /** - * Called when the access timeout for the org changes. - * - * @param newTimeout New access timeout value. - */ - public void setTimeoutMs(int newTimeout) { } - - /** - * The current inactivity timeout before the app locks, in milliseconds. - * - * @return the inactivity timeout - */ - public int getTimeoutMs() { return 0; } - - /** - * The exact length of the passcode if it is known. It may be unknown on upgrade before first unlock. - * Use {@link PasscodeManager#getPasscodeLengthKnown()} to check if return is exact length or org minimum. - * - * @return passcode length - */ - public int getPasscodeLength() { return 0; } - - /** - * Whether or not the exact passcode length is known. It may be unknown on upgrade before first unlock. - * Use {@link PasscodeManager#getPasscodeLength()} to get the length. - * - * @return true if the length is known - */ - public boolean getPasscodeLengthKnown() { return false; } - - /** - * Whether or not the connected app allows biometric as an alternative to passcode. - * - * @return true if biometric is allowed - */ - public boolean biometricAllowed() { return false; } - - /** - * Whether or not the user has been shown the screen prompting them to enroll in biometric unlock. - * @return true if the user has been prompted to enable biometric - */ - public boolean biometricEnrollmentShown() { return false; } - - /** - * Whether or not the user has enabled the ability to use biometric to bypass passcode. - * - * @return true if the user has enabled biometric - */ - public boolean biometricEnabled() { return false; } - - /** - * @param ctx Context. - * @param passcodeLength The new passcode length to set. - */ - public void setPasscodeLength(Context ctx, int passcodeLength) { } - - /** - * This method can be used to force the stored or default passcode length to be trusted - * upon upgrade if set to 'true'. - * - * @param ctx Context - * @param lengthKnown Whether or not the passcode length is known. - */ - public void setPasscodeLengthKnown(Context ctx, boolean lengthKnown) { } - - /** - * Called when the biometric unlock requirement for the org changes. - * - * This API is intended for internal Salesforce only. Setting this value in an app overrides the server's connected app policy and is not recommended. - * Although setting {@code allowed} to false prevents users from being able to enroll in biometric unlock, the proper - * way to prevent user enrollment is through the connected app. - * @see Using Passcodes - */ - public void setBiometricAllowed(Context ctx, boolean allowed) { } - - /** - * By default biometric enrollment is only shown to the user once. - * - * @param shown set to true to show biometric prompt on next passcode unlock. - */ - public void setBiometricEnrollmentShown(Context ctx, boolean shown) { } - - /** - * Enables biometric input. - * - * This API is intended to let the end user toggle the use of biometric entry. Setting this property to false does not prevent - * the biometric enrollment screen from being shown to the user, nor does it prevent the user from enabling the - * feature. - * - * To prevent users from enrolling in biometric, ask an administrator in the Salesforce org to configure the - * connected app. For details, see Using Passcodes - * in the Mobile SDK Development Guide. - * - * If you absolutely must disable biometric input at the app level see {@link PasscodeManager#setBiometricAllowed(Context, boolean)}. - */ - public void setBiometricEnabled(Context ctx, boolean enabled) { } - - /** - * @return true if time elapsed since the last user activity in the app exceeds the timeoutMs - */ - public boolean shouldLock() { return false; } - - public void showLockActivity(Context ctx) { } - - /** - * This is used when unlocking via the fingerprint authentication. - * The passcode hash isn't updated as the authentication is verified by the OS. - */ - public void unlock() { } - - public String hashForVerification(String passcode) { return ""; } - - /** - * Key for hashing and salts to be preprended and appended to data to increase entropy. - */ - public static class HashConfig { - - public final String prefix; - public final String suffix; - public final String key; - - public HashConfig(String prefix, String suffix, String key) { - this.prefix = prefix; - this.suffix = suffix; - this.key = key; - } - } -} diff --git a/libs/SalesforceSDK/src/com/salesforce/androidsdk/security/SalesforceKeyGenerator.java b/libs/SalesforceSDK/src/com/salesforce/androidsdk/security/SalesforceKeyGenerator.java index 8c8a719e0e..8adf563e38 100644 --- a/libs/SalesforceSDK/src/com/salesforce/androidsdk/security/SalesforceKeyGenerator.java +++ b/libs/SalesforceSDK/src/com/salesforce/androidsdk/security/SalesforceKeyGenerator.java @@ -108,32 +108,6 @@ public static String getEncryptionKey(String name) { return encryptionKey; } - /** - * Returns the legacy encryption key. This should be called only as a means to migrate to the new key. - * - * @param name Unique name associated with this legacy encryption key. - * @return Legacy encryption key. - * @deprecated Will be removed in Mobile SDK 10.0. - */ - public static String getLegacyEncryptionKey(String name) { - if (TextUtils.isEmpty(name)) { - return null; - } - String encryptionKey = null; - try { - final String keyString = getUniqueId(name); - byte[] secretKey = keyString.getBytes(StandardCharsets.UTF_8); - final MessageDigest md = MessageDigest.getInstance(SHA1); - secretKey = md.digest(secretKey); - byte[] dest = new byte[16]; - System.arraycopy(secretKey, 0, dest, 0, 16); - encryptionKey = Base64.encodeToString(dest, Base64.NO_WRAP); - } catch (Exception ex) { - SalesforceSDKLogger.e(TAG, "Exception thrown while getting legacy encryption key", ex); - } - return encryptionKey; - } - /** * Returns a randomly generated 128-byte key that's URL safe. * diff --git a/libs/SalesforceSDK/src/com/salesforce/androidsdk/security/ScreenLockManager.java b/libs/SalesforceSDK/src/com/salesforce/androidsdk/security/ScreenLockManager.java index d1bfbabc15..3c4f899199 100644 --- a/libs/SalesforceSDK/src/com/salesforce/androidsdk/security/ScreenLockManager.java +++ b/libs/SalesforceSDK/src/com/salesforce/androidsdk/security/ScreenLockManager.java @@ -45,6 +45,7 @@ * @author bpage */ public class ScreenLockManager { + public static final String MOBILE_POLICY_PREF = "mobile_policy"; public static final String SCREEN_LOCK = "screen_lock"; @@ -100,25 +101,30 @@ public void reset() { /** * Screen lock specific cleanup for account logout/removal. * - * @param account the account being removed + * @param account The account being removed. */ public void cleanUp(UserAccount account) { - // CleanUp and remove Lock for account. - Context ctx = SalesforceSDKManager.getInstance().getAppContext(); - SharedPreferences accountPrefs = ctx.getSharedPreferences(MOBILE_POLICY_PREF - + account.getUserLevelFilenameSuffix(), Context.MODE_PRIVATE); - accountPrefs.edit().remove(SCREEN_LOCK).apply(); - // Determine if any other users still need ScreenLock. - List accounts = SalesforceSDKManager.getInstance() + // Clean up and remove lock for account. + final Context ctx = SalesforceSDKManager.getInstance().getAppContext(); + if (account != null) { + final SharedPreferences accountPrefs = ctx.getSharedPreferences(MOBILE_POLICY_PREF + + account.getUserLevelFilenameSuffix(), Context.MODE_PRIVATE); + accountPrefs.edit().remove(SCREEN_LOCK).apply(); + } + + // Determine if any other users still need Screen Lock. + final List accounts = SalesforceSDKManager.getInstance() .getUserAccountManager().getAuthenticatedUsers(); if (accounts != null) { accounts.remove(account); - for (UserAccount mAccount : accounts) { - accountPrefs = ctx.getSharedPreferences(MOBILE_POLICY_PREF - + mAccount.getUserLevelFilenameSuffix(), Context.MODE_PRIVATE); - if (accountPrefs.getBoolean(SCREEN_LOCK, false)) { - return; + for (final UserAccount mAccount : accounts) { + if (mAccount != null) { + final SharedPreferences accountPrefs = ctx.getSharedPreferences(MOBILE_POLICY_PREF + + mAccount.getUserLevelFilenameSuffix(), Context.MODE_PRIVATE); + if (accountPrefs.getBoolean(SCREEN_LOCK, false)) { + return; + } } } } diff --git a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/CustomServerUrlEditor.java b/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/CustomServerUrlEditor.java index 9ada370c92..f324b0a2c8 100644 --- a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/CustomServerUrlEditor.java +++ b/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/CustomServerUrlEditor.java @@ -73,16 +73,6 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, rootView.getContext().setTheme(isDarkTheme ? R.style.SalesforceSDK_Dialog_Dark : R.style.SalesforceSDK_Dialog); getDialog().setTitle(R.string.sf__server_url_add_title); - // TODO: Remove this when min API becomes 24. - if (!isDarkTheme && Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { - EditText label = getRootView().findViewById(R.id.sf__picker_custom_label); - label.setTextColor(getResources().getColor(R.color.sf__text_color)); - label.setHintTextColor(getResources().getColor(R.color.sf__hint_color)); - EditText url = getRootView().findViewById(R.id.sf__picker_custom_url); - url.setTextColor(getResources().getColor(R.color.sf__text_color)); - url.setHintTextColor(getResources().getColor(R.color.sf__hint_color)); - } - /* * Sets handlers in the code for the dialog. */ diff --git a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/FingerprintAuthDialogFragment.java b/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/FingerprintAuthDialogFragment.java deleted file mode 100644 index f5cb2d96ea..0000000000 --- a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/FingerprintAuthDialogFragment.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2016-present, salesforce.com, inc. - * All rights reserved. - * Redistribution and use of this software in source and binary forms, with or - * without modification, are permitted provided that the following conditions - * are met: - * - Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - Neither the name of salesforce.com, inc. nor the names of its contributors - * may be used to endorse or promote products derived from this software without - * specific prior written permission of salesforce.com, inc. - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package com.salesforce.androidsdk.ui; - -import android.app.Dialog; -import android.app.DialogFragment; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -/** - * A dialog which uses Fingerprint APIs to authenticate the user, and falls back to password - * authentication if fingerprint is not available. - * - * @deprecated Will be removed in Mobile SDK 10.0. - */ -public class FingerprintAuthDialogFragment extends DialogFragment { - - - @Override - public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } - - @Override - public void onResume() { super.onResume(); } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { return null; } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { return null; } - - public void setContext(PasscodeActivity ctx) { } -} diff --git a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/OAuthWebviewHelper.java b/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/OAuthWebviewHelper.java index a24ed6ee2e..3918877063 100644 --- a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/OAuthWebviewHelper.java +++ b/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/OAuthWebviewHelper.java @@ -335,12 +335,13 @@ private void loadLoginPageInChrome(URI uri) { final Resources resources = activity.getResources(); intentBuilder.setCloseButtonIcon(BitmapFactory.decodeResource(resources, R.drawable.sf__action_back)); - intentBuilder.setToolbarColor(resources.getColor(R.color.sf__primary_color)); + intentBuilder.setToolbarColor(getContext().getColor(R.color.sf__primary_color)); // Adds a menu item to change server. final Intent changeServerIntent = new Intent(activity, ServerPickerActivity.class); final PendingIntent changeServerPendingIntent = PendingIntent.getActivity(activity, - LoginActivity.PICK_SERVER_REQUEST_CODE, changeServerIntent, PendingIntent.FLAG_CANCEL_CURRENT); + LoginActivity.PICK_SERVER_REQUEST_CODE, changeServerIntent, + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); intentBuilder.addMenuItem(activity.getString(R.string.sf__pick_server), changeServerPendingIntent); final CustomTabsIntent customTabsIntent = intentBuilder.build(); diff --git a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/PasscodeActivity.java b/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/PasscodeActivity.java deleted file mode 100644 index 095a818b49..0000000000 --- a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/PasscodeActivity.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2011-present, salesforce.com, inc. - * All rights reserved. - * Redistribution and use of this software in source and binary forms, with or - * without modification, are permitted provided that the following conditions - * are met: - * - Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - Neither the name of salesforce.com, inc. nor the names of its contributors - * may be used to endorse or promote products derived from this software without - * specific prior written permission of salesforce.com, inc. - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package com.salesforce.androidsdk.ui; - -import android.app.Activity; -import android.os.Bundle; -import android.view.KeyEvent; - - -/** - * Passcode activity: takes care of creating/verifying a user passcode. - * - * @deprecated Will be removed in Mobile SDK 10.0. Use {@link ScreenLockActivity} instead. - */ -public class PasscodeActivity extends Activity { - - protected static final int MAX_PASSCODE_ATTEMPTS = 10; - public enum PasscodeMode { - Create, - CreateConfirm, - Check, - Change, - EnableBiometric, - BiometricCheck - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - protected void biometricDeclined() { } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { return false; } - - /** - * Saves the entered text before activity rotation. - */ - @Override - protected void onSaveInstanceState(Bundle savedInstance) { } - - public PasscodeMode getMode() { - return PasscodeMode.Create; - } - - public void setMode(PasscodeMode newMode) { } - - /** - * Used from tests to allow/disallow automatic logout when wrong passcode has been entered too many times. - * - * @param b True - if logout is enabled, False - otherwise. - */ - public void enableLogout(boolean b) { } - - /** - * Used for tests to allow biometric when the device is not set up - * - * @param b True - if biometric checks skipped, False - otherwise. - */ - public void forceBiometric(boolean b) { } - - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { } - - public void unlockViaFingerprintScan() { } -} \ No newline at end of file diff --git a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/PasscodeField.java b/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/PasscodeField.java deleted file mode 100644 index 47d338d5fa..0000000000 --- a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/PasscodeField.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2019-present, salesforce.com, inc. - * All rights reserved. - * Redistribution and use of this software in source and binary forms, with or - * without modification, are permitted provided that the following conditions - * are met: - * - Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - Neither the name of salesforce.com, inc. nor the names of its contributors - * may be used to endorse or promote products derived from this software without - * specific prior written permission of salesforce.com, inc. - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package com.salesforce.androidsdk.ui; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.util.AttributeSet; -import android.widget.EditText; - -/** - * @deprecated Will be removed in Mobile SDK 10.0. - */ - -@SuppressLint("AppCompatCustomView") -public class PasscodeField extends EditText { - - /** - * {@inheritDoc} - */ - public PasscodeField(Context context) { super(context); } - - /** - * {@inheritDoc} - */ - public PasscodeField(Context context, AttributeSet attrs) { super(context, attrs); } - - /** - * {@inheritDoc} - */ - public PasscodeField(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } - - /** - * This override is necessary to ensure the user is always typing on the end of the passcode string. - * - * {@inheritDoc} - */ - @Override - public void onSelectionChanged(int start, int end) { super.onSelectionChanged(start, end); } - - /** - * Override provided to disable suggestions. - * - * @return false - */ - @Override - public boolean isSuggestionsEnabled() { - return false; - } -} diff --git a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/SalesforceActivity.java b/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/SalesforceActivity.java index 254499bf80..e3c230c674 100644 --- a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/SalesforceActivity.java +++ b/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/SalesforceActivity.java @@ -54,11 +54,6 @@ public void onResume() { delegate.onResume(true); } - @Override - public void onUserInteraction() { - delegate.onUserInteraction(); - } - @Override public void onPause() { super.onPause(); diff --git a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/SalesforceActivityDelegate.java b/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/SalesforceActivityDelegate.java index f8bd0de230..6d607cb719 100644 --- a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/SalesforceActivityDelegate.java +++ b/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/SalesforceActivityDelegate.java @@ -106,11 +106,6 @@ public void authenticatedRestClient(RestClient client) { } } - /* - * @deprecated Will be removed in Mobile SDK 10.0. - */ - public void onUserInteraction() { } - public void onPause() { } public void onDestroy() { diff --git a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/SalesforceExpandableListActivity.java b/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/SalesforceExpandableListActivity.java index c6ca68e70a..fbfe6ee495 100644 --- a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/SalesforceExpandableListActivity.java +++ b/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/SalesforceExpandableListActivity.java @@ -56,11 +56,6 @@ public void onResume() { delegate.onResume(true); } - @Override - public void onUserInteraction() { - delegate.onUserInteraction(); - } - @Override public void onPause() { super.onPause(); diff --git a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/SalesforceListActivity.java b/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/SalesforceListActivity.java index 0b929a5112..9769cabe77 100644 --- a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/SalesforceListActivity.java +++ b/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/SalesforceListActivity.java @@ -56,11 +56,6 @@ public void onResume() { delegate.onResume(true); } - @Override - public void onUserInteraction() { - delegate.onUserInteraction(); - } - @Override public void onPause() { super.onPause(); diff --git a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/ScreenLockActivity.java b/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/ScreenLockActivity.java index cbb89cc6ac..700321bcc0 100644 --- a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/ScreenLockActivity.java +++ b/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/ScreenLockActivity.java @@ -32,7 +32,6 @@ import static com.salesforce.androidsdk.security.ScreenLockManager.MOBILE_POLICY_PREF; import static com.salesforce.androidsdk.security.ScreenLockManager.SCREEN_LOCK; -import android.app.KeyguardManager; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.Intent; @@ -42,7 +41,6 @@ import android.os.Build; import android.os.Bundle; import android.provider.Settings; -import android.util.Log; import android.view.View; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; @@ -70,8 +68,6 @@ * Locks the app behind OS provided authentication. */ public class ScreenLockActivity extends FragmentActivity { - private static final int API_29_REQUEST_CODE = 123; - private static final String TAG = "ScreenLockActivity"; private static final int SETUP_REQUEST_CODE = 70; private static final String appName = SalesforceSDKManager.getInstance().provideAppName(); @@ -119,18 +115,6 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == SETUP_REQUEST_CODE) { presentAuth(); } - - /* - * Get the results of KeyguardManager on API 29. - * TODO: Remove when min API > 29. - */ - if (requestCode == API_29_REQUEST_CODE) { - if (resultCode == -1) { - finishSuccess(); - } else { - onAuthError(""); - } - } } @Override @@ -175,22 +159,7 @@ private void presentAuth() { break; case BiometricManager.BIOMETRIC_SUCCESS: resetUI(); - - /* - * This is necessary due to an Android bug that can't be fixed. - * https://issuetracker.google.com/issues/145231213 - * - * TODO: Remove when min API > 29. - */ - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { - KeyguardManager keyguard = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); - Intent lockScreenIntent = keyguard.createConfirmDeviceCredentialIntent( - getString(R.string.sf__screen_lock_title, appName), - getString(R.string.sf__screen_lock_subtitle, appName)); - startActivityForResult(lockScreenIntent, API_29_REQUEST_CODE); - } else { - biometricPrompt.authenticate(getPromptInfo()); - } + biometricPrompt.authenticate(getPromptInfo()); break; } } diff --git a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/ServerPickerActivity.java b/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/ServerPickerActivity.java index 21f16e0960..5464b4c6b0 100644 --- a/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/ServerPickerActivity.java +++ b/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/ServerPickerActivity.java @@ -26,6 +26,8 @@ */ package com.salesforce.androidsdk.ui; +import static java.security.AccessController.getContext; + import android.app.ActionBar; import android.app.Activity; import android.app.FragmentManager; @@ -212,9 +214,9 @@ private void setRadioState(RadioGroup radioGroup, LoginServer server) { final SalesforceServerRadioButton rb = new SalesforceServerRadioButton(this, server.name, server.url, server.isCustom); boolean isDarkTheme = SalesforceSDKManager.getInstance().isDarkTheme(); - int textColor = getResources().getColor(isDarkTheme ? R.color.sf__text_color_dark : R.color.sf__text_color); + int textColor = getColor(isDarkTheme ? R.color.sf__text_color_dark : R.color.sf__text_color); rb.setTextColor(textColor); - rb.getButtonDrawable().setTint(getResources().getColor(R.color.sf__primary_color)); + rb.getButtonDrawable().setTint(getColor(R.color.sf__primary_color)); radioGroup.addView(rb); ((ScrollView) radioGroup.getParent()).scrollTo(0, radioGroup.getBottom()); } diff --git a/libs/SmartStore/AndroidManifest.xml b/libs/SmartStore/AndroidManifest.xml index dcf1607eb1..da7e3d4c02 100644 --- a/libs/SmartStore/AndroidManifest.xml +++ b/libs/SmartStore/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="76" + android:versionName="10.0.0.dev"> diff --git a/libs/SmartStore/build.gradle b/libs/SmartStore/build.gradle index 36f6aa0786..7d773c92a1 100644 --- a/libs/SmartStore/build.gradle +++ b/libs/SmartStore/build.gradle @@ -9,7 +9,7 @@ apply plugin: 'com.android.library' dependencies { api project(':libs:SalesforceSDK') - api 'net.zetetic:android-database-sqlcipher:4.4.3' + api 'net.zetetic:android-database-sqlcipher:4.5.0' androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test:rules:1.4.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' @@ -18,11 +18,11 @@ dependencies { } android { - compileSdkVersion 30 + compileSdkVersion 32 defaultConfig { - targetSdkVersion 30 - minSdkVersion 23 + targetSdkVersion 32 + minSdkVersion 24 } buildTypes { @@ -49,27 +49,26 @@ android { res.srcDirs = ['../test/SmartStoreTest/res'] } } - defaultConfig { + packagingOptions { + resources { + excludes += ['META-INF/LICENSE', 'META-INF/LICENSE.txt', 'META-INF/DEPENDENCIES', 'META-INF/NOTICE'] + pickFirsts += ['protobuf.meta'] + } + } + defaultConfig { testApplicationId "com.salesforce.androidsdk.smartstore.tests" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } - packagingOptions { - exclude 'META-INF/LICENSE' - exclude 'META-INF/LICENSE.txt' - exclude 'META-INF/DEPENDENCIES' - exclude 'META-INF/NOTICE' - pickFirst 'protobuf.meta' - } + lint { + abortOnError false + xmlReport true + } - lintOptions { - xmlReport true - abortOnError false - } } ext { PUBLISH_GROUP_ID = 'com.salesforce.mobilesdk' - PUBLISH_VERSION = '9.2.1' + PUBLISH_VERSION = '10.0.0' PUBLISH_ARTIFACT_ID = 'SmartStore' } diff --git a/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/app/SmartStoreUpgradeManager.java b/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/app/SmartStoreUpgradeManager.java index 982f36336f..19c5bcfa22 100644 --- a/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/app/SmartStoreUpgradeManager.java +++ b/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/app/SmartStoreUpgradeManager.java @@ -27,7 +27,6 @@ package com.salesforce.androidsdk.smartstore.app; import com.salesforce.androidsdk.app.SalesforceSDKUpgradeManager; -import com.salesforce.androidsdk.smartstore.store.DBOpenHelper; import com.salesforce.androidsdk.smartstore.util.SmartStoreLogger; /** @@ -80,9 +79,6 @@ protected synchronized void upgradeSmartStore() { try { final String majorVersionNum = installedVersion.substring(0, 3); double installedVerDouble = Double.parseDouble(majorVersionNum); - if (installedVerDouble < 8.2) { - upgradeTo8Dot2(); - } } catch (Exception e) { SmartStoreLogger.e(TAG, "Failed to parse installed version."); } @@ -96,8 +92,4 @@ protected synchronized void upgradeSmartStore() { public String getInstalledSmartStoreVersion() { return getInstalledVersion(SMART_STORE_KEY); } - - private void upgradeTo8Dot2() { - DBOpenHelper.upgradeTo8Dot2(); - } } diff --git a/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/store/AlterSoupLongOperation.java b/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/store/AlterSoupLongOperation.java index 577f7faea3..df4e1994bf 100644 --- a/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/store/AlterSoupLongOperation.java +++ b/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/store/AlterSoupLongOperation.java @@ -119,6 +119,20 @@ public AlterSoupLongOperation() { } + /** + * Constructor + * + * @param store + * @param soupName + * @param newIndexSpecs + * @param reIndexData + * @throws JSONException + */ + public AlterSoupLongOperation(SmartStore store, String soupName, IndexSpec[] newIndexSpecs, + boolean reIndexData) throws JSONException { + this(store, soupName, new SoupSpec(soupName), newIndexSpecs, reIndexData); + } + /** * Constructor * @@ -127,8 +141,11 @@ public AlterSoupLongOperation() { * @param newSoupSpec * @param newIndexSpecs * @param reIndexData - * @throws JSONException + * @throws JSONException + * + * Deprecated: we are removing external storage and soup spec in 11.0 - use other constructor instead */ + @Deprecated public AlterSoupLongOperation(SmartStore store, String soupName, SoupSpec newSoupSpec, IndexSpec[] newIndexSpecs, boolean reIndexData) throws JSONException { diff --git a/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/store/DBOpenHelper.java b/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/store/DBOpenHelper.java index 49fdf96b5c..70b79a4257 100644 --- a/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/store/DBOpenHelper.java +++ b/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/store/DBOpenHelper.java @@ -26,6 +26,8 @@ */ package com.salesforce.androidsdk.smartstore.store; +import static com.salesforce.androidsdk.smartstore.app.SmartStoreSDKManager.GLOBAL_SUFFIX; + import android.content.Context; import android.text.TextUtils; @@ -52,8 +54,6 @@ import java.util.List; import java.util.Map; -import static com.salesforce.androidsdk.smartstore.app.SmartStoreSDKManager.GLOBAL_SUFFIX; - /** * Helper class to manage SmartStore's database creation and version management. */ @@ -196,6 +196,11 @@ protected void loadLibs(Context context) { SQLiteDatabase.loadLibs(context); } + @Override + public void onConfigure(final SQLiteDatabase db) { + db.enableWriteAheadLogging(); + } + @Override public void onCreate(SQLiteDatabase db) { @@ -340,34 +345,6 @@ public static synchronized void deleteAllDatabases(Context ctx, UserAccount user } } - /** - * One time upgrade steps from older versions to Mobile SDK 8.2. Only for internal use! - * - * @deprecated Will be removed in Mobile SDK 10.0. - */ - public static void upgradeTo8Dot2() { - final Context context = SmartStoreSDKManager.getInstance().getAppContext(); - final String oldEncryptionKey = SmartStoreSDKManager.getLegacyEncryptionKey(); - final String newEncryptionKey = SmartStoreSDKManager.getEncryptionKey(); - - // Migrates all user and global databases to the new encryption key. - final File[] userFiles = ManagedFilesHelper.getFiles(context, - DATABASES, "00D", ".db", null); - final File[] globalFiles = ManagedFilesHelper.getFiles(context, - DATABASES, GLOBAL_SUFFIX, ".db", null); - int numUserFiles = userFiles.length; - int numGlobalFiles = globalFiles.length; - final File[] allFiles = new File[numUserFiles + numGlobalFiles]; - System.arraycopy(userFiles, 0, allFiles, 0, numUserFiles); - System.arraycopy(globalFiles, 0, allFiles, numUserFiles, numGlobalFiles); - for (final File file : allFiles) { - final DBOpenHelper openHelper = new DBOpenHelper(context, file.getName()); - final SQLiteDatabase db = openHelper.getWritableDatabase(oldEncryptionKey); - changeKey(db, oldEncryptionKey, newEncryptionKey); - reEncryptAllFiles(db, oldEncryptionKey, newEncryptionKey); - } - } - /** * Determines if a smart store currently exists for the given account and/or community id. * @@ -547,17 +524,7 @@ public static synchronized void reEncryptAllFiles(SQLiteDatabase db, String oldK String result; try { String json = Encryptor.getStringFromFile(blob); - - /* - * If the key length is 24, then it's the old key (16 bytes for the - * key and 8 bytes for the IV. If the key length is 44, then it's the - * new key (32 bytes for the key and 12 bytes for the IV). - */ - if (oldKey.getBytes().length == 24) { - result = Encryptor.legacyDecrypt(json, oldKey); - } else { - result = Encryptor.decrypt(json, oldKey); - } + result = Encryptor.decrypt(json, oldKey); blob.delete(); final FileOutputStream outputStream = new FileOutputStream(blob, false); outputStream.write(Encryptor.encrypt(result, newKey).getBytes()); diff --git a/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/store/KeyValueEncryptedFileStore.java b/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/store/KeyValueEncryptedFileStore.java index 25ee3d70ef..4291497a89 100644 --- a/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/store/KeyValueEncryptedFileStore.java +++ b/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/store/KeyValueEncryptedFileStore.java @@ -43,7 +43,6 @@ import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; -import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -419,10 +418,6 @@ public boolean accept(File file, String name) { return files == null ? new File[0] : files; } - void encryptStringToFile(File file, String content, String encryptionKey) throws IOException { - encryptStreamToFile(file, new ByteArrayInputStream(content.getBytes()), encryptionKey); - } - String decryptFileAsString(File file, String encryptionKey) throws IOException { return Encryptor.getStringFromStream(decryptFileAsSteam(file, encryptionKey)); } @@ -447,16 +442,24 @@ InputStream decryptFileAsSteam(File file, String encryptionKey) throws IOExcepti } } + void encryptStringToFile(File file, String content, String encryptionKey) throws IOException { + encryptBytesToFile(file, content.getBytes(), encryptionKey); + } + void encryptStreamToFile(File file, InputStream stream, String encryptionKey) throws IOException { + final ByteArrayOutputStream b = new ByteArrayOutputStream(); + int nextByte = stream.read(); + while (nextByte != -1) { + b.write(nextByte); + nextByte = stream.read(); + } + byte[] content = b.toByteArray(); + encryptBytesToFile(file, content, encryptionKey); + } + + void encryptBytesToFile(File file, byte[] content, String encryptionKey) throws IOException { FileOutputStream f = null; try { - final ByteArrayOutputStream b = new ByteArrayOutputStream(); - int nextByte = stream.read(); - while (nextByte != -1) { - b.write(nextByte); - nextByte = stream.read(); - } - byte[] content = b.toByteArray(); byte[] encryptedContent = Encryptor.encryptWithoutBase64Encoding(content, encryptionKey); f = new FileOutputStream(file); if (encryptedContent != null) { diff --git a/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/store/SmartStore.java b/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/store/SmartStore.java index 59212baef6..adc2b35e42 100644 --- a/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/store/SmartStore.java +++ b/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/store/SmartStore.java @@ -29,29 +29,23 @@ import android.content.ContentValues; import android.database.Cursor; import android.text.TextUtils; - import androidx.annotation.NonNull; - import com.salesforce.androidsdk.analytics.EventBuilderHelper; import com.salesforce.androidsdk.app.SalesforceSDKManager; import com.salesforce.androidsdk.smartstore.store.LongOperation.LongOperationType; import com.salesforce.androidsdk.smartstore.store.QuerySpec.QueryType; import com.salesforce.androidsdk.smartstore.util.SmartStoreLogger; -import com.salesforce.androidsdk.util.JSONObjectHelper; - -import net.sqlcipher.database.SQLiteDatabase; -import net.sqlcipher.database.SQLiteOpenHelper; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import net.sqlcipher.database.SQLiteDatabase; +import net.sqlcipher.database.SQLiteOpenHelper; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; /** * Smart store @@ -297,7 +291,10 @@ public void registerSoup(String soupName, IndexSpec[] indexSpecs) { * Create rows in soup index map table for indexSpecs * @param soupSpec * @param indexSpecs + * + * Deprecated: we are removing external storage and soup spec in 11.0 - use registerSoup(String soupName, IndexSpec[] indexSpecs) instead */ + @Deprecated public void registerSoupWithSpec(final SoupSpec soupSpec, final IndexSpec[] indexSpecs) { final SQLiteDatabase db = getDatabase(); synchronized (db) { @@ -552,7 +549,10 @@ public void alterSoup(String soupName, IndexSpec[] indexSpecs, * @param indexSpecs array of index specs * @param reIndexData * @throws JSONException + * + * Deprecated: we are removing external storage and soup spec in 11.0 - use alterSoup(String soupName, IndexSpec[] indexSpecs) instead */ + @Deprecated public void alterSoup(String soupName, SoupSpec soupSpec, IndexSpec[] indexSpecs, boolean reIndexData) throws JSONException { AlterSoupLongOperation operation = new AlterSoupLongOperation(this, soupName, soupSpec, indexSpecs, reIndexData); @@ -790,7 +790,10 @@ public List getAllSoupNames() { * Returns the entire SoupSpec of the given soup. * @param soupName * @return SoupSpec for given soup name. + * + * Deprecated: we are removing external storage and soup spec in 11.0 */ + @Deprecated public SoupSpec getSoupSpec(String soupName) { final SQLiteDatabase db = getDatabase(); List features = DBHelper.getInstance(db).getFeatures(db, soupName); @@ -798,19 +801,41 @@ public SoupSpec getSoupSpec(String soupName) { } /** - * Run a query given by its query Spec, only returned results from selected page - * @param querySpec - * @param pageIndex + * Run a query given by its query spec + * Returns results from selected page + * + * @param querySpec the query to run + * @param pageIndex the page to return * @throws JSONException */ public JSONArray query(QuerySpec querySpec, int pageIndex) throws JSONException { + return queryWithArgs(querySpec, pageIndex, null); + } + + /** + * Run a query given by its query spec with optional "where args" (i.e. bind args) + * Provided bind args will be substituted to the ? found in the query + * NB: Bind args are only supported for smart queries + * Returns results from selected page + * + * @param querySpec the query to run + * @param pageIndex the page to return + * @param whereArgs the bind args (optional - only supported for smart queries) + * + * @throws JSONException + */ + public JSONArray queryWithArgs(QuerySpec querySpec, int pageIndex, String... whereArgs) throws JSONException { + if (whereArgs != null && querySpec.queryType != QueryType.smart) { + throw new SmartStoreException("whereArgs can only be provided for smart queries"); + } + JSONArray resultAsArray = new JSONArray(); - runQuery(resultAsArray, null, querySpec, pageIndex); + runQuery(resultAsArray, null, querySpec, pageIndex, whereArgs); return resultAsArray; } /** - * Run a query given by its query Spec, only returned results from selected page - * without deserializing any JSON + * Run a query given by its query Spec + * Returns results from selected page without deserializing any JSON * * @param resultBuilder string builder to which results are appended * @param querySpec @@ -818,7 +843,7 @@ public JSONArray query(QuerySpec querySpec, int pageIndex) throws JSONException */ public void queryAsString(StringBuilder resultBuilder, QuerySpec querySpec, int pageIndex) { try { - runQuery(null, resultBuilder, querySpec, pageIndex); + runQuery(null, resultBuilder, querySpec, pageIndex, null); } catch (JSONException e) { // shouldn't happen since we call runQuery with a string builder @@ -826,7 +851,7 @@ public void queryAsString(StringBuilder resultBuilder, QuerySpec querySpec, int } } - private void runQuery(JSONArray resultAsArray, StringBuilder resultAsStringBuilder, QuerySpec querySpec, int pageIndex) throws JSONException { + private void runQuery(JSONArray resultAsArray, StringBuilder resultAsStringBuilder, QuerySpec querySpec, int pageIndex, String... whereArgs) throws JSONException { boolean computeResultAsString = resultAsStringBuilder != null; final SQLiteDatabase db = getDatabase(); @@ -840,7 +865,7 @@ private void runQuery(JSONArray resultAsArray, StringBuilder resultAsStringBuild String limit = offsetRows + "," + numberRows; Cursor cursor = null; try { - cursor = DBHelper.getInstance(db).limitRawQuery(db, sql, limit, querySpec.getArgs()); + cursor = DBHelper.getInstance(db).limitRawQuery(db, sql, limit, querySpec.getArgs() != null ? querySpec.getArgs() : whereArgs); if (computeResultAsString) { resultAsStringBuilder.append("["); @@ -1743,7 +1768,10 @@ public static void updateTableNameAndAddColumns(SQLiteDatabase db, String oldNam * @param soupName Name of the soup to determine external storage enablement. * * @return True if soup uses external storage; false otherwise. + * + * Deprecated: we are removing external storage and soup spec in 11.0 */ + @Deprecated public boolean usesExternalStorage(String soupName) { final SQLiteDatabase db = getDatabase(); synchronized (db) { diff --git a/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/store/SoupSpec.java b/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/store/SoupSpec.java index bf2fbd0bdb..f9e40f2ce4 100644 --- a/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/store/SoupSpec.java +++ b/libs/SmartStore/src/com/salesforce/androidsdk/smartstore/store/SoupSpec.java @@ -36,7 +36,10 @@ /** * Object representation for soup specifications, such as soup name and features. + * + * Deprecated: we are removing external storage and soup spec in 11.0 */ +@Deprecated public class SoupSpec { /** Soup features **/ public static final String FEATURE_EXTERNAL_STORAGE = "externalStorage"; diff --git a/libs/test/MobileSyncTest/src/com/salesforce/androidsdk/mobilesync/util/SOQLMutatorTest.java b/libs/test/MobileSyncTest/src/com/salesforce/androidsdk/mobilesync/util/SOQLMutatorTest.java index 89a94d92a6..e5847227d9 100644 --- a/libs/test/MobileSyncTest/src/com/salesforce/androidsdk/mobilesync/util/SOQLMutatorTest.java +++ b/libs/test/MobileSyncTest/src/com/salesforce/androidsdk/mobilesync/util/SOQLMutatorTest.java @@ -231,14 +231,7 @@ public void testTokenizeWithParenthesesInQuotes() { private void tryTokenize(String soql, String expectedTokensJoined) { List tokens = new SOQLMutator.SOQLTokenizer(soql).tokenize(); - // TODO: Change this to "String actualTokensJoined = String.join("#", tokens);" in 8.0 when the project is upgraded to Java 8. - int lastIndex = tokens.size() - 1; - StringBuilder actualTokensJoined = new StringBuilder(); - for (int i = 0; i < lastIndex; i++) { - actualTokensJoined.append(tokens.get(i).concat("#")); - } - actualTokensJoined.append(tokens.get(lastIndex)); - - Assert.assertEquals(expectedTokensJoined, actualTokensJoined.toString()); + String actualTokensJoined = String.join("#", tokens); + Assert.assertEquals(expectedTokensJoined, actualTokensJoined); } } diff --git a/libs/test/SalesforceAnalyticsTest/src/com/salesforce/androidsdk/analytics/store/EventStoreManagerTest.java b/libs/test/SalesforceAnalyticsTest/src/com/salesforce/androidsdk/analytics/store/EventStoreManagerTest.java index 1bc59c55cd..d3ecb56459 100644 --- a/libs/test/SalesforceAnalyticsTest/src/com/salesforce/androidsdk/analytics/store/EventStoreManagerTest.java +++ b/libs/test/SalesforceAnalyticsTest/src/com/salesforce/androidsdk/analytics/store/EventStoreManagerTest.java @@ -45,7 +45,10 @@ import org.junit.runner.RunWith; import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.UUID; /** @@ -159,6 +162,30 @@ public void testFetchAllEvents() throws Exception { Assert.assertTrue("Stored event should be the same as generated event", events.contains(event2)); } + /** + * Test for iterating over all stored events. + * + * @throws Exception + */ + @Test + public void testIterateAllEvents() throws Exception { + final Set events = new HashSet<>(); + events.add(createTestEvent()); + events.add(createTestEvent()); + for (InstrumentationEvent event : events) { + storeManager.storeEvent(event); + } + + final Iterable eventsIterable = storeManager.iterateAllEvents(); + Assert.assertNotNull("Iterable of events stored should not be null", eventsIterable); + final Iterator iterator = eventsIterable.iterator(); + Assert.assertTrue("Iterator should return the first event", iterator.hasNext()); + Assert.assertTrue("Stored event should be the same as generated event", events.contains(iterator.next())); + Assert.assertTrue("Iterator should return the second event", iterator.hasNext()); + Assert.assertTrue("Stored event should be the same as generated event", events.contains(iterator.next())); + Assert.assertFalse("Iterator should only return two events", iterator.hasNext()); + } + /** * Test for deleting one event by specifying event ID. * diff --git a/libs/test/SalesforceHybridTest/res/xml/config.xml b/libs/test/SalesforceHybridTest/res/xml/config.xml index 7705966ca7..6a7dd11cb8 100644 --- a/libs/test/SalesforceHybridTest/res/xml/config.xml +++ b/libs/test/SalesforceHybridTest/res/xml/config.xml @@ -1,7 +1,7 @@ + version = "10.0.0"> diff --git a/libs/test/SalesforceReactTest/assets/drawable-mdpi/node_modules_reactnative_libraries_logbox_ui_logboximages_alerttriangle.png b/libs/test/SalesforceReactTest/assets/drawable-mdpi/node_modules_reactnative_libraries_logbox_ui_logboximages_alerttriangle.png index 7af688c7e3..48e05d7be9 100644 Binary files a/libs/test/SalesforceReactTest/assets/drawable-mdpi/node_modules_reactnative_libraries_logbox_ui_logboximages_alerttriangle.png and b/libs/test/SalesforceReactTest/assets/drawable-mdpi/node_modules_reactnative_libraries_logbox_ui_logboximages_alerttriangle.png differ diff --git a/libs/test/SalesforceReactTest/assets/drawable-mdpi/node_modules_reactnative_libraries_logbox_ui_logboximages_chevronleft.png b/libs/test/SalesforceReactTest/assets/drawable-mdpi/node_modules_reactnative_libraries_logbox_ui_logboximages_chevronleft.png index cae430f216..6b07c51b4a 100644 Binary files a/libs/test/SalesforceReactTest/assets/drawable-mdpi/node_modules_reactnative_libraries_logbox_ui_logboximages_chevronleft.png and b/libs/test/SalesforceReactTest/assets/drawable-mdpi/node_modules_reactnative_libraries_logbox_ui_logboximages_chevronleft.png differ diff --git a/libs/test/SalesforceReactTest/assets/drawable-mdpi/node_modules_reactnative_libraries_logbox_ui_logboximages_chevronright.png b/libs/test/SalesforceReactTest/assets/drawable-mdpi/node_modules_reactnative_libraries_logbox_ui_logboximages_chevronright.png index a70bff02a5..251bb7283d 100644 Binary files a/libs/test/SalesforceReactTest/assets/drawable-mdpi/node_modules_reactnative_libraries_logbox_ui_logboximages_chevronright.png and b/libs/test/SalesforceReactTest/assets/drawable-mdpi/node_modules_reactnative_libraries_logbox_ui_logboximages_chevronright.png differ diff --git a/libs/test/SalesforceReactTest/assets/drawable-mdpi/node_modules_reactnative_libraries_logbox_ui_logboximages_close.png b/libs/test/SalesforceReactTest/assets/drawable-mdpi/node_modules_reactnative_libraries_logbox_ui_logboximages_close.png index c55016073e..43471b9cf2 100644 Binary files a/libs/test/SalesforceReactTest/assets/drawable-mdpi/node_modules_reactnative_libraries_logbox_ui_logboximages_close.png and b/libs/test/SalesforceReactTest/assets/drawable-mdpi/node_modules_reactnative_libraries_logbox_ui_logboximages_close.png differ diff --git a/libs/test/SalesforceReactTest/assets/drawable-mdpi/node_modules_reactnative_libraries_logbox_ui_logboximages_loader.png b/libs/test/SalesforceReactTest/assets/drawable-mdpi/node_modules_reactnative_libraries_logbox_ui_logboximages_loader.png index 2d9c76b2b8..7647edab86 100644 Binary files a/libs/test/SalesforceReactTest/assets/drawable-mdpi/node_modules_reactnative_libraries_logbox_ui_logboximages_loader.png and b/libs/test/SalesforceReactTest/assets/drawable-mdpi/node_modules_reactnative_libraries_logbox_ui_logboximages_loader.png differ diff --git a/libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/analytics/SalesforceAnalyticsManagerTest.java b/libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/analytics/SalesforceAnalyticsManagerTest.java new file mode 100644 index 0000000000..ad262d05c9 --- /dev/null +++ b/libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/analytics/SalesforceAnalyticsManagerTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2016-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.androidsdk.analytics; + +import static org.junit.Assert.assertEquals; + +import com.salesforce.androidsdk.analytics.model.InstrumentationEvent; +import com.salesforce.androidsdk.analytics.transform.AILTNTransform; +import com.salesforce.androidsdk.analytics.transform.Transform; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +public class SalesforceAnalyticsManagerTest { + SalesforceAnalyticsManager manager; + + @Before + public void setup() { + manager = SalesforceAnalyticsManager.getUnauthenticatedInstance(); + manager.removeRemotePublisher(AILTNTransform.class); + manager.addRemotePublisher(TestTransform.class, TestPublisher.class); + SalesforceAnalyticsManager.setEventPublishBatchSize(2); + } + + @After + public void teardown() { + TestPublisher.reset(); + manager.removeRemotePublisher(TestTransform.class); + manager.addRemotePublisher(AILTNTransform.class, AILTNPublisher.class); + } + + @Test + public void testBatchPublish() { + storeEvents(3); + // Sanity Check + assertEquals(3, manager.getEventStoreManager().getNumStoredEvents()); + + manager.publishAllEvents(); + + assertEquals(0, manager.getEventStoreManager().getNumStoredEvents()); + assertEquals(2, TestPublisher.publishedEvents.size()); + assertEquals(2, TestPublisher.publishedEvents.get(0).length()); + assertEquals(1, TestPublisher.publishedEvents.get(1).length()); + } + + @Test + public void testFailedBatchPublish() { + storeEvents(3); + // Sanity Check + assertEquals(3, manager.getEventStoreManager().getNumStoredEvents()); + + TestPublisher.publishSuccessResult = false; + manager.publishAllEvents(); + + assertEquals(3, manager.getEventStoreManager().getNumStoredEvents()); + assertEquals(1, TestPublisher.publishedEvents.size()); + assertEquals(2, TestPublisher.publishedEvents.get(0).length()); + } + + private void storeEvents(int count) { + for (int i = 0; i < count; i++) { + try { + manager.getEventStoreManager().storeEvent(new InstrumentationEvent(new JSONObject("{\"eventId\": \"" + i + "\"}"))); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + } + + public static class TestTransform implements Transform { + @Override + public JSONObject transform(InstrumentationEvent event) { + try { + JSONObject result = new JSONObject(); + result.put("id", event.getEventId()); + return result; + } catch (JSONException e) { + return null; + } + } + } + + public static class TestPublisher implements AnalyticsPublisher { + protected static List publishedEvents = new ArrayList<>(); + protected static boolean publishSuccessResult = true; + @Override + public boolean publish(JSONArray events) { + publishedEvents.add(events); + return publishSuccessResult; + } + + public static void reset() { + publishedEvents = new ArrayList<>(); + publishSuccessResult = true; + } + } +} diff --git a/libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/util/AuthConfigUtilTest.java b/libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/util/AuthConfigUtilTest.java index ec7be8e33c..01792ca2fb 100644 --- a/libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/util/AuthConfigUtilTest.java +++ b/libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/util/AuthConfigUtilTest.java @@ -35,7 +35,7 @@ import com.salesforce.androidsdk.app.SalesforceSDKManager; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -import java9.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletableFuture; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/libs/test/SmartStoreTest/src/com/salesforce/androidsdk/smartstore/store/KeyValueEncryptedFileStoreTest.java b/libs/test/SmartStoreTest/src/com/salesforce/androidsdk/smartstore/store/KeyValueEncryptedFileStoreTest.java index e0b1b1ea01..56ae488428 100644 --- a/libs/test/SmartStoreTest/src/com/salesforce/androidsdk/smartstore/store/KeyValueEncryptedFileStoreTest.java +++ b/libs/test/SmartStoreTest/src/com/salesforce/androidsdk/smartstore/store/KeyValueEncryptedFileStoreTest.java @@ -26,6 +26,8 @@ */ package com.salesforce.androidsdk.smartstore.store; +import static com.salesforce.androidsdk.smartstore.tests.R.drawable.sf__icon; + import android.content.Context; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; @@ -735,9 +737,33 @@ public void testKeySetCountDeleteAllWithBadKeyFile() throws IOException { } + /** + * Read some binary file from assets, save it to the key value store then get it back + * Make sure it's identical to the original file + */ + @Test + public void testBinaryStorage() throws IOException { + // Saving resource icon to key value store + keyValueStore.saveStream("icon", getResourceIconStream()); + + // Retrieving icon back from key value store + byte[] savedIconBytes = Encryptor.getByteArrayStreamFromStream(keyValueStore.getStream("icon")).toByteArray(); + + // Comparing bytes + byte[] resourceIconBytes = Encryptor.getByteArrayStreamFromStream(getResourceIconStream()).toByteArray(); + Assert.assertEquals(resourceIconBytes.length, savedIconBytes.length); + for (int i=0; i { + for (int seq = 0; seq < countReadsPerThread; seq++) { + readAll("reader-" + padNumber(j), "seq-" + padNumber(seq)); + } + latch.countDown(); + }); + } + } + + /** + * Spawn countThreads of writer threads that will each do countWritesPerThread upserts + * @param countWriterThreads + * @param countWritesPerThread + * @param latch + */ + private void spawnWriters(int countWriterThreads, int countWritesPerThread, CountDownLatch latch) { + for (int i = 0; i < countWriterThreads; i++) { + int j = i; + pool.execute(() -> { + store.beginTransaction(); + for (int seq = 0; seq < countWritesPerThread; seq++) { + writeOne("writer-" + padNumber(j), "seq-" + padNumber(seq)); + } + store.setTransactionSuccessful(); + store.endTransaction(); + latch.countDown(); + }); + } + } + + private String padNumber(int n) { + return String.format("%05d", n); + } + + +} diff --git a/libs/test/SmartStoreTest/src/com/salesforce/androidsdk/smartstore/store/SmartStoreTest.java b/libs/test/SmartStoreTest/src/com/salesforce/androidsdk/smartstore/store/SmartStoreTest.java index 0207cfca70..ee28b74e9a 100644 --- a/libs/test/SmartStoreTest/src/com/salesforce/androidsdk/smartstore/store/SmartStoreTest.java +++ b/libs/test/SmartStoreTest/src/com/salesforce/androidsdk/smartstore/store/SmartStoreTest.java @@ -112,7 +112,7 @@ public void testRuntimeSettings() { */ @Test public void testSQLCipherVersion() { - Assert.assertEquals("Wrong sqlcipher version", "4.4.3 community", store.getSQLCipherVersion()); + Assert.assertEquals("Wrong sqlcipher version", "4.5.0 community", store.getSQLCipherVersion()); } /** @@ -1335,6 +1335,12 @@ public void testGetDatabaseSize() throws JSONException { JSONObject soupElt = new JSONObject("{'key':'abcd" + i + "', 'value':'va" + i + "', 'otherValue':'ova" + i + "'}"); store.create(TEST_SOUP, soupElt); } + + // With WAL enabled we must force a WAL checkpoint if we want the actual DB file to reflect the new content: + store.getDatabase() + .query("PRAGMA wal_checkpoint(FULL);") + .moveToNext(); + Assert.assertTrue("Database should be larger now", store.getDatabaseSize() > initialSize); } diff --git a/libs/test/SmartStoreTest/src/com/salesforce/androidsdk/smartstore/store/SmartStoreTestCase.java b/libs/test/SmartStoreTest/src/com/salesforce/androidsdk/smartstore/store/SmartStoreTestCase.java index af66f9086d..9d10e4adeb 100644 --- a/libs/test/SmartStoreTest/src/com/salesforce/androidsdk/smartstore/store/SmartStoreTestCase.java +++ b/libs/test/SmartStoreTest/src/com/salesforce/androidsdk/smartstore/store/SmartStoreTestCase.java @@ -180,9 +180,9 @@ protected void checkExplainQueryPlan(String soupName, int index, boolean coverin JSONObject explainQueryPlan = store.getLastExplainQueryPlan(); String soupTableName = getSoupTableName(soupName); String indexName = soupTableName + "_" + index + "_idx"; - String expectedDetailPrefix = String.format("%s TABLE %s USING %sINDEX %s", dbOperation, soupTableName, covering ? "COVERING " : "", indexName); + String expectedDetailPrefix = String.format("%s %s USING %sINDEX %s", dbOperation, soupTableName, covering ? "COVERING " : "", indexName); String detail = explainQueryPlan.getJSONArray(DBHelper.EXPLAIN_ROWS).getJSONObject(0).getString("detail"); - Assert.assertTrue("Wrong query plan:" + detail, detail.startsWith(expectedDetailPrefix)); + Assert.assertTrue("Query plan: " + detail + " - not starting with " + expectedDetailPrefix, detail.startsWith(expectedDetailPrefix)); } protected void checkFileSystem(String soupName, long[] expectedIds, boolean shouldExist) { diff --git a/native/NativeSampleApps/AppConfigurator/build.gradle b/native/NativeSampleApps/AppConfigurator/build.gradle index 6b621dc397..bc9cef3808 100644 --- a/native/NativeSampleApps/AppConfigurator/build.gradle +++ b/native/NativeSampleApps/AppConfigurator/build.gradle @@ -5,11 +5,11 @@ dependencies { } android { - compileSdkVersion 30 + compileSdkVersion 32 defaultConfig { - targetSdkVersion 30 - minSdkVersion 23 + targetSdkVersion 32 + minSdkVersion 24 } sourceSets { @@ -24,14 +24,13 @@ android { } } packagingOptions { - exclude 'META-INF/LICENSE' - exclude 'META-INF/LICENSE.txt' - exclude 'META-INF/DEPENDENCIES' - exclude 'META-INF/NOTICE' + resources { + excludes += ['META-INF/LICENSE', 'META-INF/LICENSE.txt', 'META-INF/DEPENDENCIES', 'META-INF/NOTICE'] + } + } + lint { + abortOnError false + xmlReport true } - lintOptions { - xmlReport true - abortOnError false - } } diff --git a/native/NativeSampleApps/ConfiguredApp/build.gradle b/native/NativeSampleApps/ConfiguredApp/build.gradle index 6b621dc397..bc9cef3808 100644 --- a/native/NativeSampleApps/ConfiguredApp/build.gradle +++ b/native/NativeSampleApps/ConfiguredApp/build.gradle @@ -5,11 +5,11 @@ dependencies { } android { - compileSdkVersion 30 + compileSdkVersion 32 defaultConfig { - targetSdkVersion 30 - minSdkVersion 23 + targetSdkVersion 32 + minSdkVersion 24 } sourceSets { @@ -24,14 +24,13 @@ android { } } packagingOptions { - exclude 'META-INF/LICENSE' - exclude 'META-INF/LICENSE.txt' - exclude 'META-INF/DEPENDENCIES' - exclude 'META-INF/NOTICE' + resources { + excludes += ['META-INF/LICENSE', 'META-INF/LICENSE.txt', 'META-INF/DEPENDENCIES', 'META-INF/NOTICE'] + } + } + lint { + abortOnError false + xmlReport true } - lintOptions { - xmlReport true - abortOnError false - } } diff --git a/native/NativeSampleApps/MobileSyncExplorer/build.gradle b/native/NativeSampleApps/MobileSyncExplorer/build.gradle index 36ccef0a19..0e9ebf410a 100644 --- a/native/NativeSampleApps/MobileSyncExplorer/build.gradle +++ b/native/NativeSampleApps/MobileSyncExplorer/build.gradle @@ -5,11 +5,11 @@ dependencies { } android { - compileSdkVersion 30 + compileSdkVersion 32 defaultConfig { - targetSdkVersion 30 - minSdkVersion 23 + targetSdkVersion 32 + minSdkVersion 24 } sourceSets { @@ -25,15 +25,14 @@ android { } } - packagingOptions { - exclude 'META-INF/LICENSE' - exclude 'META-INF/LICENSE.txt' - exclude 'META-INF/DEPENDENCIES' - exclude 'META-INF/NOTICE' - } + packagingOptions { + resources { + excludes += ['META-INF/LICENSE', 'META-INF/LICENSE.txt', 'META-INF/DEPENDENCIES', 'META-INF/NOTICE'] + } + } + lint { + abortOnError false + xmlReport true + } - lintOptions { - xmlReport true - abortOnError false - } } diff --git a/native/NativeSampleApps/RestExplorer/build.gradle b/native/NativeSampleApps/RestExplorer/build.gradle index c9b5b0e44f..1ebf619f5a 100644 --- a/native/NativeSampleApps/RestExplorer/build.gradle +++ b/native/NativeSampleApps/RestExplorer/build.gradle @@ -15,11 +15,11 @@ dependencies { } android { - compileSdkVersion 30 + compileSdkVersion 32 defaultConfig { - targetSdkVersion 30 - minSdkVersion 23 + targetSdkVersion 32 + minSdkVersion 24 } buildTypes { @@ -47,20 +47,19 @@ android { res.srcDirs = ['../test/RestExplorerTest/res'] } } - defaultConfig { + packagingOptions { + resources { + excludes += ['META-INF/LICENSE', 'META-INF/LICENSE.txt', 'META-INF/DEPENDENCIES', 'META-INF/NOTICE'] + pickFirsts += ['protobuf.meta'] + } + } + defaultConfig { testApplicationId "com.salesforce.samples.restexplorer.tests" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } - packagingOptions { - exclude 'META-INF/LICENSE' - exclude 'META-INF/LICENSE.txt' - exclude 'META-INF/DEPENDENCIES' - exclude 'META-INF/NOTICE' - pickFirst 'protobuf.meta' - } + lint { + abortOnError false + xmlReport true + } - lintOptions { - xmlReport true - abortOnError false - } } diff --git a/native/NativeSampleApps/RestExplorer/res/layout/explorer_header.xml b/native/NativeSampleApps/RestExplorer/res/layout/explorer_header.xml index 549a221c83..01f8de9483 100644 --- a/native/NativeSampleApps/RestExplorer/res/layout/explorer_header.xml +++ b/native/NativeSampleApps/RestExplorer/res/layout/explorer_header.xml @@ -6,26 +6,25 @@ + android:layout_marginStart="3dp" /> -