diff --git a/src/android/libs/SalesforceHybrid/res/xml/config.xml b/src/android/libs/SalesforceHybrid/res/xml/config.xml
index 00263ce8..ce875f89 100644
--- a/src/android/libs/SalesforceHybrid/res/xml/config.xml
+++ b/src/android/libs/SalesforceHybrid/res/xml/config.xml
@@ -3,6 +3,8 @@
id = "com.salesforce.androidsdk"
version = "9.2.0">
+
+
diff --git a/src/android/libs/SalesforceSDK/AndroidManifest.xml b/src/android/libs/SalesforceSDK/AndroidManifest.xml
index 76286b29..07e6811e 100644
--- a/src/android/libs/SalesforceSDK/AndroidManifest.xml
+++ b/src/android/libs/SalesforceSDK/AndroidManifest.xml
@@ -31,31 +31,35 @@
android:theme="@style/SalesforceSDK"
android:launchMode="singleInstance"
android:screenOrientation="portrait"
- android:windowSoftInputMode="adjustResize" />
+ android:windowSoftInputMode="adjustResize"
+ android:exported="true" />
-
-
+
+ android:theme="@style/SalesforceSDK.ScreenLock" />
+ android:theme="@style/SalesforceSDK"
+ android:exported="false" />
+ android:screenOrientation="portrait"
+ android:exported="false" />
+ android:screenOrientation="portrait"
+ android:exported="false" />
-
+
diff --git a/src/android/libs/SalesforceSDK/build.gradle b/src/android/libs/SalesforceSDK/build.gradle
index 11aa9458..c04c0b4d 100644
--- a/src/android/libs/SalesforceSDK/build.gradle
+++ b/src/android/libs/SalesforceSDK/build.gradle
@@ -11,7 +11,7 @@ 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.3.2'
+ api 'androidx.core:core:1.6.0'
/*
* Don't upgrade browser library version until minApiVersion >= 24.
@@ -19,6 +19,8 @@ dependencies {
*/
api 'androidx.browser:browser:1.0.0'
implementation 'com.google.android.material:material:1.4.0'
+ implementation 'androidx.biometric:biometric:1.1.0'
+ 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'
diff --git a/src/android/libs/SalesforceSDK/res/drawable-hdpi/sf__face_unlock_icon.png b/src/android/libs/SalesforceSDK/res/drawable-hdpi/sf__face_unlock_icon.png
deleted file mode 100644
index d9181ba8..00000000
Binary files a/src/android/libs/SalesforceSDK/res/drawable-hdpi/sf__face_unlock_icon.png and /dev/null differ
diff --git a/src/android/libs/SalesforceSDK/res/drawable-hdpi/sf__face_unlock_icon_dark.png b/src/android/libs/SalesforceSDK/res/drawable-hdpi/sf__face_unlock_icon_dark.png
deleted file mode 100644
index 06d9c01a..00000000
Binary files a/src/android/libs/SalesforceSDK/res/drawable-hdpi/sf__face_unlock_icon_dark.png and /dev/null differ
diff --git a/src/android/libs/SalesforceSDK/res/drawable-mdpi/sf__face_unlock_icon.png b/src/android/libs/SalesforceSDK/res/drawable-mdpi/sf__face_unlock_icon.png
deleted file mode 100644
index e4e5be41..00000000
Binary files a/src/android/libs/SalesforceSDK/res/drawable-mdpi/sf__face_unlock_icon.png and /dev/null differ
diff --git a/src/android/libs/SalesforceSDK/res/drawable-mdpi/sf__face_unlock_icon_dark.png b/src/android/libs/SalesforceSDK/res/drawable-mdpi/sf__face_unlock_icon_dark.png
deleted file mode 100644
index f1ff3812..00000000
Binary files a/src/android/libs/SalesforceSDK/res/drawable-mdpi/sf__face_unlock_icon_dark.png and /dev/null differ
diff --git a/src/android/libs/SalesforceSDK/res/drawable-xhdpi/sf__face_unlock_icon.png b/src/android/libs/SalesforceSDK/res/drawable-xhdpi/sf__face_unlock_icon.png
deleted file mode 100644
index 894ee53b..00000000
Binary files a/src/android/libs/SalesforceSDK/res/drawable-xhdpi/sf__face_unlock_icon.png and /dev/null differ
diff --git a/src/android/libs/SalesforceSDK/res/drawable-xhdpi/sf__face_unlock_icon_dark.png b/src/android/libs/SalesforceSDK/res/drawable-xhdpi/sf__face_unlock_icon_dark.png
deleted file mode 100644
index f98b0b28..00000000
Binary files a/src/android/libs/SalesforceSDK/res/drawable-xhdpi/sf__face_unlock_icon_dark.png and /dev/null differ
diff --git a/src/android/libs/SalesforceSDK/res/drawable-xxhdpi/sf__face_unlock_icon.png b/src/android/libs/SalesforceSDK/res/drawable-xxhdpi/sf__face_unlock_icon.png
deleted file mode 100644
index b339f78c..00000000
Binary files a/src/android/libs/SalesforceSDK/res/drawable-xxhdpi/sf__face_unlock_icon.png and /dev/null differ
diff --git a/src/android/libs/SalesforceSDK/res/drawable-xxhdpi/sf__face_unlock_icon_dark.png b/src/android/libs/SalesforceSDK/res/drawable-xxhdpi/sf__face_unlock_icon_dark.png
deleted file mode 100644
index 4affc7f5..00000000
Binary files a/src/android/libs/SalesforceSDK/res/drawable-xxhdpi/sf__face_unlock_icon_dark.png and /dev/null differ
diff --git a/src/android/libs/SalesforceSDK/res/drawable-xxxhdpi/sf__face_unlock_icon.png b/src/android/libs/SalesforceSDK/res/drawable-xxxhdpi/sf__face_unlock_icon.png
deleted file mode 100644
index 83ee1a8a..00000000
Binary files a/src/android/libs/SalesforceSDK/res/drawable-xxxhdpi/sf__face_unlock_icon.png and /dev/null differ
diff --git a/src/android/libs/SalesforceSDK/res/drawable-xxxhdpi/sf__face_unlock_icon_dark.png b/src/android/libs/SalesforceSDK/res/drawable-xxxhdpi/sf__face_unlock_icon_dark.png
deleted file mode 100644
index 4823aecf..00000000
Binary files a/src/android/libs/SalesforceSDK/res/drawable-xxxhdpi/sf__face_unlock_icon_dark.png and /dev/null differ
diff --git a/src/android/libs/SalesforceSDK/res/drawable/sf__empty_shape.xml b/src/android/libs/SalesforceSDK/res/drawable/sf__empty_shape.xml
deleted file mode 100644
index 1f0fcf78..00000000
--- a/src/android/libs/SalesforceSDK/res/drawable/sf__empty_shape.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
diff --git a/src/android/libs/SalesforceSDK/res/drawable/sf__face_unlock_icon_layout.xml b/src/android/libs/SalesforceSDK/res/drawable/sf__face_unlock_icon_layout.xml
deleted file mode 100644
index f2081dcf..00000000
--- a/src/android/libs/SalesforceSDK/res/drawable/sf__face_unlock_icon_layout.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
- -
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/android/libs/SalesforceSDK/res/drawable/sf__fingerprint_icon.xml b/src/android/libs/SalesforceSDK/res/drawable/sf__fingerprint_icon.xml
deleted file mode 100644
index b6035c28..00000000
--- a/src/android/libs/SalesforceSDK/res/drawable/sf__fingerprint_icon.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/src/android/libs/SalesforceSDK/res/drawable/sf__fingerprint_icon_dark.xml b/src/android/libs/SalesforceSDK/res/drawable/sf__fingerprint_icon_dark.xml
deleted file mode 100644
index f3a259a2..00000000
--- a/src/android/libs/SalesforceSDK/res/drawable/sf__fingerprint_icon_dark.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/src/android/libs/SalesforceSDK/res/drawable/sf__fingerprint_icon_layout.xml b/src/android/libs/SalesforceSDK/res/drawable/sf__fingerprint_icon_layout.xml
deleted file mode 100644
index 04cdc4fa..00000000
--- a/src/android/libs/SalesforceSDK/res/drawable/sf__fingerprint_icon_layout.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
- -
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/android/libs/SalesforceSDK/res/drawable/sf__layout_background_color_button.xml b/src/android/libs/SalesforceSDK/res/drawable/sf__layout_background_color_button.xml
deleted file mode 100644
index 614161d1..00000000
--- a/src/android/libs/SalesforceSDK/res/drawable/sf__layout_background_color_button.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/android/libs/SalesforceSDK/res/drawable/sf__passcode_box_style.xml b/src/android/libs/SalesforceSDK/res/drawable/sf__passcode_box_style.xml
deleted file mode 100644
index befb4f74..00000000
--- a/src/android/libs/SalesforceSDK/res/drawable/sf__passcode_box_style.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
- -
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/android/libs/SalesforceSDK/res/drawable/sf__passcode_title_box.xml b/src/android/libs/SalesforceSDK/res/drawable/sf__passcode_title_box.xml
deleted file mode 100644
index fbebb938..00000000
--- a/src/android/libs/SalesforceSDK/res/drawable/sf__passcode_title_box.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
- -
-
-
-
-
-
-
diff --git a/src/android/libs/SalesforceSDK/res/drawable/sf__passcode_primary_color_button.xml b/src/android/libs/SalesforceSDK/res/drawable/sf__primary_color_button.xml
similarity index 100%
rename from src/android/libs/SalesforceSDK/res/drawable/sf__passcode_primary_color_button.xml
rename to src/android/libs/SalesforceSDK/res/drawable/sf__primary_color_button.xml
diff --git a/src/android/libs/SalesforceSDK/res/drawable/sf__passcode_secondary_color_button.xml b/src/android/libs/SalesforceSDK/res/drawable/sf__secondary_color_button.xml
similarity index 100%
rename from src/android/libs/SalesforceSDK/res/drawable/sf__passcode_secondary_color_button.xml
rename to src/android/libs/SalesforceSDK/res/drawable/sf__secondary_color_button.xml
diff --git a/src/android/libs/SalesforceSDK/res/drawable/sf__passcode_secondary_color_button_dark.xml b/src/android/libs/SalesforceSDK/res/drawable/sf__secondary_color_button_dark.xml
similarity index 100%
rename from src/android/libs/SalesforceSDK/res/drawable/sf__passcode_secondary_color_button_dark.xml
rename to src/android/libs/SalesforceSDK/res/drawable/sf__secondary_color_button_dark.xml
diff --git a/src/android/libs/SalesforceSDK/res/layout/sf__custom_server_url.xml b/src/android/libs/SalesforceSDK/res/layout/sf__custom_server_url.xml
index fbdf1ce4..987a40af 100644
--- a/src/android/libs/SalesforceSDK/res/layout/sf__custom_server_url.xml
+++ b/src/android/libs/SalesforceSDK/res/layout/sf__custom_server_url.xml
@@ -60,7 +60,7 @@
android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp"
android:layout_weight="1"
- android:background="@drawable/sf__passcode_secondary_color_button"
+ android:background="@drawable/sf__secondary_color_button"
android:text="@string/sf__server_url_default_cancel"
android:textAllCaps="false"
android:textColor="?attr/sfNegativeButtonTextColor"
@@ -78,7 +78,7 @@
android:layout_marginStart="10dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="10dp"
- android:background="@drawable/sf__passcode_primary_color_button"
+ android:background="@drawable/sf__primary_color_button"
android:textColor="?attr/sfColorSecondary"
android:textAllCaps="false"
android:textSize="14sp"
diff --git a/src/android/libs/SalesforceSDK/res/layout/sf__fingerprint_dialog.xml b/src/android/libs/SalesforceSDK/res/layout/sf__fingerprint_dialog.xml
deleted file mode 100644
index a73813e8..00000000
--- a/src/android/libs/SalesforceSDK/res/layout/sf__fingerprint_dialog.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/android/libs/SalesforceSDK/res/layout/sf__login.xml b/src/android/libs/SalesforceSDK/res/layout/sf__login.xml
index 5cd64c0a..716a8c6e 100644
--- a/src/android/libs/SalesforceSDK/res/layout/sf__login.xml
+++ b/src/android/libs/SalesforceSDK/res/layout/sf__login.xml
@@ -23,7 +23,7 @@
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="20dp"
- android:background="@drawable/sf__passcode_primary_color_button"
+ android:background="@drawable/sf__primary_color_button"
android:textColor="?attr/sfColorSecondary"
android:textAllCaps="false"
android:textSize="14sp"
diff --git a/src/android/libs/SalesforceSDK/res/layout/sf__passcode.xml b/src/android/libs/SalesforceSDK/res/layout/sf__passcode.xml
deleted file mode 100644
index bb4cfd38..00000000
--- a/src/android/libs/SalesforceSDK/res/layout/sf__passcode.xml
+++ /dev/null
@@ -1,202 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/android/libs/SalesforceSDK/res/layout/sf__screen_lock.xml b/src/android/libs/SalesforceSDK/res/layout/sf__screen_lock.xml
new file mode 100644
index 00000000..9e81db49
--- /dev/null
+++ b/src/android/libs/SalesforceSDK/res/layout/sf__screen_lock.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/android/libs/SalesforceSDK/res/layout/sf__server_picker.xml b/src/android/libs/SalesforceSDK/res/layout/sf__server_picker.xml
index ce203e8c..930e62dc 100644
--- a/src/android/libs/SalesforceSDK/res/layout/sf__server_picker.xml
+++ b/src/android/libs/SalesforceSDK/res/layout/sf__server_picker.xml
@@ -35,7 +35,7 @@
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="20dp"
- android:background="@drawable/sf__passcode_secondary_color_button"
+ android:background="@drawable/sf__secondary_color_button"
android:textColor="?attr/sfNegativeButtonTextColor"
android:textAllCaps="false"
android:textSize="14sp"
diff --git a/src/android/libs/SalesforceSDK/res/values/sf__attr.xml b/src/android/libs/SalesforceSDK/res/values/sf__attr.xml
index 0f51d1f8..5374c2e1 100644
--- a/src/android/libs/SalesforceSDK/res/values/sf__attr.xml
+++ b/src/android/libs/SalesforceSDK/res/values/sf__attr.xml
@@ -1,12 +1,10 @@
-
+
-
-
diff --git a/src/android/libs/SalesforceSDK/res/values/sf__dimens.xml b/src/android/libs/SalesforceSDK/res/values/sf__dimens.xml
index a700b792..d7ae2d02 100644
--- a/src/android/libs/SalesforceSDK/res/values/sf__dimens.xml
+++ b/src/android/libs/SalesforceSDK/res/values/sf__dimens.xml
@@ -9,20 +9,6 @@
3dp
3dp
-
- 0dp
- 100dp
- 300dp
- 50dp
- 10dp
- 10dp
- 160dp
- 21dp
- 21dp
- 10dp
- 20dp
- 20dp
-
7dp
0dp
@@ -52,4 +38,9 @@
22dp
17dp
12dp
+
+
+ 10dp
+ 20dp
+ 20dp
\ No newline at end of file
diff --git a/src/android/libs/SalesforceSDK/res/values/sf__strings.xml b/src/android/libs/SalesforceSDK/res/values/sf__strings.xml
index d344570c..54a48b2b 100644
--- a/src/android/libs/SalesforceSDK/res/values/sf__strings.xml
+++ b/src/android/libs/SalesforceSDK/res/values/sf__strings.xml
@@ -37,36 +37,6 @@
Apply
Reset
Change Server
-
-
- Passcode
- Create a passcode
- Passcode
- Create a passcode
- Passcode
- Verify passcode
- Passcode
- Enter passcode
- Passcodes do not match. Try again.
- Incorrect passcode. %d attempts remaining.
- Final passcode attempt.
- Log out
- Verify Passcode
- Fingerprint icon
- Face Unlock icon
- Fingerprint Unlock
- Face Unlock
- Iris Scanner Unlock
- Set up Fingerprint Unlock
- Set up Face Unlock
- Set up Iris Scanner Unlock
- You can now unlock %s using biometric for extra security and convenience.
- Not Now
- Enable
- Passcode field
- Passcode is %d alphanumeric characters long.
- Passcode confirmed.
- You are logged out.
Clearing user data will log the current user out. Are you sure you want to perform this action?
@@ -87,25 +57,19 @@
Used to specify that only authorized hosts can be shown.
Used to specify IDP app\'s URL scheme to be used.
-
- Fingerprint Unlock
- Unlock using your fingerprint to access your %s account.
- Touch sensor
- Fingerprint authentication failed
-
-
- Face Unlock
- Use Face Unlock to access your %s account.
- Face Unlock failed
-
-
- Iris Scanner Unlock
- Use the Iris Scanner to access your %s account.
- Iris authentication failed
-
-
- Cancel
- Success
+
+ Logout
+ Application Icon
+ Authentication Failed.
+ Authentication error.
+ Authentication Success.
+ %s Lock
+ Unlock to access your %s account.
+ Hardware unavailable. Try again later.
+ An unknown error has occurred.
+ %s requires a device lock screen.
+ Setup Lock Screen
+ Retry Unlock
Mobile SDK Dev Support
diff --git a/src/android/libs/SalesforceSDK/res/values/sf__styles.xml b/src/android/libs/SalesforceSDK/res/values/sf__styles.xml
index d5a9b7de..cefaa4fc 100644
--- a/src/android/libs/SalesforceSDK/res/values/sf__styles.xml
+++ b/src/android/libs/SalesforceSDK/res/values/sf__styles.xml
@@ -110,8 +110,7 @@
- @color/sf__background
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/app/SalesforceSDKManager.java b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/app/SalesforceSDKManager.java
index 5ad3c057..d9068f6c 100644
--- a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/app/SalesforceSDKManager.java
+++ b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/app/SalesforceSDKManager.java
@@ -49,6 +49,12 @@
import android.view.View;
import android.webkit.CookieManager;
+import androidx.annotation.NonNull;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.ProcessLifecycleOwner;
+
import com.salesforce.androidsdk.BuildConfig;
import com.salesforce.androidsdk.R;
import com.salesforce.androidsdk.accounts.UserAccount;
@@ -73,6 +79,7 @@
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;
@@ -103,7 +110,7 @@
* use the static getInstance() method to access the
* singleton SalesforceSDKManager object.
*/
-public class SalesforceSDKManager {
+public class SalesforceSDKManager implements LifecycleObserver {
/**
* Current version of this SDK.
@@ -147,9 +154,8 @@ public class SalesforceSDKManager {
private LoginOptions loginOptions;
private final Class extends Activity> mainActivityClass;
private Class extends Activity> loginActivityClass = LoginActivity.class;
- private Class extends PasscodeActivity> passcodeActivityClass = PasscodeActivity.class;
private Class extends AccountSwitcherActivity> switcherActivityClass = AccountSwitcherActivity.class;
- private PasscodeManager passcodeManager;
+ private ScreenLockManager screenLockManager;
private LoginServerManager loginServerManager;
private boolean isTestRun = false;
private boolean isLoggingOut = false;
@@ -176,9 +182,9 @@ public enum Theme {
}
/**
- * PasscodeManager object lock.
+ * ScreenLockManager object lock.
*/
- private final Object passcodeManagerLock = new Object();
+ private final Object screenLockManagerLock = new Object();
/**
* Dev support
@@ -265,6 +271,7 @@ protected SalesforceSDKManager(Context context, Class extends Activity> 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);
}
/**
@@ -400,21 +407,19 @@ public static void initNative(Context context, Class extends Activity> mainAct
* The custom class must subclass PasscodeActivity.
*
* @param activity Subclass of PasscodeActivity.
+ *
+ * @deprecated Will be removed in Mobile SDK 10.0.
*/
- public void setPasscodeActivity(Class extends PasscodeActivity> activity) {
- if (activity != null) {
- passcodeActivityClass = activity;
- }
- }
+ public void setPasscodeActivity(Class extends PasscodeActivity> 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 extends PasscodeActivity> getPasscodeActivity() {
- return passcodeActivityClass;
- }
+ public Class extends PasscodeActivity> getPasscodeActivity() { return PasscodeActivity.class; }
/**
* Indicates whether the SDK should automatically log out when the
@@ -512,17 +517,26 @@ public synchronized Class extends PushService> getPushServiceType() {
}
/**
- * Returns the passcode manager that's associated with SalesforceSDKManager.
+ * Returns the descriptor of the passcode activity class that's currently in use.
+ *
+ * @return Passcode activity class descriptor.
*
- * @return PasscodeManager instance.
+ * @deprecated Will be removed in Mobile SDK 10.0.
*/
- public PasscodeManager getPasscodeManager() {
- synchronized (passcodeManagerLock) {
- if (passcodeManager == null) {
- passcodeManager = new PasscodeManager(context);
+ public PasscodeManager getPasscodeManager() { return new PasscodeManager(context); }
+
+ /**
+ * Returns the ScreenLock manager that's associated with SalesforceSDKManager.
+ *
+ * @return ScreenLockManager instance.
+ */
+ public ScreenLockManager getScreenLockManager() {
+ synchronized (screenLockManagerLock) {
+ if (screenLockManager == null) {
+ screenLockManager = new ScreenLockManager();
}
- return passcodeManager;
- }
+ return screenLockManager;
+ }
}
/**
@@ -743,8 +757,8 @@ private void cleanUp(Activity frontActivity, Account account, boolean shouldDism
/*
* Checks how many accounts are left that are authenticated. If only one
* account is left, this is the account that is being removed. In this
- * case, we can safely reset passcode manager, admin prefs, and encryption keys.
- * Otherwise, we don't reset passcode manager and admin prefs since
+ * case, we can safely reset screen lock manager, admin prefs, and encryption keys.
+ * Otherwise, we don't reset screen lock manager and admin prefs since
* there might be other accounts on that same org, and these policies
* are stored at the org level.
*/
@@ -753,8 +767,9 @@ private void cleanUp(Activity frontActivity, Account account, boolean shouldDism
getAdminPermsManager().resetAll();
adminSettingsManager = null;
adminPermsManager = null;
- getPasscodeManager().reset(context);
- passcodeManager = null;
+
+ getScreenLockManager().reset();
+ screenLockManager = null;
}
}
@@ -767,6 +782,7 @@ protected void cleanUp(UserAccount userAccount) {
SalesforceAnalyticsManager.reset(userAccount);
RestClient.clearCaches(userAccount);
UserAccountManager.getInstance().clearCachedCurrentUser();
+ getScreenLockManager().cleanUp(userAccount);
}
/**
@@ -929,31 +945,9 @@ public void logout(Account account, Activity frontActivity, final boolean showLo
private void removeAccount(ClientManager clientMgr, final boolean showLoginPage,
String refreshToken, String loginServer,
Account account, Activity frontActivity) {
- cleanUp(frontActivity, account, showLoginPage);
- /*
- * Removes the existing account, if any. 'account == null' does not
- * guarantee that there are no accounts to remove. In the 'Forgot Passcode'
- * flow there could be accounts to remove, but we don't have them, since
- * we don't have the passcode hash to decrypt them. Hence, we query
- * AccountManager directly here and remove the accounts for the case
- * where 'account == null'. If AccountManager doesn't have accounts
- * either, then there's nothing to do.
- */
- if (account == null) {
- final AccountManager accMgr = AccountManager.get(context);
- if (accMgr != null) {
- final Account[] accounts = accMgr.getAccountsByType(getAccountType());
- if (accounts.length > 0) {
- for (int i = 0; i < accounts.length - 1; i++) {
- clientMgr.removeAccounts(accounts);
- }
- clientMgr.removeAccount(accounts[accounts.length - 1]);
- }
- }
- } else {
- clientMgr.removeAccount(account);
- }
+ cleanUp(frontActivity, account, showLoginPage);
+ clientMgr.removeAccount(account);
isLoggingOut = false;
notifyLogoutComplete(showLoginPage);
@@ -1072,21 +1066,14 @@ public String getAccountType() {
return context.getString(R.string.account_type);
}
+ @NonNull
@Override
public String toString() {
- final StringBuilder sb = new StringBuilder();
- sb.append(this.getClass()).append(": {\n")
- .append(" accountType: ").append(getAccountType()).append("\n")
- .append(" userAgent: ").append(getUserAgent()).append("\n")
- .append(" mainActivityClass: ").append(getMainActivityClass()).append("\n")
- .append("\n");
- if (passcodeManager != null) {
-
- // passcodeManager may be null at startup if the app is running in debug mode.
- sb.append(" hasStoredPasscode: ").append(passcodeManager.hasStoredPasscode(context)).append("\n");
- }
- sb.append("}\n");
- return sb.toString();
+ return this.getClass() + ": {\n" +
+ " accountType: " + getAccountType() + "\n" +
+ " userAgent: " + getUserAgent() + "\n" +
+ " mainActivityClass: " + getMainActivityClass() + "\n" +
+ "\n";
}
/**
@@ -1467,4 +1454,14 @@ public void setViewNavigationVisibility(Activity activity) {
}
}
}
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+ protected void onAppBackgrounded() {
+ getScreenLockManager().onAppBackgrounded();
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_START)
+ protected void onAppForegrounded() {
+ getScreenLockManager().onAppForegrounded();
+ }
}
diff --git a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/app/SalesforceSDKUpgradeManager.java b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/app/SalesforceSDKUpgradeManager.java
index df100b9c..9ee1e255 100644
--- a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/app/SalesforceSDKUpgradeManager.java
+++ b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/app/SalesforceSDKUpgradeManager.java
@@ -26,6 +26,9 @@
*/
package com.salesforce.androidsdk.app;
+import static com.salesforce.androidsdk.security.ScreenLockManager.MOBILE_POLICY_PREF;
+import static com.salesforce.androidsdk.security.ScreenLockManager.SCREEN_LOCK;
+
import android.content.Context;
import android.content.SharedPreferences;
@@ -93,6 +96,9 @@ protected synchronized void upgradeAccMgr() {
if (installedVerDouble < 8.2) {
upgradeTo8Dot2();
}
+ if (installedVerDouble < 9.2) {
+ upgradeTo9Dot2();
+ }
} catch (Exception e) {
SalesforceSDKLogger.e(TAG, "Failed to parse installed version.");
}
@@ -150,4 +156,61 @@ private void migrateAnalyticsData() {
}
}
}
+
+ // TODO: Remove upgrade step in Mobile SDK 11.0
+ private void upgradeTo9Dot2() {
+ final String KEY_PASSCODE ="passcode";
+ final String KEY_TIMEOUT = "access_timeout";
+ final String KEY_PASSCODE_LENGTH = "passcode_length";
+ final String KEY_FAILED_ATTEMPTS = "failed_attempts";
+ final String KEY_PASSCODE_LENGTH_KNOWN = "passcode_length_known";
+ final String KEY_BIOMETRIC_ALLOWED = "biometric_allowed";
+ final String KEY_BIOMETRIC_ENROLLMENT = "biometric_enrollment";
+ final String KEY_BIOMETRIC_ENABLED = "biometric_enabled";
+ final Context ctx = SalesforceSDKManager.getInstance().getAppContext();
+
+ final SharedPreferences globalPrefs = ctx.getSharedPreferences(MOBILE_POLICY_PREF, Context.MODE_PRIVATE);
+ if (globalPrefs.contains(KEY_TIMEOUT) && globalPrefs.contains(KEY_PASSCODE_LENGTH)) {
+ SharedPreferences.Editor globalEditor = globalPrefs.edit();
+ globalEditor.remove(KEY_PASSCODE);
+ globalEditor.remove(KEY_TIMEOUT);
+ globalEditor.remove(KEY_FAILED_ATTEMPTS);
+ globalEditor.remove(KEY_PASSCODE_LENGTH);
+ globalEditor.remove(KEY_PASSCODE_LENGTH_KNOWN);
+ globalEditor.remove(KEY_BIOMETRIC_ALLOWED);
+ globalEditor.remove(KEY_BIOMETRIC_ENROLLMENT);
+ globalEditor.remove(KEY_BIOMETRIC_ENABLED);
+
+ globalEditor.putBoolean(SCREEN_LOCK, true).apply();
+
+ // Set which users should have screen lock
+ final UserAccountManager manager = SalesforceSDKManager.getInstance().getUserAccountManager();
+ final List accounts = manager.getAuthenticatedUsers();
+
+ if (accounts != null) {
+ for (UserAccount account : accounts) {
+ final SharedPreferences orgPrefs = ctx.getSharedPreferences(MOBILE_POLICY_PREF
+ + account.getOrgLevelFilenameSuffix(), Context.MODE_PRIVATE);
+ if (orgPrefs.contains(KEY_TIMEOUT) && orgPrefs.contains(KEY_PASSCODE_LENGTH)) {
+ // Delete passcode keys at org level
+ SharedPreferences.Editor orgEditor = globalPrefs.edit();
+ orgEditor.remove(KEY_PASSCODE);
+ orgEditor.remove(KEY_TIMEOUT);
+ orgEditor.remove(KEY_FAILED_ATTEMPTS);
+ orgEditor.remove(KEY_PASSCODE_LENGTH);
+ orgEditor.remove(KEY_PASSCODE_LENGTH_KNOWN);
+ orgEditor.remove(KEY_BIOMETRIC_ALLOWED);
+ orgEditor.remove(KEY_BIOMETRIC_ENROLLMENT);
+ orgEditor.remove(KEY_BIOMETRIC_ENABLED);
+ orgEditor.apply();
+
+ // Set screen lock key at user level
+ final SharedPreferences userPrefs = ctx.getSharedPreferences(MOBILE_POLICY_PREF
+ + account.getUserLevelFilenameSuffix(), Context.MODE_PRIVATE);
+ userPrefs.edit().putBoolean(SCREEN_LOCK, true).apply();
+ }
+ }
+ }
+ }
+ }
}
diff --git a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/auth/HttpAccess.java b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/auth/HttpAccess.java
index f96b29fb..c04fd812 100644
--- a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/auth/HttpAccess.java
+++ b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/auth/HttpAccess.java
@@ -69,8 +69,9 @@ public class HttpAccess {
* Initializes HttpAccess. Should be called from the application.
*/
public static void init(Context app) {
- assert DEFAULT == null : "HttpAccess.init should be called once per process";
- DEFAULT = new HttpAccess(app, null /* user agent will be calculated at request time */);
+ if (DEFAULT == null) {
+ DEFAULT = new HttpAccess(app, null /* user agent will be calculated at request time */);
+ }
}
/**
diff --git a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/auth/OAuth2.java b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/auth/OAuth2.java
index 1f1c64ee..fa0231e5 100644
--- a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/auth/OAuth2.java
+++ b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/auth/OAuth2.java
@@ -90,15 +90,12 @@ public class OAuth2 {
private static final String INSTANCE_URL = "instance_url";
private static final String JSON = "json";
private static final String MOBILE_POLICY = "mobile_policy";
- private static final String PIN_LENGTH = "pin_length";
- private static final String BIOMETRIC_UNLOCK = "biometric_unlock";
private static final String REFRESH_TOKEN = "refresh_token";
private static final String HYBRID_REFRESH = "hybrid_refresh";
private static final String RESPONSE_TYPE = "response_type";
private static final String SCOPE = "scope";
private static final String REDIRECT_URI = "redirect_uri";
private static final String DEVICE_ID = "device_id";
- private static final String SCREEN_LOCK = "screen_lock";
private static final String HYBRID_TOKEN = "hybrid_token";
private static final String USERNAME = "username";
private static final String EMAIL = "email";
@@ -529,6 +526,7 @@ public static class IdServiceResponse {
public String displayName;
public String pictureUrl;
public String thumbnailUrl;
+ public boolean mobilePolicy;
public int pinLength = -1;
public int screenLockTimeout = -1;
public boolean biometricUnlockAllowed = true;
@@ -555,17 +553,7 @@ public IdServiceResponse(Response response) {
}
customAttributes = parsedResponse.optJSONObject(CUSTOM_ATTRIBUTES);
customPermissions = parsedResponse.optJSONObject(CUSTOM_PERMISSIONS);
- if (parsedResponse.has(MOBILE_POLICY)) {
- pinLength = parsedResponse.getJSONObject(MOBILE_POLICY).getInt(PIN_LENGTH);
- screenLockTimeout = parsedResponse.getJSONObject(MOBILE_POLICY).getInt(SCREEN_LOCK);
- if (customAttributes != null) {
- String bioAttribute = customAttributes.optString(BIOMETRIC_UNLOCK).toLowerCase(Locale.US);
- if (bioAttribute.equals("false")) {
- biometricUnlockAllowed = false;
- SalesforceSDKLogger.i(TAG, "Biometric Unlock disabled by connected app.");
- }
- }
- }
+ mobilePolicy = parsedResponse.has(MOBILE_POLICY);
} catch (Exception e) {
SalesforceSDKLogger.w(TAG, "Could not parse identity response", e);
}
diff --git a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/security/PasscodeManager.java b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/security/PasscodeManager.java
index 69680ac4..651bdf9f 100644
--- a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/security/PasscodeManager.java
+++ b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/security/PasscodeManager.java
@@ -49,98 +49,32 @@
*
* @author wmathurin
* @author bhariharan
+ *
+ * @deprecated Will be removed in Mobile SDK 10.0. Use {@link ScreenLockManager} instead.
*/
public class PasscodeManager {
-
- // UUID keys
- private static final String VKEY = "vkey";
- private static final String VSUFFIX = "vsuffix";
- private static final String VPREFIX = "vprefix";
- private static final String TAG = "PasscodeManager";
// Default min passcode length
public static final int MIN_PASSCODE_LENGTH = 4;
- // Key in preference for the passcode
- protected static final String KEY_PASSCODE ="passcode";
-
- // Private preference where we stored the passcode (hashed)
- protected static final String PASSCODE_PREF_NAME = "user";
-
- // Private preference where we stored the org settings.
- protected static final String MOBILE_POLICY_PREF = "mobile_policy";
-
- // Key in preference for the access timeout.
- protected static final String KEY_TIMEOUT = "access_timeout";
-
- // Key in preference for the passcode length.
- protected static final String KEY_PASSCODE_LENGTH = "passcode_length";
-
- // Key in preferences for actual passcode length known
- protected static final String KEY_PASSCODE_LENGTH_KNOWN = "passcode_length_known";
-
- // Key in preference for connect app biometric flag.
- protected static final String KEY_BIOMETRIC_ALLOWED = "biometric_allowed";
-
- // Key in preferences to indicate if the user has been prompted to use biometric.
- protected static final String KEY_BIOMETRIC_ENROLLMENT = "biometric_enrollment";
-
- // Key in preferences to indicate if the user has enabled biometric.
- protected static final String KEY_BIOMETRIC_ENABLED = "biometric_enabled";
-
- // Key in preference to indicate passcode change is required.
- protected static final String KEY_PASSCODE_CHANGE_REQUIRED= "passcode_change_required";
-
- // Key in preference for failed attempts
- protected static final String KEY_FAILED_ATTEMPTS = "failed_attempts";
-
// Request code used to start passcode activity
public static final int PASSCODE_REQUEST_CODE = 777;
- // Misc
- private HashConfig verificationHashConfig;
- private Handler handler;
- private long lastActivity;
- boolean locked;
- private int timeoutMs;
- private int passcodeLength;
- private boolean biometricAllowed;
- private boolean biometricEnrollmentShown;
- private boolean biometricEnabled;
- private boolean passcodeChangeRequired;
- private LockChecker lockChecker;
- private boolean passcodeLengthKnown;
-
/**
* Parameterized constructor.
*
* @param ctx Context.
*/
- public PasscodeManager(Context ctx) {
- this(ctx, new HashConfig(SalesforceKeyGenerator.getUniqueId(VPREFIX),
- SalesforceKeyGenerator.getUniqueId(VSUFFIX),
- SalesforceKeyGenerator.getUniqueId(VKEY)));
- }
+ public PasscodeManager(Context ctx) { }
- public PasscodeManager(Context ctx, HashConfig verificationHashConfig) {
- this.passcodeLength = MIN_PASSCODE_LENGTH;
- this.lastActivity = now();
- this.verificationHashConfig = verificationHashConfig;
- readMobilePolicy(ctx);
-
- // Locked at app startup if you're authenticated.
- this.locked = true;
- lockChecker = new LockChecker();
- }
+ public PasscodeManager(Context ctx, HashConfig verificationHashConfig) { }
/**
* Returns true if a passcode change is required.
*
* @return true if passcode change required.
*/
- public boolean isPasscodeChangeRequired() {
- return passcodeChangeRequired;
- }
+ public boolean isPasscodeChangeRequired() { return false; }
/**
@@ -148,10 +82,7 @@ public boolean isPasscodeChangeRequired() {
* @param ctx Context.
* @param passcodeChangeRequired value to set passcode change required flag to
*/
- public void setPasscodeChangeRequired(Context ctx, boolean passcodeChangeRequired) {
- this.passcodeChangeRequired = passcodeChangeRequired;
- storeMobilePolicy(ctx);
- }
+ public void setPasscodeChangeRequired(Context ctx, boolean passcodeChangeRequired) { }
/**
* Returns the timeout value for the specified account.
@@ -159,15 +90,7 @@ public void setPasscodeChangeRequired(Context ctx, boolean passcodeChangeRequire
* @param account UserAccount instance.
* @return Timeout value.
*/
- public int getTimeoutMsForOrg(UserAccount account) {
- if (account == null) {
- return 0;
- }
- final Context context = SalesforceSDKManager.getInstance().getAppContext();
- final SharedPreferences sp = context.getSharedPreferences(MOBILE_POLICY_PREF
- + account.getOrgLevelFilenameSuffix(), Context.MODE_PRIVATE);
- return sp.getInt(KEY_TIMEOUT, 0);
- }
+ public int getTimeoutMsForOrg(UserAccount account) { return 0; }
/**
* Returns the minimum passcode length for the specified account.
@@ -175,15 +98,7 @@ public int getTimeoutMsForOrg(UserAccount account) {
* @param account UserAccount instance.
* @return Minimum passcode length.
*/
- public int getPasscodeLengthForOrg(UserAccount account) {
- if (account == null) {
- return MIN_PASSCODE_LENGTH;
- }
- final Context context = SalesforceSDKManager.getInstance().getAppContext();
- final SharedPreferences sp = context.getSharedPreferences(MOBILE_POLICY_PREF
- + account.getOrgLevelFilenameSuffix(), Context.MODE_PRIVATE);
- return sp.getInt(KEY_PASSCODE_LENGTH, MIN_PASSCODE_LENGTH);
- }
+ public int getPasscodeLengthForOrg(UserAccount account) { return 0; }
/**
* Stores the mobile policy for the specified account.
@@ -194,114 +109,13 @@ public int getPasscodeLengthForOrg(UserAccount account) {
* @param bioAllowed If biometric Unlock is Allowed by connected App
*/
@SuppressLint("ApplySharedPref")
- public void storeMobilePolicyForOrg(UserAccount account, int timeout, int passLen, boolean bioAllowed) {
- if (account == null) {
- return;
- }
- final Context context = SalesforceSDKManager.getInstance().getAppContext();
- final SharedPreferences sp = context.getSharedPreferences(MOBILE_POLICY_PREF
- + account.getOrgLevelFilenameSuffix(), Context.MODE_PRIVATE);
- final Editor e = sp.edit();
- e.putInt(KEY_TIMEOUT, timeout);
- e.putInt(KEY_PASSCODE_LENGTH, passLen);
- e.putBoolean(KEY_PASSCODE_LENGTH_KNOWN, passcodeLengthKnown);
- e.putBoolean(KEY_BIOMETRIC_ALLOWED, bioAllowed);
- e.commit();
- }
-
- /**
- * Stores the mobile policy in a private file.
- *
- * @param context Context.
- */
- @SuppressLint("ApplySharedPref")
- private void storeMobilePolicy(Context context) {
-
- // Context will be null only in test runs.
- if (context != null) {
- final SharedPreferences sp = context.getSharedPreferences(MOBILE_POLICY_PREF,
- Context.MODE_PRIVATE);
- Editor e = sp.edit();
- e.putInt(KEY_TIMEOUT, timeoutMs);
- e.putInt(KEY_PASSCODE_LENGTH, passcodeLength);
- e.putBoolean(KEY_PASSCODE_LENGTH_KNOWN, passcodeLengthKnown);
- e.putBoolean(KEY_PASSCODE_CHANGE_REQUIRED, passcodeChangeRequired);
- e.putBoolean(KEY_BIOMETRIC_ALLOWED, biometricAllowed);
- e.putBoolean(KEY_BIOMETRIC_ENROLLMENT, biometricEnrollmentShown);
- e.putBoolean(KEY_BIOMETRIC_ENABLED, biometricEnabled);
- e.commit();
- }
- }
-
- /**
- * Reads the mobile policy from a private file.
- *
- * @param context Context.
- */
- private void readMobilePolicy(Context context) {
-
- // Context will be null only in test runs.
- if (context != null) {
- final SharedPreferences sp = context.getSharedPreferences(MOBILE_POLICY_PREF,
- Context.MODE_PRIVATE);
- if (!sp.contains(KEY_TIMEOUT) || !sp.contains(KEY_PASSCODE_LENGTH)) {
- timeoutMs = 0;
- passcodeLength = MIN_PASSCODE_LENGTH;
- passcodeChangeRequired = false;
- biometricAllowed = true;
- biometricEnrollmentShown = false;
- biometricEnabled = false;
- storeMobilePolicy(context);
- return;
- }
- timeoutMs = sp.getInt(KEY_TIMEOUT, 0);
- passcodeLength = sp.getInt(KEY_PASSCODE_LENGTH, MIN_PASSCODE_LENGTH);
- passcodeLengthKnown = sp.getBoolean(KEY_PASSCODE_LENGTH_KNOWN, false);
- passcodeChangeRequired = sp.getBoolean(KEY_PASSCODE_CHANGE_REQUIRED, false);
- biometricAllowed = sp.getBoolean(KEY_BIOMETRIC_ALLOWED, true);
- biometricEnrollmentShown = sp.getBoolean(KEY_BIOMETRIC_ENROLLMENT, false);
- biometricEnabled = sp.getBoolean(KEY_BIOMETRIC_ENABLED, false);
- }
- }
+ 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) {
-
- // Deletes the underlying org policy files for all orgs.
- final String sharedPrefPath = ctx.getApplicationInfo().dataDir + "/shared_prefs";
- final File dir = new File(sharedPrefPath);
- final PasscodeFileFilter fileFilter = new PasscodeFileFilter();
- for (final File file : dir.listFiles()) {
- if (file != null && fileFilter.accept(dir, file.getName())) {
- file.delete();
- }
- }
- lastActivity = now();
- locked = true;
- SharedPreferences sp = ctx.getSharedPreferences(PASSCODE_PREF_NAME,
- Context.MODE_PRIVATE);
- Editor e = sp.edit();
- e.remove(KEY_PASSCODE);
- e.remove(KEY_FAILED_ATTEMPTS);
- e.remove(KEY_PASSCODE_LENGTH);
- e.remove(KEY_PASSCODE_LENGTH_KNOWN);
- e.remove(KEY_BIOMETRIC_ALLOWED);
- e.remove(KEY_BIOMETRIC_ENROLLMENT);
- e.remove(KEY_BIOMETRIC_ENABLED);
- e.commit();
- timeoutMs = 0;
- passcodeLength = MIN_PASSCODE_LENGTH;
- passcodeLengthKnown = false;
- passcodeChangeRequired = false;
- biometricAllowed = true;
- biometricEnrollmentShown = false;
- biometricEnabled = false;
- storeMobilePolicy(ctx);
- handler = null;
- }
+ public void reset(Context ctx) { }
/**
* Resets the passcode policies for a particular org upon logout.
@@ -310,80 +124,29 @@ public void reset(Context ctx) {
* @param account User account.
*/
@SuppressLint("ApplySharedPref")
- public void reset(Context context, UserAccount account) {
- if (account == null) {
- return;
- }
- final SharedPreferences sp = context.getSharedPreferences(MOBILE_POLICY_PREF
- + account.getOrgLevelFilenameSuffix(), Context.MODE_PRIVATE);
- final Editor e = sp.edit();
- e.clear();
- e.commit();
- }
+ public void reset(Context context, UserAccount account) { }
/**
* Enable/disable passcode screen.
*/
- public void setEnabled(boolean enabled) {
- if (enabled) {
- handler = new Handler();
- handler.postDelayed(lockChecker, 20 * 1000);
- } else {
- if (handler != null) {
- handler.removeCallbacks(lockChecker);
- }
- handler = null;
- }
- }
+ public void setEnabled(boolean enabled) { }
/**
* @return true if passcode manager is enabled.
*/
- public boolean isEnabled() {
- return (handler != null);
- }
+ public boolean isEnabled() { return false; }
/**
* @return the new failure count
*/
- public int addFailedPasscodeAttempt() {
- int failedAttempts = getFailedPasscodeAttempts() + 1;
- setFailedPasscodeAttempts(failedAttempts);
- return failedAttempts;
- }
+ 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) {
- final SharedPreferences sp = ctx.getSharedPreferences(PASSCODE_PREF_NAME, Context.MODE_PRIVATE);
- String hashedPasscode = sp.getString(KEY_PASSCODE, null);
- hashedPasscode = removeNewLine(hashedPasscode);
- if (hashedPasscode != null) {
- return hashedPasscode.equals(hashForVerification(passcode));
- }
-
- /*
- * If the stored passcode hash is null, there is no passcode.
- */
- return true;
- }
-
- /**
- * Removes a trailing newline character from the hash.
- *
- * @param hash Hash.
- * @return Hash with trailing newline character removed.
- */
- private String removeNewLine(String hash) {
- int length = hash == null ? 0 : hash.length();
- if (length > 0 && hash.endsWith("\n")) {
- return hash.substring(0, length - 1);
- }
- return hash;
- }
+ public boolean check(Context ctx, String passcode) { return true; }
/**
* Store the given passcode (hashed) in private preference
@@ -391,81 +154,40 @@ private String removeNewLine(String hash) {
* @param passcode Passcode.
*/
@SuppressLint("ApplySharedPref")
- public void store(Context ctx, String passcode) {
- SharedPreferences sp = ctx.getSharedPreferences(PASSCODE_PREF_NAME, Context.MODE_PRIVATE);
- Editor e = sp.edit();
- e.putString(KEY_PASSCODE, hashForVerification(passcode));
- e.putInt(KEY_PASSCODE_LENGTH, passcode.length());
- e.putBoolean(KEY_PASSCODE_LENGTH_KNOWN, true);
- e.putBoolean(KEY_BIOMETRIC_ENROLLMENT, biometricEnrollmentShown);
- e.putBoolean(KEY_BIOMETRIC_ENABLED, biometricEnabled);
- e.commit();
- setPasscodeChangeRequired(ctx,false);
- setPasscodeLengthKnown(ctx, true);
- }
+ public void store(Context ctx, String passcode) { }
/**
* @param ctx Context.
* @return true if passcode was already created
*/
- public boolean hasStoredPasscode(Context ctx) {
- SharedPreferences sp = ctx.getSharedPreferences(PASSCODE_PREF_NAME, Context.MODE_PRIVATE);
- return sp.contains(KEY_PASSCODE);
- }
+ public boolean hasStoredPasscode(Context ctx) { return false; }
/**
* @return number of failed passcode attempts
*/
- public int getFailedPasscodeAttempts() {
- SharedPreferences sp = SalesforceSDKManager.getInstance().getAppContext().getSharedPreferences(PASSCODE_PREF_NAME, Context.MODE_PRIVATE);
- return sp.getInt(KEY_FAILED_ATTEMPTS, 0);
- }
-
- @SuppressLint("ApplySharedPref")
- private void setFailedPasscodeAttempts(int failedPasscodeAttempts) {
- SharedPreferences sp = SalesforceSDKManager.getInstance().getAppContext().getSharedPreferences(PASSCODE_PREF_NAME, Context.MODE_PRIVATE);
- Editor e = sp.edit();
- e.putInt(KEY_FAILED_ATTEMPTS, failedPasscodeAttempts);
- e.commit();
- }
+ public int getFailedPasscodeAttempts() { return 0; }
/**
* @return true if locked
*/
- public boolean isLocked() {
- return timeoutMs > 0 && locked;
- }
+ public boolean isLocked() { return false; }
/**
* @param ctx Context.
*/
- public void lock(Context ctx) {
- showLockActivity(ctx);
- }
+ public void lock(Context ctx) { }
/**
* @param frontActivity
* @param registerActivity
* @return
*/
- public boolean lockIfNeeded(Activity frontActivity, boolean registerActivity) {
- if (isEnabled() && (isLocked() || shouldLock() || passcodeChangeRequired)) {
- lock(frontActivity);
- return true;
- } else {
- if (registerActivity) updateLast();
- return false;
- }
- }
+ public boolean lockIfNeeded(Activity frontActivity, boolean registerActivity) { return false; }
/**
* To be called by passcode protected activity when being paused
*/
- public void onPause(Activity ctx) {
-
- // Disables passcode manager.
- setEnabled(false);
- }
+ public void onPause(Activity ctx) { }
/**
* To be called by passcode protected activity when being resumed
@@ -474,65 +196,26 @@ public void onPause(Activity ctx) {
*
* @return true if the resume should be allowed to continue and false otherwise
*/
- public boolean onResume(Activity ctx) {
-
- // Enables passcode manager.
- setEnabled(true);
-
- // Brings up passcode screen if needed.
- lockIfNeeded(ctx, true);
-
- // If locked, do nothing - when the app gets unlocked we will be back here.
- return !isLocked();
- }
+ public boolean onResume(Activity ctx) { return false; }
/**
* To be called by passcode protected activity whenever there is a user interaction
*/
- public void recordUserInteraction() {
- updateLast();
- }
+ public void recordUserInteraction() { }
/**
* Called when the access timeout for the org changes.
*
* @param newTimeout New access timeout value.
*/
- public void setTimeoutMs(int newTimeout) {
-
- // Access timeout hasn't changed.
- if (timeoutMs == newTimeout) {
- return;
- }
-
- /*
- * Either access timeout has changed from one non-zero value to another,
- * which doesn't alter the passcode situation, or the app goes from
- * no passcode to passcode, which will trigger the passcode creation flow.
- */
- if (timeoutMs == 0 || (timeoutMs > 0 && newTimeout > 0)) {
-
- // Updates timeout only if the new timeout is smaller than the old one.
- if (timeoutMs == 0 || timeoutMs > newTimeout) {
- timeoutMs = newTimeout;
- }
- storeMobilePolicy(SalesforceSDKManager.getInstance().getAppContext());
- return;
- }
-
- // Passcode to no passcode.
- timeoutMs = newTimeout;
- reset(SalesforceSDKManager.getInstance().getAppContext());
- }
+ public void setTimeoutMs(int newTimeout) { }
/**
* The current inactivity timeout before the app locks, in milliseconds.
*
* @return the inactivity timeout
*/
- public int getTimeoutMs() {
- return timeoutMs;
- }
+ public int getTimeoutMs() { return 0; }
/**
* The exact length of the passcode if it is known. It may be unknown on upgrade before first unlock.
@@ -540,9 +223,7 @@ public int getTimeoutMs() {
*
* @return passcode length
*/
- public int getPasscodeLength() {
- return passcodeLength;
- }
+ public int getPasscodeLength() { return 0; }
/**
* Whether or not the exact passcode length is known. It may be unknown on upgrade before first unlock.
@@ -550,50 +231,33 @@ public int getPasscodeLength() {
*
* @return true if the length is known
*/
- public boolean getPasscodeLengthKnown() {
- return passcodeLengthKnown;
- }
+ 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 biometricAllowed;
- }
+ 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 biometricEnrollmentShown;
- }
+ 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 biometricEnabled;
- }
+ public boolean biometricEnabled() { return false; }
/**
* @param ctx Context.
* @param passcodeLength The new passcode length to set.
*/
- public void setPasscodeLength(Context ctx, int passcodeLength) {
- if (passcodeLength > this.passcodeLength) {
- if (hasStoredPasscode(ctx) && passcodeLengthKnown) {
- this.passcodeChangeRequired = true;
- }
- this.passcodeLength = passcodeLength;
- }
- this.passcodeLengthKnown = true;
- storeMobilePolicy(ctx);
- }
+ public void setPasscodeLength(Context ctx, int passcodeLength) { }
/**
* This method can be used to force the stored or default passcode length to be trusted
@@ -602,10 +266,7 @@ public void setPasscodeLength(Context ctx, int passcodeLength) {
* @param ctx Context
* @param lengthKnown Whether or not the passcode length is known.
*/
- public void setPasscodeLengthKnown(Context ctx, boolean lengthKnown) {
- this.passcodeLengthKnown = lengthKnown;
- storeMobilePolicy(ctx);
- }
+ public void setPasscodeLengthKnown(Context ctx, boolean lengthKnown) { }
/**
* Called when the biometric unlock requirement for the org changes.
@@ -615,22 +276,14 @@ public void setPasscodeLengthKnown(Context ctx, boolean lengthKnown) {
* way to prevent user enrollment is through the connected app.
* @see Using Passcodes
*/
- public void setBiometricAllowed(Context ctx, boolean allowed) {
- if (this.biometricAllowed) {
- this.biometricAllowed = allowed;
- }
- storeMobilePolicy(ctx);
- }
+ 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) {
- biometricEnrollmentShown = shown;
- storeMobilePolicy(ctx);
- }
+ public void setBiometricEnrollmentShown(Context ctx, boolean shown) { }
/**
* Enables biometric input.
@@ -645,84 +298,22 @@ public void setBiometricEnrollmentShown(Context ctx, boolean shown) {
*
* If you absolutely must disable biometric input at the app level see {@link PasscodeManager#setBiometricAllowed(Context, boolean)}.
*/
- public void setBiometricEnabled(Context ctx, boolean enabled) {
- biometricEnabled = enabled && biometricAllowed();
- storeMobilePolicy(ctx);
- }
+ 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 timeoutMs > 0 && now() >= (lastActivity + timeoutMs);
- }
+ public boolean shouldLock() { return false; }
- public void showLockActivity(Context ctx) {
- locked = true;
- if (ctx == null) {
- ctx = SalesforceSDKManager.getInstance().getAppContext();
- }
-
- final Intent i = new Intent(ctx, SalesforceSDKManager.getInstance().getPasscodeActivity());
- i.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
- i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
- i.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
- if (ctx == SalesforceSDKManager.getInstance().getAppContext()) {
- i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- }
- if (ctx instanceof Activity) {
- ((Activity) ctx).startActivityForResult(i, PASSCODE_REQUEST_CODE);
- } else {
- ctx.startActivity(i);
- }
- EventsObservable.get().notifyEvent(EventType.AppLocked);
- }
+ 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() {
- EventBuilderHelper.createAndStoreEvent("passcodeUnlock", null, TAG, null);
- locked = false;
- setFailedPasscodeAttempts(0);
- updateLast();
- EventsObservable.get().notifyEvent(EventType.AppUnlocked);
- }
-
- protected long now() {
- return System.currentTimeMillis();
- }
+ public void unlock() { }
- private void updateLast() {
- lastActivity = now();
- }
-
- public String hashForVerification(String passcode) {
- return hash(passcode, verificationHashConfig);
- }
-
- private String hash(String passcode, HashConfig hashConfig) {
- return Encryptor.hash(hashConfig.prefix + passcode + hashConfig.suffix, hashConfig.key);
- }
-
- /**
- * Thread checking periodically to see how much has elapsed since the last recorded activity
- * When that elapsed time exceed timeoutMs, it locks the app
- */
- private class LockChecker implements Runnable {
- public void run() {
- try {
- if (!locked) {
- lockIfNeeded(null, false);
- }
- } finally {
- if (handler != null) {
- handler.postDelayed(this, 20 * 1000);
- }
- }
- }
- }
+ public String hashForVerification(String passcode) { return ""; }
/**
* Key for hashing and salts to be preprended and appended to data to increase entropy.
@@ -739,19 +330,4 @@ public HashConfig(String prefix, String suffix, String key) {
this.key = key;
}
}
-
- /**
- * This class acts as a filter to identify only the relevant passcode files.
- *
- * @author bhariharan
- */
- private static class PasscodeFileFilter implements FilenameFilter {
-
- private static final String PASSCODE_FILE_PREFIX = MOBILE_POLICY_PREF + "_";
-
- @Override
- public boolean accept(File dir, String filename) {
- return (filename != null && filename.startsWith(PASSCODE_FILE_PREFIX));
- }
- }
}
diff --git a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/security/ScreenLockManager.java b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/security/ScreenLockManager.java
new file mode 100644
index 00000000..d1bfbabc
--- /dev/null
+++ b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/security/ScreenLockManager.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2021-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.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.salesforce.androidsdk.accounts.UserAccount;
+import com.salesforce.androidsdk.app.SalesforceSDKManager;
+import com.salesforce.androidsdk.ui.ScreenLockActivity;
+import com.salesforce.androidsdk.util.EventsObservable;
+
+import java.util.List;
+
+/**
+ * Manages if the app should be locked.
+ *
+ * @author bpage
+ */
+public class ScreenLockManager {
+ public static final String MOBILE_POLICY_PREF = "mobile_policy";
+ public static final String SCREEN_LOCK = "screen_lock";
+
+ private boolean backgroundedSinceUnlock = true;
+
+ /**
+ * Stores the mobile policy for the org upon user login.
+ *
+ * @param account the newly add account
+ * @param screenLockRequired if the account requires screen lock or not
+ */
+ public void storeMobilePolicy(UserAccount account, boolean screenLockRequired) {
+ Context ctx = SalesforceSDKManager.getInstance().getAppContext();
+ SharedPreferences accountSharedPrefs = ctx.getSharedPreferences(MOBILE_POLICY_PREF
+ + account.getUserLevelFilenameSuffix(), Context.MODE_PRIVATE);
+ accountSharedPrefs.edit().putBoolean(SCREEN_LOCK, screenLockRequired).apply();
+
+ SharedPreferences globalPrefs = ctx.getSharedPreferences(MOBILE_POLICY_PREF, Context.MODE_PRIVATE);
+ if (screenLockRequired) {
+ globalPrefs.edit().putBoolean(SCREEN_LOCK, true).apply();
+
+ // This is needed to block access to the app immediately on login
+ backgroundedSinceUnlock = true;
+ onAppForegrounded();
+ }
+ }
+
+ /**
+ * To be called by the protected activity to lock the device when being resumed.
+ */
+ public void onAppForegrounded() {
+ if (shouldLock()) {
+ lock();
+ }
+ }
+
+ /**
+ * To be called by the protected activity is paused to denote that the app should lock.
+ */
+ public void onAppBackgrounded() {
+ backgroundedSinceUnlock = true;
+ }
+
+ /**
+ * Resets and removes the screen lock.
+ */
+ public void reset() {
+ Context ctx = SalesforceSDKManager.getInstance().getAppContext();
+ SharedPreferences globalPrefs = ctx.getSharedPreferences(MOBILE_POLICY_PREF, Context.MODE_PRIVATE);
+ globalPrefs.edit().remove(SCREEN_LOCK).apply();
+ }
+
+ /**
+ * Screen lock specific cleanup for account logout/removal.
+ *
+ * @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()
+ .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;
+ }
+ }
+ }
+
+ // If we have returned, no other accounts require Screen Lock.
+ reset();
+ }
+
+ /**
+ * Unlocks the app.
+ */
+ public void unlock() {
+ backgroundedSinceUnlock = false;
+ }
+
+ @VisibleForTesting
+ protected boolean shouldLock() {
+ return backgroundedSinceUnlock && readMobilePolicy();
+ }
+
+ private boolean readMobilePolicy() {
+ Context ctx = SalesforceSDKManager.getInstance().getAppContext();
+ SharedPreferences sharedPrefs = ctx.getSharedPreferences(MOBILE_POLICY_PREF, Context.MODE_PRIVATE);
+ return sharedPrefs.getBoolean(SCREEN_LOCK, false);
+ }
+
+ private void lock() {
+ Context ctx = SalesforceSDKManager.getInstance().getAppContext();
+ Intent intent = new Intent(ctx, ScreenLockActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ ctx.startActivity(intent);
+ EventsObservable.get().notifyEvent(EventsObservable.EventType.AppLocked);
+ }
+}
diff --git a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/FingerprintAuthDialogFragment.java b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/FingerprintAuthDialogFragment.java
index bcdd3b8a..f5cb2d96 100644
--- a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/FingerprintAuthDialogFragment.java
+++ b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/FingerprintAuthDialogFragment.java
@@ -26,127 +26,34 @@
*/
package com.salesforce.androidsdk.ui;
-import android.Manifest.permission;
import android.app.Dialog;
import android.app.DialogFragment;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.graphics.Color;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
-import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
-import android.hardware.fingerprint.FingerprintManager.CryptoObject;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.Window;
-import android.widget.Button;
-import android.widget.TextView;
-
-import com.salesforce.androidsdk.R;
-import com.salesforce.androidsdk.app.SalesforceSDKManager;
-
-import javax.crypto.Cipher;
/**
* 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 {
- private TextView mStatusText;
- private PasscodeActivity mContext;
@Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- boolean isDarkTheme = SalesforceSDKManager.getInstance().isDarkTheme();
-
- // Do not create a new Fragment when the Activity is re-created such as orientation changes.
- setRetainInstance(true);
- setStyle(DialogFragment.STYLE_NORMAL, (isDarkTheme ? R.style.SalesforceSDK_Dialog_Dark : R.style.SalesforceSDK_Dialog));
- }
+ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); }
@Override
- public void onResume() {
- super.onResume();
- FingerprintManager fingerprintManager = (FingerprintManager) mContext.getSystemService(Context.FINGERPRINT_SERVICE);
- if (mContext.checkSelfPermission(permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
-
- // If we got so far, we already got the permission in the PasscodeActivity. This is an OS mandated check.
- return;
- }
- fingerprintManager.authenticate(new CryptoObject((Cipher) null), null, 0, new AuthenticationCallback() {
-
- @Override
- public void onAuthenticationError(int errorCode, CharSequence errString) {
- super.onAuthenticationError(errorCode, errString);
- }
-
- @Override
- public void onAuthenticationSucceeded(AuthenticationResult result) {
- super.onAuthenticationSucceeded(result);
- if (mStatusText != null) {
- mStatusText.setText(R.string.sf__biometric_success);
- mStatusText.setTextColor(Color.GREEN);
- }
- if (FingerprintAuthDialogFragment.this.getFragmentManager() != null) {
- FingerprintAuthDialogFragment.this.dismiss();
- }
- mContext.unlockViaFingerprintScan();
- }
-
- @Override
- public void onAuthenticationFailed() {
- super.onAuthenticationFailed();
- if (mStatusText != null) {
- mStatusText.setText(R.string.sf__fingerprint_failed);
- mStatusText.setTextColor(Color.RED);
- }
- }
-
- @Override
- public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
- super.onAuthenticationHelp(helpCode, helpString);
- if (mStatusText != null) {
- mStatusText.setText(helpString.toString());
- mStatusText.setTextColor(Color.RED);
- }
- }
- }, null);
- }
+ public void onResume() { super.onResume(); }
@Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final Dialog dialog = super.onCreateDialog(savedInstanceState);
- dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
- return dialog;
- }
+ public Dialog onCreateDialog(Bundle savedInstanceState) { return null; }
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- final View v = inflater.inflate(R.layout.sf__fingerprint_dialog, container, false);
- final Button cancelButton = v.findViewById(R.id.sf__use_password_button);
- cancelButton.setOnClickListener(new View.OnClickListener() {
-
- @Override
- public void onClick(View view) {
- dismiss();
- mContext.biometricDeclined();
- }
- });
- mStatusText = v.findViewById(R.id.sf__fingerprint_status);
-
- TextView textView = v.findViewById(R.id.sf__fingerprint_description);
- textView.setText(getString(R.string.sf__fingerprint_description, SalesforceSDKManager.getInstance().provideAppName()));
-
- getDialog().setCanceledOnTouchOutside(false);
- return v;
- }
+ Bundle savedInstanceState) { return null; }
- public void setContext(PasscodeActivity ctx) {
- mContext = ctx;
- }
+ public void setContext(PasscodeActivity ctx) { }
}
diff --git a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/OAuthWebviewHelper.java b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/OAuthWebviewHelper.java
index f5234fe7..a24ed6ee 100644
--- a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/OAuthWebviewHelper.java
+++ b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/OAuthWebviewHelper.java
@@ -70,7 +70,7 @@
import com.salesforce.androidsdk.push.PushMessaging;
import com.salesforce.androidsdk.rest.ClientManager;
import com.salesforce.androidsdk.rest.ClientManager.LoginOptions;
-import com.salesforce.androidsdk.security.PasscodeManager;
+import com.salesforce.androidsdk.security.ScreenLockManager;
import com.salesforce.androidsdk.util.EventsObservable;
import com.salesforce.androidsdk.util.EventsObservable.EventType;
import com.salesforce.androidsdk.util.MapUtil;
@@ -620,25 +620,8 @@ protected void onPostExecute(OAuth2.TokenEndpointResponse tr) {
// Save the user account
addAccount(account);
- // Screen lock required by mobile policy.
- if (id.screenLockTimeout > 0) {
-
- // Stores the mobile policy for the org.
- final PasscodeManager passcodeManager = mgr.getPasscodeManager();
- passcodeManager.storeMobilePolicyForOrg(account, id.screenLockTimeout * 1000 * 60, id.pinLength, id.biometricUnlockAllowed);
- passcodeManager.setTimeoutMs(id.screenLockTimeout * 1000 * 60);
- // NB setPasscodeLength(...)
- // If there was a passcode and the length is increased, the passcode manager will remember that a passcode change is required
- // The next SalesforceActivity to resume, will cause the locking screen to popup in passcode change mode
- passcodeManager.setPasscodeLength((Activity) getContext(), id.pinLength);
- passcodeManager.setBiometricAllowed((Activity) getContext(), id.biometricUnlockAllowed);
- }
-
- // No screen lock required or no mobile policy specified.
- else {
- final PasscodeManager passcodeManager = mgr.getPasscodeManager();
- passcodeManager.storeMobilePolicyForOrg(account, 0, PasscodeManager.MIN_PASSCODE_LENGTH , true);
- }
+ final ScreenLockManager screenLockManager = mgr.getScreenLockManager();
+ screenLockManager.storeMobilePolicy(account, id.mobilePolicy);
// All done
callback.finish(account);
diff --git a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/PasscodeActivity.java b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/PasscodeActivity.java
index 3e1697fd..095a818b 100644
--- a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/PasscodeActivity.java
+++ b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/PasscodeActivity.java
@@ -26,64 +26,19 @@
*/
package com.salesforce.androidsdk.ui;
-import android.Manifest;
-import android.Manifest.permission;
-import android.annotation.TargetApi;
import android.app.Activity;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.pm.PackageManager;
-import android.hardware.biometrics.BiometricManager;
-import android.hardware.biometrics.BiometricPrompt;
-import android.hardware.fingerprint.FingerprintManager;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
import android.os.Bundle;
-import android.os.CancellationSignal;
-import android.text.Editable;
-import android.text.TextWatcher;
import android.view.KeyEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.Button;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.salesforce.androidsdk.R;
-import com.salesforce.androidsdk.accounts.UserAccount;
-import com.salesforce.androidsdk.accounts.UserAccountManager;
-import com.salesforce.androidsdk.app.SalesforceSDKManager;
-import com.salesforce.androidsdk.security.PasscodeManager;
-
-import java.util.List;
/**
* 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 {
- private static final String EXTRA_KEY = "input_text";
protected static final int MAX_PASSCODE_ATTEMPTS = 10;
-
- final private int REQUEST_CODE_ASK_PERMISSIONS = 11;
-
- private PasscodeMode currentMode;
- private TextView title, instr, bioInstrTitle, bioInstr;
- private PasscodeField passcodeField;
- private LinearLayout passcodeBox, biometricBox;
- private Button logoutButton, notNowButton, enableButton, verifyButton;
- private View fingerImage, faceImage;
- private PasscodeManager passcodeManager;
- private String firstPasscode, biometricTitle, biometricDescription;
- private boolean logoutEnabled;
- private boolean forceBiometric;
- private BiometricType biometricType = BiometricType.Fingerprint;
-
public enum PasscodeMode {
Create,
CreateConfirm,
@@ -93,584 +48,44 @@ public enum PasscodeMode {
BiometricCheck
}
- private enum BiometricType {
- Fingerprint,
- FaceUnlock,
- Iris
- }
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- passcodeManager = SalesforceSDKManager.getInstance().getPasscodeManager();
- boolean isDarkTheme = SalesforceSDKManager.getInstance().isDarkTheme();
- setTheme(isDarkTheme ? R.style.SalesforceSDK_Passcode_Dark : R.style.SalesforceSDK_Passcode);
-
- // This makes the navigation bar visible on light themes.
- SalesforceSDKManager.getInstance().setViewNavigationVisibility(this);
-
- // Protect against screenshots.
- getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
- WindowManager.LayoutParams.FLAG_SECURE);
- setContentView(R.layout.sf__passcode);
-
- title = findViewById(R.id.sf__passcode_title);
- instr = findViewById(R.id.sf__passcode_instructions);
- passcodeField = findViewById(R.id.sf__passcode_text);
- passcodeField.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- final String passcode = s.toString();
- if (passcodeManager.getPasscodeLengthKnown() && passcode.length() == passcodeManager.getPasscodeLength()) {
- onSubmit(passcode);
- }
- }
-
- @Override
- public void afterTextChanged(Editable s) { }
- });
- if (passcodeManager.getPasscodeLengthKnown()) {
- passcodeField.setHint(getString(R.string.sf__accessibility_passcode_length_hint, passcodeManager.getPasscodeLength()));
- }
- passcodeBox = findViewById(R.id.sf__passcode_box);
- logoutButton = findViewById(R.id.sf__passcode_logout_button);
- logoutButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- signoutAllUsers();
- }
- });
- verifyButton = findViewById(R.id.sf__passcode_verify_button);
- verifyButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- Editable passcode = passcodeField.getText();
- if (passcode != null) {
- onSubmit(passcode.toString());
- }
- }
- });
-
- fingerImage = findViewById(R.id.sf__fingerprint_icon);
- faceImage = findViewById(R.id.sf__face_unlock_icon);
- bioInstrTitle = findViewById(R.id.sf__biometric_instructions_title);
- bioInstr = findViewById(R.id.sf__biometric_instructions);
- bioInstr.setText(getString(R.string.sf__biometric_allow_instructions, SalesforceSDKManager.getInstance().provideAppName()));
- passcodeField.announceForAccessibility(bioInstrTitle.getText());
- biometricBox = findViewById(R.id.sf__biometric_box);
- notNowButton = findViewById(R.id.sf__biometric_not_now_button);
- notNowButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- biometricDeclined();
- }
- });
- enableButton = findViewById(R.id.sf__biometric_enable_button);
- enableButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- launchBiometricAuth();
- }
- });
-
- clearUi();
- // Asking passcode manager is a change passcode flow is required
- if (passcodeManager.isPasscodeChangeRequired()) {
- setMode(PasscodeMode.Change);
- } else {
- if (passcodeManager.hasStoredPasscode(this)) {
- PasscodeMode mode = passcodeManager.biometricEnabled() ? PasscodeMode.BiometricCheck : PasscodeMode.Check;
- setMode(mode);
- } else {
- setMode(PasscodeMode.Create);
- }
- }
- logoutEnabled = true;
- forceBiometric = false;
- if (savedInstanceState != null) {
- final String inputText = savedInstanceState.getString(EXTRA_KEY);
- if (passcodeField != null && inputText != null) {
- passcodeField.setText(inputText.trim());
- }
- }
-
- // Determine biometric hardware, default is fingerprint.
- // TODO: Remove check when min API >= 29
- if (VERSION.SDK_INT >= VERSION_CODES.Q) {
- if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
- biometricType = BiometricType.FaceUnlock;
- } else if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_IRIS)) {
- biometricType = BiometricType.Iris;
- }
- }
}
- protected void biometricDeclined() {
- if (passcodeManager.biometricEnabled()) {
- setMode(PasscodeMode.Check);
- } else {
- passcodeManager.setBiometricEnabled(PasscodeActivity.this, false);
- passcodeManager.unlock();
- done();
- }
- }
+ protected void biometricDeclined() { }
@Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- moveTaskToBack(true);
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }
+ public boolean onKeyDown(int keyCode, KeyEvent event) { return false; }
/**
* Saves the entered text before activity rotation.
*/
@Override
- protected void onSaveInstanceState(Bundle savedInstance) {
- if (passcodeField != null && passcodeField.getText() != null) {
- savedInstance.putString(EXTRA_KEY, passcodeField.getText().toString());
- }
- }
+ protected void onSaveInstanceState(Bundle savedInstance) { }
public PasscodeMode getMode() {
- return currentMode;
+ return PasscodeMode.Create;
}
- public void setMode(PasscodeMode newMode) {
- if (newMode == currentMode) return;
- if (newMode == PasscodeMode.EnableBiometric && !canShowBiometric()) {
- return;
- }
- if (newMode == PasscodeMode.BiometricCheck && !canShowBiometric()) {
- newMode = PasscodeMode.Check;
- }
-
- clearUi();
- switch(newMode) {
- case Check:
- title.setText(getString(R.string.sf__passcode_enter_title));
- title.setVisibility(View.VISIBLE);
- instr.setText(getString(R.string.sf__passcode_enter_instructions));
- instr.setVisibility(View.VISIBLE);
- passcodeBox.setVisibility(View.VISIBLE);
- passcodeField.setVisibility(View.VISIBLE);
- passcodeField.requestFocus();
-
- if (!passcodeManager.getPasscodeLengthKnown()) {
- verifyButton.setVisibility(View.VISIBLE);
- }
- showKeyboard();
- sendAccessibilityEvent(instr.getText().toString());
- break;
- case Create:
- title.setText(getString(R.string.sf__passcode_create_title));
- title.setVisibility(View.VISIBLE);
- // Check if passcodes did not match
- int instructionText = (currentMode == PasscodeMode.CreateConfirm) ? R.string.sf__passcodes_dont_match
- : R.string.sf__passcode_create_instructions;
- instr.setText(getString(instructionText));
- instr.setVisibility(View.VISIBLE);
- passcodeBox.setVisibility(View.VISIBLE);
- passcodeField.setVisibility(View.VISIBLE);
- passcodeField.requestFocus();
- showKeyboard();
- sendAccessibilityEvent(instr.getText().toString());
- break;
- case CreateConfirm:
- title.setText(getString(R.string.sf__passcode_confirm_title));
- title.setVisibility(View.VISIBLE);
- instr.setText(getString(R.string.sf__passcode_confirm_instructions));
- instr.setVisibility(View.VISIBLE);
- passcodeBox.setVisibility(View.VISIBLE);
- passcodeField.setVisibility(View.VISIBLE);
- passcodeField.requestFocus();
- showKeyboard();
- sendAccessibilityEvent(instr.getText().toString());
- break;
- case Change:
- title.setText(getString(R.string.sf__passcode_change_title));
- title.setVisibility(View.VISIBLE);
- instr.setText(getString(R.string.sf__passcode_change_instructions));
- instr.setVisibility(View.VISIBLE);
- passcodeBox.setVisibility(View.VISIBLE);
- passcodeField.setVisibility(View.VISIBLE);
- passcodeField.requestFocus();
- showKeyboard();
- sendAccessibilityEvent(instr.getText().toString());
- break;
- case EnableBiometric:
- switch(biometricType) {
- case FaceUnlock:
- title.setText(getString(R.string.sf__biometric_face_title));
- bioInstrTitle.setText(getString(R.string.sf__biometric_face_allow_instructions_title));
- faceImage.setVisibility(View.VISIBLE);
- break;
- case Iris:
- title.setText(getString(R.string.sf__biometric_iris_title));
- bioInstrTitle.setText(getString(R.string.sf__biometric_iris_allow_instructions_title));
- faceImage.setVisibility(View.VISIBLE);
- break;
- case Fingerprint:
- title.setText(getString(R.string.sf__biometric_fingerprint_title));
- bioInstrTitle.setText(getString(R.string.sf__biometric_fingerprint_allow_instructions_title));
- fingerImage.setVisibility(View.VISIBLE);
- }
-
- hideKeyboard();
- title.setVisibility(View.VISIBLE);
- biometricBox.setVisibility(View.VISIBLE);
- bioInstrTitle.setVisibility(View.VISIBLE);
- sendAccessibilityEvent(bioInstrTitle.getText().toString());
- bioInstr.setVisibility(View.VISIBLE);
- notNowButton.setVisibility(View.VISIBLE);
- enableButton.setVisibility(View.VISIBLE);
- passcodeManager.setBiometricEnrollmentShown(this, true);
- break;
- case BiometricCheck:
- hideKeyboard();
- launchBiometricAuth();
- }
- passcodeField.setText("");
- currentMode = newMode;
- }
+ 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) {
- logoutEnabled = b;
- }
+ 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) {
- forceBiometric = b;
- }
-
- protected boolean onSubmit(String enteredPasscode) {
- boolean showBiometricEnrollment = !passcodeManager.biometricEnabled() &&
- !passcodeManager.biometricEnrollmentShown() &&
- passcodeManager.biometricAllowed() &&
- canShowBiometric();
-
- switch (getMode()) {
- case Create:
- case Change:
- firstPasscode = enteredPasscode;
- setMode(PasscodeMode.CreateConfirm);
- return true;
-
- case CreateConfirm:
- if (enteredPasscode.equals(firstPasscode)) {
- passcodeManager.store(this, enteredPasscode);
-
- if (showBiometricEnrollment) {
- setMode(PasscodeMode.EnableBiometric);
- } else {
- passcodeManager.unlock();
- done();
- }
- } else {
- setMode(PasscodeMode.Create);
- }
- return true;
-
- case Check:
- if (passcodeManager.check(this, enteredPasscode)) {
- sendAccessibilityEvent(getString(R.string.sf__accessibility_unlock_announcement));
- if (!passcodeManager.getPasscodeLengthKnown()) {
- passcodeManager.setPasscodeLength(this, enteredPasscode.length());
- }
-
- if (showBiometricEnrollment) {
- setMode(PasscodeMode.EnableBiometric);
- } else {
- passcodeManager.unlock();
- done();
- }
- } else {
- logoutButton.setVisibility(View.VISIBLE);
- int attempts = passcodeManager.addFailedPasscodeAttempt();
- passcodeField.setText("");
- int maxAttempts = getMaxPasscodeAttempts();
- if (attempts < maxAttempts - 1) {
- instr.setText(getString(R.string.sf__passcode_try_again, (maxAttempts - attempts)));
- sendAccessibilityEvent(instr.getText().toString());
- } else if (attempts < maxAttempts) {
- instr.setText(getString(R.string.sf__passcode_final));
- sendAccessibilityEvent(instr.getText().toString());
- } else {
- signoutAllUsers();
- }
- }
- return true;
- }
- return false;
- }
-
- protected void done() {
- setResult(RESULT_OK);
- finish();
- }
-
- /**
- * @return maximum number of passcode attempts
- */
- protected int getMaxPasscodeAttempts() {
- return MAX_PASSCODE_ATTEMPTS;
- }
-
- private void signoutAllUsers() {
- passcodeManager.reset(this);
- sendAccessibilityEvent(getString(R.string.sf__accessibility_logged_out_announcement));
-
- // Used for tests
- if (!logoutEnabled) {
- return;
- }
-
- final UserAccountManager userAccMgr = SalesforceSDKManager.getInstance().getUserAccountManager();
- final List userAccounts = userAccMgr.getAuthenticatedUsers();
-
- /*
- * If the user forgot his/her passcode, we log all the authenticated
- * users out. All the existing accounts except the last account
- * are removed without dismissing the PasscodeActivity. The last
- * account is removed, after which the PasscodeActivity is dismissed,
- * and the login page is brought up at this point.
- */
- if (userAccounts != null) {
- int numAccounts = userAccounts.size();
- if (numAccounts > 0) {
- for (int i = 0; i < numAccounts - 1; i++) {
- final UserAccount account = userAccounts.get(i);
- userAccMgr.signoutUser(account, null, false);
- }
- final UserAccount lastAccount = userAccounts.get(numAccounts - 1);
- userAccMgr.signoutUser(lastAccount, PasscodeActivity.this);
- }
- } else {
- userAccMgr.signoutCurrentUser(PasscodeActivity.this);
- }
- }
-
- /**
- * Displays the fingerprint dialog. This can be overridden to provide
- * a custom fingerprint auth layout if the app chooses to do so.
- */
- protected void showFingerprintDialog() {
- final FingerprintAuthDialogFragment fingerprintAuthDialog = new FingerprintAuthDialogFragment();
- fingerprintAuthDialog.setContext(this);
- fingerprintAuthDialog.show(getFragmentManager(), "fingerprintDialog");
- }
-
- /**
- * Displays the dialog provided by the OS for biometric authentication
- * using {@link BiometricPrompt}.
- */
- @TargetApi(VERSION_CODES.P)
- protected void showBiometricDialog() {
- // TODO: Remove this check once minAPI >= 28.
- if (VERSION.SDK_INT >= VERSION_CODES.P) {
- switch(biometricType) {
- case FaceUnlock:
- biometricTitle = getString(R.string.sf__face_unlock_title);
- biometricDescription = getString(R.string.sf__face_unlock_description,
- SalesforceSDKManager.getInstance().provideAppName());
- break;
- case Iris:
- biometricTitle = getString(R.string.sf__iris_title);
- biometricDescription = getString(R.string.sf__iris_description,
- SalesforceSDKManager.getInstance().provideAppName());
- break;
- case Fingerprint:
- biometricTitle = getString(R.string.sf__fingerprint_title);
- biometricDescription = getString(R.string.sf__fingerprint_description,
- SalesforceSDKManager.getInstance().provideAppName());
- }
-
- final BiometricPrompt.Builder bioBuilder = new BiometricPrompt.Builder(this);
- bioBuilder.setDescription(biometricDescription);
- bioBuilder.setTitle(biometricTitle);
- bioBuilder.setNegativeButton(getString(R.string.sf__biometric_cancel), getMainExecutor(),
- new DialogInterface.OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- biometricDeclined();
- }
- });
-
- final BiometricPrompt bioPrompt = bioBuilder.build();
- bioPrompt.authenticate(new CancellationSignal(), getMainExecutor(),
- new BiometricPrompt.AuthenticationCallback() {
-
- @Override
- public void onAuthenticationError(int errorCode, CharSequence errString) {
- super.onAuthenticationError(errorCode, errString);
- biometricDeclined();
- }
-
- @Override
- public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
- super.onAuthenticationHelp(helpCode, helpString);
- }
-
- @Override
- public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
- unlockViaFingerprintScan();
- }
-
- @Override
- public void onAuthenticationFailed() {
- super.onAuthenticationFailed();
- }
- });
- }
- }
-
- @TargetApi(VERSION_CODES.Q)
- private boolean isBiometricEnabled() {
- // Used for tests
- if (forceBiometric) {
- return true;
- }
-
- // TODO: Remove this check once minAPI >= 29.
- if (VERSION.SDK_INT >= VERSION_CODES.Q) {
- if (checkSelfPermission(permission.USE_BIOMETRIC) != PackageManager.PERMISSION_GRANTED) {
- requestPermissions(new String[]{permission.USE_BIOMETRIC}, REQUEST_CODE_ASK_PERMISSIONS);
- } else {
- return canAuth();
- }
- }
- return false;
- }
-
- /*
- * TODO: Remove this annotation once minAPI >= 29.
- */
- @TargetApi(29)
- private boolean canAuth() {
- final BiometricManager biometricManager = (BiometricManager) this.getSystemService(Context.BIOMETRIC_SERVICE);
- if (biometricManager != null) {
- if (VERSION.SDK_INT >= VERSION_CODES.R) {
- return BiometricManager.BIOMETRIC_SUCCESS == biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK
- | BiometricManager.Authenticators.BIOMETRIC_STRONG);
- } else {
- return BiometricManager.BIOMETRIC_SUCCESS == biometricManager.canAuthenticate();
- }
- }
- return false;
- }
-
- private boolean isFingerprintEnabled() {
- // Used for tests
- if (forceBiometric) {
- return true;
- }
- final FingerprintManager fingerprintManager = (FingerprintManager) this.getSystemService(Context.FINGERPRINT_SERVICE);
-
- // Here, this activity is the current activity.
- if (checkSelfPermission(Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
- requestPermissions(new String[]{ permission.USE_FINGERPRINT}, REQUEST_CODE_ASK_PERMISSIONS);
- } else {
- return fingerprintManager != null && fingerprintManager.isHardwareDetected()
- && fingerprintManager.hasEnrolledFingerprints();
- }
- return false;
- }
+ public void forceBiometric(boolean b) { }
@Override
- public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
- if (requestCode == REQUEST_CODE_ASK_PERMISSIONS && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- launchBiometricAuth();
- return;
- }
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- }
-
- public void unlockViaFingerprintScan() {
- if (!passcodeManager.biometricEnabled()) {
- passcodeManager.setBiometricEnabled(this, true);
- }
- passcodeManager.unlock();
- done();
- }
-
- private boolean canShowBiometric() {
- boolean bioEnabled = (VERSION.SDK_INT >= VERSION_CODES.Q ? isBiometricEnabled() : isFingerprintEnabled());
- return passcodeManager.biometricAllowed() && bioEnabled;
- }
-
- private void launchBiometricAuth() {
- if (passcodeManager != null && canShowBiometric()) {
- // TODO: Remove this check once minAPI >= 28.
- if (VERSION.SDK_INT >= VERSION_CODES.P) {
- showBiometricDialog();
- } else if (VERSION.SDK_INT >= VERSION_CODES.M) {
- showFingerprintDialog();
- }
- }
- }
-
- private void clearUi() {
- title.setVisibility(View.GONE);
- instr.setVisibility(View.GONE);
- passcodeField.setVisibility(View.GONE);
- passcodeBox.setVisibility(View.GONE);
- logoutButton.setVisibility(View.GONE);
- verifyButton.setVisibility(View.GONE);
- bioInstrTitle.setVisibility(View.GONE);
- bioInstr.setVisibility(View.GONE);
- notNowButton.setVisibility(View.GONE);
- enableButton.setVisibility(View.GONE);
- biometricBox.setVisibility(View.GONE);
- fingerImage.setVisibility(View.GONE);
- faceImage.setVisibility(View.GONE);
- }
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { }
- private void hideKeyboard() {
- InputMethodManager imm = (InputMethodManager) this.getSystemService(Activity.INPUT_METHOD_SERVICE);
- if (imm != null && this.passcodeField != null) {
- imm.hideSoftInputFromWindow(this.passcodeField.getWindowToken(), 0);
- }
- }
-
- private void showKeyboard() {
- AccessibilityManager am = (AccessibilityManager) this.getSystemService(Context.ACCESSIBILITY_SERVICE);
- if (am != null && am.isEnabled()) {
- // Check if keyboard is shown based on verify button, which is oriented to the bottom of
- // the layout. Checking window instead of screen even works for split screen.
- int[] location = new int[2];
- verifyButton.getLocationInWindow(location);
- if (location[1] == 0) {
- passcodeField.requestFocus();
- }
- } else {
- passcodeField.requestFocus();
- }
- }
-
- private void sendAccessibilityEvent(String text) {
- AccessibilityManager am = (AccessibilityManager) this.getSystemService(Context.ACCESSIBILITY_SERVICE);
- if (am != null && am.isEnabled()) {
- AccessibilityEvent event = AccessibilityEvent.obtain();
- event.setEventType(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- event.setClassName(getClass().getName());
- event.setPackageName(this.getPackageName());
- event.getText().add(text);
- am.sendAccessibilityEvent(event);
- }
- }
+ public void unlockViaFingerprintScan() { }
}
\ No newline at end of file
diff --git a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/PasscodeField.java b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/PasscodeField.java
index ff08f6ab..47d338d5 100644
--- a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/PasscodeField.java
+++ b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/PasscodeField.java
@@ -28,50 +28,30 @@
import android.annotation.SuppressLint;
import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
import android.util.AttributeSet;
-import android.view.ActionMode;
-import android.view.Menu;
-import android.view.MenuItem;
import android.widget.EditText;
-import com.salesforce.androidsdk.R;
-import com.salesforce.androidsdk.app.SalesforceSDKManager;
-import com.salesforce.androidsdk.security.PasscodeManager;
+/**
+ * @deprecated Will be removed in Mobile SDK 10.0.
+ */
@SuppressLint("AppCompatCustomView")
public class PasscodeField extends EditText {
- private static final int MAX_PASSCODE_LENGTH = 8;
- private static final int CIRCLE_DIAMETER = 22;
- private static final int LINE_WIDTH = 2;
- private static final int DEFAULT_PADDING = 20;
- private static final int CIRCLE_SPACING = 16;
-
/**
* {@inheritDoc}
*/
- public PasscodeField(Context context) {
- super(context);
- disableActions();
- }
+ public PasscodeField(Context context) { super(context); }
/**
* {@inheritDoc}
*/
- public PasscodeField(Context context, AttributeSet attrs) {
- super(context, attrs);
- disableActions();
- }
+ public PasscodeField(Context context, AttributeSet attrs) { super(context, attrs); }
/**
* {@inheritDoc}
*/
- public PasscodeField(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- disableActions();
- }
+ 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.
@@ -79,17 +59,7 @@ public PasscodeField(Context context, AttributeSet attrs, int defStyleAttr) {
* {@inheritDoc}
*/
@Override
- public void onSelectionChanged(int start, int end) {
- CharSequence text = getText();
- if (text != null) {
- if (start != text.length() || end != text.length()) {
- setSelection(text.length(), text.length());
- return;
- }
- }
-
- super.onSelectionChanged(start, end);
- }
+ public void onSelectionChanged(int start, int end) { super.onSelectionChanged(start, end); }
/**
* Override provided to disable suggestions.
@@ -100,81 +70,4 @@ public void onSelectionChanged(int start, int end) {
public boolean isSuggestionsEnabled() {
return false;
}
-
-
- /**
- * Override provided to draw the passcode field UI element instead of the standard EditText
- *
- * @param canvas the provided canvas
- */
- @Override
- @SuppressLint("DrawAllocation") // For Paint
- protected void onDraw(Canvas canvas) {
- PasscodeManager passcodeManager = SalesforceSDKManager.getInstance().getPasscodeManager();
- boolean passcodeLengthKnown = passcodeManager.getPasscodeLengthKnown();
- int passcodeLength = passcodeManager.getPasscodeLength();
-
- float density = getResources().getDisplayMetrics().density;
- float diameter = CIRCLE_DIAMETER * density;
- float lineWidth = LINE_WIDTH * density;
- float padding = DEFAULT_PADDING * density;
- float spacing = CIRCLE_SPACING * density;
-
- Paint openCirclePaint = new Paint();
- Paint typedCirclePaint = new Paint();
-
- int circleColor = getResources().getColor(R.color.sf__primary_color);
- openCirclePaint.setColor(circleColor);
- openCirclePaint.setStyle(Paint.Style.STROKE);
- openCirclePaint.setStrokeWidth(lineWidth);
- typedCirclePaint.setColor(circleColor);
-
- int circleSpacing = 0;
- int lengthForSpacing = passcodeLengthKnown ? passcodeLength : MAX_PASSCODE_LENGTH;
- float yPosition = getHeight()/2f;
- float startX = (getWidth() - (diameter * lengthForSpacing) - (spacing * (lengthForSpacing - 1)) + (lineWidth * lengthForSpacing * 2)) / 2;
-
- // If passcode length is unknown (upgrade) don't draw open circles and left align
- if (passcodeLengthKnown) {
- for (int count = 0; count < passcodeLength; count++) {
- canvas.drawCircle(startX + circleSpacing, yPosition, diameter/2, openCirclePaint);
- circleSpacing += diameter + spacing;
- }
- } else {
- startX = (startX > padding) ? padding : startX;
- }
-
- circleSpacing = 0;
- for (int count = 0; count < getText().length(); count++) {
- canvas.drawCircle(startX + circleSpacing, yPosition, diameter/2, typedCirclePaint);
- circleSpacing += diameter + spacing;
- }
- }
-
- /**
- * Overrides the Custom Insert Action callbacks to disable all actions, such as select and paste.
- */
- private void disableActions() {
- this.setCustomInsertionActionModeCallback(new ActionMode.Callback() {
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- return false;
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- return false;
- }
-
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- return false;
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {
-
- }
- });
- }
}
diff --git a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/SalesforceActivityDelegate.java b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/SalesforceActivityDelegate.java
index fe40f21f..f8bd0de2 100644
--- a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/SalesforceActivityDelegate.java
+++ b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/SalesforceActivityDelegate.java
@@ -34,7 +34,7 @@
import com.salesforce.androidsdk.app.SalesforceSDKManager;
import com.salesforce.androidsdk.rest.ClientManager;
import com.salesforce.androidsdk.rest.RestClient;
-import com.salesforce.androidsdk.security.PasscodeManager;
+import com.salesforce.androidsdk.security.ScreenLockManager;
import com.salesforce.androidsdk.util.EventsObservable;
import com.salesforce.androidsdk.util.LogoutCompleteReceiver;
import com.salesforce.androidsdk.util.UserSwitchReceiver;
@@ -45,8 +45,8 @@
public class SalesforceActivityDelegate {
- private Activity activity;
- private PasscodeManager passcodeManager;
+ private final Activity activity;
+ private ScreenLockManager screenLockManager;
private UserSwitchReceiver userSwitchReceiver;
private LogoutCompleteReceiver logoutCompleteReceiver;
@@ -56,8 +56,7 @@ public SalesforceActivityDelegate(Activity activity) {
}
public void onCreate() {
- // Gets an instance of the passcode manager.
- passcodeManager = SalesforceSDKManager.getInstance().getPasscodeManager();
+ screenLockManager = SalesforceSDKManager.getInstance().getScreenLockManager();
userSwitchReceiver = new ActivityUserSwitchReceiver();
activity.registerReceiver(userSwitchReceiver, new IntentFilter(UserAccountManager.USER_SWITCH_INTENT_ACTION));
logoutCompleteReceiver = new ActivityLogoutCompleteReceiver();
@@ -68,54 +67,51 @@ public void onCreate() {
}
/**
- * Brings up passcode screen if needed
+ * Brings up ScreenLock if needed
* Build RestClient if requested and then calls activity.onResume(restClient)
* Otherwise calls activity.onResume(null)
*
* @param buildRestClient
*/
public void onResume(boolean buildRestClient) {
- // Brings up the passcode screen if needed.
- if (passcodeManager.onResume(activity)) {
- if (buildRestClient) {
- // Gets login options.
- final String accountType = SalesforceSDKManager.getInstance().getAccountType();
- final ClientManager.LoginOptions loginOptions = SalesforceSDKManager.getInstance().getLoginOptions();
-
- // Gets a rest client.
- new ClientManager(
- SalesforceSDKManager.getInstance().getAppContext(),
- accountType,
- loginOptions,
- SalesforceSDKManager.getInstance().shouldLogoutWhenTokenRevoked()
- ).getRestClient(activity, new ClientManager.RestClientCallback() {
-
- @Override
- public void authenticatedRestClient(RestClient client) {
- if (client == null) {
- SalesforceSDKManager.getInstance().logout(activity);
- return;
- }
- ((SalesforceActivityInterface) activity).onResume(client);
-
- // Lets observers know that rendition is complete.
- EventsObservable.get().notifyEvent(EventsObservable.EventType.RenditionComplete);
+ // Brings up the ScreenLock if needed.
+ if (buildRestClient) {
+ // Gets login options.
+ final String accountType = SalesforceSDKManager.getInstance().getAccountType();
+ final ClientManager.LoginOptions loginOptions = SalesforceSDKManager.getInstance().getLoginOptions();
+
+ // Gets a rest client.
+ new ClientManager(
+ SalesforceSDKManager.getInstance().getAppContext(),
+ accountType,
+ loginOptions,
+ SalesforceSDKManager.getInstance().shouldLogoutWhenTokenRevoked()
+ ).getRestClient(activity, new ClientManager.RestClientCallback() {
+
+ @Override
+ public void authenticatedRestClient(RestClient client) {
+ if (client == null) {
+ SalesforceSDKManager.getInstance().logout(activity);
+ return;
}
- });
- }
- else {
- ((SalesforceActivityInterface) activity).onResume(null);
- }
+ ((SalesforceActivityInterface) activity).onResume(client);
+
+ // Lets observers know that rendition is complete.
+ EventsObservable.get().notifyEvent(EventsObservable.EventType.RenditionComplete);
+ }
+ });
+ }
+ else {
+ ((SalesforceActivityInterface) activity).onResume(null);
}
}
- public void onUserInteraction() {
- passcodeManager.recordUserInteraction();
- }
+ /*
+ * @deprecated Will be removed in Mobile SDK 10.0.
+ */
+ public void onUserInteraction() { }
- public void onPause() {
- passcodeManager.onPause(activity);
- }
+ public void onPause() { }
public void onDestroy() {
activity.unregisterReceiver(userSwitchReceiver);
diff --git a/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/ScreenLockActivity.java b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/ScreenLockActivity.java
new file mode 100644
index 00000000..cbb89cc6
--- /dev/null
+++ b/src/android/libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/ScreenLockActivity.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) 2021-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 static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG;
+import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK;
+import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
+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;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+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;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.biometric.BiometricManager;
+import androidx.biometric.BiometricPrompt;
+import androidx.core.content.ContextCompat;
+import androidx.core.content.res.ResourcesCompat;
+import androidx.fragment.app.FragmentActivity;
+
+import com.salesforce.androidsdk.R;
+import com.salesforce.androidsdk.accounts.UserAccount;
+import com.salesforce.androidsdk.accounts.UserAccountManager;
+import com.salesforce.androidsdk.app.SalesforceSDKManager;
+import com.salesforce.androidsdk.util.SalesforceSDKLogger;
+
+import java.util.List;
+
+/**
+ * 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();
+ private TextView errorMessage;
+ private Button logoutButton;
+ private Button actionButton;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // Protect against screenshots.
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
+ WindowManager.LayoutParams.FLAG_SECURE);
+ boolean isDarkTheme = SalesforceSDKManager.getInstance().isDarkTheme();
+ setTheme(isDarkTheme ? R.style.SalesforceSDK_ScreenLock_Dark : R.style.SalesforceSDK_ScreenLock);
+ // Makes the navigation bar visible on light themes.
+ SalesforceSDKManager.getInstance().setViewNavigationVisibility(this);
+ setContentView(R.layout.sf__screen_lock);
+
+ errorMessage = findViewById(R.id.sf__screen_lock_error_message);
+ logoutButton = findViewById(R.id.sf__screen_lock_logout_button);
+ logoutButton.setOnClickListener(v -> logoutScreenLockUsers());
+ actionButton = findViewById(R.id.sf__screen_action_button);
+ ImageView appIcon = findViewById(R.id.sf__app_icon);
+
+ try {
+ Drawable icon = getPackageManager().getApplicationIcon(getApplicationInfo().packageName);
+ appIcon.setImageDrawable(icon);
+ } catch (PackageManager.NameNotFoundException e) {
+ SalesforceSDKLogger.e(TAG, "Unable to retrieve host app icon. NameNotFoundException: " + e.getMessage());
+ appIcon.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.sf__salesforce_logo, null));
+ }
+
+ presentAuth();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ /*
+ * presentAuth again after the user has come back from security settings to ensure they
+ * actually set up a secure lock screen (pin/pattern/password/etc) instead of swipe or none.
+ */
+ 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
+ public void onBackPressed() {
+ // purposefully blank
+ }
+
+ private void presentAuth() {
+ BiometricPrompt biometricPrompt = getBiometricPrompt();
+ BiometricManager biometricManager = BiometricManager.from(this);
+
+ switch (biometricManager.canAuthenticate(getAuthenticators())) {
+ case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
+ case BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED:
+ case BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED:
+ case BiometricManager.BIOMETRIC_STATUS_UNKNOWN:
+ // This should never happen.
+ String error = getString(R.string.sf__screen_lock_error);
+ SalesforceSDKLogger.e(TAG, "Biometric manager cannot authenticate. " + error);
+ setErrorMessage(error);
+ break;
+ case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE:
+ setErrorMessage(getString(R.string.sf__screen_lock_error_hw_unavailable));
+ break;
+ case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
+ setErrorMessage(getString(R.string.sf__screen_lock_setup_required, appName));
+
+ /*
+ * Prompts the user to setup OS screen lock and biometric.
+ * TODO: Remove when min API > 29.
+ */
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ final Intent biometricIntent = new Intent(Settings.ACTION_BIOMETRIC_ENROLL);
+ biometricIntent.putExtra(Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, getAuthenticators());
+ actionButton.setOnClickListener(v -> startActivityForResult(biometricIntent, SETUP_REQUEST_CODE));
+ } else {
+ final Intent lockScreenIntent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
+ actionButton.setOnClickListener(v -> startActivityForResult(lockScreenIntent, SETUP_REQUEST_CODE));
+ }
+ actionButton.setText(getString(R.string.sf__screen_lock_setup_button));
+ actionButton.setVisibility(View.VISIBLE);
+ 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());
+ }
+ break;
+ }
+ }
+
+ private BiometricPrompt.PromptInfo getPromptInfo() {
+ return new BiometricPrompt.PromptInfo.Builder()
+ .setTitle(getString(R.string.sf__screen_lock_title, appName))
+ .setSubtitle(getString(R.string.sf__screen_lock_subtitle, appName))
+ .setAllowedAuthenticators(getAuthenticators())
+ .setConfirmationRequired(false)
+ .build();
+ }
+
+ private BiometricPrompt getBiometricPrompt() {
+ return new BiometricPrompt(this, ContextCompat.getMainExecutor(this),
+ new BiometricPrompt.AuthenticationCallback() {
+ @Override
+ public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
+ super.onAuthenticationError(errorCode, errString);
+ onAuthError(errString);
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
+ super.onAuthenticationSucceeded(result);
+ finishSuccess();
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+ super.onAuthenticationFailed();
+ setErrorMessage(getString(R.string.sf__screen_lock_auth_failed));
+ sendAccessibilityEvent(getString(R.string.sf__screen_lock_auth_failed));
+ }
+ });
+ }
+
+ private void onAuthError(CharSequence errString) {
+ String authError = getString(R.string.sf__screen_lock_auth_error);
+
+ if (errString.length() == 0) {
+ errString = authError;
+ }
+ setErrorMessage(errString.toString());
+ sendAccessibilityEvent(authError);
+
+ actionButton.setVisibility(View.VISIBLE);
+ actionButton.setText(getString(R.string.sf__screen_lock_retry_button));
+ actionButton.setOnClickListener(v -> presentAuth());
+ }
+
+ private void finishSuccess() {
+ resetUI();
+ sendAccessibilityEvent(getString(R.string.sf__screen_lock_auth_success));
+ SalesforceSDKManager.getInstance().getScreenLockManager().unlock();
+ finish();
+ }
+
+ private int getAuthenticators() {
+ // TODO: Remove when min API > 29.
+ return (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
+ ? BIOMETRIC_STRONG | DEVICE_CREDENTIAL
+ : BIOMETRIC_WEAK | DEVICE_CREDENTIAL;
+ }
+
+ private void logoutScreenLockUsers() {
+ final UserAccountManager manager = SalesforceSDKManager.getInstance().getUserAccountManager();
+ final List accounts = manager.getAuthenticatedUsers();
+ Context ctx = SalesforceSDKManager.getInstance().getAppContext();
+
+ if (accounts != null) {
+ for (UserAccount account : accounts) {
+ SharedPreferences accountPrefs = ctx.getSharedPreferences(MOBILE_POLICY_PREF
+ + account.getUserLevelFilenameSuffix(), Context.MODE_PRIVATE);
+ if (accountPrefs.getBoolean(SCREEN_LOCK, false)) {
+ manager.signoutUser(account, null);
+ }
+ }
+ }
+
+ sendAccessibilityEvent("You are logged out.");
+ finish();
+ }
+
+ private void setErrorMessage(String message) {
+ errorMessage.setText(message);
+ errorMessage.setVisibility(View.VISIBLE);
+ logoutButton.setVisibility(View.VISIBLE);
+ }
+
+ private void resetUI() {
+ logoutButton.setVisibility(View.GONE);
+ errorMessage.setVisibility(View.GONE);
+ actionButton.setVisibility(View.GONE);
+ }
+
+ private void sendAccessibilityEvent(String text) {
+ AccessibilityManager am = (AccessibilityManager) this.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ if (am != null && am.isEnabled()) {
+ AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.setEventType(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ event.setClassName(getClass().getName());
+ event.setPackageName(this.getPackageName());
+ event.getText().add(text);
+ am.sendAccessibilityEvent(event);
+ }
+ }
+}
diff --git a/src/android/libs/SmartStore/build.gradle b/src/android/libs/SmartStore/build.gradle
index 09437e5e..d2553ddc 100644
--- a/src/android/libs/SmartStore/build.gradle
+++ b/src/android/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.2'
+ api 'net.zetetic:android-database-sqlcipher:4.4.3'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test:rules:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
diff --git a/src/android/libs/SmartStore/res/layout/sf__key_value_inspector.xml b/src/android/libs/SmartStore/res/layout/sf__key_value_inspector.xml
index fdfa3fc9..bdad09e4 100644
--- a/src/android/libs/SmartStore/res/layout/sf__key_value_inspector.xml
+++ b/src/android/libs/SmartStore/res/layout/sf__key_value_inspector.xml
@@ -58,7 +58,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="0"
- android:background="@drawable/sf__passcode_primary_color_button"
+ android:background="@drawable/sf__primary_color_button"
android:onClick="onGetValueClick"
android:text="@string/sf__inspector_get_value_button"
android:textAllCaps="false"