diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 44a71234d..19d6c35ff 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -25,7 +25,7 @@ jobs:
uses: actions/setup-java@v3
with:
distribution: zulu
- java-version: 11
+ java-version: 17
- name: Download Libdigidocpp Android (androidarm) artifact
uses: dawidd6/action-download-artifact@v2
with:
@@ -62,11 +62,11 @@ jobs:
run: |
pwd
./gradlew common-lib:updateLibdigidocpp --dir=libdigidocpp-files/
- - name: Setup Gradle and Build
+ - name: Setup Gradle, Test and Build
uses: gradle/gradle-build-action@v2
with:
gradle-version: wrapper
- arguments: clean --no-daemon fetchAndPackageDefaultConfiguration -PappVersionName=${{ env.APP_VERSION_NAME }}.${{ env.BUILD_NUMBER }} assembleRelease --info
+ arguments: clean test --no-daemon fetchAndPackageDefaultConfiguration -PappVersionName=${{ env.APP_VERSION_NAME }}.${{ env.BUILD_NUMBER }} assembleRelease --info
- name: Sign app APK
uses: r0adkll/sign-android-release@v1
id: signed_apk
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index dadf2e6d9..4776e2322 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -5,7 +5,6 @@ on: [push, pull_request]
jobs:
analyze:
name: CodeQL Analyze
- if: contains(github.repository, 'open-eid/MOPP-Android') && contains(github.ref, 'master')
runs-on: ubuntu-latest
permissions:
actions: read
@@ -27,7 +26,7 @@ jobs:
uses: actions/setup-java@v3
with:
distribution: zulu
- java-version: 11
+ java-version: 17
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
diff --git a/app/build.gradle b/app/build.gradle
index 6d01b95ad..e58b84ccf 100755
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -60,8 +60,8 @@ android {
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_11
- targetCompatibility JavaVersion.VERSION_11
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
}
lint {
@@ -82,6 +82,7 @@ dependencies {
implementation "androidx.recyclerview:recyclerview:${androidxRecyclerviewVersion}"
implementation "androidx.cardview:cardview:${androidxCardviewVersion}"
implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"
+ implementation "androidx.work:work-runtime:${androidxWorkRuntime}"
implementation "com.takisoft.preferencex:preferencex:${preferencexVersion}"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index afa8c1fda..9d738aa9d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -14,8 +14,8 @@
+ android:screenOrientation="user">
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/Activity.java b/app/src/main/java/ee/ria/DigiDoc/android/Activity.java
index b4ee6f7d1..ea8b9d123 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/Activity.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/Activity.java
@@ -1,10 +1,10 @@
package ee.ria.DigiDoc.android;
import static ee.ria.DigiDoc.android.Constants.DIR_EXTERNALLY_OPENED_FILES;
+import static ee.ria.DigiDoc.android.utils.IntentUtils.setIntentData;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
-import android.content.ClipData;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
@@ -20,8 +20,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.content.FileProvider;
import androidx.preference.PreferenceManager;
+import androidx.work.WorkManager;
import com.google.android.gms.common.util.CollectionUtils;
import com.google.android.gms.tasks.Task;
@@ -32,6 +32,7 @@
import java.io.File;
import java.lang.ref.WeakReference;
import java.nio.file.Path;
+import java.util.Optional;
import java.util.concurrent.Callable;
import javax.inject.Inject;
@@ -80,6 +81,8 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
handleCrashOnPreviousExecution();
+ WorkManager.getInstance(this).cancelAllWork();
+
Intent intent = sanitizeIntent(getIntent());
if ((Intent.ACTION_SEND.equals(intent.getAction()) || Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction()) || Intent.ACTION_VIEW.equals(intent.getAction())) && intent.getType() != null) {
@@ -189,8 +192,10 @@ private Intent sanitizeIntent(Intent intent) {
Uri normalizedUri = FileUtil.normalizeUri(Uri.parse(intent.getDataString()));
intent.setDataAndNormalize(normalizedUri);
}
- if (intent.getExtras() != null && !(intent.getExtras().containsKey(Intent.EXTRA_REFERRER) &&
- intent.getExtras().get(Intent.EXTRA_REFERRER).equals(R.string.application_name))) {
+ if (intent.getExtras() != null) {
+ if (intent.getExtras().containsKey(Intent.EXTRA_REFERRER)) {
+ intent.getExtras().getString(Intent.EXTRA_REFERRER);
+ }
intent.replaceExtras(new Bundle());
}
return intent;
@@ -215,7 +220,7 @@ private void initializeApplicationFileTypesAssociation() {
@Override
protected void attachBaseContext(Context newBase) {
- Application.ApplicationComponent component = Application.component(newBase);
+ ApplicationApp.ApplicationComponent component = ApplicationApp.component(newBase);
navigator = component.navigator();
rootScreenFactory = component.rootScreenFactory();
settingsDataStore = component.settingsDataStore();
@@ -232,6 +237,14 @@ public void onBackPressed() {
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
+ // If user selects a file from provider menu (Open from -> RIA DigiDoc), it starts a new activity
+ // Replace the main activity after the new file has been selected
+ if (Optional.ofNullable(data)
+ .map(Intent::getAction)
+ .filter(action -> action.equals(Intent.ACTION_GET_CONTENT))
+ .isPresent()) {
+ navigator.onCreate(this, findViewById(android.R.id.content), null);
+ }
navigator.onActivityResult(requestCode, resultCode, data);
}
@@ -345,17 +358,6 @@ private static String getFileName(File file) {
}
}
}
-
- private static Intent setIntentData(Intent intent, Path filePath, android.app.Activity activity) {
- intent.setData(Uri.parse(filePath.toUri().toString()));
- intent.setClipData(ClipData.newRawUri(filePath.getFileName().toString(), FileProvider.getUriForFile(
- activity,
- activity.getString(R.string.file_provider_authority),
- filePath.toFile())));
- return intent;
- }
-
-
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/Application.java b/app/src/main/java/ee/ria/DigiDoc/android/ApplicationApp.java
similarity index 94%
rename from app/src/main/java/ee/ria/DigiDoc/android/Application.java
rename to app/src/main/java/ee/ria/DigiDoc/android/ApplicationApp.java
index 9b756afe9..b73b6adf7 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/Application.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/ApplicationApp.java
@@ -26,8 +26,10 @@
import android.content.Intent;
import android.content.SharedPreferences;
import android.hardware.usb.UsbManager;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
import android.os.ResultReceiver;
import android.os.StrictMode;
import android.util.Log;
@@ -72,6 +74,7 @@
import ee.ria.DigiDoc.android.main.diagnostics.source.FileSystemDiagnosticsDataSource;
import ee.ria.DigiDoc.android.main.home.HomeViewModel;
import ee.ria.DigiDoc.android.main.settings.SettingsDataStore;
+import ee.ria.DigiDoc.android.main.settings.create.TSACertificateAddViewModel;
import ee.ria.DigiDoc.android.signature.create.SignatureCreateViewModel;
import ee.ria.DigiDoc.android.signature.data.SignatureContainerDataSource;
import ee.ria.DigiDoc.android.signature.data.source.FileSystemSignatureContainerDataSource;
@@ -98,7 +101,7 @@
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
import timber.log.Timber;
-public class Application extends android.app.Application {
+public class ApplicationApp extends android.app.Application {
private static ConfigurationProvider configurationProvider;
@@ -223,11 +226,11 @@ private void setupConfiguration() {
Bundle bundle = new Bundle();
bundle.putParcelable(ConfigurationConstants.CONFIGURATION_PROVIDER, configurationProvider);
- ConfigurationProviderReceiver confProviderReceiver = new ConfigurationProviderReceiver(new Handler());
+ ConfigurationProviderReceiver confProviderReceiver = new ConfigurationProviderReceiver(new Handler(Looper.getMainLooper()));
confProviderReceiver.send(ConfigurationManagerService.NEW_CONFIGURATION_LOADED, bundle);
// Load configuration again in asynchronous manner, from central if needed or cache if present.
- initAsyncConfigurationLoad(new ConfigurationProviderReceiver(new Handler()), false);
+ initAsyncConfigurationLoad(new ConfigurationProviderReceiver(new Handler(Looper.getMainLooper())), false);
}
private boolean isDefaultConfNewerThanCachedConf(ConfigurationProperties confProperties, CachedConfigurationHandler cachedConfHandler) {
@@ -238,7 +241,7 @@ private boolean isDefaultConfNewerThanCachedConf(ConfigurationProperties confPro
// Following configuration updating should be asynchronous
public void updateConfiguration(DiagnosticsView diagnosticsView) {
- ConfigurationProviderReceiver confProviderReceiver = new ConfigurationProviderReceiver(new Handler());
+ ConfigurationProviderReceiver confProviderReceiver = new ConfigurationProviderReceiver(new Handler(Looper.getMainLooper()));
confProviderReceiver.setDiagnosticView(diagnosticsView);
initAsyncConfigurationLoad(confProviderReceiver, true);
}
@@ -301,7 +304,11 @@ void setDiagnosticView(DiagnosticsView diagnosticView) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
- configurationProvider = resultData.getParcelable(ConfigurationConstants.CONFIGURATION_PROVIDER);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ configurationProvider = resultData.getParcelable(ConfigurationConstants.CONFIGURATION_PROVIDER, ConfigurationProvider.class);
+ } else {
+ configurationProvider = resultData.getParcelable(ConfigurationConstants.CONFIGURATION_PROVIDER);
+ }
if (resultCode == ConfigurationManagerService.NEW_CONFIGURATION_LOADED) {
setupSignLib();
}
@@ -316,13 +323,13 @@ protected void onReceiveResult(int resultCode, Bundle resultData) {
private ApplicationComponent component;
private void setupDagger() {
- component = DaggerApplication_ApplicationComponent.builder()
+ component = DaggerApplicationApp_ApplicationComponent.builder()
.application(this)
.build();
}
public static ApplicationComponent component(Context context) {
- return ((Application) context.getApplicationContext()).component;
+ return ((ApplicationApp) context.getApplicationContext()).component;
}
@Singleton
@@ -417,6 +424,10 @@ static ViewModelProvider.Factory viewModelFactory(
@SuppressWarnings("unused")
@Binds @IntoMap @ClassKey(EIDHomeViewModel.class)
abstract ViewModel eidHomeViewModel(EIDHomeViewModel viewModel);
+
+ @SuppressWarnings("unused")
+ @Binds @IntoMap @ClassKey(TSACertificateAddViewModel.class)
+ abstract ViewModel tsaCertificateAddViewModel(TSACertificateAddViewModel viewModel);
}
@Module
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/Constants.java b/app/src/main/java/ee/ria/DigiDoc/android/Constants.java
index 9bfb3b375..ff012479d 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/Constants.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/Constants.java
@@ -13,6 +13,7 @@ public final class Constants {
public static final int RC_CRYPTO_CREATE_INITIAL = 2;
public static final int RC_CRYPTO_CREATE_DATA_FILE_ADD = 3;
public static final int SAVE_FILE = 4;
+ public static final int RC_TSA_CERT_ADD = 5;
/**
* Sub-directory name in {@link android.content.Context#getFilesDir() files dir} for signature
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/accessibility/AccessibilityUtils.java b/app/src/main/java/ee/ria/DigiDoc/android/accessibility/AccessibilityUtils.java
index 842e04eed..1ebe4dac7 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/accessibility/AccessibilityUtils.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/accessibility/AccessibilityUtils.java
@@ -11,6 +11,7 @@
import android.widget.EditText;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.view.AccessibilityDelegateCompat;
@@ -19,6 +20,8 @@
import com.google.android.material.textfield.TextInputLayout;
+import net.cachapa.expandablelayout.ExpandableLayout;
+
import ee.ria.DigiDoc.android.Activity;
import ee.ria.DigiDoc.common.TextUtil;
@@ -42,7 +45,7 @@ public static void interrupt(Context context) {
public static void sendAccessibilityEvent(Context context, int eventType, CharSequence message) {
AccessibilityManager accessibilityManager = (AccessibilityManager) context.getSystemService(ACCESSIBILITY_SERVICE);
if (accessibilityManager.isEnabled()) {
- AccessibilityEvent event = AccessibilityEvent.obtain();
+ AccessibilityEvent event = getAccessibilityEvent();
event.setEventType(eventType);
event.getText().add(message);
accessibilityManager.sendAccessibilityEvent(event);
@@ -52,7 +55,7 @@ public static void sendAccessibilityEvent(Context context, int eventType, CharSe
public static void sendAccessibilityEvent(Context context, int eventType, CharSequence... messages) {
AccessibilityManager accessibilityManager = (AccessibilityManager) context.getSystemService(ACCESSIBILITY_SERVICE);
if (accessibilityManager.isEnabled()) {
- AccessibilityEvent event = AccessibilityEvent.obtain();
+ AccessibilityEvent event = getAccessibilityEvent();
event.setEventType(eventType);
event.getText().add(combineMessages(messages));
accessibilityManager.sendAccessibilityEvent(event);
@@ -62,7 +65,7 @@ public static void sendAccessibilityEvent(Context context, int eventType, CharSe
public static void sendDelayedAccessibilityEvent(Context context, int eventType, CharSequence message) {
AccessibilityManager accessibilityManager = (AccessibilityManager) context.getSystemService(ACCESSIBILITY_SERVICE);
if (accessibilityManager.isEnabled()) {
- AccessibilityEvent event = AccessibilityEvent.obtain();
+ AccessibilityEvent event = getAccessibilityEvent();
event.setEventType(eventType);
event.getText().add(message);
accessibilityManager.sendAccessibilityEvent(event);
@@ -100,7 +103,7 @@ public static void disableContentDescription(View view) {
public static void disableDoubleTapToActivateFeedback(View view) {
ViewCompat.setAccessibilityDelegate(view, new AccessibilityDelegateCompat() {
@Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
+ public void onInitializeAccessibilityNodeInfo(@NonNull View host, @NonNull AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
info.addAction(AccessibilityNodeInfoCompat.ACTION_FOCUS);
info.removeAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK);
@@ -111,7 +114,7 @@ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCo
public static void setSingleCharactersContentDescription(TextView textView) {
ViewCompat.setAccessibilityDelegate(textView, new AccessibilityDelegateCompat() {
@Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
+ public void onInitializeAccessibilityNodeInfo(@NonNull View host, @NonNull AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
StringBuilder textViewAccessibility = new StringBuilder();
String[] personalCodeTextSplit = textView.getText().toString().split(",");
@@ -142,6 +145,29 @@ public static boolean isLargeFontEnabled(Resources resources) {
return configuration.fontScale > 1;
}
+ public static boolean isSmallFontEnabled(Resources resources) {
+ Configuration configuration = resources.getConfiguration();
+ return configuration.fontScale < 1;
+ }
+
+ public static void setCustomClickAccessibilityFeedBack(TextView titleView, ExpandableLayout containerView) {
+ ViewCompat.setAccessibilityDelegate(titleView, new AccessibilityDelegateCompat() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ String message;
+ if (containerView.isExpanded()) {
+ message = "deactivate";
+ } else {
+ message = "activate";
+ }
+ AccessibilityNodeInfoCompat.AccessibilityActionCompat customClick = new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ AccessibilityNodeInfoCompat.ACTION_CLICK, message);
+ info.addAction(customClick);
+ }
+ });
+ }
+
private static String combineMessages(CharSequence... messages) {
StringBuilder combinedMessage = new StringBuilder();
for (CharSequence message : messages) {
@@ -149,4 +175,12 @@ private static String combineMessages(CharSequence... messages) {
}
return combinedMessage.toString();
}
+
+ private static AccessibilityEvent getAccessibilityEvent() {
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
+ return new AccessibilityEvent();
+ } else {
+ return AccessibilityEvent.obtain();
+ }
+ }
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/crypto/create/CryptoCreateAdapter.java b/app/src/main/java/ee/ria/DigiDoc/android/crypto/create/CryptoCreateAdapter.java
index 854e0fa1d..99153b8ec 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/crypto/create/CryptoCreateAdapter.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/crypto/create/CryptoCreateAdapter.java
@@ -25,7 +25,7 @@
import java.util.List;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils;
import ee.ria.DigiDoc.android.utils.Formatter;
import ee.ria.DigiDoc.android.utils.display.DisplayUtil;
@@ -388,7 +388,7 @@ static final class RecipientViewHolder extends CreateViewHolder {
RecipientViewHolder(View itemView) {
super(itemView);
- formatter = Application.component(itemView.getContext()).formatter();
+ formatter = ApplicationApp.component(itemView.getContext()).formatter();
AccessibilityUtils.disableDoubleTapToActivateFeedback(itemView.findViewById(R.id.cryptoRecipient));
nameView = itemView.findViewById(R.id.cryptoRecipientName);
infoView = itemView.findViewById(R.id.cryptoRecipientInfo);
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/crypto/create/CryptoCreateScreen.java b/app/src/main/java/ee/ria/DigiDoc/android/crypto/create/CryptoCreateScreen.java
index 728c1f69f..c51e2c82c 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/crypto/create/CryptoCreateScreen.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/crypto/create/CryptoCreateScreen.java
@@ -1,9 +1,11 @@
package ee.ria.DigiDoc.android.crypto.create;
+import static android.view.View.GONE;
import static android.view.accessibility.AccessibilityEvent.TYPE_ANNOUNCEMENT;
import static com.jakewharton.rxbinding4.view.RxView.clicks;
import static com.jakewharton.rxbinding4.widget.RxToolbar.navigationClicks;
import static ee.ria.DigiDoc.android.utils.BundleUtils.getFile;
+import static ee.ria.DigiDoc.android.utils.BundleUtils.putBoolean;
import static ee.ria.DigiDoc.android.utils.BundleUtils.putFile;
import static ee.ria.DigiDoc.android.utils.Predicates.duplicates;
import static ee.ria.DigiDoc.android.utils.TintUtils.tintCompoundDrawables;
@@ -33,7 +35,7 @@
import ee.ria.DigiDoc.R;
import ee.ria.DigiDoc.android.Activity;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils;
import ee.ria.DigiDoc.android.model.idcard.IdCardData;
import ee.ria.DigiDoc.android.model.idcard.IdCardDataResponse;
@@ -42,6 +44,7 @@
import ee.ria.DigiDoc.android.utils.files.EmptyFileException;
import ee.ria.DigiDoc.android.utils.mvi.MviView;
import ee.ria.DigiDoc.android.utils.mvi.State;
+import ee.ria.DigiDoc.android.utils.navigator.ContentView;
import ee.ria.DigiDoc.android.utils.navigator.Screen;
import ee.ria.DigiDoc.android.utils.widget.ConfirmationDialog;
import ee.ria.DigiDoc.android.utils.widget.ErrorDialog;
@@ -54,19 +57,21 @@
import io.reactivex.rxjava3.subjects.PublishSubject;
import io.reactivex.rxjava3.subjects.Subject;
-public final class CryptoCreateScreen extends Controller implements Screen,
+public final class CryptoCreateScreen extends Controller implements Screen, ContentView,
MviView {
private static final String KEY_CONTAINER_FILE = "containerFile";
private static final String KEY_INTENT = "intent";
+ private static final String KEY_IS_FROM_SIGNATURE_VIEW = "isFromSignatureView";
public static CryptoCreateScreen create() {
return new CryptoCreateScreen(Bundle.EMPTY);
}
- public static CryptoCreateScreen open(File containerFile) {
+ public static CryptoCreateScreen open(File containerFile, boolean isFromSignatureView) {
Bundle args = new Bundle();
putFile(args, KEY_CONTAINER_FILE, containerFile);
+ putBoolean(args, KEY_IS_FROM_SIGNATURE_VIEW, isFromSignatureView);
return new CryptoCreateScreen(args);
}
@@ -78,6 +83,7 @@ public static CryptoCreateScreen open(android.content.Intent intent) {
@Nullable private File containerFile;
@Nullable private final android.content.Intent intent;
+ private final boolean isFromSignatureView;
private final Subject idCardTokenAvailableSubject = PublishSubject.create();
@@ -95,8 +101,10 @@ public static CryptoCreateScreen open(android.content.Intent intent) {
private Button encryptButton;
private RecyclerView listView;
private TextView decryptButton;
+ private TextView signButton;
private TextView sendButton;
- private View buttonSpaceView;
+ private View cryptoButtonSpaceView;
+ private View signatureButtonSpaceView;
private DecryptDialog decryptDialog;
private ErrorDialog errorDialog;
@@ -116,11 +124,12 @@ public CryptoCreateScreen(Bundle args) {
containerFile = args.containsKey(KEY_CONTAINER_FILE)
? getFile(args, KEY_CONTAINER_FILE)
: null;
- intent = args.getParcelable(KEY_INTENT);
+ intent = getIntent(args);
+ isFromSignatureView = args.getBoolean(KEY_IS_FROM_SIGNATURE_VIEW);
}
private Observable initialIntent() {
- return Observable.just(Intent.InitialIntent.create(containerFile, intent));
+ return Observable.just(Intent.InitialIntent.create(containerFile, intent, isFromSignatureView));
}
@SuppressWarnings("unchecked")
@@ -216,6 +225,10 @@ private Observable sendIntent() {
return clicks(sendButton).map(ignored -> Intent.SendIntent.create(containerFile));
}
+ private Observable signIntent() {
+ return clicks(signButton).map(ignored -> Intent.SignIntent.create(containerFile));
+ }
+
private Observable errorIntents() {
return cancels(errorDialog)
.map(ignored -> {
@@ -236,7 +249,7 @@ public Observable intents() {
return Observable.mergeArray(initialIntent(), nameUpdateIntent(), upButtonClickIntent(), dataFilesAddIntent(),
dataFileRemoveIntent(), dataFileSaveIntent(), dataFileViewIntent(), recipientsAddButtonClickIntent(),
recipientRemoveIntent(), encryptIntent(), decryptionIntent(), decryptIntent(),
- sendIntent(), errorIntents());
+ signIntent(), sendIntent(), errorIntents());
}
@Override
@@ -295,10 +308,14 @@ public void render(ViewState state) {
encryptButton.setVisibility(state.encryptButtonVisible() ? View.VISIBLE : View.GONE);
decryptButton.setVisibility(state.decryptButtonVisible() ? View.VISIBLE : View.GONE);
+ signButton.setVisibility(state.decryptButtonVisible() && !isFromSignatureView ? View.VISIBLE : View.GONE);
sendButton.setVisibility(state.sendButtonVisible() ? View.VISIBLE : View.GONE);
- buttonSpaceView.setVisibility(state.sendButtonVisible() &&
+ cryptoButtonSpaceView.setVisibility(state.sendButtonVisible() &&
(state.encryptButtonVisible() || state.decryptButtonVisible())
? View.VISIBLE : View.GONE);
+ signatureButtonSpaceView.setVisibility(
+ state.sendButtonVisible() &&
+ state.decryptButtonVisible() ? View.VISIBLE : View.GONE);
decryptionIdCardDataResponse = state.decryptionIdCardDataResponse();
boolean decryptionPin1Locked = false;
@@ -362,17 +379,18 @@ private void showSuccessNotification() {
}
private void setActivity(boolean activity) {
- activityOverlayView.setVisibility(activity ? View.VISIBLE : View.GONE);
- activityIndicatorView.setVisibility(activity ? View.VISIBLE : View.GONE);
+ activityOverlayView.setVisibility(activity ? View.VISIBLE : GONE);
+ activityIndicatorView.setVisibility(activity ? View.VISIBLE : GONE);
encryptButton.setEnabled(!activity);
decryptButton.setEnabled(!activity);
+ signButton.setEnabled(!activity);
sendButton.setEnabled(!activity);
}
@Override
protected void onContextAvailable(@NonNull Context context) {
super.onContextAvailable(context);
- viewModel = Application.component(context).navigator()
+ viewModel = ApplicationApp.component(context).navigator()
.viewModel(getInstanceId(), CryptoCreateViewModel.class);
}
@@ -397,9 +415,11 @@ protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup
activityIndicatorView = view.findViewById(R.id.activityIndicator);
encryptButton = view.findViewById(R.id.cryptoCreateEncryptButton);
decryptButton = view.findViewById(R.id.cryptoCreateDecryptButton);
+ signButton = view.findViewById(R.id.cryptoCreateSignatureButton);
sendButton = view.findViewById(R.id.cryptoCreateSendButton);
sendButton.setContentDescription(getResources().getString(R.string.share_container));
- buttonSpaceView = view.findViewById(R.id.cryptoCreateButtonSpace);
+ cryptoButtonSpaceView = view.findViewById(R.id.cryptoCreateCryptoButtonSpace);
+ signatureButtonSpaceView = view.findViewById(R.id.cryptoCreateSignatureButtonSpace);
decryptDialog = new DecryptDialog(inflater.getContext());
this.errorDialog = new ErrorDialog(inflater.getContext());
@@ -411,15 +431,23 @@ protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup
tintCompoundDrawables(encryptButton, true);
tintCompoundDrawables(decryptButton, true);
+ tintCompoundDrawables(signButton, true);
tintCompoundDrawables(sendButton, true);
- decryptButton.setContentDescription(getResources().getString(R.string.decrypt_content_description, 1, 2));
- sendButton.setContentDescription(getResources().getString(R.string.decrypt_send_content_description, 2, 2));
+ decryptButton.setContentDescription(getResources().getString(R.string.decrypt_content_description, 1, 3));
+ signButton.setContentDescription(getResources().getString(R.string.sign_send_content_description, 2, 3));
+ sendButton.setContentDescription(getResources().getString(R.string.decrypt_send_content_description, 3, 3));
disposables.attach();
disposables.add(viewModel.viewStates().subscribe(this::render));
viewModel.process(intents());
+ ContentView.addInvisibleElement(getApplicationContext(), view);
+
+ View lastElementView = view.findViewById(R.id.lastInvisibleElement);
+
+ ContentView.addInvisibleElementScrollListener(listView, lastElementView);
+
return view;
}
@@ -428,7 +456,16 @@ protected void onDestroyView(@NonNull View view) {
decryptDialog.dismiss();
errorDialog.dismiss();
sivaConfirmationDialog.dismiss();
+ ContentView.removeInvisibleElementScrollListener(listView);
disposables.detach();
super.onDestroyView(view);
}
+
+ private android.content.Intent getIntent(Bundle bundle) {
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
+ return bundle.getParcelable(KEY_INTENT, android.content.Intent.class);
+ } else {
+ return bundle.getParcelable(KEY_INTENT);
+ }
+ }
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/crypto/create/CryptoRecipientsScreen.java b/app/src/main/java/ee/ria/DigiDoc/android/crypto/create/CryptoRecipientsScreen.java
index 62db32eaa..73040bed8 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/crypto/create/CryptoRecipientsScreen.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/crypto/create/CryptoRecipientsScreen.java
@@ -8,6 +8,7 @@
import static com.jakewharton.rxbinding4.widget.RxToolbar.navigationClicks;
import static ee.ria.DigiDoc.android.Constants.MAXIMUM_PERSONAL_CODE_LENGTH;
import static ee.ria.DigiDoc.android.Constants.VOID;
+import static ee.ria.DigiDoc.android.utils.ViewUtil.moveView;
import android.content.Context;
import android.graphics.Color;
@@ -42,12 +43,13 @@
import org.apache.commons.lang3.StringUtils;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils;
import ee.ria.DigiDoc.android.utils.ViewDisposables;
import ee.ria.DigiDoc.android.utils.display.DisplayUtil;
import ee.ria.DigiDoc.android.utils.mvi.MviView;
import ee.ria.DigiDoc.android.utils.mvi.State;
+import ee.ria.DigiDoc.android.utils.navigator.ContentView;
import ee.ria.DigiDoc.android.utils.navigator.Navigator;
import ee.ria.DigiDoc.android.utils.navigator.Screen;
import ee.ria.DigiDoc.android.utils.validator.PersonalCodeValidator;
@@ -57,7 +59,7 @@
import io.reactivex.rxjava3.subjects.PublishSubject;
import io.reactivex.rxjava3.subjects.Subject;
-public final class CryptoRecipientsScreen extends Controller implements Screen,
+public final class CryptoRecipientsScreen extends Controller implements Screen, ContentView,
MviView, Navigator.BackButtonClickListener {
private static final String KEY_CRYPTO_CREATE_SCREEN_ID = "cryptoCreateScreenId";
@@ -79,6 +81,7 @@ public static CryptoRecipientsScreen create(String cryptoCreateScreenId) {
private Toolbar toolbarView;
private SearchView searchView;
private EditText searchViewInnerText;
+ private RecyclerView listView;
private CryptoCreateAdapter adapter;
private View doneButton;
private View activityOverlayView;
@@ -177,9 +180,9 @@ public boolean onBackButtonClick() {
@Override
protected void onContextAvailable(@NonNull Context context) {
super.onContextAvailable(context);
- viewModel = Application.component(context).navigator()
+ viewModel = ApplicationApp.component(context).navigator()
.viewModel(cryptoCreateScreenId, CryptoCreateViewModel.class);
- navigator = Application.component(context).navigator();
+ navigator = ApplicationApp.component(context).navigator();
}
@Override
@@ -333,13 +336,20 @@ public void afterTextChanged(Editable s) {
searchView.setSubmitButtonEnabled(true);
searchView.setEnabled(true);
- RecyclerView listView = view.findViewById(R.id.cryptoRecipientsList);
+ listView = view.findViewById(R.id.cryptoRecipientsList);
listView.setLayoutManager(new LinearLayoutManager(container.getContext()));
listView.setAdapter(adapter = new CryptoCreateAdapter());
listView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
doneButton = view.findViewById(R.id.cryptoRecipientsDoneButton);
activityOverlayView = view.findViewById(R.id.activityOverlay);
activityIndicatorView = view.findViewById(R.id.activityIndicator);
+
+ ContentView.addInvisibleElement(getApplicationContext(), view);
+
+ View lastElementView = view.findViewById(R.id.lastInvisibleElement);
+
+ ContentView.addInvisibleElementScrollListener(listView, lastElementView);
+
return view;
}
@@ -372,6 +382,7 @@ protected void onDetach(@NonNull View view) {
disposables.detach();
navigator.removeBackButtonClickListener(this);
searchView.setOnCloseListener(null);
+ ContentView.removeInvisibleElementScrollListener(listView);
ee.ria.DigiDoc.android.utils.TextUtil.removeTextWatcher(searchViewInnerText,searchViewTextWatcher);
super.onDetach(view);
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/crypto/create/Intent.java b/app/src/main/java/ee/ria/DigiDoc/android/crypto/create/Intent.java
index a402324e2..addd713b2 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/crypto/create/Intent.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/crypto/create/Intent.java
@@ -26,9 +26,12 @@ abstract class InitialIntent implements Intent {
@Nullable abstract android.content.Intent intent();
+ abstract boolean isFromSignatureView();
+
static InitialIntent create(@Nullable File containerFile,
- @Nullable android.content.Intent intent) {
- return new AutoValue_Intent_InitialIntent(containerFile, intent);
+ @Nullable android.content.Intent intent,
+ boolean isFromSignatureView) {
+ return new AutoValue_Intent_InitialIntent(containerFile, intent, isFromSignatureView);
}
}
@@ -301,4 +304,14 @@ static SendIntent create(File containerFile) {
return new AutoValue_Intent_SendIntent(containerFile);
}
}
+
+ @AutoValue
+ abstract class SignIntent implements Intent {
+
+ abstract File containerFile();
+
+ static SignIntent create(File containerFile) {
+ return new AutoValue_Intent_SignIntent(containerFile);
+ }
+ }
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/crypto/create/Processor.java b/app/src/main/java/ee/ria/DigiDoc/android/crypto/create/Processor.java
index 88eb4d34f..3d8e4bede 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/crypto/create/Processor.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/crypto/create/Processor.java
@@ -13,6 +13,7 @@
import static ee.ria.DigiDoc.android.utils.IntentUtils.createGetContentIntent;
import static ee.ria.DigiDoc.android.utils.IntentUtils.createSaveIntent;
import static ee.ria.DigiDoc.android.utils.IntentUtils.parseGetContentIntent;
+import static ee.ria.DigiDoc.android.utils.IntentUtils.setIntentData;
import static ee.ria.DigiDoc.crypto.CryptoContainer.createContainerFileName;
import static ee.ria.DigiDoc.crypto.CryptoContainer.isContainerFileName;
@@ -40,6 +41,7 @@
import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils;
import ee.ria.DigiDoc.android.model.idcard.IdCardDataResponse;
import ee.ria.DigiDoc.android.model.idcard.IdCardService;
+import ee.ria.DigiDoc.android.signature.create.SignatureCreateScreen;
import ee.ria.DigiDoc.android.signature.update.SignatureUpdateScreen;
import ee.ria.DigiDoc.android.utils.ToastUtil;
import ee.ria.DigiDoc.android.utils.files.EmptyFileException;
@@ -118,6 +120,8 @@ final class Processor implements ObservableTransformer {
private final ObservableTransformer send;
+ private final ObservableTransformer sign;
+
@Inject Processor(Navigator navigator, RecipientRepository recipientRepository,
ContentResolver contentResolver, FileSystem fileSystem,
Application application, IdCardService idCardService) {
@@ -139,7 +143,7 @@ final class Processor implements ObservableTransformer {
return parseIntent(androidIntent, application, fileSystem.getExternallyOpenedFilesDir());
} else {
navigator.execute(Transaction.activityForResult(RC_CRYPTO_CREATE_INITIAL,
- createGetContentIntent(), null));
+ createGetContentIntent(true), null));
return navigator.activityResults()
.filter(activityResult ->
activityResult.requestCode() == RC_CRYPTO_CREATE_INITIAL)
@@ -149,7 +153,7 @@ final class Processor implements ObservableTransformer {
return parseIntent(data, application, fileSystem.getExternallyOpenedFilesDir())
.onErrorReturn(throwable -> {
if (throwable instanceof EmptyFileException) {
- ToastUtil.showEmptyFileError(navigator.activity(), application);
+ ToastUtil.showEmptyFileError(navigator.activity());
}
navigator.execute(Transaction.pop());
return Result.InitialResult.failure(throwable);
@@ -201,7 +205,7 @@ final class Processor implements ObservableTransformer {
}
navigator.execute(Transaction.activityForResult(RC_CRYPTO_CREATE_DATA_FILE_ADD,
- createGetContentIntent(), null));
+ createGetContentIntent(true), null));
return navigator.activityResults()
.filter(activityResult ->
activityResult.requestCode() == RC_CRYPTO_CREATE_DATA_FILE_ADD)
@@ -215,7 +219,7 @@ final class Processor implements ObservableTransformer {
ImmutableList.Builder builder =
ImmutableList.builder().addAll(dataFiles);
ImmutableList validFiles = FileSystem.getFilesWithValidSize(fileStreams);
- ToastUtil.handleEmptyFileError(validFiles, application, navigator.activity());
+ ToastUtil.handleEmptyFileError(validFiles, navigator.activity());
for (FileStream fileStream : validFiles) {
File dataFile = fileSystem.cache(fileStream);
if (dataFiles.contains(dataFile)) {
@@ -311,7 +315,7 @@ final class Processor implements ObservableTransformer {
.fromCallable(() -> {
File file = intent.dataFile();
if (CryptoContainer.isContainerFileName(file.getName())) {
- return Transaction.push(CryptoCreateScreen.open(file));
+ return Transaction.push(CryptoCreateScreen.open(file, false));
} else if (SignedContainer.isContainer(navigator.activity(), file)) {
return Transaction.push(
SignatureUpdateScreen.create(true, true, file, false, false, null, true));
@@ -491,6 +495,13 @@ final class Processor implements ObservableTransformer {
.activity(createActionIntent(application, intent.containerFile(), android.content.Intent.ACTION_SEND), null));
return Observable.empty();
});
+
+ sign = upstream -> upstream.switchMap(signIntent -> {
+ android.content.Intent intent = new android.content.Intent();
+ android.content.Intent intentWithData = setIntentData(intent, signIntent.containerFile().toPath(), navigator.activity());
+ navigator.execute(Transaction.push(SignatureCreateScreen.create(intentWithData)));
+ return Observable.empty();
+ });
}
@SuppressWarnings("unchecked")
@@ -517,6 +528,7 @@ public ObservableSource apply(Observable upstream) {
shared.ofType(Intent.EncryptIntent.class).compose(encrypt),
shared.ofType(Intent.DecryptionIntent.class).compose(decryption),
shared.ofType(Intent.DecryptIntent.class).compose(decrypt),
+ shared.ofType(Intent.SignIntent.class).compose(sign),
shared.ofType(Intent.SendIntent.class).compose(send)));
}
@@ -525,7 +537,7 @@ private Observable parseIntent(android.content.Intent inte
.fromCallable(() -> {
ImmutableList validFiles = FileSystem.getFilesWithValidSize(
parseGetContentIntent(application.getApplicationContext(), contentResolver, intent, externallyOpenedFileDir));
- ToastUtil.handleEmptyFileError(validFiles, application, application.getApplicationContext());
+ ToastUtil.handleEmptyFileError(validFiles, application.getApplicationContext());
if (validFiles.size() == 1
&& isContainerFileName(validFiles.get(0).displayName())) {
File file = fileSystem.addSignatureContainer(validFiles.get(0));
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/crypto/home/CryptoHomeView.java b/app/src/main/java/ee/ria/DigiDoc/android/crypto/home/CryptoHomeView.java
index 648d1792c..512bc35a5 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/crypto/home/CryptoHomeView.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/crypto/home/CryptoHomeView.java
@@ -9,7 +9,7 @@
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils;
import ee.ria.DigiDoc.android.crypto.create.CryptoCreateScreen;
import ee.ria.DigiDoc.android.main.home.HomeToolbar;
@@ -43,7 +43,7 @@ public CryptoHomeView(Context context, @Nullable AttributeSet attrs, int defStyl
inflate(context, R.layout.crypto_home, this);
toolbarView = findViewById(R.id.toolbar);
createButton = findViewById(R.id.cryptoHomeCreateButton);
- navigator = Application.component(context).navigator();
+ navigator = ApplicationApp.component(context).navigator();
AccessibilityUtils.setViewAccessibilityPaneTitle(this, R.string.main_home_navigation_crypto);
createButton.postDelayed(() -> {
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/eid/CertificateDataView.java b/app/src/main/java/ee/ria/DigiDoc/android/eid/CertificateDataView.java
index fd1d05110..86f468f5c 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/eid/CertificateDataView.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/eid/CertificateDataView.java
@@ -10,7 +10,7 @@
import androidx.annotation.Nullable;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.utils.Formatter;
import ee.ria.DigiDoc.common.Certificate;
import ee.ria.DigiDoc.idcard.CertificateType;
@@ -45,7 +45,7 @@ public CertificateDataView(Context context, @Nullable AttributeSet attrs, int de
public CertificateDataView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- formatter = Application.component(context).formatter();
+ formatter = ApplicationApp.component(context).formatter();
setOrientation(VERTICAL);
inflate(context, R.layout.eid_home_certificate_data, this);
titleView = findViewById(R.id.eidHomeCertificateDataTitle);
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/eid/CodeUpdateView.java b/app/src/main/java/ee/ria/DigiDoc/android/eid/CodeUpdateView.java
index 5d4736e04..f6490cb74 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/eid/CodeUpdateView.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/eid/CodeUpdateView.java
@@ -31,6 +31,7 @@
import ee.ria.DigiDoc.android.eid.CodeUpdateError.CodeSameAsCurrentError;
import ee.ria.DigiDoc.android.eid.CodeUpdateError.CodeTooEasyError;
import ee.ria.DigiDoc.android.utils.mvi.State;
+import ee.ria.DigiDoc.android.utils.navigator.ContentView;
import io.reactivex.rxjava3.core.Observable;
import static com.jakewharton.rxbinding4.widget.RxToolbar.navigationClicks;
@@ -38,7 +39,7 @@
import static ee.ria.DigiDoc.android.utils.ErrorMessageUtil.setTextViewError;
import static ee.ria.DigiDoc.android.utils.InputMethodUtils.hideSoftKeyboard;
-public final class CodeUpdateView extends CoordinatorLayout {
+public final class CodeUpdateView extends CoordinatorLayout implements ContentView {
private final Toolbar toolbarView;
private final TextView successMessageView;
@@ -88,6 +89,8 @@ public CodeUpdateView(Context context, @Nullable AttributeSet attrs, int defStyl
activityOverlayView = findViewById(R.id.activityOverlay);
activityIndicatorView = findViewById(R.id.activityIndicator);
scrollView = findViewById(R.id.eidHomeCodeUpdateScroll);
+
+ ContentView.addInvisibleElement(context, this);
}
public void render(@State String state, CodeUpdateAction action,
@@ -219,7 +222,7 @@ public void render(@State String state, CodeUpdateAction action,
setTextViewError(getContext(), getResources().getString(action.newDateOfBirthErrorRes()), repeatLabelViewText, repeatLabelView, repeatView);
setViewContentDescription(repeatLabelViewText, getResources().getString(action.newDateOfBirthErrorRes()));
} else if (newError instanceof CodeTooEasyError) {
- setTextViewError(getContext(), getResources().getString(action.newDateOfBirthErrorRes()), repeatLabelViewText, repeatLabelView, repeatView);
+ setTextViewError(getContext(), getResources().getString(action.newTooEasyErrorRes()), repeatLabelViewText, repeatLabelView, repeatView);
setViewContentDescription(repeatLabelViewText, getResources().getString(action.newTooEasyErrorRes()));
} else if (newError instanceof CodeSameAsCurrentError) {
setTextViewError(getContext(), getResources().getString(action.newSameAsCurrentErrorRes()), repeatLabelViewText, repeatLabelView, repeatView);
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/eid/EIDDataView.java b/app/src/main/java/ee/ria/DigiDoc/android/eid/EIDDataView.java
index e69d74ac2..dbbb5c6ed 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/eid/EIDDataView.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/eid/EIDDataView.java
@@ -19,8 +19,7 @@
import net.cachapa.expandablelayout.ExpandableLayout;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.Application;
-import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.model.idcard.IdCardData;
import ee.ria.DigiDoc.android.utils.Formatter;
import ee.ria.DigiDoc.common.TextUtil;
@@ -29,10 +28,10 @@
import io.reactivex.rxjava3.core.Observable;
import static com.jakewharton.rxbinding4.view.RxView.clicks;
+import static ee.ria.DigiDoc.android.accessibility.AccessibilityUtils.setCustomClickAccessibilityFeedBack;
import static ee.ria.DigiDoc.android.utils.TintUtils.tintCompoundDrawables;
import java.time.LocalDate;
-import java.util.Date;
public final class EIDDataView extends LinearLayout {
@@ -74,7 +73,7 @@ public EIDDataView(Context context, @Nullable AttributeSet attrs, int defStyleAt
public EIDDataView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- formatter = Application.component(context).formatter();
+ formatter = ApplicationApp.component(context).formatter();
setOrientation(VERTICAL);
inflate(context, R.layout.eid_home_data, this);
typeView = findViewById(R.id.eidHomeDataType);
@@ -126,7 +125,7 @@ public void render(@NonNull IdCardData data, boolean certificateContainerExpande
: R.drawable.ic_icon_accordion_collapsed;
certificatesTitleView.setCompoundDrawablesRelativeWithIntrinsicBounds(drawable, 0, 0, 0);
tintCompoundDrawables(certificatesTitleView);
- setCustomClickAccessibilityFeedBack(certificatesTitleView);
+ setCustomClickAccessibilityFeedBack(certificatesTitleView, certificatesContainerView);
certificatesContainerView.setExpanded(certificateContainerExpanded);
@@ -178,22 +177,4 @@ public Observable actions() {
CodeUpdateAction.create(CodeType.PUK, CodeUpdateType.UNBLOCK))
);
}
-
- private void setCustomClickAccessibilityFeedBack(TextView certificatesTitleView) {
- ViewCompat.setAccessibilityDelegate(certificatesTitleView, new AccessibilityDelegateCompat() {
- @Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- String message;
- if (certificatesContainerView.isExpanded()) {
- message = "deactivate";
- } else {
- message = "activate";
- }
- AccessibilityNodeInfoCompat.AccessibilityActionCompat customClick = new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
- AccessibilityNodeInfoCompat.ACTION_CLICK, message);
- info.addAction(customClick);
- }
- });
- }
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/eid/EIDHomeView.java b/app/src/main/java/ee/ria/DigiDoc/android/eid/EIDHomeView.java
index ce92f2c4c..e2edacd16 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/eid/EIDHomeView.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/eid/EIDHomeView.java
@@ -3,16 +3,18 @@
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
-import androidx.annotation.Nullable;
-import androidx.coordinatorlayout.widget.CoordinatorLayout;
import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+import androidx.coordinatorlayout.widget.CoordinatorLayout;
+
import com.google.common.collect.ImmutableMap;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils;
import ee.ria.DigiDoc.android.main.home.HomeToolbar;
import ee.ria.DigiDoc.android.main.home.HomeView;
@@ -49,6 +51,7 @@ public final class EIDHomeView extends FrameLayout implements MviView dialog.cancel());
- navigator = Application.component(context).navigator();
+ navigator = ApplicationApp.component(context).navigator();
}
private Observable initialIntent() {
@@ -133,7 +137,7 @@ public void render(ViewState state) {
if (data != null) {
dataView.render(data, state.certificatesContainerExpanded());
}
- progressMessageView.setVisibility(data == null ? VISIBLE : GONE);
+ progressLayout.setVisibility(data == null ? VISIBLE : GONE);
dataView.setVisibility(data == null ? GONE : VISIBLE);
codeUpdateAction = state.codeUpdateAction();
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/eid/Processor.java b/app/src/main/java/ee/ria/DigiDoc/android/eid/Processor.java
index f7f74adda..060dfeb28 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/eid/Processor.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/eid/Processor.java
@@ -170,6 +170,8 @@ private static CodeUpdateResponse validate(CodeUpdateAction action, CodeUpdateRe
.add(dateOfBirth.format(DateTimeFormatter.ofPattern("yyyy")))
.add(dateOfBirth.format(DateTimeFormatter.ofPattern("MMdd")))
.add(dateOfBirth.format(DateTimeFormatter.ofPattern("ddMM")))
+ .add(dateOfBirth.format(DateTimeFormatter.ofPattern("ddMMyyyy")))
+ .add(dateOfBirth.format(DateTimeFormatter.ofPattern("yyyyMM")))
.add(dateOfBirth.format(DateTimeFormatter.ofPattern("yyyyMMdd")));
}
ImmutableSet dateOfBirthValues = dateOfBirthValuesBuilder.build();
@@ -221,7 +223,6 @@ private static boolean isCodeTooEasy(String code) {
// Reset sequence
if (Math.abs(d) == 9) {
- delta = null;
if ((currentNumber == 9 && nextNumber == 0)) {
d = -1;
} else if ((currentNumber == 0 && nextNumber == 9)) {
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/about/AboutAdapter.java b/app/src/main/java/ee/ria/DigiDoc/android/main/about/AboutAdapter.java
index d54abaea6..cc7b24be7 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/main/about/AboutAdapter.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/about/AboutAdapter.java
@@ -15,9 +15,10 @@
import ee.ria.DigiDoc.BuildConfig;
import ee.ria.DigiDoc.R;
+import ee.ria.DigiDoc.android.utils.navigator.ContentView;
import ee.ria.DigiDoc.common.TextUtil;
-final class AboutAdapter extends RecyclerView.Adapter {
+final class AboutAdapter extends RecyclerView.Adapter implements ContentView {
private final ImmutableList components = ImmutableList.builder()
.add(Component.create(
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/about/AboutScreen.java b/app/src/main/java/ee/ria/DigiDoc/android/main/about/AboutScreen.java
index 148833c11..753454d62 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/main/about/AboutScreen.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/about/AboutScreen.java
@@ -1,5 +1,10 @@
package ee.ria.DigiDoc.android.main.about;
+import static com.jakewharton.rxbinding4.widget.RxToolbar.navigationClicks;
+import static ee.ria.DigiDoc.android.utils.navigator.ContentView.addInvisibleElement;
+import static ee.ria.DigiDoc.android.utils.navigator.ContentView.addInvisibleElementScrollListener;
+import static ee.ria.DigiDoc.android.utils.navigator.ContentView.removeInvisibleElementScrollListener;
+
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -14,20 +19,20 @@
import com.bluelinelabs.conductor.Controller;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils;
import ee.ria.DigiDoc.android.utils.ViewDisposables;
import ee.ria.DigiDoc.android.utils.navigator.Screen;
import ee.ria.DigiDoc.android.utils.navigator.Transaction;
-import static com.jakewharton.rxbinding4.widget.RxToolbar.navigationClicks;
-
public final class AboutScreen extends Controller implements Screen {
public static AboutScreen create() {
return new AboutScreen();
}
+ private RecyclerView listView;
+
private final ViewDisposables disposables = new ViewDisposables();
@SuppressWarnings("WeakerAccess")
@@ -45,13 +50,17 @@ protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup
toolbarView.setTitle(R.string.main_about_title);
toolbarView.setNavigationIcon(androidx.appcompat.R.drawable.abc_ic_ab_back_material);
toolbarView.setNavigationContentDescription(R.string.back);
- RecyclerView listView = view.findViewById(R.id.mainAboutList);
+ listView = view.findViewById(R.id.mainAboutList);
listView.setLayoutManager(new LinearLayoutManager(container.getContext()));
listView.setAdapter(new AboutAdapter());
+ addInvisibleElement(getApplicationContext(), view);
+ View lastElementView = view.findViewById(R.id.lastInvisibleElement);
+ addInvisibleElementScrollListener(listView, lastElementView);
+
disposables.attach();
disposables.add(navigationClicks(toolbarView).subscribe(ignored ->
- Application.component(container.getContext()).navigator().execute(Transaction.pop())));
+ ApplicationApp.component(container.getContext()).navigator().execute(Transaction.pop())));
return view;
}
@@ -59,6 +68,7 @@ protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup
@Override
protected void onDestroyView(@NonNull View view) {
disposables.detach();
+ removeInvisibleElementScrollListener(listView);
super.onDestroyView(view);
}
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/accessibility/AccessibilityView.java b/app/src/main/java/ee/ria/DigiDoc/android/main/accessibility/AccessibilityView.java
index 8d161d97d..70cfeb762 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/main/accessibility/AccessibilityView.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/accessibility/AccessibilityView.java
@@ -1,20 +1,25 @@
package ee.ria.DigiDoc.android.main.accessibility;
import static com.jakewharton.rxbinding4.widget.RxToolbar.navigationClicks;
+import static ee.ria.DigiDoc.android.utils.TextUtil.getInvisibleElementTextView;
+import static ee.ria.DigiDoc.android.utils.ViewUtil.findLastElement;
import android.content.Context;
import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
import android.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.utils.ViewDisposables;
+import ee.ria.DigiDoc.android.utils.navigator.ContentView;
import ee.ria.DigiDoc.android.utils.navigator.Navigator;
import ee.ria.DigiDoc.android.utils.navigator.Transaction;
-public class AccessibilityView extends CoordinatorLayout {
+public class AccessibilityView extends CoordinatorLayout implements ContentView {
private final Toolbar toolbarView;
@@ -34,12 +39,14 @@ public AccessibilityView(Context context, AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
inflate(context, R.layout.main_accessibility, this);
toolbarView = findViewById(R.id.toolbar);
- navigator = Application.component(context).navigator();
+ navigator = ApplicationApp.component(context).navigator();
disposables = new ViewDisposables();
toolbarView.setTitle(R.string.main_accessibility_title);
toolbarView.setNavigationIcon(androidx.appcompat.R.drawable.abc_ic_ab_back_material);
toolbarView.setNavigationContentDescription(R.string.back);
+
+ ContentView.addInvisibleElement(getContext(), this);
}
@Override
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/diagnostics/DiagnosticsScreen.java b/app/src/main/java/ee/ria/DigiDoc/android/main/diagnostics/DiagnosticsScreen.java
index 8d430a5b0..07997d962 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/main/diagnostics/DiagnosticsScreen.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/diagnostics/DiagnosticsScreen.java
@@ -10,10 +10,9 @@
import androidx.annotation.Nullable;
import java.io.File;
-import java.util.List;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.utils.ViewDisposables;
import ee.ria.DigiDoc.android.utils.mvi.MviView;
import ee.ria.DigiDoc.android.utils.navigator.Screen;
@@ -43,7 +42,7 @@ protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup
@Override
protected void onContextAvailable(@NonNull Context context) {
super.onContextAvailable(context);
- viewModel = Application.component(context).navigator()
+ viewModel = ApplicationApp.component(context).navigator()
.viewModel(getInstanceId(), DiagnosticsViewModel.class);
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/diagnostics/DiagnosticsView.java b/app/src/main/java/ee/ria/DigiDoc/android/main/diagnostics/DiagnosticsView.java
index 0ffe7d156..e3b4e9009 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/main/diagnostics/DiagnosticsView.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/diagnostics/DiagnosticsView.java
@@ -7,6 +7,7 @@
import static ee.ria.DigiDoc.android.main.diagnostics.DiagnosticsScreen.diagnosticsFileSaveClicksSubject;
import android.content.Context;
+import android.graphics.Color;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Build;
@@ -46,12 +47,14 @@
import ee.ria.DigiDoc.BuildConfig;
import ee.ria.DigiDoc.R;
import ee.ria.DigiDoc.android.Activity;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils;
import ee.ria.DigiDoc.android.utils.ClickableDialogUtil;
import ee.ria.DigiDoc.android.utils.TSLException;
import ee.ria.DigiDoc.android.utils.TSLUtil;
import ee.ria.DigiDoc.android.utils.ViewDisposables;
+import ee.ria.DigiDoc.android.utils.ViewUtil;
+import ee.ria.DigiDoc.android.utils.navigator.ContentView;
import ee.ria.DigiDoc.android.utils.navigator.Navigator;
import ee.ria.DigiDoc.android.utils.navigator.Transaction;
import ee.ria.DigiDoc.android.utils.widget.ConfirmationDialog;
@@ -69,7 +72,7 @@
import okhttp3.Response;
import timber.log.Timber;
-public final class DiagnosticsView extends CoordinatorLayout {
+public final class DiagnosticsView extends CoordinatorLayout implements ContentView {
private final Navigator navigator;
private final SimpleDateFormat dateFormat;
@@ -92,7 +95,9 @@ public DiagnosticsView(Context context) {
AccessibilityUtils.setViewAccessibilityPaneTitle(this, R.string.main_diagnostics_title);
toolbarView = findViewById(R.id.toolbar);
View saveDiagnosticsButton = findViewById(R.id.configurationSaveButton);
- navigator = Application.component(context).navigator();
+ navigator = ApplicationApp.component(context).navigator();
+
+ ContentView.addInvisibleElement(getContext(), this);
diagnosticsRestartConfirmationDialog = new ConfirmationDialog(navigator.activity(),
R.string.main_diagnostics_restart_message, R.id.mainDiagnosticsRestartConfirmationDialog);
@@ -104,7 +109,7 @@ public DiagnosticsView(Context context) {
(activateLogFileGenerating.isChecked() &&
FileUtil.logsExist(FileUtil.getLogsDirectory(getContext()))) ? VISIBLE : GONE);
- ConfigurationProvider configurationProvider = ((Application) context.getApplicationContext()).getConfigurationProvider();
+ ConfigurationProvider configurationProvider = ((ApplicationApp) context.getApplicationContext()).getConfigurationProvider();
disposables = new ViewDisposables();
toolbarView.setTitle(R.string.main_diagnostics_title);
@@ -273,8 +278,8 @@ private String getTSLFileVersion(InputStream tslInputStream, String tslFileName)
}
private void updateConfiguration() {
- Application application = (Application) getContext().getApplicationContext();
- application.updateConfiguration(this);
+ ApplicationApp applicationApp = (ApplicationApp) getContext().getApplicationContext();
+ applicationApp.updateConfiguration(this);
}
private void setData(ConfigurationProvider configurationProvider) {
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/home/HomeMenuView.java b/app/src/main/java/ee/ria/DigiDoc/android/main/home/HomeMenuView.java
index 7472b0816..53116e8b6 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/main/home/HomeMenuView.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/home/HomeMenuView.java
@@ -28,6 +28,7 @@
import ee.ria.DigiDoc.R;
import ee.ria.DigiDoc.android.utils.display.DisplayUtil;
+import ee.ria.DigiDoc.android.utils.navigator.ContentView;
import ee.ria.DigiDoc.common.TextUtil;
import io.reactivex.rxjava3.core.Observable;
@@ -35,7 +36,7 @@
import static com.jakewharton.rxbinding4.widget.RxRadioGroup.checkedChanges;
import static ee.ria.DigiDoc.android.utils.TintUtils.tintCompoundDrawables;
-public final class HomeMenuView extends NestedScrollView {
+public final class HomeMenuView extends NestedScrollView implements ContentView {
private final View closeButton;
@@ -139,6 +140,8 @@ public HomeMenuView(@NonNull Context context, @Nullable AttributeSet attrs, int
}, 2000);
setFontSize();
+
+ ContentView.addInvisibleElement(getContext(), this);
}
@Override
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/home/HomeScreen.java b/app/src/main/java/ee/ria/DigiDoc/android/main/home/HomeScreen.java
index 0aee2b018..f3468e22d 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/main/home/HomeScreen.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/home/HomeScreen.java
@@ -23,11 +23,19 @@ public static HomeScreen create(Intent intent) {
@SuppressWarnings("WeakerAccess")
public HomeScreen(Bundle args) {
super(R.id.mainHomeScreen, args);
- intent = args.getParcelable(KEY_INTENT);
+ intent = getIntent(args);
}
@Override
protected View view(Context context) {
return new HomeView(context, intent, getInstanceId());
}
+
+ private android.content.Intent getIntent(Bundle bundle) {
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
+ return bundle.getParcelable(KEY_INTENT, android.content.Intent.class);
+ } else {
+ return bundle.getParcelable(KEY_INTENT);
+ }
+ }
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/home/HomeView.java b/app/src/main/java/ee/ria/DigiDoc/android/main/home/HomeView.java
index 72a08caf1..920433634 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/main/home/HomeView.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/home/HomeView.java
@@ -17,23 +17,25 @@
import com.google.android.material.internal.BaselineLayout;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.crypto.home.CryptoHomeView;
import ee.ria.DigiDoc.android.eid.EIDHomeView;
import ee.ria.DigiDoc.android.signature.home.SignatureHomeView;
import ee.ria.DigiDoc.android.utils.TextUtil;
import ee.ria.DigiDoc.android.utils.ViewDisposables;
import ee.ria.DigiDoc.android.utils.mvi.MviView;
+import ee.ria.DigiDoc.android.utils.navigator.ContentView;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.subjects.PublishSubject;
import io.reactivex.rxjava3.subjects.Subject;
import static com.jakewharton.rxbinding4.material.RxBottomNavigationView.itemSelections;
import static ee.ria.DigiDoc.android.utils.Predicates.duplicates;
+import static ee.ria.DigiDoc.android.utils.ViewUtil.moveView;
import static ee.ria.DigiDoc.android.utils.rxbinding.app.RxDialog.cancels;
@SuppressLint("ViewConstructor")
-public final class HomeView extends LinearLayout implements MviView {
+public final class HomeView extends LinearLayout implements ContentView, MviView {
public interface HomeViewChild {
@@ -90,9 +92,14 @@ public HomeView(Context context, android.content.Intent intent, String screenId)
menuDialog = new HomeMenuDialog(context);
menuView = menuDialog.getMenuView();
- viewModel = Application.component(context).navigator().viewModel(screenId,
+ viewModel = ApplicationApp.component(context).navigator().viewModel(screenId,
HomeViewModel.class);
viewModel.eidScreenId(eidScreenId);
+
+ ContentView.addInvisibleElement(getContext(), navigationContainerView);
+
+ View lastElementView = findViewById(R.id.lastInvisibleElement);
+ moveView(lastElementView);
}
private void setCustomAccessibilityFeedback(BottomNavigationItemView bottomNavigationItemView) {
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/home/Processor.java b/app/src/main/java/ee/ria/DigiDoc/android/main/home/Processor.java
index 07c675bc1..253cc6107 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/main/home/Processor.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/home/Processor.java
@@ -1,5 +1,9 @@
package ee.ria.DigiDoc.android.main.home;
+import static android.content.Intent.ACTION_VIEW;
+import static ee.ria.DigiDoc.android.utils.IntentUtils.createBrowserIntent;
+import static ee.ria.DigiDoc.android.utils.IntentUtils.parseGetContentIntent;
+
import android.app.Application;
import android.content.ContentResolver;
import android.content.Context;
@@ -40,10 +44,6 @@
import io.reactivex.rxjava3.core.ObservableSource;
import io.reactivex.rxjava3.core.ObservableTransformer;
-import static android.content.Intent.ACTION_VIEW;
-import static ee.ria.DigiDoc.android.utils.IntentUtils.createBrowserIntent;
-import static ee.ria.DigiDoc.android.utils.IntentUtils.parseGetContentIntent;
-
final class Processor implements ObservableTransformer {
private static final SparseIntArray NAVIGATION_ITEM_VIEWS = new SparseIntArray();
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsDataStore.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsDataStore.java
index 4a347359f..ebf0e7fa0 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsDataStore.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsDataStore.java
@@ -3,6 +3,7 @@
import android.app.Application;
import android.content.SharedPreferences;
import android.content.res.Resources;
+
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
@@ -182,4 +183,23 @@ public void setIsLogFileGenerationRunning(boolean isRunning) {
editor.commit();
}
+ public void setTSACertName(String cert) {
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putString(resources.getString(R.string.main_settings_tsa_cert_key), cert);
+ editor.commit();
+ }
+
+ public String getTSACertName() {
+ return preferences.getString(resources.getString(R.string.main_settings_tsa_cert_key), "");
+ }
+
+ public void setIsTsaCertificateViewVisible(boolean isVisible) {
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putBoolean(resources.getString(R.string.main_settings_tsa_cert_view), isVisible);
+ editor.commit();
+ }
+
+ public boolean getIsTsaCertificateViewVisible() {
+ return preferences.getBoolean(resources.getString(R.string.main_settings_tsa_cert_view), false);
+ }
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsScreen.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsScreen.java
index b4be7a102..75f83dfe2 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsScreen.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsScreen.java
@@ -25,19 +25,4 @@ public SettingsScreen() {
protected View view(Context context) {
return new SettingsView(context);
}
-
- @Override
- protected void onDestroyView(@NonNull View view) {
- FragmentActivity activity = (FragmentActivity) getActivity();
- if (activity != null) {
- FragmentManager fragmentManager = activity.getSupportFragmentManager();
- Fragment fragment = fragmentManager.findFragmentById(R.id.mainSettingsFragment);
- if (fragment != null) {
- fragmentManager.beginTransaction()
- .remove(fragment)
- .commitAllowingStateLoss();
- }
- }
- super.onDestroyView(view);
- }
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsView.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsView.java
index 9f824b882..d8b1c4fa3 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsView.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsView.java
@@ -1,30 +1,29 @@
package ee.ria.DigiDoc.android.main.settings;
-import android.content.ComponentName;
+import static com.jakewharton.rxbinding4.view.RxView.clicks;
+import static com.jakewharton.rxbinding4.widget.RxToolbar.navigationClicks;
+import static ee.ria.DigiDoc.android.main.settings.util.SettingsUtil.getToolbarViewTitle;
+
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
import android.util.AttributeSet;
-import android.view.View;
+import android.widget.Button;
import android.widget.TextView;
import android.widget.Toolbar;
-import androidx.appcompat.widget.SwitchCompat;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.Activity;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
+import ee.ria.DigiDoc.android.main.settings.access.SettingsAccessScreen;
import ee.ria.DigiDoc.android.utils.ViewDisposables;
+import ee.ria.DigiDoc.android.utils.navigator.ContentView;
import ee.ria.DigiDoc.android.utils.navigator.Navigator;
import ee.ria.DigiDoc.android.utils.navigator.Transaction;
-import static com.jakewharton.rxbinding4.widget.RxToolbar.navigationClicks;
-
-public final class SettingsView extends CoordinatorLayout {
+public final class SettingsView extends CoordinatorLayout implements ContentView {
private final Toolbar toolbarView;
- private final TextView toolbarTitleView;
+ private final Button accessCategory;
private final Navigator navigator;
@@ -42,10 +41,12 @@ public SettingsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
inflate(context, R.layout.main_settings, this);
toolbarView = findViewById(R.id.toolbar);
- toolbarTitleView = getToolbarViewTitle();
- navigator = Application.component(context).navigator();
+ navigator = ApplicationApp.component(context).navigator();
+ TextView toolbarTitleView = getToolbarViewTitle(toolbarView);
disposables = new ViewDisposables();
+ accessCategory = findViewById(R.id.mainSettingsAccessCategory);
+
toolbarView.setTitle(R.string.main_settings_title);
toolbarView.setNavigationIcon(androidx.appcompat.R.drawable.abc_ic_ab_back_material);
toolbarView.setNavigationContentDescription(R.string.back);
@@ -53,47 +54,6 @@ public SettingsView(Context context, AttributeSet attrs, int defStyleAttr) {
if (toolbarTitleView != null) {
toolbarTitleView.setContentDescription("\u202F");
}
-
- Activity activityContext = (Activity) this.getContext();
- SwitchCompat openAllFileTypesSwitch = findViewById(R.id.mainSettingsOpenAllFileTypes);
- if (openAllFileTypesSwitch != null && activityContext != null) {
- openAllFileTypesSwitch.setChecked(activityContext.getSettingsDataStore().getIsOpenAllFileTypesEnabled());
-
- openAllFileTypesSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
- activityContext.getSettingsDataStore().setIsOpenAllFileTypesEnabled(isChecked);
- restartIntent();
- });
- }
-
- SwitchCompat allowScreenshotsSwitch = findViewById(R.id.mainSettingsAllowScreenshots);
- if (allowScreenshotsSwitch != null && activityContext != null) {
- allowScreenshotsSwitch.setChecked(activityContext.getSettingsDataStore().getIsScreenshotAllowed());
-
- allowScreenshotsSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
- activityContext.getSettingsDataStore().setIsScreenshotAllowed(isChecked);
- restartIntent();
- });
- }
- }
-
- private TextView getToolbarViewTitle() {
- for (int i = 0; i < toolbarView.getChildCount(); i++) {
- View childView = toolbarView.getChildAt(i);
- if (childView instanceof TextView) {
- return (TextView) childView;
- }
- }
-
- return null;
- }
-
- private void restartIntent() {
- PackageManager packageManager = getContext().getPackageManager();
- Intent intent = packageManager.getLaunchIntentForPackage(getContext().getPackageName());
- ComponentName componentName = intent.getComponent();
- Intent restartIntent = Intent.makeRestartActivityTask(componentName);
- restartIntent.setAction(Intent.ACTION_CONFIGURATION_CHANGED);
- getContext().startActivity(restartIntent);
}
@Override
@@ -102,6 +62,10 @@ public void onAttachedToWindow() {
disposables.attach();
disposables.add(navigationClicks(toolbarView).subscribe(o ->
navigator.execute(Transaction.pop())));
+ disposables.add(clicks(accessCategory).subscribe(o ->
+ navigator.execute(
+ Transaction.push(SettingsAccessScreen.create())))
+ );
}
@Override
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/SettingsAccessScreen.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/SettingsAccessScreen.java
new file mode 100644
index 000000000..6577a7b28
--- /dev/null
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/SettingsAccessScreen.java
@@ -0,0 +1,44 @@
+package ee.ria.DigiDoc.android.main.settings.access;
+
+import android.content.Context;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+
+import ee.ria.DigiDoc.R;
+import ee.ria.DigiDoc.android.utils.navigator.conductor.ConductorScreen;
+
+public final class SettingsAccessScreen extends ConductorScreen {
+
+ public static SettingsAccessScreen create() {
+ return new SettingsAccessScreen();
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ public SettingsAccessScreen() {
+ super(R.id.mainSettingsAccessScreen);
+ }
+
+ @Override
+ protected View view(Context context) {
+ return new SettingsAccessView(context);
+ }
+
+ @Override
+ protected void onDestroyView(@NonNull View view) {
+ FragmentActivity activity = (FragmentActivity) getActivity();
+ if (activity != null) {
+ FragmentManager fragmentManager = activity.getSupportFragmentManager();
+ Fragment fragment = fragmentManager.findFragmentById(R.id.mainSettingsFragment);
+ if (fragment != null) {
+ fragmentManager.beginTransaction()
+ .remove(fragment)
+ .commitAllowingStateLoss();
+ }
+ }
+ super.onDestroyView(view);
+ }
+}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/SettingsAccessView.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/SettingsAccessView.java
new file mode 100644
index 000000000..3e3bc94f8
--- /dev/null
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/SettingsAccessView.java
@@ -0,0 +1,242 @@
+package ee.ria.DigiDoc.android.main.settings.access;
+
+import static com.jakewharton.rxbinding4.view.RxView.clicks;
+import static com.jakewharton.rxbinding4.widget.RxToolbar.navigationClicks;
+import static ee.ria.DigiDoc.android.main.settings.util.SettingsUtil.getToolbarViewTitle;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toolbar;
+
+import androidx.appcompat.widget.SwitchCompat;
+import androidx.coordinatorlayout.widget.CoordinatorLayout;
+
+import org.bouncycastle.asn1.x500.RDN;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+
+import java.io.File;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import ee.ria.DigiDoc.R;
+import ee.ria.DigiDoc.android.Activity;
+import ee.ria.DigiDoc.android.ApplicationApp;
+import ee.ria.DigiDoc.android.main.settings.SettingsDataStore;
+import ee.ria.DigiDoc.android.main.settings.create.ChooseFileScreen;
+import ee.ria.DigiDoc.android.main.settings.create.TSACertificateAddViewModel;
+import ee.ria.DigiDoc.android.main.settings.create.ViewState;
+import ee.ria.DigiDoc.android.signature.detail.CertificateDetailScreen;
+import ee.ria.DigiDoc.android.utils.ViewDisposables;
+import ee.ria.DigiDoc.android.utils.navigator.Navigator;
+import ee.ria.DigiDoc.android.utils.navigator.Transaction;
+import ee.ria.DigiDoc.common.CertificateUtil;
+import ee.ria.DigiDoc.common.FileUtil;
+import ee.ria.DigiDoc.configuration.util.FileUtils;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.subjects.PublishSubject;
+import io.reactivex.rxjava3.subjects.Subject;
+import timber.log.Timber;
+
+public final class SettingsAccessView extends CoordinatorLayout {
+
+ private final Toolbar toolbarView;
+ private final LinearLayout tsaCertContainer;
+ private final TextView tsaCertIssuedTo;
+ private final TextView tsaCertValidTo;
+ private final Button addCertificateButton;
+ private final Button showCertificateButton;
+ private X509Certificate tsaCertificate;
+
+ private final Navigator navigator;
+ private final SettingsDataStore settingsDataStore;
+
+ private final ViewDisposables disposables;
+ private final TSACertificateAddViewModel viewModel;
+
+ private static boolean isTsaCertificateViewVisible;
+ private static final Subject isTsaCertificateViewVisibleSubject = PublishSubject.create();
+
+ String viewId = String.valueOf(View.generateViewId());
+
+ public SettingsAccessView(Context context) {
+ this(context, null);
+ }
+
+ public SettingsAccessView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SettingsAccessView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ viewModel = ApplicationApp.component(context).navigator()
+ .viewModel(viewId, TSACertificateAddViewModel.class);
+ inflate(context, R.layout.main_settings_access, this);
+ toolbarView = findViewById(R.id.toolbar);
+ TextView toolbarTitleView = getToolbarViewTitle(toolbarView);
+ navigator = ApplicationApp.component(context).navigator();
+ settingsDataStore = ApplicationApp.component(context).settingsDataStore();
+ disposables = new ViewDisposables();
+
+ toolbarView.setTitle(R.string.main_settings_access_button);
+ toolbarView.setNavigationIcon(androidx.appcompat.R.drawable.abc_ic_ab_back_material);
+ toolbarView.setNavigationContentDescription(R.string.back);
+
+ if (toolbarTitleView != null) {
+ toolbarTitleView.setContentDescription("\u202F");
+ }
+
+ tsaCertContainer = findViewById(R.id.mainSettingsTsaCertificateContainer);
+ tsaCertIssuedTo = findViewById(R.id.mainSettingsTsaCertificateIssuedTo);
+ tsaCertValidTo = findViewById(R.id.mainSettingsTsaCertificateValidTo);
+ addCertificateButton = findViewById(R.id.mainSettingsTsaCertificateAddCertificateButton);
+ showCertificateButton = findViewById(R.id.mainSettingsTsaCertificateShowCertificateButton);
+
+ if (settingsDataStore != null) {
+ isTsaCertificateViewVisible = settingsDataStore.getIsTsaCertificateViewVisible();
+ setTSAContainerViewVisibility(isTsaCertificateViewVisible);
+ }
+
+ Activity activityContext = (Activity) this.getContext();
+ SwitchCompat openAllFileTypesSwitch = findViewById(R.id.mainSettingsOpenAllFileTypes);
+ if (openAllFileTypesSwitch != null && activityContext != null) {
+ openAllFileTypesSwitch.setChecked(activityContext.getSettingsDataStore().getIsOpenAllFileTypesEnabled());
+
+ openAllFileTypesSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ activityContext.getSettingsDataStore().setIsOpenAllFileTypesEnabled(isChecked);
+ restartIntent();
+ });
+ }
+
+ SwitchCompat allowScreenshotsSwitch = findViewById(R.id.mainSettingsAllowScreenshots);
+ if (allowScreenshotsSwitch != null && activityContext != null) {
+ allowScreenshotsSwitch.setChecked(activityContext.getSettingsDataStore().getIsScreenshotAllowed());
+
+ allowScreenshotsSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ activityContext.getSettingsDataStore().setIsScreenshotAllowed(isChecked);
+ restartIntent();
+ });
+ }
+ }
+
+ private void restartIntent() {
+ PackageManager packageManager = getContext().getPackageManager();
+ Intent intent = packageManager.getLaunchIntentForPackage(getContext().getPackageName());
+ ComponentName componentName = intent.getComponent();
+ Intent restartIntent = Intent.makeRestartActivityTask(componentName);
+ restartIntent.setAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ getContext().startActivity(restartIntent);
+ }
+
+ public void render(ViewState state) {
+ if (settingsDataStore != null) {
+ String tsaCertName = settingsDataStore.getTSACertName();
+
+ File tsaFile = FileUtil.getTSAFile(getContext(), tsaCertName);
+
+ if (tsaFile != null) {
+ String fileContents = FileUtils.readFileContent(tsaFile.getPath());
+ try {
+ tsaCertificate = CertificateUtil.x509Certificate(fileContents);
+ X509CertificateHolder certificateHolder = new JcaX509CertificateHolder(tsaCertificate);
+ String issuer = getIssuer(certificateHolder);
+ tsaCertIssuedTo.setText(String.format("%s %s",
+ getResources().getText(R.string.main_settings_timestamp_cert_issued_to_title),
+ issuer));
+ tsaCertValidTo.setText(String.format("%s %s",
+ getResources().getText(R.string.main_settings_timestamp_cert_valid_to_title),
+ getFormattedDateTime(certificateHolder.getNotAfter())));
+ } catch (CertificateException e) {
+ Timber.log(Log.ERROR, e, "Unable to get TSA certificate");
+
+ // Remove invalid files
+ FileUtils.removeFile(tsaFile.getPath());
+ settingsDataStore.setTSACertName(null);
+
+ tsaCertIssuedTo.setText(getResources().getText(R.string.main_settings_timestamp_cert_issued_to_title));
+ tsaCertValidTo.setText(getResources().getText(R.string.main_settings_timestamp_cert_valid_to_title));
+ }
+ }
+ }
+ }
+
+ private String getIssuer(X509CertificateHolder certificateHolder) {
+ RDN[] organizationRDNs = certificateHolder.getIssuer().getRDNs(BCStyle.O);
+ if (organizationRDNs.length > 0) {
+ return organizationRDNs[0].getFirst().getValue().toString();
+ }
+ RDN[] organizationUnitRDNs = certificateHolder.getIssuer().getRDNs(BCStyle.OU);
+ if (organizationUnitRDNs.length > 0) {
+ return organizationUnitRDNs[0].getFirst().getValue().toString();
+ }
+ RDN[] commonNameRDNs = certificateHolder.getIssuer().getRDNs(BCStyle.CN);
+ if (commonNameRDNs.length > 0) {
+ return commonNameRDNs[0].getFirst().getValue().toString();
+ }
+
+ return "-";
+ }
+
+ private static String getFormattedDateTime(Date date) {
+ try {
+ SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy", Locale.getDefault());
+ if (date != null) {
+ return dateFormat.format(date);
+ }
+ } catch (IllegalStateException e) {
+ Timber.log(Log.ERROR, e, "Unable to format date");
+ }
+ return "-";
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ disposables.attach();
+ disposables.add(navigationClicks(toolbarView).subscribe(o ->
+ navigator.execute(Transaction.pop())));
+ disposables.add(clicks(showCertificateButton).subscribe(o -> {
+ if (tsaCertificate != null) {
+ navigator.execute(Transaction.push(CertificateDetailScreen.create(tsaCertificate)));
+ }
+ }));
+ disposables.add(clicks(addCertificateButton).subscribe(o ->
+ navigator.execute(Transaction.push(ChooseFileScreen.create()))));
+ disposables.add(observeTsaCertificateViewVisibleChanges().subscribe(isVisible -> {
+ settingsDataStore.setIsTsaCertificateViewVisible(isVisible);
+ setTSAContainerViewVisibility(isVisible);
+ }));
+ disposables.add(viewModel.viewStates().subscribe(this::render));
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ disposables.detach();
+ super.onDetachedFromWindow();
+ }
+
+ public static void setTsaCertificateViewVisibleValue(boolean value) {
+ isTsaCertificateViewVisible = value;
+ isTsaCertificateViewVisibleSubject.onNext(value);
+ }
+
+ public static Observable observeTsaCertificateViewVisibleChanges() {
+ return isTsaCertificateViewVisibleSubject;
+ }
+
+ private void setTSAContainerViewVisibility(boolean isVisible) {
+ tsaCertContainer.setVisibility(!isVisible ? GONE : VISIBLE);
+ }
+}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsFragment.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/SettingsFragment.java
similarity index 97%
rename from app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsFragment.java
rename to app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/SettingsFragment.java
index 9760e169d..83c039858 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsFragment.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/SettingsFragment.java
@@ -1,4 +1,4 @@
-package ee.ria.DigiDoc.android.main.settings;
+package ee.ria.DigiDoc.android.main.settings.access;
import android.os.Bundle;
import android.view.View;
@@ -24,7 +24,7 @@ public final class SettingsFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
- setPreferencesFromResource(R.xml.main_settings, null);
+ setPreferencesFromResource(R.xml.main_settings_access, null);
}
@Override
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/TsaUrlPreference.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/TsaUrlPreference.java
similarity index 86%
rename from app/src/main/java/ee/ria/DigiDoc/android/main/settings/TsaUrlPreference.java
rename to app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/TsaUrlPreference.java
index 8916ff5bc..b8b90b442 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/TsaUrlPreference.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/TsaUrlPreference.java
@@ -1,4 +1,4 @@
-package ee.ria.DigiDoc.android.main.settings;
+package ee.ria.DigiDoc.android.main.settings.access;
import static android.view.accessibility.AccessibilityEvent.TYPE_ANNOUNCEMENT;
@@ -6,10 +6,7 @@
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
-import android.view.View;
-import android.view.ViewGroup;
import android.widget.CheckBox;
-import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatCheckBox;
@@ -17,8 +14,9 @@
import com.takisoft.preferencex.EditTextPreference;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils;
+import ee.ria.DigiDoc.android.main.settings.access.SettingsAccessView;
import ee.ria.DigiDoc.configuration.ConfigurationProvider;
public class TsaUrlPreference extends EditTextPreference {
@@ -41,7 +39,7 @@ public TsaUrlPreference(Context context, @Nullable AttributeSet attrs, int defSt
public TsaUrlPreference(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- configurationProvider = ((Application) context.getApplicationContext()).getConfigurationProvider();
+ configurationProvider = ((ApplicationApp) context.getApplicationContext()).getConfigurationProvider();
checkBox = new AppCompatCheckBox(context);
checkBox.setId(android.R.id.checkbox);
checkBox.setText(R.string.main_settings_tsa_url_use_default);
@@ -52,6 +50,7 @@ public TsaUrlPreference(Context context, @Nullable AttributeSet attrs, int defSt
setViewId(R.id.mainSettingsAccessToTimeStampingService);
setOnPreferenceChangeListener((preference, newValue) -> {
+ SettingsAccessView.setTsaCertificateViewVisibleValue(!checkBox.isChecked());
AccessibilityUtils.sendAccessibilityEvent(context, TYPE_ANNOUNCEMENT, R.string.setting_value_changed);
return true;
});
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/TsaUrlPreferenceDialogFragment.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/TsaUrlPreferenceDialogFragment.java
similarity index 93%
rename from app/src/main/java/ee/ria/DigiDoc/android/main/settings/TsaUrlPreferenceDialogFragment.java
rename to app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/TsaUrlPreferenceDialogFragment.java
index 1438022e8..baa9fa5b8 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/TsaUrlPreferenceDialogFragment.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/TsaUrlPreferenceDialogFragment.java
@@ -1,4 +1,4 @@
-package ee.ria.DigiDoc.android.main.settings;
+package ee.ria.DigiDoc.android.main.settings.access;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -16,7 +16,7 @@
import androidx.preference.EditTextPreferenceDialogFragmentCompat;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils;
import ee.ria.DigiDoc.android.utils.SecureUtil;
import ee.ria.DigiDoc.android.utils.TextUtil;
@@ -30,7 +30,7 @@ protected void onBindDialogView(View view) {
super.onBindDialogView(view);
TsaUrlPreference tsaUrlPreference = getTsaUrlPreference();
if (tsaUrlPreference != null) {
- ConfigurationProvider configurationProvider = ((Application) getContext().getApplicationContext()).getConfigurationProvider();
+ ConfigurationProvider configurationProvider = ((ApplicationApp) getContext().getApplicationContext()).getConfigurationProvider();
CheckBox checkBox = tsaUrlPreference.getCheckBox();
AppCompatEditText appCompatEditText = TextUtil.getTextView(view);
@@ -50,6 +50,8 @@ protected void onBindDialogView(View view) {
checkBox.setChecked(TextUtils.isEmpty(tsaUrlPreference.getText()));
+ SettingsAccessView.setTsaCertificateViewVisibleValue(!checkBox.isChecked());
+
ViewGroup parent = ((ViewGroup) appCompatEditText.getParent());
View oldCheckBox = appCompatEditText.findViewById(checkBox.getId());
if (oldCheckBox != null) {
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/UUIDPreference.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/UUIDPreference.java
similarity index 98%
rename from app/src/main/java/ee/ria/DigiDoc/android/main/settings/UUIDPreference.java
rename to app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/UUIDPreference.java
index 797fb46fb..7fffc7c1a 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/UUIDPreference.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/UUIDPreference.java
@@ -18,7 +18,7 @@
*
*/
-package ee.ria.DigiDoc.android.main.settings;
+package ee.ria.DigiDoc.android.main.settings.access;
import static android.view.accessibility.AccessibilityEvent.TYPE_ANNOUNCEMENT;
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/UUIDPreferenceDialogFragment.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/UUIDPreferenceDialogFragment.java
similarity index 98%
rename from app/src/main/java/ee/ria/DigiDoc/android/main/settings/UUIDPreferenceDialogFragment.java
rename to app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/UUIDPreferenceDialogFragment.java
index e86ba7bee..8293dee6d 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/UUIDPreferenceDialogFragment.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/UUIDPreferenceDialogFragment.java
@@ -18,7 +18,7 @@
*
*/
-package ee.ria.DigiDoc.android.main.settings;
+package ee.ria.DigiDoc.android.main.settings.access;
import static android.view.accessibility.AccessibilityEvent.TYPE_ANNOUNCEMENT;
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/create/Action.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/create/Action.java
new file mode 100644
index 000000000..0747d89cd
--- /dev/null
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/create/Action.java
@@ -0,0 +1,35 @@
+package ee.ria.DigiDoc.android.main.settings.create;
+
+import static ee.ria.DigiDoc.android.Constants.RC_TSA_CERT_ADD;
+import static ee.ria.DigiDoc.android.utils.IntentUtils.createGetContentIntent;
+
+import androidx.annotation.Nullable;
+
+import com.google.auto.value.AutoValue;
+
+import ee.ria.DigiDoc.android.utils.mvi.MviAction;
+import ee.ria.DigiDoc.android.utils.navigator.Transaction;
+import ee.ria.DigiDoc.android.utils.navigator.TransactionAction;
+
+interface Action extends MviAction {
+
+ @AutoValue
+ abstract class InitialAction implements Action {
+
+ static InitialAction create() {
+ return new AutoValue_Action_InitialAction();
+ }
+ }
+
+ @AutoValue
+ abstract class ChooseFileAction implements Action,
+ TransactionAction {
+
+ @Nullable abstract android.content.Intent intent();
+
+ static ChooseFileAction create() {
+ return new AutoValue_Action_ChooseFileAction(Transaction.activityForResult(
+ RC_TSA_CERT_ADD, createGetContentIntent(false), null), null);
+ }
+ }
+}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/create/ChooseFileScreen.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/create/ChooseFileScreen.java
new file mode 100644
index 000000000..c12b38bae
--- /dev/null
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/create/ChooseFileScreen.java
@@ -0,0 +1,86 @@
+package ee.ria.DigiDoc.android.main.settings.create;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import ee.ria.DigiDoc.R;
+import ee.ria.DigiDoc.android.ApplicationApp;
+import ee.ria.DigiDoc.android.utils.ViewDisposables;
+import ee.ria.DigiDoc.android.utils.mvi.MviView;
+import ee.ria.DigiDoc.android.utils.navigator.Navigator;
+import ee.ria.DigiDoc.android.utils.navigator.Screen;
+import ee.ria.DigiDoc.android.utils.navigator.Transaction;
+import ee.ria.DigiDoc.android.utils.navigator.conductor.ConductorScreen;
+import io.reactivex.rxjava3.core.Observable;
+
+public final class ChooseFileScreen extends ConductorScreen implements Screen, MviView {
+
+ public static ChooseFileScreen create() {
+ return new ChooseFileScreen();
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ public ChooseFileScreen() {
+ super(R.id.mainSettingsChooseFileScreen);
+ }
+
+ @Override
+ protected View view(Context context) {
+ return new View(context);
+ }
+
+ @Override
+ protected void onDestroyView(@NonNull View view) {
+ super.onDestroyView(view);
+ }
+
+ @Override
+ public Observable intents() {
+ return Observable.mergeArray(chooseFileIntent());
+ }
+
+ private final ViewDisposables disposables = new ViewDisposables();
+ private TSACertificateAddViewModel viewModel;
+
+ @NonNull
+ @Override
+ protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container, @Nullable Bundle savedViewState) {
+ disposables.attach();
+ disposables.add(viewModel.viewStates().subscribe(this::render));
+ viewModel.process(intents());
+
+ return super.onCreateView(inflater, container, savedViewState);
+ }
+
+ @Override
+ protected void onContextAvailable(@NonNull Context context) {
+ super.onContextAvailable(context);
+ viewModel = ApplicationApp.component(context).navigator()
+ .viewModel(getInstanceId(), TSACertificateAddViewModel.class);
+ }
+
+ @Override
+ protected void onContextUnavailable() {
+ viewModel = null;
+ super.onContextUnavailable();
+ }
+
+ @Override
+ public void render(ViewState state) {
+
+ if (state.context() != null) {
+ Navigator navigator = ApplicationApp.component(state.context()).navigator();
+ navigator.execute(Transaction.pop());
+ }
+ }
+
+ private Observable chooseFileIntent() {
+ return Observable.just(new Intent.ChooseFileIntent().create());
+ }
+}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/create/Intent.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/create/Intent.java
new file mode 100644
index 000000000..145a1ee63
--- /dev/null
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/create/Intent.java
@@ -0,0 +1,32 @@
+package ee.ria.DigiDoc.android.main.settings.create;
+
+import com.google.auto.value.AutoValue;
+
+import ee.ria.DigiDoc.android.utils.mvi.MviIntent;
+
+public interface Intent extends MviIntent {
+
+ @AutoValue
+ class ChooseFileIntent implements Intent {
+
+ public static ChooseFileIntent clear() {
+ return null;
+ }
+
+ public ChooseFileIntent create() {
+ return new AutoValue_Intent_ChooseFileIntent();
+ }
+ }
+
+ @AutoValue
+ class OpenAccessIntent implements Intent {
+
+ public static OpenAccessIntent clear() {
+ return null;
+ }
+
+ public OpenAccessIntent create() {
+ return new AutoValue_Intent_OpenAccessIntent();
+ }
+ }
+}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/create/Processor.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/create/Processor.java
new file mode 100644
index 000000000..dc6a3d967
--- /dev/null
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/create/Processor.java
@@ -0,0 +1,141 @@
+package ee.ria.DigiDoc.android.main.settings.create;
+
+import static android.app.Activity.RESULT_OK;
+import static ee.ria.DigiDoc.android.utils.IntentUtils.parseGetContentIntent;
+import static ee.ria.DigiDoc.common.CommonConstants.DIR_TSA_CERT;
+
+import android.app.Application;
+import android.util.Log;
+
+import androidx.documentfile.provider.DocumentFile;
+
+import com.google.common.collect.ImmutableList;
+
+import org.apache.commons.io.FileUtils;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import ee.ria.DigiDoc.R;
+import ee.ria.DigiDoc.android.main.settings.SettingsDataStore;
+import ee.ria.DigiDoc.android.utils.ToastUtil;
+import ee.ria.DigiDoc.android.utils.files.EmptyFileException;
+import ee.ria.DigiDoc.android.utils.files.FileStream;
+import ee.ria.DigiDoc.android.utils.files.FileSystem;
+import ee.ria.DigiDoc.android.utils.navigator.ActivityResult;
+import ee.ria.DigiDoc.android.utils.navigator.ActivityResultException;
+import ee.ria.DigiDoc.android.utils.navigator.Navigator;
+import ee.ria.DigiDoc.android.utils.navigator.Transaction;
+import ee.ria.DigiDoc.common.ActivityUtil;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.core.ObservableSource;
+import io.reactivex.rxjava3.core.ObservableTransformer;
+import io.reactivex.rxjava3.exceptions.CompositeException;
+import timber.log.Timber;
+
+final class Processor implements ObservableTransformer {
+
+ private final ObservableTransformer
+ chooseFile;
+
+ @Inject Processor(Navigator navigator,
+ Application application,
+ SettingsDataStore settingsDataStore,
+ FileSystem fileSystem) {
+
+ chooseFile = upstream -> upstream
+ .switchMap(action -> {
+ if (action.intent() != null) {
+ throw new ActivityResultException(ActivityResult.create(
+ action.transaction().getRequestCode(), RESULT_OK, action.intent()));
+ }
+ navigator.execute(action.transaction());
+ return navigator.activityResults()
+ .filter(activityResult ->
+ activityResult.requestCode()
+ == action.transaction().getRequestCode())
+ .doOnNext(activityResult -> {
+ throw new ActivityResultException(activityResult);
+ })
+ .map(activityResult -> Result.ChooseFileResult.create(navigator.activity()));
+ })
+ .onErrorResumeNext(throwable -> {
+ if (!(throwable instanceof ActivityResultException)) {
+ return Observable.error(throwable);
+ }
+ ActivityResult activityResult = ((ActivityResultException) throwable)
+ .activityResult;
+ if (activityResult.resultCode() == RESULT_OK) {
+ if (activityResult.data() != null) {
+ ImmutableList validFiles = FileSystem.getFilesWithValidSize(
+ parseGetContentIntent(navigator.activity(), application.getContentResolver(), activityResult.data(), fileSystem.getExternallyOpenedFilesDir()));
+ ToastUtil.handleEmptyFileError(validFiles, navigator.activity());
+
+ try (InputStream initialStream = application.getContentResolver().openInputStream(activityResult.data().getData())) {
+ DocumentFile documentFile = DocumentFile.fromSingleUri(navigator.activity(), activityResult.data().getData());
+ if (documentFile != null) {
+ File tsaCertFolder = new File(navigator.activity().getFilesDir(), DIR_TSA_CERT);
+ if (!tsaCertFolder.exists()) {
+ boolean isFolderCreated = tsaCertFolder.mkdirs();
+ Timber.log(Log.DEBUG, String.format("TSA cert folder created: %s", isFolderCreated));
+ }
+
+ String fileName = documentFile.getName();
+ if (fileName == null || fileName.isEmpty()) {
+ fileName = "tsaCert";
+ }
+ File tsaFile = new File(tsaCertFolder, fileName);
+
+ FileUtils.copyInputStreamToFile(initialStream, tsaFile);
+
+ settingsDataStore.setTSACertName(tsaFile.getName());
+
+ return Observable.just(Result.ChooseFileResult.create(navigator.activity()));
+ }
+ } catch (Exception e) {
+ Timber.log(Log.ERROR, e, "Unable to read TSA certificate file data");
+ }
+
+ return Observable.just(Result.ChooseFileResult.create(navigator.activity()));
+ } else {
+ Timber.log(Log.ERROR, "Data from file chooser is empty");
+ ToastUtil.showError(navigator.activity(), R.string.signature_create_error);
+
+ navigator.execute(Transaction.pop());
+ }
+ } else {
+ if (ActivityUtil.isExternalFileOpened(navigator.activity())) {
+ ActivityUtil.restartActivity(application.getApplicationContext(), navigator.activity());
+ } else {
+ navigator.execute(Transaction.pop());
+ }
+ return Observable.just(Result.ChooseFileResult.create(navigator.activity()));
+ }
+
+ return Observable.empty();
+ })
+ .onErrorReturn(throwable -> {
+ List exceptions = ((CompositeException) throwable).getExceptions();
+ if (!exceptions.isEmpty()) {
+ boolean isEmptyFileException = exceptions.stream().anyMatch(exception ->
+ (exception instanceof EmptyFileException));
+ if (isEmptyFileException) {
+ ToastUtil.showEmptyFileError(navigator.activity());
+ } else {
+ ToastUtil.showError(navigator.activity(), R.string.signature_create_error);
+ }
+ }
+ navigator.execute(Transaction.pop());
+ return Result.ChooseFileResult.create(navigator.activity());
+ });
+ }
+
+ @Override
+ public ObservableSource apply(Observable upstream) {
+ return upstream.publish(shared -> Observable.mergeArray(
+ shared.ofType(Action.ChooseFileAction.class).compose(chooseFile)));
+ }
+}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/create/Result.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/create/Result.java
new file mode 100644
index 000000000..b0a91225b
--- /dev/null
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/create/Result.java
@@ -0,0 +1,43 @@
+package ee.ria.DigiDoc.android.main.settings.create;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import com.google.auto.value.AutoValue;
+
+import ee.ria.DigiDoc.android.utils.mvi.MviResult;
+
+interface Result extends MviResult {
+
+ @AutoValue
+ abstract class ChooseFileResult implements Result {
+
+ @Nullable abstract Context context();
+
+ @Override
+ public ViewState reduce(ViewState state) {
+ return state.buildWith()
+ .context(context())
+ .build();
+ }
+
+ static ChooseFileResult create(@Nullable Context context) {
+ return new AutoValue_Result_ChooseFileResult(context);
+ }
+ }
+
+ @AutoValue
+ abstract class OpenAccessResult implements Result {
+
+ @Override
+ public ViewState reduce(ViewState state) {
+ return state.buildWith()
+ .build();
+ }
+
+ static OpenAccessResult create() {
+ return new AutoValue_Result_OpenAccessResult();
+ }
+ }
+}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/create/TSACertificateAddViewModel.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/create/TSACertificateAddViewModel.java
new file mode 100644
index 000000000..e149ff80c
--- /dev/null
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/create/TSACertificateAddViewModel.java
@@ -0,0 +1,31 @@
+package ee.ria.DigiDoc.android.main.settings.create;
+
+import javax.inject.Inject;
+
+import ee.ria.DigiDoc.android.utils.mvi.BaseMviViewModel;
+
+public final class TSACertificateAddViewModel extends BaseMviViewModel {
+
+ @Inject TSACertificateAddViewModel(Processor processor) {
+ super(processor);
+ }
+
+ @Override
+ protected Class extends Intent> initialIntentType() {
+ return Intent.ChooseFileIntent.class;
+ }
+
+ @Override
+ protected Action action(Intent intent) {
+ if (intent instanceof Intent.ChooseFileIntent) {
+ return Action.ChooseFileAction.create();
+ } else {
+ throw new IllegalArgumentException("Unknown intent " + intent);
+ }
+ }
+
+ @Override
+ protected ViewState initialViewState() {
+ return ViewState.initial();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/create/ViewState.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/create/ViewState.java
new file mode 100644
index 000000000..26dfe2cf7
--- /dev/null
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/create/ViewState.java
@@ -0,0 +1,29 @@
+package ee.ria.DigiDoc.android.main.settings.create;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import com.google.auto.value.AutoValue;
+
+import ee.ria.DigiDoc.android.utils.mvi.MviViewState;
+
+@AutoValue
+public abstract class ViewState implements MviViewState {
+
+ @Nullable abstract Context context();
+
+ abstract Builder buildWith();
+
+ static ViewState initial() {
+ return new AutoValue_ViewState.Builder()
+ .context(null)
+ .build();
+ }
+
+ @AutoValue.Builder
+ interface Builder {
+ Builder context(Context context);
+ ViewState build();
+ }
+}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/util/SettingsUtil.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/util/SettingsUtil.java
new file mode 100644
index 000000000..a5ce405e7
--- /dev/null
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/util/SettingsUtil.java
@@ -0,0 +1,19 @@
+package ee.ria.DigiDoc.android.main.settings.util;
+
+import android.view.View;
+import android.widget.TextView;
+import android.widget.Toolbar;
+
+public class SettingsUtil {
+
+ public static TextView getToolbarViewTitle(Toolbar toolbarView) {
+ for (int i = 0; i < toolbarView.getChildCount(); i++) {
+ View childView = toolbarView.getChildAt(i);
+ if (childView instanceof TextView) {
+ return (TextView) childView;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/sharing/SharingScreenView.java b/app/src/main/java/ee/ria/DigiDoc/android/main/sharing/SharingScreenView.java
index a82eac2fb..d061d83d5 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/main/sharing/SharingScreenView.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/main/sharing/SharingScreenView.java
@@ -1,26 +1,28 @@
package ee.ria.DigiDoc.android.main.sharing;
+import static android.app.Activity.RESULT_OK;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
-import androidx.coordinatorlayout.widget.CoordinatorLayout;
-import androidx.core.content.FileProvider;
-
import android.util.Log;
import android.webkit.MimeTypeMap;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toolbar;
-import android.widget.Toast;
+
+import androidx.coordinatorlayout.widget.CoordinatorLayout;
+import androidx.core.content.FileProvider;
import java.io.File;
import java.util.ArrayList;
+import java.util.Optional;
import ee.ria.DigiDoc.R;
import ee.ria.DigiDoc.android.Activity;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.Constants;
import ee.ria.DigiDoc.android.utils.ToastUtil;
import ee.ria.DigiDoc.android.utils.ViewDisposables;
@@ -43,7 +45,7 @@ public SharingScreenView(Context context) {
super(context);
inflate(context, R.layout.sharing_choose_container_file, this);
toolbarView = findViewById(R.id.toolbar);
- navigator = Application.component(context).navigator();
+ navigator = ApplicationApp.component(context).navigator();
disposables = new ViewDisposables();
@@ -116,24 +118,21 @@ private void showFilesListener(ListView listView, ArrayList containerFiles
File requestFile = containerFilesList.get(position);
try {
- Activity activity = (Activity) getContext();
-
Uri fileProviderUri = FileProvider.getUriForFile(getContext(),
- activity.getString(R.string.file_provider_authority),
+ navigator.activity().getString(R.string.file_provider_authority),
requestFile);
- Intent returnIntent = new Intent(Intent.ACTION_VIEW);
+ Intent returnIntent = navigator.activity().getIntent();
if (fileProviderUri != null) {
returnIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
returnIntent.setDataAndType(fileProviderUri, getFileExtensionFromFileUri(fileProviderUri));
- activity.setResult(Activity.RESULT_OK, returnIntent);
-
- returnToParentApplication(activity, returnIntent);
+ navigator.activity().setResult(RESULT_OK, returnIntent);
+ navigator.activity().finish();
} else {
returnIntent.setDataAndType(null, "");
- activity.setResult(Activity.RESULT_CANCELED, returnIntent);
- activity.finish();
+ navigator.activity().setResult(Activity.RESULT_CANCELED, returnIntent);
+ navigator.activity().finish();
restartToMainApp();
}
} catch (IllegalArgumentException e) {
@@ -153,9 +152,12 @@ private void returnToParentApplication(Activity activity, Intent intent) {
}
private boolean isIntentWithExtraReferrer(Activity activity) {
- return activity.getIntent() != null && activity.getIntent().getExtras() != null &&
- activity.getIntent().getExtras().get(Intent.EXTRA_REFERRER) != null &&
- activity.getIntent().getExtras().get(Intent.EXTRA_REFERRER).equals(R.string.application_name);
+ return Optional
+ .ofNullable(activity.getIntent())
+ .map(Intent::getExtras)
+ .map(extras -> extras.get(Intent.EXTRA_REFERRER))
+ .filter(extraReferrer -> extraReferrer.equals(R.string.application_name))
+ .isPresent();
}
private String getFileExtensionFromFileUri(Uri uri) {
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/create/Action.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/create/Action.java
index f3cda0fa2..87ce9159d 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/create/Action.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/create/Action.java
@@ -1,6 +1,10 @@
package ee.ria.DigiDoc.android.signature.create;
+import static ee.ria.DigiDoc.android.Constants.RC_SIGNATURE_CREATE_DOCUMENTS_ADD;
+import static ee.ria.DigiDoc.android.utils.IntentUtils.createGetContentIntent;
+
import android.content.Intent;
+
import androidx.annotation.Nullable;
import com.google.auto.value.AutoValue;
@@ -9,9 +13,6 @@
import ee.ria.DigiDoc.android.utils.navigator.Transaction;
import ee.ria.DigiDoc.android.utils.navigator.TransactionAction;
-import static ee.ria.DigiDoc.android.Constants.RC_SIGNATURE_CREATE_DOCUMENTS_ADD;
-import static ee.ria.DigiDoc.android.utils.IntentUtils.createGetContentIntent;
-
interface Action extends MviAction {
@AutoValue
@@ -22,7 +23,7 @@ abstract class ChooseFilesAction implements Action,
static ChooseFilesAction create(@Nullable Intent intent) {
return new AutoValue_Action_ChooseFilesAction(Transaction.activityForResult(
- RC_SIGNATURE_CREATE_DOCUMENTS_ADD, createGetContentIntent(), null), intent);
+ RC_SIGNATURE_CREATE_DOCUMENTS_ADD, createGetContentIntent(true), null), intent);
}
}
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/create/Processor.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/create/Processor.java
index 78b3fa97e..67535df88 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/create/Processor.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/create/Processor.java
@@ -14,7 +14,6 @@
import javax.inject.Inject;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.Activity;
import ee.ria.DigiDoc.android.signature.data.SignatureContainerDataSource;
import ee.ria.DigiDoc.android.signature.update.SignatureUpdateScreen;
import ee.ria.DigiDoc.android.utils.ClickableDialogUtil;
@@ -37,7 +36,6 @@
import io.reactivex.rxjava3.core.ObservableSource;
import io.reactivex.rxjava3.core.ObservableTransformer;
import io.reactivex.rxjava3.exceptions.CompositeException;
-import io.reactivex.rxjava3.schedulers.Schedulers;
import timber.log.Timber;
final class Processor implements ObservableTransformer {
@@ -60,13 +58,13 @@ final class Processor implements ObservableTransformer {
.switchMap(action -> {
if (action.intent() != null) {
throw new ActivityResultException(ActivityResult.create(
- action.transaction().requestCode(), RESULT_OK, action.intent()));
+ action.transaction().getRequestCode(), RESULT_OK, action.intent()));
}
navigator.execute(action.transaction());
return navigator.activityResults()
.filter(activityResult ->
activityResult.requestCode()
- == action.transaction().requestCode())
+ == action.transaction().getRequestCode())
.doOnNext(activityResult -> {
throw new ActivityResultException(activityResult);
})
@@ -82,11 +80,11 @@ final class Processor implements ObservableTransformer {
if (activityResult.data() != null) {
ImmutableList validFiles = FileSystem.getFilesWithValidSize(
parseGetContentIntent(navigator.activity(), application.getContentResolver(), activityResult.data(), fileSystem.getExternallyOpenedFilesDir()));
- ToastUtil.handleEmptyFileError(validFiles, application, navigator.activity());
+ ToastUtil.handleEmptyFileError(validFiles, navigator.activity());
return handleFiles(navigator, signatureContainerDataSource, validFiles)
- .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
+ .subscribeOn(AndroidSchedulers.mainThread())
.doOnError(throwable1 -> {
Timber.log(Log.ERROR, throwable1,
String.format("Unable to add file to container. Error: %s",
@@ -118,7 +116,7 @@ final class Processor implements ObservableTransformer {
boolean isEmptyFileException = exceptions.stream().anyMatch(exception ->
(exception instanceof EmptyFileException));
if (isEmptyFileException) {
- ToastUtil.showEmptyFileError(navigator.activity(), application);
+ ToastUtil.showEmptyFileError(navigator.activity());
} else {
ToastUtil.showError(navigator.activity(), R.string.signature_create_error);
}
@@ -142,8 +140,8 @@ private Observable addFilesToContainer(Navigator navig
return signatureContainerDataSource
.addContainer(navigator.activity(), validFiles, false)
.toObservable()
- .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
+ .subscribeOn(AndroidSchedulers.mainThread())
.doOnNext(containerAdd ->
navigator.execute(Transaction.replace(SignatureUpdateScreen
.create(containerAdd.isExistingContainer(), false,
@@ -170,6 +168,7 @@ private Observable handleFiles(Navigator navigator,
ImmutableList validFiles) {
return SivaUtil.isSivaConfirmationNeeded(validFiles, navigator.activity())
.observeOn(AndroidSchedulers.mainThread())
+ .subscribeOn(AndroidSchedulers.mainThread())
.flatMap(isSivaConfirmationNeeded -> {
if (isSivaConfirmationNeeded) {
sivaConfirmationDialog.show();
@@ -197,6 +196,15 @@ private Observable handleFiles(Navigator navigator,
return addFilesToContainer(navigator, signatureContainerDataSource, validFiles, true);
}
return Observable.just(Result.ChooseFilesResult.create());
+ })
+ .onErrorResumeNext(throwable -> {
+ if (throwable instanceof NoInternetConnectionException) {
+ ToastUtil.showError(navigator.activity(), R.string.no_internet_connection);
+ } else {
+ ToastUtil.showError(navigator.activity(), R.string.signature_create_error);
+ }
+ navigator.execute(Transaction.pop());
+ return Observable.just(Result.ChooseFilesResult.create());
});
}
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/create/SignatureCreateScreen.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/create/SignatureCreateScreen.java
index f4167c660..54f9a6257 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/create/SignatureCreateScreen.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/create/SignatureCreateScreen.java
@@ -24,11 +24,19 @@ public static SignatureCreateScreen create(@Nullable android.content.Intent inte
@SuppressWarnings("WeakerAccess")
public SignatureCreateScreen(Bundle args) {
super(R.id.signatureCreateScreen);
- intent = args.getParcelable(KEY_INTENT);
+ intent = getIntent(args);
}
@Override
protected View view(Context context) {
return new SignatureCreateView(context, getInstanceId(), intent);
}
+
+ private android.content.Intent getIntent(Bundle bundle) {
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
+ return bundle.getParcelable(KEY_INTENT, android.content.Intent.class);
+ } else {
+ return bundle.getParcelable(KEY_INTENT);
+ }
+ }
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/create/SignatureCreateView.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/create/SignatureCreateView.java
index 847d3a761..e4afc6bdd 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/create/SignatureCreateView.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/create/SignatureCreateView.java
@@ -7,13 +7,14 @@
import android.widget.FrameLayout;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.utils.ViewDisposables;
import ee.ria.DigiDoc.android.utils.mvi.MviView;
+import ee.ria.DigiDoc.android.utils.navigator.ContentView;
import io.reactivex.rxjava3.core.Observable;
@SuppressLint("ViewConstructor")
-public final class SignatureCreateView extends FrameLayout implements MviView {
+public final class SignatureCreateView extends FrameLayout implements ContentView, MviView {
@Nullable private final android.content.Intent intent;
@@ -24,9 +25,11 @@ public SignatureCreateView(@NonNull Context context, String screenId,
@Nullable android.content.Intent intent) {
super(context);
this.intent = intent;
- viewModel = Application.component(context).navigator()
+ viewModel = ApplicationApp.component(context).navigator()
.viewModel(screenId, SignatureCreateViewModel.class);
inflate(context, R.layout.signature_create, this);
+
+ ContentView.addInvisibleElement(getContext(), this);
}
private Observable initialIntent() {
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/detail/CertificateDetailView.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/detail/CertificateDetailView.java
index 38a1ffb24..d9e8dbbfd 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/detail/CertificateDetailView.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/detail/CertificateDetailView.java
@@ -8,7 +8,6 @@
import android.text.TextUtils;
import android.util.Log;
import android.webkit.URLUtil;
-import android.widget.TextView;
import android.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
@@ -46,17 +45,18 @@
import java.util.Map;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils;
import ee.ria.DigiDoc.android.utils.TextUtil;
import ee.ria.DigiDoc.android.utils.ViewDisposables;
import ee.ria.DigiDoc.android.utils.ViewSavedState;
+import ee.ria.DigiDoc.android.utils.navigator.ContentView;
import ee.ria.DigiDoc.android.utils.navigator.Navigator;
import ee.ria.DigiDoc.android.utils.navigator.Transaction;
import timber.log.Timber;
@SuppressLint("ViewConstructor")
-public final class CertificateDetailView extends CoordinatorLayout {
+public final class CertificateDetailView extends CoordinatorLayout implements ContentView {
private final Navigator navigator;
private final Toolbar toolbarView;
@@ -69,7 +69,7 @@ public CertificateDetailView(Context context, X509Certificate certificate) {
inflate(context, R.layout.certificate_details_screen, this);
AccessibilityUtils.setViewAccessibilityPaneTitle(this, R.string.certificate_details_title);
- navigator = Application.component(context).navigator();
+ navigator = ApplicationApp.component(context).navigator();
toolbarView = findViewById(R.id.toolbar);
toolbarView.setTitle(R.string.certificate_details_title);
@@ -77,6 +77,8 @@ public CertificateDetailView(Context context, X509Certificate certificate) {
toolbarView.setNavigationContentDescription(R.string.back);
setData(certificate);
+
+ ContentView.addInvisibleElement(context, this);
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/detail/SignatureDetailView.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/detail/SignatureDetailView.java
index f4cc0a30f..d85a42f23 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/detail/SignatureDetailView.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/detail/SignatureDetailView.java
@@ -1,14 +1,27 @@
package ee.ria.DigiDoc.android.signature.detail;
+import static com.jakewharton.rxbinding4.view.RxView.clicks;
import static com.jakewharton.rxbinding4.widget.RxToolbar.navigationClicks;
+import static ee.ria.DigiDoc.android.accessibility.AccessibilityUtils.setCustomClickAccessibilityFeedBack;
+import static ee.ria.DigiDoc.android.utils.TintUtils.tintCompoundDrawables;
import android.annotation.SuppressLint;
import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.os.Build;
import android.os.Parcelable;
+import android.text.util.Linkify;
import android.util.Log;
+import android.widget.LinearLayout;
+import android.widget.TextView;
import android.widget.Toolbar;
+import androidx.annotation.ColorInt;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
+import androidx.core.content.ContextCompat;
+
+import net.cachapa.expandablelayout.ExpandableLayout;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
@@ -20,24 +33,36 @@
import java.security.cert.X509Certificate;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils;
import ee.ria.DigiDoc.android.utils.TextUtil;
import ee.ria.DigiDoc.android.utils.ViewDisposables;
import ee.ria.DigiDoc.android.utils.ViewSavedState;
+import ee.ria.DigiDoc.android.utils.navigator.ContentView;
import ee.ria.DigiDoc.android.utils.navigator.Navigator;
import ee.ria.DigiDoc.android.utils.navigator.Transaction;
import ee.ria.DigiDoc.sign.Signature;
+import ee.ria.DigiDoc.sign.SignatureStatus;
import ee.ria.DigiDoc.sign.SignedContainer;
import ee.ria.libdigidocpp.Container;
+import io.reactivex.rxjava3.core.Observable;
import timber.log.Timber;
@SuppressLint("ViewConstructor")
-public final class SignatureDetailView extends CoordinatorLayout {
+public final class SignatureDetailView extends CoordinatorLayout implements ContentView {
private final Navigator navigator;
private final Toolbar toolbarView;
+ private final LinearLayout errorContainer;
+ private final TextView errorTitle;
+ private final TextView errorDetails;
+ private final TextView technicalInformationButtonTitle;
+ private final ExpandableLayout technicalInformationContainerView;
+ private final TextView technicalInformationText;
+
+ @ColorInt private final int titleColor;
+
private final ViewDisposables disposables = new ViewDisposables();
public SignatureDetailView(Context context, Signature signature, SignedContainer signedContainer) {
@@ -46,13 +71,32 @@ public SignatureDetailView(Context context, Signature signature, SignedContainer
inflate(context, R.layout.signature_detail_screen, this);
AccessibilityUtils.setViewAccessibilityPaneTitle(this, R.string.signature_details_title);
- navigator = Application.component(context).navigator();
+ navigator = ApplicationApp.component(context).navigator();
toolbarView = findViewById(R.id.toolbar);
+ errorContainer = findViewById(R.id.signersCertificateErrorContainer);
+ errorTitle = findViewById(R.id.signersCertificateErrorTitle);
+ errorDetails = findViewById(R.id.signersCertificateErrorDetails);
+ technicalInformationButtonTitle = findViewById(R.id.signersCertificateTechnicalInformationButtonTitle);
+ technicalInformationContainerView = findViewById(R.id.signersCertificateTechnicalInformationContainerView);
+ technicalInformationText = findViewById(R.id.signersCertificateTechnicalInformationText);
+
toolbarView.setTitle(R.string.signature_details_title);
toolbarView.setNavigationIcon(androidx.appcompat.R.drawable.abc_ic_ab_back_material);
toolbarView.setNavigationContentDescription(R.string.back);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ try (TypedArray a = getContext().obtainStyledAttributes(new int[]{
+ android.R.attr.textColorSecondary, R.attr.colorAccent})) {
+ titleColor = a.getColor(1, Color.BLACK);
+ }
+ } else {
+ TypedArray a = getContext().obtainStyledAttributes(new int[] {
+ android.R.attr.textColorSecondary, R.attr.colorAccent});
+ titleColor = a.getColor(1, Color.BLACK);
+ a.recycle();
+ }
+
if (isCertificateInExistence(signature.signingCertificate())) {
findViewById(R.id.signersCertificateButton).setOnClickListener(view -> navigator.execute(Transaction.push(CertificateDetailScreen.create(signature.signingCertificate()))));
}
@@ -65,7 +109,11 @@ public SignatureDetailView(Context context, Signature signature, SignedContainer
findViewById(R.id.signatureDetailOCSPCertificateButton).setOnClickListener(view -> navigator.execute(Transaction.push(CertificateDetailScreen.create(signature.ocspCertificate()))));
}
+ setExpandedState(false);
+ setWarningsData(signature);
setData(signature, signedContainer);
+
+ ContentView.addInvisibleElement(context, this);
}
private String getContainerMimeType(SignedContainer signedContainer) {
@@ -112,12 +160,52 @@ private static String getX509CertificateSubject(X509Certificate x509Certificate)
}
}
+ private void setExpandedState(boolean isExpanded) {
+ technicalInformationButtonTitle.setTextColor(titleColor);
+ int drawable = isExpanded
+ ? R.drawable.ic_icon_accordion_expanded
+ : R.drawable.ic_icon_accordion_collapsed;
+ technicalInformationButtonTitle.setCompoundDrawablesRelativeWithIntrinsicBounds(drawable, 0, 0, 0);
+ tintCompoundDrawables(technicalInformationButtonTitle);
+ setCustomClickAccessibilityFeedBack(technicalInformationButtonTitle, technicalInformationContainerView);
+
+ technicalInformationContainerView.setExpanded(isExpanded);
+ }
+
+ private void setWarningsData(Signature signature) {
+ SignatureStatus status = signature.status();
+ if (status != SignatureStatus.VALID) {
+ errorContainer.setVisibility(VISIBLE);
+ String diagnosticsInfo = signature.diagnosticsInfo();
+ if (status == SignatureStatus.WARNING) {
+ if (diagnosticsInfo.contains("Signature digest weak")) {
+ errorDetails.setText(getResources().getString(R.string.signature_error_details_reason_weak));
+ } else {
+ errorDetails.setText(getResources().getString(R.string.signature_error_details_reason_warning));
+ }
+ errorTitle.setTextColor(ContextCompat.getColor(getContext(), R.color.warningText));
+ } else if (status == SignatureStatus.NON_QSCD) {
+ errorDetails.setText(getResources().getString(R.string.signature_error_details_reason_nonqscd));
+ errorTitle.setTextColor(ContextCompat.getColor(getContext(), R.color.warningText));
+ } else if (status == SignatureStatus.UNKNOWN) {
+ errorDetails.setText(getResources().getString(R.string.signature_error_details_reason_unknown));
+ errorTitle.setTextColor(ContextCompat.getColor(getContext(), R.color.error));
+ } else if (status == SignatureStatus.INVALID) {
+ errorDetails.setText(getResources().getString(R.string.signature_error_details_invalid_reason));
+ errorTitle.setTextColor(ContextCompat.getColor(getContext(), R.color.error));
+ }
+ Linkify.addLinks(errorDetails, Linkify.WEB_URLS);
+ technicalInformationText.setText(diagnosticsInfo);
+ }
+ }
+
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
disposables.attach();
disposables.add(navigationClicks(toolbarView).subscribe(o ->
navigator.execute(Transaction.pop())));
+ disposables.add(certificateContainerStates().subscribe(this::setExpandedState));
}
@Override
@@ -126,6 +214,11 @@ public void onDetachedFromWindow() {
super.onDetachedFromWindow();
}
+ public Observable certificateContainerStates() {
+ return clicks(technicalInformationButtonTitle)
+ .map(ignored -> !technicalInformationContainerView.isExpanded());
+ }
+
private boolean isCertificateInExistence(X509Certificate certificate) {
return certificate != null;
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/home/SignatureHomeView.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/home/SignatureHomeView.java
index 4d58cf916..75524ea1f 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/home/SignatureHomeView.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/home/SignatureHomeView.java
@@ -9,7 +9,7 @@
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils;
import ee.ria.DigiDoc.android.main.home.HomeToolbar;
import ee.ria.DigiDoc.android.main.home.HomeView;
@@ -43,7 +43,7 @@ public SignatureHomeView(Context context, @Nullable AttributeSet attrs, int defS
inflate(context, R.layout.signature_home, this);
toolbarView = findViewById(R.id.toolbar);
createButton = findViewById(R.id.signatureHomeCreateButton);
- navigator = Application.component(context).navigator();
+ navigator = ApplicationApp.component(context).navigator();
disposables = new ViewDisposables();
AccessibilityUtils.setViewAccessibilityPaneTitle(this, R.string.main_home_navigation_signature);
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/list/Intent.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/list/Intent.java
index f925c0065..4849db4b2 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/list/Intent.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/list/Intent.java
@@ -2,9 +2,6 @@
import android.content.Context;
-import androidx.annotation.Nullable;
-
-import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import java.io.File;
@@ -16,83 +13,104 @@
import io.reactivex.rxjava3.core.Observable;
interface Intent extends MviIntent {
+ Action action();
+}
- @AutoValue
- abstract class InitialIntent implements Intent {
+class InitialIntent implements Intent {
+ private InitialIntent() {}
- static InitialIntent create() {
- return new AutoValue_Intent_InitialIntent();
- }
+ static InitialIntent create() {
+ return new InitialIntent();
}
- @AutoValue
- abstract class UpButtonIntent implements Intent {
-
- static UpButtonIntent create() {
- return new AutoValue_Intent_UpButtonIntent();
- }
+ @Override
+ public Action action() {
+ return Action.ContainersLoadAction.create(true);
}
+}
- @AutoValue
- abstract class ContainerOpenIntent implements Intent, Action {
+class UpButtonIntent implements Intent {
+ private UpButtonIntent() {}
- @Nullable abstract File containerFile();
+ static UpButtonIntent create() {
+ return new UpButtonIntent();
+ }
- abstract boolean confirmation();
+ @Override
+ public Action action() {
+ return Action.NavigateUpAction.create();
+ }
+}
- abstract boolean isSivaConfirmed();
+class ContainerOpenIntent implements Intent {
+ private final File containerFile;
+ private final boolean confirmation;
+ private final boolean isSivaConfirmed;
- static Observable confirmation(File containerFile, Context context) {
- return SivaUtil.isSivaConfirmationNeeded(
- ImmutableList.of(FileStream.create(containerFile)), context)
- .observeOn(AndroidSchedulers.mainThread())
- .map(isSivaConfirmationNeeded -> create(containerFile, isSivaConfirmationNeeded, false))
- .subscribeOn(AndroidSchedulers.mainThread());
- }
+ private ContainerOpenIntent(File containerFile, boolean confirmation, boolean isSivaConfirmed) {
+ this.containerFile = containerFile;
+ this.confirmation = confirmation;
+ this.isSivaConfirmed = isSivaConfirmed;
+ }
- static ContainerOpenIntent open(File containerFile, boolean isSivaConfirmed) {
- return create(containerFile, false, isSivaConfirmed);
- }
+ static Observable confirmation(File containerFile, Context context) {
+ return SivaUtil.isSivaConfirmationNeeded(
+ ImmutableList.of(FileStream.create(containerFile)), context)
+ .observeOn(AndroidSchedulers.mainThread())
+ .map(isSivaConfirmationNeeded -> new ContainerOpenIntent(containerFile, isSivaConfirmationNeeded, false))
+ .subscribeOn(AndroidSchedulers.mainThread());
+ }
- static ContainerOpenIntent cancel() {
- return create(null, false, true);
- }
+ static ContainerOpenIntent open(File containerFile, boolean isSivaConfirmed) {
+ return new ContainerOpenIntent(containerFile, false, isSivaConfirmed);
+ }
- private static ContainerOpenIntent create(@Nullable File containerFile, boolean confirmation, boolean isSivaConfirmed) {
- return new AutoValue_Intent_ContainerOpenIntent(containerFile, confirmation, isSivaConfirmed);
- }
+ static ContainerOpenIntent cancel() {
+ return new ContainerOpenIntent(null, false, true);
}
- @AutoValue
- abstract class ContainerRemoveIntent implements Intent {
+ @Override
+ public Action action() {
+ return Action.ContainerOpenAction.create(containerFile, confirmation, isSivaConfirmed);
+ }
+}
- @Nullable abstract File containerFile();
+class ContainerRemoveIntent implements Intent {
+ private final File containerFile;
+ private final boolean confirmation;
- abstract boolean confirmation();
+ private ContainerRemoveIntent(File containerFile, boolean confirmation) {
+ this.containerFile = containerFile;
+ this.confirmation = confirmation;
+ }
- static ContainerRemoveIntent confirmation(File containerFile) {
- return create(containerFile, true);
- }
+ static ContainerRemoveIntent confirmation(File containerFile) {
+ return new ContainerRemoveIntent(containerFile, true);
+ }
- static ContainerRemoveIntent remove(File containerFile) {
- return create(containerFile, false);
- }
+ static ContainerRemoveIntent remove(File containerFile) {
+ return new ContainerRemoveIntent(containerFile, false);
+ }
- static ContainerRemoveIntent cancel() {
- return create(null, false);
- }
+ static ContainerRemoveIntent cancel() {
+ return new ContainerRemoveIntent(null, false);
+ }
- private static ContainerRemoveIntent create(@Nullable File containerFile,
- boolean confirmation) {
- return new AutoValue_Intent_ContainerRemoveIntent(containerFile, confirmation);
- }
+ @Override
+ public Action action() {
+ return Action.ContainerRemoveAction.create(containerFile, confirmation);
}
+}
- @AutoValue
- abstract class RefreshIntent implements Intent {
+class RefreshIntent implements Intent {
+ private RefreshIntent() {}
+
+ static RefreshIntent create() {
+ return new RefreshIntent();
+ }
- static RefreshIntent create() {
- return new AutoValue_Intent_RefreshIntent();
- }
+ @Override
+ public Action action() {
+ return Action.ContainersLoadAction.create(false);
}
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/list/Processor.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/list/Processor.java
index f73fc1d9a..7ac695257 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/list/Processor.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/list/Processor.java
@@ -67,7 +67,7 @@ final class Processor implements ObservableTransformer {
} else {
File containerFile = action.containerFile();
if (CryptoContainer.isContainerFileName(containerFile.getName())) {
- navigator.execute(Transaction.push(CryptoCreateScreen.open(containerFile)));
+ navigator.execute(Transaction.push(CryptoCreateScreen.open(containerFile, false)));
} else {
navigator.execute(Transaction.push(SignatureUpdateScreen
.create(true, false, containerFile, false, false,
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/list/SignatureListScreen.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/list/SignatureListScreen.java
index 9bd8b7f0a..ec13af666 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/list/SignatureListScreen.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/list/SignatureListScreen.java
@@ -1,17 +1,26 @@
package ee.ria.DigiDoc.android.signature.list;
-import android.content.Context;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+import static android.view.accessibility.AccessibilityEvent.TYPE_ANNOUNCEMENT;
+import static com.jakewharton.rxbinding4.widget.RxToolbar.navigationClicks;
+import static ee.ria.DigiDoc.android.utils.navigator.ContentView.addInvisibleElementScrollListener;
+import static ee.ria.DigiDoc.android.utils.navigator.ContentView.addInvisibleElementToObject;
+import static ee.ria.DigiDoc.android.utils.navigator.ContentView.removeInvisibleElementScrollListener;
+import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.LinearLayout;
import android.widget.Toolbar;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
import com.bluelinelabs.conductor.Controller;
import com.google.common.io.Files;
@@ -19,7 +28,7 @@
import ee.ria.DigiDoc.R;
import ee.ria.DigiDoc.android.Activity;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils;
import ee.ria.DigiDoc.android.utils.ViewDisposables;
import ee.ria.DigiDoc.android.utils.mvi.MviView;
@@ -28,11 +37,6 @@
import ee.ria.DigiDoc.sign.SignedContainer;
import io.reactivex.rxjava3.core.Observable;
-import static android.view.View.GONE;
-import static android.view.View.VISIBLE;
-import static android.view.accessibility.AccessibilityEvent.TYPE_ANNOUNCEMENT;
-import static com.jakewharton.rxbinding4.widget.RxToolbar.navigationClicks;
-
public final class SignatureListScreen extends Controller implements Screen,
MviView {
@@ -59,20 +63,20 @@ public static SignatureListScreen create() {
public SignatureListScreen() {
}
- private Observable initialIntent() {
- return Observable.just(Intent.InitialIntent.create());
+ private Observable initialIntent() {
+ return Observable.just(InitialIntent.create());
}
- private Observable upButtonIntent() {
+ private Observable upButtonIntent() {
return navigationClicks(toolbarView)
- .map(ignored -> Intent.UpButtonIntent.create());
+ .map(ignored -> UpButtonIntent.create());
}
- private Observable containerOpenIntent() {
+ private Observable containerOpenIntent() {
return Observable.merge(adapter.itemClicks()
- .flatMap(file -> Intent.ContainerOpenIntent.confirmation(file, getApplicationContext())),
+ .flatMap(file -> ContainerOpenIntent.confirmation(file, getApplicationContext())),
sivaConfirmationDialog.positiveButtonClicks()
- .map(ignored -> Intent.ContainerOpenIntent.open(sivaConfirmationContainerFile, true)),
+ .map(ignored -> ContainerOpenIntent.open(sivaConfirmationContainerFile, true)),
sivaConfirmationDialog.cancels()
.map(ignored -> {
if (sivaConfirmationContainerFile != null &&
@@ -81,31 +85,31 @@ private Observable containerOpenIntent() {
SignedContainer signedContainer = SignedContainer.open(sivaConfirmationContainerFile);
if (signedContainer.dataFiles().size() == 1 &&
Files.getFileExtension(signedContainer.dataFiles().get(0).name()).equalsIgnoreCase("ddoc")) {
- return Intent.ContainerOpenIntent.open(sivaConfirmationContainerFile, false);
+ return ContainerOpenIntent.open(sivaConfirmationContainerFile, false);
}
}
- return Intent.ContainerOpenIntent.cancel();
+ return ContainerOpenIntent.cancel();
}));
}
- private Observable containerRemoveIntent() {
+ private Observable containerRemoveIntent() {
return Observable.merge(
adapter.removeButtonClicks()
- .map(Intent.ContainerRemoveIntent::confirmation),
+ .map(ContainerRemoveIntent::confirmation),
removeConfirmationDialog.positiveButtonClicks()
- .map(ignored -> Intent.ContainerRemoveIntent
+ .map(ignored -> ContainerRemoveIntent
.remove(removeConfirmationContainerFile)),
removeConfirmationDialog.cancels()
.map(ignored -> {
if (getApplicationContext() != null) {
AccessibilityUtils.sendAccessibilityEvent(getApplicationContext(), TYPE_ANNOUNCEMENT, R.string.document_removal_cancelled);
}
- return Intent.ContainerRemoveIntent.cancel();
+ return ContainerRemoveIntent.cancel();
}));
}
- private Observable refreshIntent() {
- return Observable.just(Intent.RefreshIntent.create());
+ private Observable refreshIntent() {
+ return Observable.just(RefreshIntent.create());
}
@SuppressWarnings("unchecked")
@@ -153,7 +157,7 @@ private void setEmpty(boolean empty) {
@Override
protected void onContextAvailable(@NonNull Context context) {
super.onContextAvailable(context);
- viewModel = Application.component(context).navigator()
+ viewModel = ApplicationApp.component(context).navigator()
.viewModel(getInstanceId(), SignatureListViewModel.class);
}
@@ -183,6 +187,12 @@ protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup
emptyView = view.findViewById(R.id.listEmpty);
activityIndicatorView = view.findViewById(R.id.activityIndicator);
activityOverlayView = view.findViewById(R.id.activityOverlay);
+
+ LinearLayout signatureLayout = view.findViewById(R.id.signatureListLayout);
+ addInvisibleElementToObject(getApplicationContext(), signatureLayout);
+ View lastElementView = view.findViewById(R.id.lastInvisibleElement);
+ addInvisibleElementScrollListener(listView, lastElementView);
+
return view;
}
@@ -190,6 +200,7 @@ protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup
protected void onDestroyView(@NonNull View view) {
removeConfirmationDialog.dismiss();
sivaConfirmationDialog.dismiss();
+ removeInvisibleElementScrollListener(listView);
super.onDestroyView(view);
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/list/SignatureListViewModel.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/list/SignatureListViewModel.java
index 1e528367c..0a949223b 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/list/SignatureListViewModel.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/list/SignatureListViewModel.java
@@ -13,34 +13,15 @@ public final class SignatureListViewModel extends
@Override
protected Class extends Intent> initialIntentType() {
- return Intent.InitialIntent.class;
+ return InitialIntent.class;
}
@Override
protected Action action(Intent intent) {
- if (intent instanceof Intent.InitialIntent) {
- return Action.ContainersLoadAction.create(true);
- } else if (intent instanceof Intent.UpButtonIntent) {
- return Action.NavigateUpAction.create();
- } else if (intent instanceof Intent.ContainerRemoveIntent) {
- Intent.ContainerRemoveIntent containerRemoveIntent =
- (Intent.ContainerRemoveIntent) intent;
- return Action.ContainerRemoveAction.create(containerRemoveIntent.containerFile(),
- containerRemoveIntent.confirmation());
- } else if (intent instanceof Intent.ContainerOpenIntent) {
- Intent.ContainerOpenIntent containerOpenIntent =
- (Intent.ContainerOpenIntent) intent;
- return Action.ContainerOpenAction.create(containerOpenIntent.containerFile(),
- containerOpenIntent.confirmation(), containerOpenIntent.isSivaConfirmed());
- } else if (intent instanceof Intent.RefreshIntent) {
- return Action.ContainersLoadAction.create(false);
- } else if (intent instanceof Action) {
- return (Action) intent;
- } else {
- throw new IllegalArgumentException("Unknown intent " + intent);
- }
+ return intent.action();
}
+
@Override
protected ViewState initialViewState() {
return ViewState.initial();
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/Action.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/Action.java
index 66edf6312..285ad4966 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/Action.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/Action.java
@@ -45,7 +45,7 @@ abstract class DocumentsAddAction implements Action,
static DocumentsAddAction create(@Nullable File containerFile) {
return new AutoValue_Action_DocumentsAddAction(
Transaction.activityForResult(RC_SIGNATURE_UPDATE_DOCUMENTS_ADD,
- createGetContentIntent(), null),
+ createGetContentIntent(true), null),
containerFile);
}
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/Intent.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/Intent.java
index d0af083fb..56c82a751 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/Intent.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/Intent.java
@@ -7,7 +7,6 @@
import androidx.annotation.Nullable;
-import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import java.io.File;
@@ -22,219 +21,307 @@
interface Intent extends MviIntent {
- @AutoValue
- abstract class InitialIntent implements Intent {
+ Action action();
+}
- abstract boolean isExistingContainer();
+class InitialIntent implements Intent {
- abstract File containerFile();
+ private final boolean isExistingContainer;
+ private final File containerFile;
+ @Nullable private final Integer signatureAddMethod;
+ private final boolean signatureAddSuccessMessageVisible;
- @Nullable abstract Integer signatureAddMethod();
+ private InitialIntent(boolean isExistingContainer, File containerFile,
+ @Nullable Integer signatureAddMethod,
+ boolean signatureAddSuccessMessageVisible) {
+ this.isExistingContainer = isExistingContainer;
+ this.containerFile = containerFile;
+ this.signatureAddMethod = signatureAddMethod;
+ this.signatureAddSuccessMessageVisible = signatureAddSuccessMessageVisible;
+ }
- abstract boolean signatureAddSuccessMessageVisible();
+ public static InitialIntent create(boolean isExistingContainer, File containerFile,
+ Integer signatureAddMethod,
+ boolean signatureAddSuccessMessageVisible) {
+ return new InitialIntent(isExistingContainer, containerFile, signatureAddMethod, signatureAddSuccessMessageVisible);
+ }
- static InitialIntent create(boolean isExistingContainer, File containerFile,
- @Nullable Integer signatureAddMethod,
- boolean signatureAddSuccessMessageVisible) {
- return new AutoValue_Intent_InitialIntent(isExistingContainer, containerFile,
- signatureAddMethod, signatureAddSuccessMessageVisible);
- }
+ @Override
+ public Action action() {
+ return Action.ContainerLoadAction.create(containerFile, signatureAddMethod,
+ signatureAddSuccessMessageVisible, isExistingContainer);
}
+}
- @AutoValue
- abstract class NameUpdateIntent implements Intent, Action {
+class NameUpdateIntent implements Intent, Action {
- @Nullable abstract File containerFile();
+ @Nullable File containerFile;
- @Nullable abstract String name();
+ @Nullable String name;
- static NameUpdateIntent show(File file) {
- return create(file, null);
- }
+ public NameUpdateIntent(@Nullable File containerFile, @Nullable String name) {
+ this.containerFile = containerFile;
+ this.name = name;
+ }
- static NameUpdateIntent update(File file, String name) {
- return create(file, name);
- }
+ static NameUpdateIntent show(File file) {
+ return create(file, null);
+ }
- static NameUpdateIntent clear() {
- return create(null, null);
- }
+ static NameUpdateIntent update(File file, String name) {
+ return create(file, name);
+ }
- private static NameUpdateIntent create(@Nullable File containerFile,
- @Nullable String name) {
- return new AutoValue_Intent_NameUpdateIntent(containerFile, name);
- }
+ static NameUpdateIntent clear() {
+ return create(null, null);
}
- @AutoValue
- abstract class DocumentsAddIntent implements Intent {
+ private static NameUpdateIntent create(@Nullable File containerFile,
+ @Nullable String name) {
+ return new NameUpdateIntent(containerFile, name);
+ }
+
+ @Override
+ public Action action() {
+ return null;
+ }
+}
- @Nullable abstract File containerFile();
+class DocumentsAddIntent implements Intent {
+ @Nullable private final File containerFile;
- static DocumentsAddIntent create(File containerFile) {
- return new AutoValue_Intent_DocumentsAddIntent(containerFile);
- }
+ public DocumentsAddIntent(@Nullable File containerFile) {
+ this.containerFile = containerFile;
+ }
- static DocumentsAddIntent clear() {
- return new AutoValue_Intent_DocumentsAddIntent(null);
- }
+ static DocumentsAddIntent create(File containerFile) {
+ return new DocumentsAddIntent(containerFile);
+ }
+
+ static DocumentsAddIntent clear() {
+ return new DocumentsAddIntent(null);
}
- @AutoValue
- abstract class DocumentViewIntent implements Intent, Action {
+ @Override
+ public Action action() {
+ return Action.DocumentsAddAction.create(containerFile);
+ }
+}
- @Nullable abstract File containerFile();
+class DocumentViewIntent implements Intent, Action {
- @Nullable abstract DataFile document();
+ @Nullable File containerFile;
+ @Nullable DataFile document;
+ boolean confirmation;
- abstract boolean confirmation();
+ public DocumentViewIntent(@Nullable File containerFile, @Nullable DataFile document, boolean confirmation) {
+ this.containerFile = containerFile;
+ this.document = document;
+ this.confirmation = confirmation;
+ }
- static DocumentViewIntent confirmation(Context context, File containerFile, DataFile document) throws Exception {
- String containerFileExtension = getFileExtension(containerFile.getName()).toLowerCase(Locale.US);
- String documentFileExtension = getFileExtension(document.name()).toLowerCase(Locale.US);
- if (!containerFileExtension.equals("pdf") && SignedContainer.isContainer(context, containerFile)) {
- try {
- boolean isConfirmationNeeded = SivaUtil.isSivaConfirmationNeeded(containerFile, document);
- return create(containerFile, document, isConfirmationNeeded);
- } catch (Exception e) {
- Timber.log(Log.ERROR, e, "Unable to get data file from container");
- return create(containerFile, document, false);
- }
- } else if (containerFileExtension.equals("pdf") && documentFileExtension.equals("pdf")) {
- return create(containerFile, document, false);
- } else {
+ static DocumentViewIntent confirmation(Context context, File containerFile, DataFile document) throws Exception {
+ String containerFileExtension = getFileExtension(containerFile.getName()).toLowerCase(Locale.US);
+ String documentFileExtension = getFileExtension(document.name()).toLowerCase(Locale.US);
+ if (!containerFileExtension.equals("pdf") && SignedContainer.isContainer(context, containerFile)) {
+ try {
boolean isConfirmationNeeded = SivaUtil.isSivaConfirmationNeeded(containerFile, document);
return create(containerFile, document, isConfirmationNeeded);
+ } catch (Exception e) {
+ Timber.log(Log.ERROR, e, "Unable to get data file from container");
+ return create(containerFile, document, false);
}
-
- }
-
- static DocumentViewIntent cancel() {
- return create(null, null, false);
- }
-
- static DocumentViewIntent open(File containerFile, DataFile document) {
+ } else if (containerFileExtension.equals("pdf") && documentFileExtension.equals("pdf")) {
return create(containerFile, document, false);
+ } else {
+ boolean isConfirmationNeeded = SivaUtil.isSivaConfirmationNeeded(containerFile, document);
+ return create(containerFile, document, isConfirmationNeeded);
}
- static DocumentViewIntent create(@Nullable File containerFile, @Nullable DataFile document, boolean confirmation) {
- return new AutoValue_Intent_DocumentViewIntent(containerFile, document, confirmation);
- }
}
- @AutoValue
- abstract class DocumentSaveIntent implements Intent, Action {
+ static DocumentViewIntent cancel() {
+ return create(null, null, false);
+ }
- abstract File containerFile();
+ static DocumentViewIntent open(File containerFile, DataFile document) {
+ return create(containerFile, document, false);
+ }
- abstract DataFile document();
+ static DocumentViewIntent create(@Nullable File containerFile, @Nullable DataFile document, boolean confirmation) {
+ return new DocumentViewIntent(containerFile, document, confirmation);
+ }
- static DocumentSaveIntent create(File containerFile, DataFile document) {
- return new AutoValue_Intent_DocumentSaveIntent(containerFile, document);
- }
+ @Override
+ public Action action() {
+ return null;
}
+}
- @AutoValue
- abstract class SignatureViewIntent implements Intent {
+class DocumentSaveIntent implements Intent, Action {
- abstract File containerFile();
+ File containerFile;
- abstract Signature signature();
+ DataFile document;
- static SignatureViewIntent create(File containerFile, Signature signature) {
- return new AutoValue_Intent_SignatureViewIntent(containerFile, signature);
- }
+ public DocumentSaveIntent(File containerFile, DataFile document) {
+ this.containerFile = containerFile;
+ this.document = document;
+ }
+
+ static DocumentSaveIntent create(File containerFile, DataFile document) {
+ return new DocumentSaveIntent(containerFile, document);
}
- @AutoValue
- abstract class DocumentRemoveIntent implements Intent {
+ @Override
+ public Action action() {
+ return null;
+ }
+}
- abstract boolean showConfirmation();
+class SignatureViewIntent implements Intent {
- @Nullable abstract File containerFile();
+ File containerFile;
- abstract ImmutableList documents();
+ Signature signature;
- @Nullable abstract DataFile document();
+ public SignatureViewIntent(File containerFile, Signature signature) {
+ this.containerFile = containerFile;
+ this.signature = signature;
+ }
- static DocumentRemoveIntent showConfirmation(File containerFile, ImmutableList documents, DataFile document) {
- return new AutoValue_Intent_DocumentRemoveIntent(true, containerFile, documents, document);
- }
+ static SignatureViewIntent create(File containerFile, Signature signature) {
+ return new SignatureViewIntent(containerFile, signature);
+ }
- static DocumentRemoveIntent remove(File containerFile, ImmutableList documents, DataFile document) {
- return new AutoValue_Intent_DocumentRemoveIntent(false, containerFile, documents, document);
- }
+ @Override
+ public Action action() {
+ return Action.SignatureViewAction.create(containerFile, signature);
+ }
+}
- static DocumentRemoveIntent clear() {
- return new AutoValue_Intent_DocumentRemoveIntent(false, null, ImmutableList.of(), null);
- }
+class DocumentRemoveIntent implements Intent {
+
+ boolean showConfirmation;
+ @Nullable File containerFile;
+ ImmutableList documents;
+
+ @Nullable DataFile document;
+
+ public DocumentRemoveIntent(boolean showConfirmation, File containerFile, ImmutableList documents, DataFile document) {
+ this.showConfirmation = showConfirmation;
+ this.containerFile = containerFile;
+ this.documents = documents;
+ this.document = document;
}
- @AutoValue
- abstract class SignatureRemoveIntent implements Intent {
+ static DocumentRemoveIntent showConfirmation(File containerFile, ImmutableList documents, DataFile document) {
+ return new DocumentRemoveIntent(true, containerFile, documents, document);
+ }
- abstract boolean showConfirmation();
+ static DocumentRemoveIntent remove(File containerFile, ImmutableList documents, DataFile document) {
+ return new DocumentRemoveIntent(false, containerFile, documents, document);
+ }
- @Nullable abstract File containerFile();
+ static DocumentRemoveIntent clear() {
+ return new DocumentRemoveIntent(false, null, ImmutableList.of(), null);
+ }
- @Nullable abstract Signature signature();
+ @Override
+ public Action action() {
+ return Action.DocumentRemoveAction.create(showConfirmation, containerFile, documents, document);
+ }
+}
- static SignatureRemoveIntent showConfirmation(File containerFile, Signature signature) {
- return new AutoValue_Intent_SignatureRemoveIntent(true, containerFile, signature);
- }
+class SignatureRemoveIntent implements Intent {
- static SignatureRemoveIntent remove(File containerFile, Signature signature) {
- return new AutoValue_Intent_SignatureRemoveIntent(false, containerFile, signature);
- }
+ boolean showConfirmation;
+ @Nullable File containerFile;
+ @Nullable Signature signature;
- static SignatureRemoveIntent clear() {
- return new AutoValue_Intent_SignatureRemoveIntent(false, null, null);
- }
+ public SignatureRemoveIntent(boolean showConfirmation, @Nullable File containerFile, @Nullable Signature signature) {
+ this.showConfirmation = showConfirmation;
+ this.containerFile = containerFile;
+ this.signature = signature;
}
- @AutoValue
- abstract class SignatureAddIntent implements Intent {
+ static SignatureRemoveIntent showConfirmation(File containerFile, Signature signature) {
+ return new SignatureRemoveIntent(true, containerFile, signature);
+ }
- @Nullable abstract Integer method();
+ static SignatureRemoveIntent remove(File containerFile, Signature signature) {
+ return new SignatureRemoveIntent(false, containerFile, signature);
+ }
- @Nullable abstract Boolean existingContainer();
+ static SignatureRemoveIntent clear() {
+ return new SignatureRemoveIntent(false, null, null);
+ }
- @Nullable abstract File containerFile();
+ @Override
+ public Action action() {
+ return Action.SignatureRemoveAction.create(showConfirmation, containerFile, signature);
+ }
+}
- @Nullable abstract SignatureAddRequest request();
+class SignatureAddIntent implements Intent {
+
+ @Nullable Integer method;
+ @Nullable Boolean existingContainer;
+ @Nullable File containerFile;
+ @Nullable SignatureAddRequest request;
+ boolean isCancelled;
+
+ public SignatureAddIntent(@Nullable Integer method, @Nullable Boolean existingContainer,
+ @Nullable File containerFile, @Nullable SignatureAddRequest request,
+ boolean isCancelled) {
+ this.method = method;
+ this.existingContainer = existingContainer;
+ this.containerFile = containerFile;
+ this.request = request;
+ this.isCancelled = isCancelled;
+ }
- abstract boolean isCancelled();
+ static SignatureAddIntent show(int method, boolean existingContainer, File containerFile) {
+ return create(method, existingContainer, containerFile, null, false);
+ }
- static SignatureAddIntent show(int method, boolean existingContainer, File containerFile) {
- return create(method, existingContainer, containerFile, null, false);
- }
+ static SignatureAddIntent sign(int method, boolean existingContainer, File containerFile,
+ SignatureAddRequest request) {
+ return create(method, existingContainer, containerFile, request, false);
+ }
- static SignatureAddIntent sign(int method, boolean existingContainer, File containerFile,
- SignatureAddRequest request) {
- return create(method, existingContainer, containerFile, request, false);
- }
+ static SignatureAddIntent clear() {
+ return create(null, null, null, null, true);
+ }
- static SignatureAddIntent clear() {
- return create(null, null, null, null, true);
- }
+ private static SignatureAddIntent create(@Nullable Integer method,
+ @Nullable Boolean existingContainer,
+ @Nullable File containerFile,
+ @Nullable SignatureAddRequest request,
+ boolean isCancelled) {
+ return new SignatureAddIntent(method, existingContainer, containerFile, request, isCancelled);
+ }
- private static SignatureAddIntent create(@Nullable Integer method,
- @Nullable Boolean existingContainer,
- @Nullable File containerFile,
- @Nullable SignatureAddRequest request,
- boolean isCancelled) {
- return new AutoValue_Intent_SignatureAddIntent(method, existingContainer, containerFile,
- request, isCancelled);
- }
+ @Override
+ public Action action() {
+ return Action.SignatureAddAction.create(method, existingContainer, containerFile, request, isCancelled);
}
+}
- @AutoValue
- abstract class SendIntent implements Intent {
+class SendIntent implements Intent {
- abstract File containerFile();
+ File containerFile;
- static SendIntent create(File containerFile) {
- return new AutoValue_Intent_SendIntent(containerFile);
- }
+ public SendIntent(File containerFile) {
+ this.containerFile = containerFile;
+ }
+
+ static SendIntent create(File containerFile) {
+ return new SendIntent(containerFile);
+ }
+
+ @Override
+ public Action action() {
+ return Action.SendAction.create(containerFile);
}
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/Processor.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/Processor.java
index fcaefaf2a..0582d98c0 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/Processor.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/Processor.java
@@ -14,7 +14,6 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
-import android.os.Handler;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
@@ -50,11 +49,9 @@
import ee.ria.DigiDoc.android.utils.navigator.Transaction;
import ee.ria.DigiDoc.common.FileUtil;
import ee.ria.DigiDoc.crypto.CryptoContainer;
-import ee.ria.DigiDoc.mobileid.service.MobileSignService;
import ee.ria.DigiDoc.sign.DataFile;
import ee.ria.DigiDoc.sign.NoInternetConnectionException;
import ee.ria.DigiDoc.sign.SignedContainer;
-import ee.ria.DigiDoc.smartid.service.SmartSignService;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.ObservableSource;
@@ -68,16 +65,16 @@ final class Processor implements ObservableTransformer {
private final ObservableTransformer containerLoad;
- private final ObservableTransformer
+ private final ObservableTransformer
nameUpdate;
private final ObservableTransformer documentsAdd;
- private final ObservableTransformer documentView;
- private final ObservableTransformer documentSave;
private final ObservableTransformer {
private final PublishSubject notificationsPermissionSubject = PublishSubject.create();
- private android.content.Intent intent;
-
@Inject Processor(SignatureContainerDataSource signatureContainerDataSource,
SignatureAddSource signatureAddSource, Application application,
Navigator navigator,
@@ -131,7 +126,7 @@ final class Processor implements ObservableTransformer {
.startWithItem(Result.ContainerLoadResult.progress()));
nameUpdate = upstream -> upstream.switchMap(action -> {
- File containerFile = action.containerFile();
+ File containerFile = action.containerFile;
String name = containerFile != null ? assignName(action, containerFile) : null;
if (containerFile == null) {
@@ -193,14 +188,14 @@ final class Processor implements ObservableTransformer {
return navigator.activityResults()
.filter(activityResult ->
activityResult.requestCode()
- == action.transaction().requestCode())
+ == action.transaction().getRequestCode())
.switchMap(activityResult -> {
android.content.Intent data = activityResult.data();
if (activityResult.resultCode() == RESULT_OK && data != null) {
ImmutableList validFiles = FileSystem.getFilesWithValidSize(
parseGetContentIntent(navigator.activity(), application.getContentResolver(),
data, fileSystem.getExternallyOpenedFilesDir()));
- ToastUtil.handleEmptyFileError(validFiles, application, navigator.activity());
+ ToastUtil.handleEmptyFileError(validFiles, navigator.activity());
ImmutableList filesNotInContainer = getFilesNotInContainer(navigator.activity(), validFiles, action.containerFile());
if (filesNotInContainer.isEmpty()) {
throw new FileAlreadyExistsException(navigator.activity()
@@ -225,15 +220,15 @@ final class Processor implements ObservableTransformer {
});
documentView = upstream -> upstream.switchMap(action -> {
- if (action.containerFile() == null) {
+ if (action.containerFile == null) {
return Observable.just(Result.DocumentViewResult.idle());
- } else if (action.confirmation()) {
+ } else if (action.confirmation) {
return Observable
- .just(Result.DocumentViewResult.confirmation(action.document()));
+ .just(Result.DocumentViewResult.confirmation(action.document));
} else {
- File containerFile = action.containerFile();
+ File containerFile = action.containerFile;
return signatureContainerDataSource
- .getDocumentFile(containerFile, action.document())
+ .getDocumentFile(containerFile, action.document)
.toObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@@ -242,16 +237,16 @@ final class Processor implements ObservableTransformer {
String containerFileExtension = getFileExtension(containerFile.getName()).toLowerCase(Locale.US);
String documentFileExtension = getFileExtension(documentFile.getName()).toLowerCase(Locale.US);
boolean isPdfInSignedPdfContainer = false;
- if (action.document() != null) {
+ if (action.document != null) {
isPdfInSignedPdfContainer = containerFileExtension.equals("pdf") &&
- (SivaUtil.isSivaConfirmationNeeded(containerFile, action.document()) &&
+ (SivaUtil.isSivaConfirmationNeeded(containerFile, action.document) &&
documentFileExtension.equals("pdf"));
}
if (!isPdfInSignedPdfContainer && SignedContainer.isContainer(navigator.activity(), documentFile)) {
transaction = Transaction.push(SignatureUpdateScreen
.create(true, true, documentFile, false, false, null, true));
} else if (CryptoContainer.isContainerFileName(documentFile.getName())) {
- transaction = Transaction.push(CryptoCreateScreen.open(documentFile));
+ transaction = Transaction.push(CryptoCreateScreen.open(documentFile, true));
} else {
transaction = Transaction.activity(IntentUtils
.createActionIntent(application, documentFile,
@@ -262,7 +257,7 @@ final class Processor implements ObservableTransformer {
})
.onErrorReturn(throwable -> {
if (throwable instanceof NoInternetConnectionException) {
- ToastUtil.showError(navigator.activity(),R.string.no_internet_connection);
+ ToastUtil.showError(navigator.activity(), R.string.no_internet_connection);
} else {
ToastUtil.showError(navigator.activity(), R.string.signature_update_container_load_error);
}
@@ -274,12 +269,12 @@ final class Processor implements ObservableTransformer {
documentSave = upstream -> upstream.switchMap(action -> {
navigator.execute(Transaction.activityForResult(SAVE_FILE,
- createSaveIntent(action.document()), null));
+ createSaveIntent(action.document), null));
return navigator.activityResults()
.filter(activityResult ->
activityResult.requestCode() == SAVE_FILE)
.switchMap(activityResult -> signatureContainerDataSource
- .getDocumentFile(action.containerFile(), action.document())
+ .getDocumentFile(action.containerFile, action.document)
.toObservable()
.map(documentFile -> {
if (activityResult.resultCode() == RESULT_OK) {
@@ -372,14 +367,7 @@ final class Processor implements ObservableTransformer {
File containerFile = action.containerFile();
SignatureAddRequest request = action.request();
boolean isCancelled = action.isCancelled();
- if (method != null) {
- intent = getSigningIntent(navigator, method);
- }
if (method == null || isCancelled) {
- if (intent != null) {
- Handler handler = new Handler(navigator.activity().getMainLooper());
- handler.post(() -> stopSigningService(navigator, intent));
- }
return Observable.just(Result.SignatureAddResult.clear());
} else if (request == null && existingContainer != null && containerFile != null) {
if (SignedContainer.isLegacyContainer(containerFile)) {
@@ -400,7 +388,7 @@ final class Processor implements ObservableTransformer {
}
} else if (existingContainer != null && containerFile != null) {
return askNotificationPermission(navigator, method)
- .flatMap(ign -> signatureAddSource.sign(containerFile, request, navigator, intent)
+ .flatMap(ign -> signatureAddSource.sign(containerFile, request, navigator)
.switchMap(response -> {
if (response.container() != null) {
return Observable.fromCallable(() -> {
@@ -417,7 +405,6 @@ final class Processor implements ObservableTransformer {
.observeOn(AndroidSchedulers.mainThread())
.onErrorReturn(Result.SignatureAddResult::failure)
.startWithItem(Result.SignatureAddResult.activity(method)));
-
} else {
throw new IllegalArgumentException("Can't handle action " + action);
}
@@ -436,10 +423,10 @@ final class Processor implements ObservableTransformer {
public ObservableSource apply(Observable upstream) {
return upstream.publish(shared -> Observable.mergeArray(
shared.ofType(Action.ContainerLoadAction.class).compose(containerLoad),
- shared.ofType(Intent.NameUpdateIntent.class).compose(nameUpdate),
+ shared.ofType(NameUpdateIntent.class).compose(nameUpdate),
shared.ofType(Action.DocumentsAddAction.class).compose(documentsAdd),
- shared.ofType(Intent.DocumentViewIntent.class).compose(documentView),
- shared.ofType(Intent.DocumentSaveIntent.class).compose(documentSave),
+ shared.ofType(DocumentViewIntent.class).compose(documentView),
+ shared.ofType(DocumentSaveIntent.class).compose(documentSave),
shared.ofType(Action.DocumentRemoveAction.class).compose(documentRemove),
shared.ofType(Action.SignatureRemoveAction.class).compose(signatureRemove),
shared.ofType(Action.SignatureViewAction.class).compose(signatureView),
@@ -460,8 +447,8 @@ private String addContainerExtension(File oldContainerFileName, String newName)
return newName.concat(".").concat(oldContainerNamePart);
}
- private String assignName(Intent.NameUpdateIntent action, File containerFile) {
- String name = FileUtil.sanitizeString(action.name(), "");
+ private String assignName(NameUpdateIntent action, File containerFile) {
+ String name = FileUtil.sanitizeString(action.name, "");
if (name != null && !name.isEmpty()) {
return addContainerExtension(containerFile, name);
}
@@ -499,21 +486,6 @@ private void announceAccessibilityFilesAddedEvent(Context context, int addedData
}
}
- private android.content.Intent getSigningIntent(Navigator navigator, Integer method) {
- if (method != null) {
- if (method == R.id.signatureUpdateSignatureAddMethodMobileId) {
- return new android.content.Intent(navigator.activity(), MobileSignService.class);
- } else if (method == R.id.signatureUpdateSignatureAddMethodSmartId) {
- return new android.content.Intent(navigator.activity(), SmartSignService.class);
- }
- }
- return null;
- }
-
- private void stopSigningService(Navigator navigator, android.content.Intent intent) {
- navigator.activity().stopService(intent);
- }
-
private Observable askNotificationPermission(Navigator navigator, int method) {
if (method == R.id.signatureUpdateSignatureAddMethodMobileId ||
method == R.id.signatureUpdateSignatureAddMethodIdCard) {
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureAddSource.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureAddSource.java
index df23bcbc0..dba70d598 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureAddSource.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureAddSource.java
@@ -76,8 +76,7 @@ Observable show(int method) {
Observable extends SignatureAddResponse> sign(File containerFile,
SignatureAddRequest request,
- Navigator navigator,
- android.content.Intent intent) {
+ Navigator navigator) {
if (request instanceof MobileIdRequest) {
MobileIdRequest mobileIdRequest = (MobileIdRequest) request;
if (mobileIdRequest.rememberMe()) {
@@ -90,7 +89,7 @@ Observable extends SignatureAddResponse> sign(File containerFile,
return signatureContainerDataSource
.get(containerFile)
.flatMapObservable(container ->
- Observable.create(new MobileIdOnSubscribe(navigator, intent, container,
+ Observable.create(new MobileIdOnSubscribe(navigator, container,
localeService.applicationLocale(),
settingsDataStore.getUuid(), mobileIdRequest.personalCode(),
mobileIdRequest.phoneNo())))
@@ -126,9 +125,9 @@ Observable extends SignatureAddResponse> sign(File containerFile,
return signatureContainerDataSource
.get(containerFile)
.flatMapObservable(container ->
- Observable.create(new SmartIdOnSubscribe(navigator, intent, container,
- settingsDataStore.getUuid(), smartIdRequest.personalCode(),
- smartIdRequest.country())))
+ Observable.create(new SmartIdOnSubscribe(navigator, container,
+ localeService.applicationLocale(), settingsDataStore.getUuid(),
+ smartIdRequest.personalCode(), smartIdRequest.country())))
.switchMap(response -> {
SessionStatusResponse.ProcessStatus processStatus = response.status();
if (SessionStatusResponse.ProcessStatus.OK.equals(processStatus)) {
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateAdapter.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateAdapter.java
index 1f4ef5d5a..68bb1cab1 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateAdapter.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateAdapter.java
@@ -1,15 +1,21 @@
package ee.ria.DigiDoc.android.signature.update;
+import static android.view.accessibility.AccessibilityEvent.TYPE_ANNOUNCEMENT;
+import static androidx.core.content.res.ResourcesCompat.getColor;
+import static com.jakewharton.rxbinding4.view.RxView.clicks;
+import static ee.ria.DigiDoc.android.Constants.VOID;
+import static ee.ria.DigiDoc.android.signature.update.SignatureUpdateAdapter.SubheadItemType.DOCUMENT;
+import static ee.ria.DigiDoc.android.signature.update.SignatureUpdateAdapter.SubheadItemType.SIGNATURE;
+import static ee.ria.DigiDoc.android.signature.update.SignatureUpdateAdapter.SubheadItemType.TIMESTAMP;
+import static ee.ria.DigiDoc.android.utils.Immutables.containsType;
+
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
-import android.os.Handler;
-import android.os.Looper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
@@ -30,13 +36,10 @@
import java.lang.annotation.RetentionPolicy;
import java.time.Instant;
import java.time.Month;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
import ee.ria.DigiDoc.R;
import ee.ria.DigiDoc.android.Activity;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils;
import ee.ria.DigiDoc.android.utils.DateUtil;
import ee.ria.DigiDoc.android.utils.Formatter;
@@ -51,16 +54,6 @@
import io.reactivex.rxjava3.subjects.Subject;
import timber.log.Timber;
-import static android.view.accessibility.AccessibilityEvent.TYPE_ANNOUNCEMENT;
-import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
-import static androidx.core.content.res.ResourcesCompat.getColor;
-import static com.jakewharton.rxbinding4.view.RxView.clicks;
-import static ee.ria.DigiDoc.android.Constants.VOID;
-import static ee.ria.DigiDoc.android.signature.update.SignatureUpdateAdapter.SubheadItemType.DOCUMENT;
-import static ee.ria.DigiDoc.android.signature.update.SignatureUpdateAdapter.SubheadItemType.SIGNATURE;
-import static ee.ria.DigiDoc.android.signature.update.SignatureUpdateAdapter.SubheadItemType.TIMESTAMP;
-import static ee.ria.DigiDoc.android.utils.Immutables.containsType;
-
final class SignatureUpdateAdapter extends
RecyclerView.Adapter> {
@@ -367,7 +360,7 @@ static final class NameViewHolder extends UpdateViewHolder {
@Override
void bind(SignatureUpdateAdapter adapter, NameItem item) {
if (item.name().startsWith(".")) {
- nameView.setText("newFile" + FileUtil.sanitizeString(item.name(), ""));
+ nameView.setText(FileUtil.DEFAULT_FILENAME + FileUtil.sanitizeString(item.name(), ""));
} else {
nameView.setText(FileUtil.sanitizeString(item.name(), ""));
}
@@ -454,6 +447,7 @@ static final class SignatureViewHolder extends UpdateViewHolder {
private final Formatter formatter;
private final ColorStateList colorValid;
+ private final ColorStateList colorWarning;
private final ColorStateList colorInvalid;
private final TextView nameView;
@@ -466,9 +460,10 @@ static final class SignatureViewHolder extends UpdateViewHolder {
SignatureViewHolder(View itemView) {
super(itemView);
- formatter = Application.component(itemView.getContext()).formatter();
+ formatter = ApplicationApp.component(itemView.getContext()).formatter();
Resources resources = itemView.getResources();
colorValid = ColorStateList.valueOf(getColor(resources, R.color.success, null));
+ colorWarning = ColorStateList.valueOf(getColor(resources, R.color.warningText, null));
colorInvalid = ColorStateList.valueOf(getColor(resources, R.color.error, null));
nameView = itemView.findViewById(R.id.signatureUpdateListSignatureName);
statusView = itemView.findViewById(R.id.signatureUpdateListSignatureStatus);
@@ -515,6 +510,7 @@ void bind(SignatureUpdateAdapter adapter, SignatureItem item) {
case NON_QSCD:
statusCautionView.setVisibility(View.VISIBLE);
statusCautionView.setText(R.string.signature_update_signature_status_non_qscd);
+ statusCautionView.setTextColor(colorWarning);
break;
default:
statusCautionView.setVisibility(View.GONE);
@@ -569,6 +565,7 @@ static final class TimestampViewHolder extends UpdateViewHolder {
private final Formatter formatter;
private final ColorStateList colorValid;
+ private final ColorStateList colorWarning;
private final ColorStateList colorInvalid;
private final TextView nameView;
@@ -576,13 +573,12 @@ static final class TimestampViewHolder extends UpdateViewHolder {
private final TextView statusCautionView;
private final TextView createdAtView;
- private final Activity activityContext = (Activity)Activity.getContext().get();
-
TimestampViewHolder(View itemView) {
super(itemView);
- formatter = Application.component(itemView.getContext()).formatter();
+ formatter = ApplicationApp.component(itemView.getContext()).formatter();
Resources resources = itemView.getResources();
colorValid = ColorStateList.valueOf(getColor(resources, R.color.success, null));
+ colorWarning = ColorStateList.valueOf(getColor(resources, R.color.warningText, null));
colorInvalid = ColorStateList.valueOf(getColor(resources, R.color.error, null));
nameView = itemView.findViewById(R.id.signatureUpdateListSignatureName);
statusView = itemView.findViewById(R.id.signatureUpdateListSignatureStatus);
@@ -617,6 +613,7 @@ void bind(SignatureUpdateAdapter adapter, TimestampItem item) {
case NON_QSCD:
statusCautionView.setVisibility(View.VISIBLE);
statusCautionView.setText(R.string.signature_update_signature_status_non_qscd);
+ statusCautionView.setTextColor(colorWarning);
break;
default:
statusCautionView.setVisibility(View.GONE);
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateErrorDialog.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateErrorDialog.java
index 0d2157702..237f7cc7a 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateErrorDialog.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateErrorDialog.java
@@ -2,8 +2,8 @@
import android.content.Context;
import android.content.DialogInterface;
-import android.content.res.Configuration;
import android.text.Html;
+import android.text.Spanned;
import android.text.TextUtils;
import android.view.View;
import android.view.Window;
@@ -16,20 +16,23 @@
import java.lang.annotation.RetentionPolicy;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.model.mobileid.MobileIdMessageException;
-import ee.ria.DigiDoc.android.model.smartid.SmartIdMessageException;
+import ee.ria.DigiDoc.android.signature.update.exception.DocumentExistsException;
+import ee.ria.DigiDoc.android.signature.update.exception.DocumentRemoveException;
+import ee.ria.DigiDoc.android.signature.update.exception.GeneralSignatureUpdateException;
import ee.ria.DigiDoc.android.utils.ClickableDialogUtil;
import ee.ria.DigiDoc.android.utils.ErrorMessageUtil;
import ee.ria.DigiDoc.android.utils.files.EmptyFileException;
import ee.ria.DigiDoc.android.utils.widget.ErrorDialog;
+import ee.ria.DigiDoc.common.DetailMessageException;
import ee.ria.DigiDoc.common.DetailMessageSource;
+import ee.ria.DigiDoc.common.exception.SignatureUpdateDetailError;
+import ee.ria.DigiDoc.common.exception.SignatureUpdateError;
import ee.ria.DigiDoc.idcard.CodeVerificationException;
import ee.ria.DigiDoc.sign.CertificateRevokedException;
import ee.ria.DigiDoc.sign.NoInternetConnectionException;
import ee.ria.DigiDoc.sign.OcspInvalidTimeSlotException;
import ee.ria.DigiDoc.sign.TooManyRequestsException;
import ee.ria.DigiDoc.sign.utils.UrlMessage;
-
import io.reactivex.rxjava3.subjects.Subject;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -38,9 +41,6 @@
import static ee.ria.DigiDoc.android.signature.update.SignatureUpdateErrorDialog.Type.SIGNATURE_ADD;
import static ee.ria.DigiDoc.android.signature.update.SignatureUpdateErrorDialog.Type.SIGNATURE_REMOVE;
import static ee.ria.DigiDoc.android.utils.display.DisplayUtil.getDeviceLayoutWidth;
-import static ee.ria.DigiDoc.android.utils.display.DisplayUtil.getDeviceOrientation;
-import static ee.ria.DigiDoc.android.utils.display.DisplayUtil.getDialogLandscapeWidth;
-import static ee.ria.DigiDoc.android.utils.display.DisplayUtil.getDialogPortraitWidth;
public final class SignatureUpdateErrorDialog extends ErrorDialog implements DialogInterface.OnDismissListener {
@@ -53,10 +53,10 @@ public final class SignatureUpdateErrorDialog extends ErrorDialog implements Dia
String SIGNATURE_REMOVE = "SIGNATURE_REMOVE";
}
- private final Subject documentsAddIntentSubject;
- private final Subject documentRemoveIntentSubject;
- private final Subject signatureAddIntentSubject;
- private final Subject signatureRemoveIntentSubject;
+ private final Subject documentsAddIntentSubject;
+ private final Subject documentRemoveIntentSubject;
+ private final Subject signatureAddIntentSubject;
+ private final Subject signatureRemoveIntentSubject;
private final SignatureUpdateSignatureAddDialog signatureAddDialog;
private String type;
@@ -65,10 +65,10 @@ public final class SignatureUpdateErrorDialog extends ErrorDialog implements Dia
private View.OnLayoutChangeListener layoutChangeListener;
SignatureUpdateErrorDialog(@NonNull Context context,
- Subject documentsAddIntentSubject,
- Subject documentRemoveIntentSubject,
- Subject signatureAddIntentSubject,
- Subject signatureRemoveIntentSubject,
+ Subject documentsAddIntentSubject,
+ Subject documentRemoveIntentSubject,
+ Subject signatureAddIntentSubject,
+ Subject signatureRemoveIntentSubject,
SignatureUpdateSignatureAddDialog signatureAddDialog,
View view) {
super(context);
@@ -90,33 +90,36 @@ void show(@Nullable Throwable documentsAddError, @Nullable Throwable documentRem
setCustomLayoutChangeListener(window);
view.addOnLayoutChangeListener(getCustomLayoutChangeListener());
+ SignatureUpdateError updateError = null;
+ SignatureUpdateDetailError detailError = null;
+
if (documentsAddError != null) {
type = DOCUMENTS_ADD;
if (documentsAddError instanceof EmptyFileException) {
- setMessage(getContext().getString(R.string.empty_file_error));
+ updateError = new EmptyFileException();
} else {
- setMessage(getContext().getString(
- R.string.signature_update_documents_add_error_exists));
+ updateError = new DocumentExistsException();
}
} else if (documentRemoveError != null) {
type = DOCUMENT_REMOVE;
- setMessage(getContext().getString(R.string.signature_update_document_remove_error));
+ updateError = new DocumentRemoveException();
} else if (signatureAddError != null) {
type = SIGNATURE_ADD;
if (signatureAddError instanceof CodeVerificationException) {
- setMessage(getContext().getString(
- R.string.signature_update_id_card_sign_pin2_locked));
+ updateError = new CodeVerificationException(
+ ((CodeVerificationException) signatureAddError).getType(),
+ getContext().getString(
+ R.string.signature_update_id_card_sign_pin2_locked)
+ );
} else if (signatureAddError instanceof NoInternetConnectionException) {
- setMessage(getContext().getString(R.string.no_internet_connection));
+ updateError = new NoInternetConnectionException();
} else if (signatureAddError instanceof DetailMessageSource) {
String link = ErrorMessageUtil.extractLink(signatureAddError.getMessage());
if (!link.isEmpty()) {
- setMessage(
- Html.fromHtml("" +
+ detailError = new DetailMessageException(Html.fromHtml("" +
ErrorMessageUtil.removeLink(signatureAddError.getMessage()) + "" +
getTextFromTranslation(R.string.signature_update_signature_error_message_additional_information) + "",
- Html.FROM_HTML_MODE_LEGACY)
- );
+ Html.FROM_HTML_MODE_LEGACY));
} else {
setTitle(R.string.signature_update_signature_add_error_title);
if (((DetailMessageSource) signatureAddError).getDetailMessage() != null &&
@@ -124,32 +127,48 @@ void show(@Nullable Throwable documentsAddError, @Nullable Throwable documentRem
String errorMessage = getContext().getString(R.string.signature_update_signature_error_message_details) +
":\n" +
((DetailMessageSource) signatureAddError).getDetailMessage();
- setMessage(errorMessage);
+ detailError = new DetailMessageException(errorMessage);
} else {
- setMessage(signatureAddError.getMessage());
+ detailError = new DetailMessageException(signatureAddError.getMessage());
}
}
} else if (signatureAddError instanceof TooManyRequestsException) {
- setMessage(Html.fromHtml(UrlMessage.withURL(
- getContext(),
- R.string.signature_update_signature_error_message_too_many_requests,
- R.string.signature_update_signature_error_message_additional_information
- ), Html.FROM_HTML_MODE_LEGACY));
+ detailError = new TooManyRequestsException(
+ Html.fromHtml(UrlMessage.withURL(
+ getContext(),
+ R.string.signature_update_signature_error_message_too_many_requests,
+ R.string.signature_update_signature_error_message_additional_information
+ ), Html.FROM_HTML_MODE_LEGACY));
} else if (signatureAddError instanceof OcspInvalidTimeSlotException) {
- setMessage(Html.fromHtml(UrlMessage.withURL(
+ detailError = new OcspInvalidTimeSlotException(Html.fromHtml(UrlMessage.withURL(
getContext(),
R.string.signature_update_signature_error_message_invalid_time_slot,
R.string.signature_update_signature_error_message_additional_information
), Html.FROM_HTML_MODE_LEGACY));
} else if (signatureAddError instanceof CertificateRevokedException) {
- setMessage(getContext().getString(R.string.signature_update_signature_error_message_certificate_revoked));
+ updateError = new CertificateRevokedException(
+ getContext().getString(R.string.signature_update_signature_error_message_certificate_revoked)
+ );
} else {
setTitle(R.string.signature_update_signature_add_error);
- setMessage(signatureAddError.getMessage());
+ updateError = new GeneralSignatureUpdateException(signatureAddError.getMessage());
}
} else if (signatureRemoveError != null) {
type = SIGNATURE_REMOVE;
- setMessage(getContext().getString(R.string.signature_update_signature_remove_error));
+ updateError = new GeneralSignatureUpdateException(
+ getContext().getString(R.string.signature_update_signature_remove_error));
+ }
+
+ if (updateError != null) {
+ String message = updateError.getMessage(getContext());
+ setMessage(message);
+ } else if (detailError != null) {
+ Spanned detailMessage = detailError.getDetailMessage(getContext());
+ if (detailMessage == null) {
+ setMessage(detailError.getMessage(getContext()));
+ } else {
+ setMessage(detailMessage);
+ }
} else {
dismiss();
return;
@@ -168,13 +187,13 @@ public void onDismiss(DialogInterface dialog) {
setTitle(null);
removeListeners();
if (TextUtils.equals(type, DOCUMENTS_ADD)) {
- documentsAddIntentSubject.onNext(Intent.DocumentsAddIntent.clear());
+ documentsAddIntentSubject.onNext(DocumentsAddIntent.clear());
} else if (TextUtils.equals(type, DOCUMENT_REMOVE)) {
- documentRemoveIntentSubject.onNext(Intent.DocumentRemoveIntent.clear());
+ documentRemoveIntentSubject.onNext(DocumentRemoveIntent.clear());
} else if (TextUtils.equals(type, SIGNATURE_ADD)) {
- signatureAddIntentSubject.onNext(Intent.SignatureAddIntent.clear());
+ signatureAddIntentSubject.onNext(SignatureAddIntent.clear());
} else if (TextUtils.equals(type, SIGNATURE_REMOVE)) {
- signatureRemoveIntentSubject.onNext(Intent.SignatureRemoveIntent.clear());
+ signatureRemoveIntentSubject.onNext(SignatureRemoveIntent.clear());
}
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateProgressBar.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateProgressBar.java
index dc317b1ad..a1c9fefe3 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateProgressBar.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateProgressBar.java
@@ -16,10 +16,12 @@ static void startProgressBar(ProgressBar progressBar) {
progressBar.setMax((int) (isMobileIdProgressBar(progressBar) ? (MOBILE_ID_PROGRESS_BAR_TIMEOUT_CANCEL / 1000) : (SMART_ID_PROGRESS_BAR_TIMEOUT_CANCEL / 1000)));
timeoutTimer = new CountDownTimer(isMobileIdProgressBar(progressBar) ? MOBILE_ID_PROGRESS_BAR_TIMEOUT_CANCEL : SMART_ID_PROGRESS_BAR_TIMEOUT_CANCEL, 1000) {
+ @Override
public void onTick(long millisUntilFinished) {
progressBar.incrementProgressBy(1);
}
+ @Override
public void onFinish() {
stopProgressBar(progressBar);
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateSignatureAddDialog.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateSignatureAddDialog.java
index bf25c476f..a5ab6c66e 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateSignatureAddDialog.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateSignatureAddDialog.java
@@ -1,8 +1,16 @@
package ee.ria.DigiDoc.android.signature.update;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static ee.ria.DigiDoc.android.Constants.VOID;
+import static ee.ria.DigiDoc.android.utils.display.DisplayUtil.getDeviceLayoutWidth;
+
import android.content.Context;
+import android.content.DialogInterface;
import android.content.res.Configuration;
import android.content.res.TypedArray;
+import android.graphics.Color;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
@@ -11,23 +19,17 @@
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
+import androidx.core.content.ContextCompat;
import ee.ria.DigiDoc.R;
import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils;
import ee.ria.DigiDoc.android.utils.SecureUtil;
import ee.ria.DigiDoc.android.utils.ViewDisposables;
+import ee.ria.DigiDoc.android.utils.navigator.ContentView;
import ee.ria.DigiDoc.android.utils.rxbinding.app.ObservableDialogClickListener;
import io.reactivex.rxjava3.core.Observable;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static ee.ria.DigiDoc.android.Constants.VOID;
-import static ee.ria.DigiDoc.android.utils.display.DisplayUtil.getDeviceLayoutWidth;
-import static ee.ria.DigiDoc.android.utils.display.DisplayUtil.getDeviceOrientation;
-import static ee.ria.DigiDoc.android.utils.display.DisplayUtil.getDeviceWidth;
-import static ee.ria.DigiDoc.android.utils.display.DisplayUtil.getDialogLandscapeWidth;
-import static ee.ria.DigiDoc.android.utils.display.DisplayUtil.getDialogPortraitWidth;
-
-public final class SignatureUpdateSignatureAddDialog extends AlertDialog {
+public final class SignatureUpdateSignatureAddDialog extends AlertDialog implements ContentView {
private final SignatureUpdateSignatureAddView view;
private final ObservableDialogClickListener positiveButtonClicks;
@@ -35,6 +37,13 @@ public final class SignatureUpdateSignatureAddDialog extends AlertDialog {
private final ViewDisposables disposables = new ViewDisposables();
+ private final Button mobileIdPositiveButton;
+ private final Button mobileIdCancelButton;
+ private final Button smartIdPositiveButton;
+ private final Button smartIdCancelButton;
+ private final Button idCardPositiveButton;
+ private final Button idCardCancelButton;
+
SignatureUpdateSignatureAddDialog(@NonNull Context context) {
super(context);
SecureUtil.markAsSecure(context, getWindow());
@@ -45,15 +54,32 @@ public final class SignatureUpdateSignatureAddDialog extends AlertDialog {
view = new SignatureUpdateSignatureAddView(getContext());
view.setId(R.id.signatureUpdateSignatureAdd);
setView(view, padding, padding, padding, padding);
+
+ positiveButtonClicks = new ObservableDialogClickListener();
+
+ // Buttons to show when in portrait mode
setButton(BUTTON_POSITIVE,
getContext().getString(R.string.signature_update_signature_add_positive_button),
- positiveButtonClicks = new ObservableDialogClickListener());
+ positiveButtonClicks);
setButton(BUTTON_NEGATIVE, getContext().getString(android.R.string.cancel),
(dialog, which) -> {
cancel();
AccessibilityUtils.sendAccessibilityEvent(context, AccessibilityEvent.TYPE_ANNOUNCEMENT, R.string.signing_cancelled);
}
);
+
+ // Buttons to show when in landscape mode (to give more room for TextViews)
+ mobileIdPositiveButton = view.findViewById(R.id.signatureUpdateMobileIdSignButton);
+ mobileIdCancelButton = view.findViewById(R.id.signatureUpdateMobileIdCancelSigningButton);
+ setLandscapeButtons(getContext(), mobileIdPositiveButton, mobileIdCancelButton, positiveButtonClicks);
+
+ smartIdPositiveButton = view.findViewById(R.id.signatureUpdateSmartIdSignButton);
+ smartIdCancelButton = view.findViewById(R.id.signatureUpdateSmartIdCancelSigningButton);
+ setLandscapeButtons(getContext(), smartIdPositiveButton, smartIdCancelButton, positiveButtonClicks);
+
+ idCardPositiveButton = view.findViewById(R.id.signatureUpdateIdCardSignButton);
+ idCardCancelButton = view.findViewById(R.id.signatureUpdateIdCardCancelButton);
+ setLandscapeButtons(getContext(), idCardPositiveButton, idCardCancelButton, positiveButtonClicks);
}
public SignatureUpdateSignatureAddView view() {
@@ -74,6 +100,12 @@ public void show() {
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
setCustomLayoutChangeListener(window);
view.addOnLayoutChangeListener(getCustomLayoutChangeListener());
+
+ View parentPanel = findViewById(R.id.parentPanel);
+ int lastInvisibleElement = R.id.lastInvisibleElement;
+ if (parentPanel != null && parentPanel.findViewById(lastInvisibleElement) == null) {
+ ContentView.addInvisibleElementToObject(getContext(), findViewById(R.id.parentPanel));
+ }
}
}
@@ -84,9 +116,16 @@ public void onAttachedToWindow() {
confirmButton.setContentDescription(getContext().getString(R.string.sign_container));
Button cancelButton = getButton(BUTTON_NEGATIVE);
cancelButton.setContentDescription(getContext().getString(R.string.cancel_signing_process));
+ setButtonsBasedOnOrientation(getContext().getResources().getConfiguration().orientation);
+ mobileIdCancelButton.setTextColor(ContextCompat.getColor(getContext(), R.color.accent));
+ smartIdCancelButton.setTextColor(ContextCompat.getColor(getContext(), R.color.accent));
+ idCardCancelButton.setTextColor(ContextCompat.getColor(getContext(), R.color.accent));
disposables.attach();
- disposables.add(view.positiveButtonEnabled().subscribe(enabled ->
- getButton(BUTTON_POSITIVE).setEnabled(enabled)));
+ disposables.add(view.positiveButtonEnabled().subscribe(enabled -> {
+ Button positiveButton = getButton(BUTTON_POSITIVE);
+ Button[] additionalPositiveButtons = { mobileIdPositiveButton, smartIdPositiveButton, idCardPositiveButton };
+ updateButtonStateAndColor(positiveButton, additionalPositiveButtons, enabled);
+ }));
}
@Override
@@ -111,4 +150,53 @@ private void removeListeners() {
view.removeOnLayoutChangeListener(layoutChangeListener);
layoutChangeListener = null;
}
+
+ void setButtonsBasedOnOrientation(int orientation) {
+ Button confirmButton = getButton(BUTTON_POSITIVE);
+ Button cancelButton = getButton(BUTTON_NEGATIVE);
+
+ if (confirmButton != null && cancelButton != null) {
+ confirmButton.setVisibility(orientation == Configuration.ORIENTATION_PORTRAIT ? VISIBLE : GONE);
+ cancelButton.setVisibility(orientation == Configuration.ORIENTATION_PORTRAIT ? VISIBLE : GONE);
+ }
+
+ if (mobileIdPositiveButton != null && mobileIdCancelButton != null &&
+ smartIdPositiveButton != null && smartIdCancelButton != null &&
+ idCardPositiveButton != null && idCardCancelButton != null) {
+ int visibility = orientation == Configuration.ORIENTATION_LANDSCAPE ? VISIBLE : GONE;
+ mobileIdPositiveButton.setVisibility(visibility);
+ mobileIdCancelButton.setVisibility(visibility);
+ smartIdPositiveButton.setVisibility(visibility);
+ smartIdCancelButton.setVisibility(visibility);
+ idCardPositiveButton.setVisibility(visibility);
+ idCardCancelButton.setVisibility(visibility);
+ }
+ }
+
+ private void setLandscapeButtons(Context context, Button positiveButton, Button cancelButton, ObservableDialogClickListener clickListener) {
+ positiveButton.setText(getContext().getString(R.string.signature_update_signature_add_positive_button));
+ positiveButton.setOnClickListener(v -> clickListener.onClick(this, DialogInterface.BUTTON_POSITIVE));
+
+ cancelButton.setText(getContext().getString(android.R.string.cancel));
+ cancelButton.setOnClickListener(v -> {
+ cancel();
+ AccessibilityUtils.sendAccessibilityEvent(context, AccessibilityEvent.TYPE_ANNOUNCEMENT, R.string.signing_cancelled);
+ });
+ }
+
+ private void updateButtonStateAndColor(Button positiveButton, Button[] additionalPositiveButtons, boolean enabled) {
+ int textColor = enabled ? ContextCompat.getColor(getContext(), R.color.accent) : Color.GRAY;
+
+ for (Button button : additionalPositiveButtons) {
+ if (button != null) {
+ button.setEnabled(enabled);
+ button.setTextColor(textColor);
+ }
+ }
+
+ if (positiveButton != null) {
+ positiveButton.setEnabled(enabled);
+ positiveButton.setTextColor(textColor);
+ }
+ }
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateSignatureAddView.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateSignatureAddView.java
index b04b6d8f5..b477a1e06 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateSignatureAddView.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateSignatureAddView.java
@@ -1,16 +1,13 @@
package ee.ria.DigiDoc.android.signature.update;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static com.jakewharton.rxbinding4.widget.RxRadioGroup.checkedChanges;
+
import android.content.Context;
import android.content.res.Configuration;
-import android.text.Layout;
-import android.text.SpannableString;
-import android.text.style.AlignmentSpan;
import android.util.AttributeSet;
-import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
@@ -20,7 +17,6 @@
import androidx.annotation.Nullable;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils;
import ee.ria.DigiDoc.android.signature.update.idcard.IdCardResponse;
import ee.ria.DigiDoc.android.signature.update.idcard.IdCardView;
import ee.ria.DigiDoc.android.signature.update.mobileid.MobileIdResponse;
@@ -30,10 +26,6 @@
import ee.ria.DigiDoc.android.utils.TextUtil;
import io.reactivex.rxjava3.core.Observable;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.accessibility.AccessibilityEvent.TYPE_ANNOUNCEMENT;
-import static com.jakewharton.rxbinding4.widget.RxRadioGroup.checkedChanges;
-
public final class SignatureUpdateSignatureAddView extends LinearLayout {
private final RadioGroup methodView;
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateView.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateView.java
index ea0ce5ccb..1a2a1623d 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateView.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateView.java
@@ -3,8 +3,13 @@
import static android.util.TypedValue.COMPLEX_UNIT_SP;
import static android.view.accessibility.AccessibilityEvent.TYPE_ANNOUNCEMENT;
import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
+import static com.jakewharton.rxbinding4.view.RxView.clicks;
+import static com.jakewharton.rxbinding4.widget.RxToolbar.navigationClicks;
import static ee.ria.DigiDoc.android.accessibility.AccessibilityUtils.isLargeFontEnabled;
+import static ee.ria.DigiDoc.android.accessibility.AccessibilityUtils.isSmallFontEnabled;
import static ee.ria.DigiDoc.android.utils.TextUtil.convertPxToDp;
+import static com.jakewharton.rxbinding4.view.RxView.clicks;
+import static com.jakewharton.rxbinding4.widget.RxToolbar.navigationClicks;
import static ee.ria.DigiDoc.android.utils.TintUtils.tintCompoundDrawables;
import static ee.ria.DigiDoc.android.utils.display.DisplayUtil.getDisplayMetricsDpToInt;
import static ee.ria.DigiDoc.android.utils.rxbinding.app.RxDialog.cancels;
@@ -39,7 +44,7 @@
import ee.ria.DigiDoc.R;
import ee.ria.DigiDoc.android.Activity;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils;
import ee.ria.DigiDoc.android.signature.update.mobileid.MobileIdResponse;
import ee.ria.DigiDoc.android.signature.update.smartid.SmartIdResponse;
@@ -51,11 +56,13 @@
import ee.ria.DigiDoc.android.utils.files.FileSystem;
import ee.ria.DigiDoc.android.utils.mvi.MviView;
import ee.ria.DigiDoc.android.utils.mvi.State;
+import ee.ria.DigiDoc.android.utils.navigator.ContentView;
import ee.ria.DigiDoc.android.utils.navigator.Navigator;
import ee.ria.DigiDoc.android.utils.navigator.Transaction;
import ee.ria.DigiDoc.android.utils.widget.ConfirmationDialog;
import ee.ria.DigiDoc.android.utils.widget.NotificationDialog;
import ee.ria.DigiDoc.common.ActivityUtil;
+import ee.ria.DigiDoc.mobileid.service.MobileSignService;
import ee.ria.DigiDoc.sign.DataFile;
import ee.ria.DigiDoc.sign.NoInternetConnectionException;
import ee.ria.DigiDoc.sign.Signature;
@@ -64,11 +71,8 @@
import io.reactivex.rxjava3.subjects.PublishSubject;
import io.reactivex.rxjava3.subjects.Subject;
-import static com.jakewharton.rxbinding4.view.RxView.clicks;
-import static com.jakewharton.rxbinding4.widget.RxToolbar.navigationClicks;
-
@SuppressLint("ViewConstructor")
-public final class SignatureUpdateView extends LinearLayout implements MviView {
+public final class SignatureUpdateView extends LinearLayout implements ContentView, MviView {
private final ImmutableList ASICS_TIMESTAMP_CONTAINERS = ImmutableList.of("asics", "scs");
private static final ImmutableSet UNSIGNABLE_CONTAINER_EXTENSIONS = ImmutableSet.builder().add("adoc", "asics", "scs", "ddoc").build();
@@ -115,17 +119,17 @@ public final class SignatureUpdateView extends LinearLayout implements MviView documentsAddIntentSubject =
+ private final Subject documentsAddIntentSubject =
PublishSubject.create();
- private final Subject documentSaveIntentSubject =
+ private final Subject documentSaveIntentSubject =
PublishSubject.create();
- private final Subject documentRemoveIntentSubject =
+ private final Subject documentRemoveIntentSubject =
PublishSubject.create();
- private final Subject signatureViewIntentSubject =
+ private final Subject signatureViewIntentSubject =
PublishSubject.create();
- private final Subject signatureRemoveIntentSubject =
+ private final Subject signatureRemoveIntentSubject =
PublishSubject.create();
- private final Subject signatureAddIntentSubject =
+ private final Subject signatureAddIntentSubject =
PublishSubject.create();
@Nullable private DataFile sivaConfirmation;
@@ -154,7 +158,7 @@ public SignatureUpdateView(Context context, String screenId, boolean isExistingC
this.nestedFile = nestedFile;
this.isSivaConfirmed = isSivaConfirmed;
- navigator = Application.component(context).navigator();
+ navigator = ApplicationApp.component(context).navigator();
viewModel = navigator.viewModel(screenId, SignatureUpdateViewModel.class);
setOrientation(VERTICAL);
@@ -205,6 +209,13 @@ public SignatureUpdateView(Context context, String screenId, boolean isExistingC
setupAccessibilityTabs();
setActionButtonsTextSize();
+
+ ContentView.addInvisibleElement(getContext(), this);
+
+ View lastElementView = findViewById(R.id.lastInvisibleElement);
+ lastElementView.setVisibility(VISIBLE);
+
+ ContentView.addInvisibleElementScrollListener(listView, lastElementView);
}
@Override
@@ -212,25 +223,27 @@ protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setActionButtonsTextSize();
+
+ signatureAddDialog.setButtonsBasedOnOrientation(newConfig.orientation);
}
private void setActionButtonsTextSize() {
signatureAddButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16.0f);
sendButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16.0f);
- mobileIdCancelButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16.0f);
- smartIdCancelButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16.0f);
+ mobileIdCancelButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12.0f);
+ smartIdCancelButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12.0f);
- if (isLargeFontEnabled(getResources())) {
+ if (isLargeFontEnabled(getResources()) || isSmallFontEnabled(getResources())) {
signatureAddButton.setAutoSizeTextTypeUniformWithConfiguration(7, 12, 1, COMPLEX_UNIT_SP);
sendButton.setAutoSizeTextTypeUniformWithConfiguration(7, 12, 1, COMPLEX_UNIT_SP);
- mobileIdCancelButton.setAutoSizeTextTypeUniformWithConfiguration(7, 12, 1, COMPLEX_UNIT_SP);
- smartIdCancelButton.setAutoSizeTextTypeUniformWithConfiguration(7, 12, 1, COMPLEX_UNIT_SP);
+ mobileIdCancelButton.setAutoSizeTextTypeUniformWithConfiguration(7, 20, 1, COMPLEX_UNIT_SP);
+ smartIdCancelButton.setAutoSizeTextTypeUniformWithConfiguration(7, 20, 1, COMPLEX_UNIT_SP);
} else {
signatureAddButton.setAutoSizeTextTypeUniformWithConfiguration(11, 20, 1, COMPLEX_UNIT_SP);
sendButton.setAutoSizeTextTypeUniformWithConfiguration(11, 20, 1, COMPLEX_UNIT_SP);
- mobileIdCancelButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16.0f);
- smartIdCancelButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16.0f);
+ mobileIdCancelButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12.0f);
+ smartIdCancelButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12.0f);
}
}
@@ -521,88 +534,88 @@ private void resetSignatureAddDialog() {
signatureAddView.reset(viewModel);
}
- private Observable initialIntent() {
- return Observable.just(Intent.InitialIntent.create(isExistingContainer, containerFile,
+ private Observable initialIntent() {
+ return Observable.just(InitialIntent.create(isExistingContainer, containerFile,
signatureAddVisible ? viewModel.signatureAddMethod() : null,
signatureAddSuccessMessageVisible));
}
@SuppressWarnings("unchecked")
- private Observable nameUpdateIntent() {
+ private Observable nameUpdateIntent() {
return Observable.mergeArray(
- adapter.nameUpdateClicks().map(ignored -> Intent.NameUpdateIntent.show(containerFile)),
+ adapter.nameUpdateClicks().map(ignored -> NameUpdateIntent.show(containerFile)),
cancels(nameUpdateDialog).map(ignored -> {
AccessibilityUtils.sendAccessibilityEvent(getContext(), TYPE_ANNOUNCEMENT, R.string.container_name_change_cancelled);
- return Intent.NameUpdateIntent.clear();
+ return NameUpdateIntent.clear();
}),
- nameUpdateDialog.updates().map(name -> Intent.NameUpdateIntent.update(containerFile, name)));
+ nameUpdateDialog.updates().map(name -> NameUpdateIntent.update(containerFile, name)));
}
- private Observable addDocumentsIntent() {
+ private Observable addDocumentsIntent() {
return adapter.documentAddClicks()
- .map(ignored -> Intent.DocumentsAddIntent.create(containerFile))
+ .map(ignored -> DocumentsAddIntent.create(containerFile))
.mergeWith(documentsAddIntentSubject);
}
- private Observable documentViewIntent() {
+ private Observable documentViewIntent() {
return Observable.mergeArray(adapter.documentClicks()
- .map(document -> Intent.DocumentViewIntent.confirmation(getContext(),
+ .map(document -> DocumentViewIntent.confirmation(getContext(),
(nestedFile != null && isSivaConfirmed) ? nestedFile : containerFile, document)),
sivaConfirmationDialog.positiveButtonClicks()
- .map(ignored -> Intent.DocumentViewIntent.open(
+ .map(ignored -> DocumentViewIntent.open(
(nestedFile != null && isSivaConfirmed) ? nestedFile : containerFile,
sivaConfirmation)),
sivaConfirmationDialog.cancels()
- .map(ignored -> Intent.DocumentViewIntent.cancel()));
+ .map(ignored -> DocumentViewIntent.cancel()));
}
- private Observable documentSaveIntent() {
+ private Observable documentSaveIntent() {
return documentSaveIntentSubject;
}
- private Observable documentRemoveIntent() {
+ private Observable documentRemoveIntent() {
return documentRemoveIntentSubject;
}
- private Observable signatureViewIntent() {
+ private Observable signatureViewIntent() {
return adapter.signatureClicks()
- .map(document -> Intent.SignatureViewIntent.create(containerFile, document));
+ .map(document -> SignatureViewIntent.create(containerFile, document));
}
- private Observable signatureRemoveIntent() {
+ private Observable signatureRemoveIntent() {
return signatureRemoveIntentSubject;
}
@SuppressWarnings("unchecked")
- private Observable signatureAddIntent() {
+ private Observable signatureAddIntent() {
return Observable.mergeArray(
clicks(signatureAddButton)
.doOnNext(ignored -> resetSignatureAddDialog())
.map(ignored -> {
int method = viewModel.signatureAddMethod();
- return Intent.SignatureAddIntent.show(method, isExistingContainer, containerFile);
+ return SignatureAddIntent.show(method, isExistingContainer, containerFile);
}),
cancels(signatureAddDialog)
.doOnNext(ignored -> resetSignatureAddDialog())
- .map(ignored -> Intent.SignatureAddIntent.clear()),
+ .map(ignored -> SignatureAddIntent.clear()),
signatureAddView.methodChanges().map(method -> {
viewModel.setSignatureAddMethod(method);
sendMethodSelectionAccessibilityEvent(method);
- return Intent.SignatureAddIntent.show(method, isExistingContainer, containerFile);
+ return SignatureAddIntent.show(method, isExistingContainer, containerFile);
}),
signatureAddDialog.positiveButtonClicks().map(ignored -> {
SignatureUpdateProgressBar.stopProgressBar(mobileIdProgressBar);
SignatureUpdateProgressBar.stopProgressBar(smartIdProgressBar);
- return Intent.SignatureAddIntent.sign(signatureAddView.method(),
+ return SignatureAddIntent.sign(signatureAddView.method(),
isExistingContainer, containerFile, signatureAddView.request());
}),
signatureAddIntentSubject
);
}
- private Observable sendIntent() {
+ private Observable sendIntent() {
return clicks(sendButton)
- .map(ignored -> Intent.SendIntent.create(containerFile));
+ .map(ignored -> SendIntent.create(containerFile));
}
private void sendMethodSelectionAccessibilityEvent(int method) {
@@ -656,40 +669,41 @@ public void onAttachedToWindow() {
}));
disposables.add(adapter.scrollToTop().subscribe(ignored -> listView.scrollToPosition(0)));
disposables.add(adapter.documentSaveClicks().subscribe(document ->
- documentSaveIntentSubject.onNext(Intent.DocumentSaveIntent
+ documentSaveIntentSubject.onNext(DocumentSaveIntent
.create((nestedFile != null) ? nestedFile : containerFile, document))));
disposables.add(adapter.signatureClicks().subscribe(signature ->
- signatureViewIntentSubject.onNext(Intent.SignatureViewIntent
+ signatureViewIntentSubject.onNext(SignatureViewIntent
.create(containerFile, signature))));
disposables.add(adapter.documentRemoveClicks().subscribe(document ->
- documentRemoveIntentSubject.onNext(Intent.DocumentRemoveIntent
+ documentRemoveIntentSubject.onNext(DocumentRemoveIntent
.showConfirmation(containerFile, dataFiles, document))));
disposables.add(documentRemoveConfirmationDialog.positiveButtonClicks().subscribe(ignored ->
- documentRemoveIntentSubject.onNext(Intent.DocumentRemoveIntent
+ documentRemoveIntentSubject.onNext(DocumentRemoveIntent
.remove(containerFile, dataFiles, documentRemoveConfirmation))));
disposables.add(documentRemoveConfirmationDialog.cancels().subscribe(ignored -> {
AccessibilityUtils.sendAccessibilityEvent(getContext(), TYPE_ANNOUNCEMENT, R.string.document_removal_cancelled);
- documentRemoveIntentSubject.onNext(Intent.DocumentRemoveIntent.clear());
+ documentRemoveIntentSubject.onNext(DocumentRemoveIntent.clear());
}));
disposables.add(adapter.signatureRemoveClicks().subscribe(signature ->
- signatureRemoveIntentSubject.onNext(Intent.SignatureRemoveIntent
+ signatureRemoveIntentSubject.onNext(SignatureRemoveIntent
.showConfirmation(containerFile, signature))));
disposables.add(signatureRemoveConfirmationDialog.positiveButtonClicks()
.subscribe(ignored -> signatureRemoveIntentSubject
- .onNext(Intent.SignatureRemoveIntent
+ .onNext(SignatureRemoveIntent
.remove(containerFile, signatureRemoveConfirmation))));
disposables.add(signatureRemoveConfirmationDialog.cancels().subscribe(ignored -> {
AccessibilityUtils.sendAccessibilityEvent(getContext(), TYPE_ANNOUNCEMENT, R.string.signature_removal_cancelled);
- signatureRemoveIntentSubject.onNext(Intent.SignatureRemoveIntent.clear());
+ signatureRemoveIntentSubject.onNext(SignatureRemoveIntent.clear());
}));
disposables.add(clicks(mobileIdCancelButton).subscribe(ignored -> {
+ MobileSignService.setIsCancelled(true);
resetSignatureAddDialog();
- signatureAddIntentSubject.onNext(Intent.SignatureAddIntent.clear());
+ signatureAddIntentSubject.onNext(SignatureAddIntent.clear());
}));
disposables.add(clicks(smartIdCancelButton).subscribe(ignored -> {
SmartSignService.setIsCancelled(true);
resetSignatureAddDialog();
- signatureAddIntentSubject.onNext(Intent.SignatureAddIntent.clear());
+ signatureAddIntentSubject.onNext(SignatureAddIntent.clear());
}));
}
@@ -706,6 +720,7 @@ public void onDetachedFromWindow() {
isTitleViewFocused = false;
SignatureUpdateProgressBar.stopProgressBar(mobileIdProgressBar);
SignatureUpdateProgressBar.stopProgressBar(smartIdProgressBar);
+ ContentView.removeInvisibleElementScrollListener(listView);
super.onDetachedFromWindow();
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateViewModel.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateViewModel.java
index f856a86d3..58ab0afc6 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateViewModel.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureUpdateViewModel.java
@@ -37,46 +37,15 @@ public String sidPersonalCode() {
@Override
protected Class extends Intent> initialIntentType() {
- return Intent.InitialIntent.class;
+ return InitialIntent.class;
}
@Override
protected Action action(Intent intent) {
- if (intent instanceof Intent.InitialIntent) {
- Intent.InitialIntent initialIntent = (Intent.InitialIntent) intent;
- return Action.ContainerLoadAction
- .create(initialIntent.containerFile(), initialIntent.signatureAddMethod(),
- initialIntent.signatureAddSuccessMessageVisible(), initialIntent.isExistingContainer());
- } else if (intent instanceof Intent.DocumentsAddIntent) {
- return Action.DocumentsAddAction
- .create(((Intent.DocumentsAddIntent) intent).containerFile());
- } else if (intent instanceof Intent.DocumentRemoveIntent) {
- Intent.DocumentRemoveIntent documentRemoveIntent = (Intent.DocumentRemoveIntent) intent;
- return Action.DocumentRemoveAction.create(documentRemoveIntent.showConfirmation(),
- documentRemoveIntent.containerFile(), documentRemoveIntent.documents(), documentRemoveIntent.document());
- } else if (intent instanceof Intent.SignatureViewIntent) {
- Intent.SignatureViewIntent signatureViewIntent = (Intent.SignatureViewIntent) intent;
- return Action.SignatureViewAction.create(signatureViewIntent.containerFile(), signatureViewIntent.signature());
- } else if (intent instanceof Intent.SignatureRemoveIntent) {
- Intent.SignatureRemoveIntent signatureRemoveIntent =
- (Intent.SignatureRemoveIntent) intent;
- return Action.SignatureRemoveAction.create(signatureRemoveIntent.showConfirmation(),
- signatureRemoveIntent.containerFile(), signatureRemoveIntent.signature());
- } else if (intent instanceof Intent.SignatureAddIntent) {
- Intent.SignatureAddIntent signatureAddIntent = (Intent.SignatureAddIntent) intent;
- if (signatureAddIntent.isCancelled()) {
- return Action.SignatureAddAction.create(signatureAddIntent.method(),
- null, null, null, true);
- }
- return Action.SignatureAddAction.create(signatureAddIntent.method(),
- signatureAddIntent.existingContainer(), signatureAddIntent.containerFile(),
- signatureAddIntent.request(), false);
- } else if (intent instanceof Intent.SendIntent) {
- return Action.SendAction.create(((Intent.SendIntent) intent).containerFile());
- } else if (intent instanceof Action) {
+ if (intent instanceof Action) {
return (Action) intent;
} else {
- throw new IllegalArgumentException("Unknown intent " + intent);
+ return intent.action();
}
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/exception/DocumentExistsException.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/exception/DocumentExistsException.java
new file mode 100644
index 000000000..083fe9cbf
--- /dev/null
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/exception/DocumentExistsException.java
@@ -0,0 +1,18 @@
+package ee.ria.DigiDoc.android.signature.update.exception;
+
+import android.content.Context;
+
+import java.io.IOException;
+
+import ee.ria.DigiDoc.R;
+import ee.ria.DigiDoc.common.exception.SignatureUpdateError;
+
+public class DocumentExistsException extends IOException implements SignatureUpdateError {
+
+ public DocumentExistsException() {}
+
+ @Override
+ public String getMessage(Context context) {
+ return context.getString(R.string.signature_update_documents_add_error_exists);
+ }
+}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/exception/DocumentRemoveException.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/exception/DocumentRemoveException.java
new file mode 100644
index 000000000..5b1666435
--- /dev/null
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/exception/DocumentRemoveException.java
@@ -0,0 +1,18 @@
+package ee.ria.DigiDoc.android.signature.update.exception;
+
+import android.content.Context;
+
+import java.io.IOException;
+
+import ee.ria.DigiDoc.R;
+import ee.ria.DigiDoc.common.exception.SignatureUpdateError;
+
+public final class DocumentRemoveException extends IOException implements SignatureUpdateError {
+
+ public DocumentRemoveException() {}
+
+ @Override
+ public String getMessage(Context context) {
+ return context.getString(R.string.signature_update_document_remove_error);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/exception/GeneralSignatureUpdateException.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/exception/GeneralSignatureUpdateException.java
new file mode 100644
index 000000000..842b77363
--- /dev/null
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/exception/GeneralSignatureUpdateException.java
@@ -0,0 +1,19 @@
+package ee.ria.DigiDoc.android.signature.update.exception;
+
+import android.content.Context;
+
+import ee.ria.DigiDoc.common.exception.SignatureUpdateError;
+
+public class GeneralSignatureUpdateException extends Exception implements SignatureUpdateError {
+
+ private final String message;
+
+ public GeneralSignatureUpdateException(String message) {
+ this.message = message;
+ }
+
+ @Override
+ public String getMessage(Context context) {
+ return message;
+ }
+}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/mobileid/MobileIdOnSubscribe.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/mobileid/MobileIdOnSubscribe.java
index 2455a808a..cd7c4193c 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/mobileid/MobileIdOnSubscribe.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/mobileid/MobileIdOnSubscribe.java
@@ -1,40 +1,48 @@
package ee.ria.DigiDoc.android.signature.update.mobileid;
+import static ee.ria.DigiDoc.mobileid.dto.request.MobileCreateSignatureRequest.toJson;
+import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.ACCESS_TOKEN_PASS;
+import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.ACCESS_TOKEN_PATH;
+import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.CERTIFICATE_CERT_BUNDLE;
+import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.CREATE_SIGNATURE_CHALLENGE;
+import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.CREATE_SIGNATURE_REQUEST;
+import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.CREATE_SIGNATURE_STATUS;
+import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.MID_BROADCAST_ACTION;
+import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.MID_BROADCAST_TYPE_KEY;
+import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.SERVICE_FAULT;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.util.Log;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.preference.PreferenceManager;
+import androidx.work.Data;
+import androidx.work.ExistingWorkPolicy;
+import androidx.work.OneTimeWorkRequest;
+import androidx.work.WorkManager;
+
+import com.google.gson.Gson;
-import java.util.ArrayList;
import java.util.Locale;
+import java.util.UUID;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.model.mobileid.MobileIdMessageException;
import ee.ria.DigiDoc.android.utils.navigator.Navigator;
import ee.ria.DigiDoc.configuration.ConfigurationProvider;
import ee.ria.DigiDoc.mobileid.dto.request.MobileCreateSignatureRequest;
import ee.ria.DigiDoc.mobileid.dto.response.MobileIdServiceResponse;
import ee.ria.DigiDoc.mobileid.dto.response.RESTServiceFault;
+import ee.ria.DigiDoc.mobileid.service.MobileSignService;
import ee.ria.DigiDoc.sign.SignLib;
import ee.ria.DigiDoc.sign.SignedContainer;
import io.reactivex.rxjava3.core.ObservableEmitter;
import io.reactivex.rxjava3.core.ObservableOnSubscribe;
-import timber.log.Timber;
-
-import static ee.ria.DigiDoc.mobileid.dto.request.MobileCreateSignatureRequest.toJson;
-import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.ACCESS_TOKEN_PASS;
-import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.ACCESS_TOKEN_PATH;
-import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.CERTIFICATE_CERT_BUNDLE;
-import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.CREATE_SIGNATURE_CHALLENGE;
-import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.CREATE_SIGNATURE_REQUEST;
-import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.CREATE_SIGNATURE_STATUS;
-import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.MID_BROADCAST_ACTION;
-import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.MID_BROADCAST_TYPE_KEY;
-import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.SERVICE_FAULT;
public final class MobileIdOnSubscribe implements ObservableOnSubscribe {
@@ -45,13 +53,11 @@ public final class MobileIdOnSubscribe implements ObservableOnSubscribe emitter) {
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (navigator.activity() == null) {
- Timber.log(Log.ERROR,"Activity is null");
- IllegalStateException ise = new IllegalStateException("Activity not found. Please try again after restarting application");
- emitter.onError(ise);
- }
switch (intent.getStringExtra(MID_BROADCAST_TYPE_KEY)) {
case SERVICE_FAULT:
RESTServiceFault fault = RESTServiceFault
.fromJson(intent.getStringExtra(SERVICE_FAULT));
+ Configuration configuration = context.getResources().getConfiguration();
+ configuration.setLocale(locale);
+ Context configuredContext = context.createConfigurationContext(configuration);
if (fault.getStatus() != null) {
emitter.onError(MobileIdMessageException
- .create(navigator.activity(), fault.getStatus(), fault.getDetailMessage()));
+ .create(configuredContext, fault.getStatus(), fault.getDetailMessage()));
} else {
emitter.onError(MobileIdMessageException
- .create(navigator.activity(), fault.getResult(), fault.getDetailMessage()));
+ .create(configuredContext, fault.getResult(), fault.getDetailMessage()));
}
break;
case CREATE_SIGNATURE_CHALLENGE:
@@ -102,7 +106,7 @@ public void onReceive(Context context, Intent intent) {
break;
default:
emitter.onError(MobileIdMessageException
- .create(navigator.activity(), status.getStatus(), null));
+ .create(context, status.getStatus(), null));
break;
}
break;
@@ -114,18 +118,34 @@ public void onReceive(Context context, Intent intent) {
emitter.setCancellable(() -> broadcastManager.unregisterReceiver(receiver));
ConfigurationProvider configurationProvider =
- ((Application) navigator.activity().getApplication()).getConfigurationProvider();
+ ((ApplicationApp) navigator.activity().getApplication()).getConfigurationProvider();
String displayMessage = navigator.activity()
.getString(R.string.signature_update_mobile_id_display_message);
MobileCreateSignatureRequest request = MobileCreateSignatureRequestHelper
.create(container, uuid, configurationProvider.getMidRestUrl(),
configurationProvider.getMidSkRestUrl(), locale, personalCode, phoneNo, displayMessage);
- intent.putExtra(CREATE_SIGNATURE_REQUEST, toJson(request));
- intent.putExtra(ACCESS_TOKEN_PASS, SignLib.accessTokenPass());
- intent.putExtra(ACCESS_TOKEN_PATH, SignLib.accessTokenPath());
- intent.putStringArrayListExtra(CERTIFICATE_CERT_BUNDLE,
- new ArrayList<>(configurationProvider.getCertBundle()));
- navigator.activity().startService(intent);
+ Gson gson = new Gson();
+ String certBundleList = gson.toJson(configurationProvider.getCertBundle());
+
+ // WorkManager has 10KB data limit. Saving certs to SharedPreferences
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(navigator.activity());
+ sharedPreferences.edit().putString(CERTIFICATE_CERT_BUNDLE, certBundleList).commit();
+
+ UUID uuid = UUID.randomUUID();
+ Data inputData = new Data.Builder()
+ .putString(CREATE_SIGNATURE_REQUEST, toJson(request))
+ .putString(ACCESS_TOKEN_PASS, SignLib.accessTokenPass())
+ .putString(ACCESS_TOKEN_PATH, SignLib.accessTokenPath())
+ .build();
+
+
+ OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(MobileSignService.class)
+ .addTag(SIGNING_TAG + uuid)
+ .setId(uuid)
+ .setInputData(inputData)
+ .build();
+
+ WorkManager.getInstance(navigator.activity()).enqueueUniqueWork(uuid.toString(), ExistingWorkPolicy.REPLACE, workRequest);
}
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/smartid/SmartIdOnSubscribe.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/smartid/SmartIdOnSubscribe.java
index 7b13fe0eb..85d140b95 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/smartid/SmartIdOnSubscribe.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/smartid/SmartIdOnSubscribe.java
@@ -20,6 +20,7 @@
package ee.ria.DigiDoc.android.signature.update.smartid;
+import static ee.ria.DigiDoc.smartid.dto.request.SmartIDSignatureRequest.toJson;
import static ee.ria.DigiDoc.smartid.dto.response.SessionStatusResponse.ProcessStatus.NO_RESPONSE;
import static ee.ria.DigiDoc.smartid.service.SmartSignConstants.CERTIFICATE_CERT_BUNDLE;
import static ee.ria.DigiDoc.smartid.service.SmartSignConstants.CREATE_SIGNATURE_CHALLENGE;
@@ -36,7 +37,9 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.content.pm.PackageManager;
+import android.content.res.Configuration;
import android.os.Build;
import android.util.Log;
@@ -44,11 +47,19 @@
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.preference.PreferenceManager;
+import androidx.work.Data;
+import androidx.work.ExistingWorkPolicy;
+import androidx.work.OneTimeWorkRequest;
+import androidx.work.WorkManager;
-import java.util.ArrayList;
+import com.google.gson.Gson;
+
+import java.util.Locale;
+import java.util.UUID;
import ee.ria.DigiDoc.R;
-import ee.ria.DigiDoc.android.Application;
+import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.model.smartid.SmartIdMessageException;
import ee.ria.DigiDoc.android.utils.navigator.Navigator;
import ee.ria.DigiDoc.common.NotificationUtil;
@@ -59,6 +70,7 @@
import ee.ria.DigiDoc.smartid.dto.response.ServiceFault;
import ee.ria.DigiDoc.smartid.dto.response.SessionStatusResponse;
import ee.ria.DigiDoc.smartid.dto.response.SmartIDServiceResponse;
+import ee.ria.DigiDoc.smartid.service.SmartSignService;
import io.reactivex.rxjava3.core.ObservableEmitter;
import io.reactivex.rxjava3.core.ObservableOnSubscribe;
import timber.log.Timber;
@@ -69,22 +81,21 @@ public final class SmartIdOnSubscribe implements ObservableOnSubscribe emitter) {
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (navigator.activity() == null) {
- Timber.log(Log.ERROR, "Activity is null");
- IllegalStateException ise = new IllegalStateException("Activity not found. Please try again after restarting application");
- emitter.onError(ise);
- }
switch (intent.getStringExtra(SID_BROADCAST_TYPE_KEY)) {
case SERVICE_FAULT: {
NotificationManagerCompat.from(navigator.activity()).cancelAll();
ServiceFault serviceFault =
ServiceFault.fromJson(intent.getStringExtra(SERVICE_FAULT));
Timber.log(Log.DEBUG, "Got SERVICE_FAULT status: %s", serviceFault.getStatus());
+ Configuration configuration = context.getResources().getConfiguration();
+ configuration.setLocale(locale);
+ Context configuredContext = context.createConfigurationContext(configuration);
if (serviceFault.getStatus() == NO_RESPONSE) {
emitter.onError(SmartIdMessageException
- .create(navigator.activity(), serviceFault.getStatus()));
+ .create(configuredContext, serviceFault.getStatus()));
} else {
emitter.onError(SmartIdMessageException
- .create(navigator.activity(), serviceFault.getStatus(), serviceFault.getDetailMessage()));
+ .create(configuredContext, serviceFault.getStatus(), serviceFault.getDetailMessage()));
}
break;
}
@@ -122,9 +131,9 @@ public void onReceive(Context context, Intent intent) {
intent.getStringExtra(CREATE_SIGNATURE_CHALLENGE);
emitter.onNext(SmartIdResponse.challenge(challenge));
- if (!PowerUtil.isPowerSavingMode(navigator.activity())) {
+ if (!PowerUtil.isPowerSavingMode(context)) {
Timber.log(Log.DEBUG, "Creating notification channel");
- NotificationUtil.createNotificationChannel(navigator.activity(),
+ NotificationUtil.createNotificationChannel(context,
NOTIFICATION_CHANNEL, navigator.activity()
.getResources()
.getString(R.string.signature_update_signature_add_method_smart_id));
@@ -132,15 +141,19 @@ public void onReceive(Context context, Intent intent) {
String challengeTitle = navigator.activity()
.getResources().getString(R.string.smart_id_challenge);
- Notification notification = NotificationUtil.createNotification(navigator.activity(), NOTIFICATION_CHANNEL,
+ Notification notification = NotificationUtil.createNotification(context, NOTIFICATION_CHANNEL,
R.mipmap.ic_launcher, challengeTitle, challenge,
NotificationCompat.PRIORITY_HIGH, false);
- sendNotification(navigator.activity(), challenge, notification);
+ try {
+ sendNotification(context, challenge, notification);
+ } catch (NumberFormatException nfe) {
+ Timber.log(Log.ERROR, nfe, "Unable to send notification");
+ }
break;
case CREATE_SIGNATURE_STATUS:
- NotificationManagerCompat.from(navigator.activity()).cancelAll();
+ NotificationManagerCompat.from(context).cancelAll();
SmartIDServiceResponse status =
SmartIDServiceResponse.fromJson(
intent.getStringExtra(CREATE_SIGNATURE_STATUS));
@@ -151,7 +164,7 @@ public void onReceive(Context context, Intent intent) {
} else {
Timber.log(Log.DEBUG, "Got CREATE_SIGNATURE_STATUS error status: %s", status.getStatus());
emitter.onError(SmartIdMessageException
- .create(navigator.activity(), status.getStatus()));
+ .create(context, status.getStatus()));
}
break;
}
@@ -162,7 +175,7 @@ public void onReceive(Context context, Intent intent) {
emitter.setCancellable(() -> broadcastManager.unregisterReceiver(receiver));
ConfigurationProvider configurationProvider =
- ((Application) navigator.activity().getApplication()).getConfigurationProvider();
+ ((ApplicationApp) navigator.activity().getApplication()).getConfigurationProvider();
String displayMessage = navigator.activity()
.getString(R.string.signature_update_mobile_id_display_message);
SmartIDSignatureRequest request = SmartCreateSignatureRequestHelper
@@ -170,13 +183,28 @@ public void onReceive(Context context, Intent intent) {
configurationProvider.getSidV2SkRestUrl(), country,
personalCode, displayMessage);
- intent.putExtra(CREATE_SIGNATURE_REQUEST, request);
- intent.putStringArrayListExtra(CERTIFICATE_CERT_BUNDLE,
- new ArrayList<>(configurationProvider.getCertBundle()));
- navigator.activity().startService(intent);
+ Gson gson = new Gson();
+ String certBundleList = gson.toJson(configurationProvider.getCertBundle());
+
+ // WorkManager has 10KB data limit. Saving certs to SharedPreferences
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(navigator.activity());
+ sharedPreferences.edit().putString(CERTIFICATE_CERT_BUNDLE, certBundleList).commit();
+
+ UUID uuid = UUID.randomUUID();
+ Data inputData = new Data.Builder()
+ .putString(CREATE_SIGNATURE_REQUEST, toJson(request))
+ .build();
+
+ OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(SmartSignService.class)
+ .addTag(SIGNING_TAG + uuid)
+ .setId(uuid)
+ .setInputData(inputData)
+ .build();
+
+ WorkManager.getInstance(navigator.activity()).enqueueUniqueWork(uuid.toString(), ExistingWorkPolicy.REPLACE, workRequest);
}
- private void sendNotification(Context context, String challenge, Notification notification) {
+ private void sendNotification(Context context, String challenge, Notification notification) throws NumberFormatException {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2 || ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
NotificationManagerCompat.from(navigator.activity())
.notify(Integer.parseInt(challenge), notification);
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/utils/BundleUtils.java b/app/src/main/java/ee/ria/DigiDoc/android/utils/BundleUtils.java
index cbbb67564..bfe5d8027 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/utils/BundleUtils.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/utils/BundleUtils.java
@@ -1,30 +1,13 @@
package ee.ria.DigiDoc.android.utils;
import android.os.Bundle;
-import android.os.Parcelable;
-
-import com.google.common.collect.ImmutableList;
import java.io.File;
-import java.util.ArrayList;
public final class BundleUtils {
private BundleUtils() {}
- public static void putParcelableImmutableList(Bundle bundle, String key,
- ImmutableList value) {
- ArrayList arrayList = new ArrayList<>(value.size());
- arrayList.addAll(value);
- bundle.putParcelableArrayList(key, arrayList);
- }
-
- @SuppressWarnings("ConstantConditions")
- public static ImmutableList getParcelableImmutableList(Bundle bundle,
- String key) {
- return ImmutableList.copyOf(bundle.getParcelableArrayList(key));
- }
-
public static void putFile(Bundle bundle, String key, File value) {
bundle.putString(key, value.getAbsolutePath());
}
@@ -33,4 +16,9 @@ public static void putFile(Bundle bundle, String key, File value) {
public static File getFile(Bundle bundle, String key) {
return new File(bundle.getString(key));
}
+
+ public static void putBoolean(Bundle bundle, String key, boolean value) {
+ bundle.putBoolean(key, value);
+ }
+
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/utils/IntentUtils.java b/app/src/main/java/ee/ria/DigiDoc/android/utils/IntentUtils.java
index 05b44a93c..50cb6fb05 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/utils/IntentUtils.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/utils/IntentUtils.java
@@ -61,13 +61,13 @@ public final class IntentUtils {
*
* @return Intent to use with {@link android.app.Activity#startActivityForResult(Intent, int)}.
*/
- public static Intent createGetContentIntent() {
+ public static Intent createGetContentIntent(Boolean allowMultiple) {
return Intent
.createChooser(new Intent(Intent.ACTION_GET_CONTENT)
.setType("*/*")
.addCategory(Intent.CATEGORY_OPENABLE)
.putExtra(Intent.EXTRA_REFERRER, R.string.application_name)
- .putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true), null);
+ .putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple), null);
}
/**
@@ -272,6 +272,15 @@ public static Intent createBrowserIntent(Context context, int stringRes, Configu
return new Intent(Intent.ACTION_VIEW, Uri.parse(localizedUrl));
}
+ public static Intent setIntentData(Intent intent, Path filePath, android.app.Activity activity) {
+ intent.setData(Uri.parse(filePath.toUri().toString()));
+ intent.setClipData(ClipData.newRawUri(filePath.getFileName().toString(), FileProvider.getUriForFile(
+ activity,
+ activity.getString(R.string.file_provider_authority),
+ filePath.toFile())));
+ return intent;
+ }
+
private static String getDataFileMimetype(DataFile dataFile) {
int extensionIndex = dataFile.name().lastIndexOf(".");
String extension = extensionIndex != -1 ? dataFile.name().substring(extensionIndex + 1) : "";
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/utils/TextUtil.java b/app/src/main/java/ee/ria/DigiDoc/android/utils/TextUtil.java
index f4e06db34..defc84103 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/utils/TextUtil.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/utils/TextUtil.java
@@ -2,9 +2,11 @@
import static android.util.TypedValue.COMPLEX_UNIT_PX;
import static android.view.View.GONE;
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO;
import static android.view.View.VISIBLE;
import android.content.Context;
+import android.graphics.Color;
import android.text.Editable;
import android.text.Layout;
import android.text.SpannableString;
@@ -26,6 +28,8 @@
import com.google.android.material.textfield.TextInputLayout;
+import ee.ria.DigiDoc.R;
+
public class TextUtil {
public static AppCompatEditText getTextView(View view) {
@@ -102,6 +106,22 @@ public static int convertPxToDp(float size, Context context) {
size, context.getResources().getDisplayMetrics());
}
+ public static TextView getInvisibleElementTextView(Context context) {
+ TextView textView = new TextView(context);
+ textView.setText(R.string.last_invisible_element_name);
+ textView.setTextColor(Color.GRAY);
+ textView.setId(R.id.lastInvisibleElement);
+ textView.setTag(R.string.last_invisible_element_tag);
+ textView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+ textView.setLayoutParams(new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT));
+ textView.setEnabled(false);
+ textView.setAlpha(0.001f);
+
+ return textView;
+ }
+
public static TextWatcher addTextWatcher(EditText editText) {
float defaultTextSize = editText.getTextSize();
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/utils/ToastUtil.java b/app/src/main/java/ee/ria/DigiDoc/android/utils/ToastUtil.java
index 7d757f657..28f97696b 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/utils/ToastUtil.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/utils/ToastUtil.java
@@ -20,18 +20,17 @@
public final class ToastUtil {
public static void handleEmptyFileError(ImmutableList validFiles,
- Application application,
Context context) throws EmptyFileException {
if (validFiles.isEmpty()) {
Timber.log(Log.ERROR, "Add file to container failed");
throw new EmptyFileException();
}
if (FileSystem.isEmptyFileInList(validFiles)) {
- showEmptyFileError(context, application);
+ showEmptyFileError(context);
}
}
- public static void showEmptyFileError(Context context, Application application) {
+ public static void showEmptyFileError(Context context) {
Timber.log(Log.DEBUG, "Excluded empty files in list");
new Handler(Looper.getMainLooper()).post(() ->
showError(context, R.string.empty_file_error));
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/utils/ViewSavedState.java b/app/src/main/java/ee/ria/DigiDoc/android/utils/ViewSavedState.java
index 27abf7e8c..d1fa5d970 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/utils/ViewSavedState.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/utils/ViewSavedState.java
@@ -1,5 +1,6 @@
package ee.ria.DigiDoc.android.utils;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -37,7 +38,7 @@ private ViewSavedState(Parcelable superState, byte[] state) {
}
private ViewSavedState(Parcel source, ClassLoader loader) {
- superState = source.readParcelable(loader);
+ superState = getParcelable(source, loader);
state = source.createByteArray();
}
@@ -66,4 +67,12 @@ public ViewSavedState[] newArray(int size) {
return new ViewSavedState[size];
}
};
+
+ private Parcelable getParcelable(Parcel source, ClassLoader loader) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ return source.readParcelable(loader, Parcelable.class);
+ } else {
+ return source.readParcelable(loader);
+ }
+ }
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/utils/ViewUtil.java b/app/src/main/java/ee/ria/DigiDoc/android/utils/ViewUtil.java
new file mode 100644
index 000000000..8fb4ce0cb
--- /dev/null
+++ b/app/src/main/java/ee/ria/DigiDoc/android/utils/ViewUtil.java
@@ -0,0 +1,54 @@
+package ee.ria.DigiDoc.android.utils;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+public class ViewUtil {
+
+ public static View findLastElement(View view) {
+ if (view instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) view;
+ int subviews = viewGroup.getChildCount();
+ if (subviews > 0) {
+ return findLastElement(viewGroup.getChildAt(subviews - 1));
+ }
+ }
+
+ return view;
+ }
+
+ public static View findMainLayoutElement(View view) {
+ View mainView = findLastElement(view);
+
+ if (mainView != null) {
+ ViewParent parent = mainView.getParent();
+ if (parent != null) {
+ while ((parent instanceof LinearLayout ||
+ parent instanceof RelativeLayout ||
+ parent instanceof RecyclerView)) {
+ mainView = (View) parent;
+ parent = mainView.getParent();
+ }
+ }
+
+ }
+ return mainView;
+ }
+
+ public static void moveView(View view) {
+ if (view != null) {
+ ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
+
+ if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
+ ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) layoutParams;
+ marginLayoutParams.setMargins(marginLayoutParams.leftMargin, -100, marginLayoutParams.rightMargin, marginLayoutParams.bottomMargin);
+ view.setLayoutParams(layoutParams);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/utils/container/NameUpdateView.java b/app/src/main/java/ee/ria/DigiDoc/android/utils/container/NameUpdateView.java
index b6650c8a8..b128bdc31 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/utils/container/NameUpdateView.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/utils/container/NameUpdateView.java
@@ -12,6 +12,7 @@
import ee.ria.DigiDoc.R;
import ee.ria.DigiDoc.android.utils.display.DisplayUtil;
+import ee.ria.DigiDoc.common.FileUtil;
public final class NameUpdateView extends TextInputLayout {
@@ -43,7 +44,7 @@ public String name() {
public void name(String name) {
if (name.startsWith(".")) {
- editText.setText(nameWithoutExtension("newFile" + name));
+ editText.setText(nameWithoutExtension(FileUtil.DEFAULT_FILENAME + name));
} else {
editText.setText(nameWithoutExtension(name));
}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/utils/files/EmptyFileException.java b/app/src/main/java/ee/ria/DigiDoc/android/utils/files/EmptyFileException.java
index 35b903143..667d4eaa8 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/utils/files/EmptyFileException.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/utils/files/EmptyFileException.java
@@ -1,8 +1,18 @@
package ee.ria.DigiDoc.android.utils.files;
+import android.content.Context;
+
import java.io.IOException;
-public final class EmptyFileException extends IOException {
+import ee.ria.DigiDoc.R;
+import ee.ria.DigiDoc.common.exception.SignatureUpdateError;
+
+public final class EmptyFileException extends IOException implements SignatureUpdateError {
public EmptyFileException() {}
+
+ @Override
+ public String getMessage(Context context) {
+ return context.getString(R.string.empty_file_error);
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/utils/files/FileStream.java b/app/src/main/java/ee/ria/DigiDoc/android/utils/files/FileStream.java
index 6ac8e61d4..c4285eb94 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/utils/files/FileStream.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/utils/files/FileStream.java
@@ -29,7 +29,7 @@ public abstract class FileStream {
* Create FileStream from {@link ContentResolver} and content {@link Uri}.
*/
public static FileStream create(ContentResolver contentResolver, Uri uri, long fileSize) {
- String displayName = uri.getLastPathSegment() == null ? "newFile" : FilenameUtils.getName(uri.getLastPathSegment());
+ String displayName = uri.getLastPathSegment() == null ? FileUtil.DEFAULT_FILENAME : FilenameUtils.getName(uri.getLastPathSegment());
Uri sanitizedUri = FileUtil.normalizeUri(uri);
Cursor cursor = contentResolver.query(sanitizedUri, new String[]{OpenableColumns.DISPLAY_NAME}, null,
null, null);
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/utils/navigator/ContentView.java b/app/src/main/java/ee/ria/DigiDoc/android/utils/navigator/ContentView.java
new file mode 100644
index 000000000..5f555ef96
--- /dev/null
+++ b/app/src/main/java/ee/ria/DigiDoc/android/utils/navigator/ContentView.java
@@ -0,0 +1,40 @@
+package ee.ria.DigiDoc.android.utils.navigator;
+
+import static ee.ria.DigiDoc.android.utils.ViewUtil.findMainLayoutElement;
+
+import android.content.Context;
+import android.view.View;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import ee.ria.DigiDoc.BuildConfig;
+
+public interface ContentView {
+
+ // Adds an invisible label element to the bottom of the view.
+ // Used for autotests by testers
+ static void addInvisibleElement(Context context, View view) {
+ if (BuildConfig.DEBUG) {
+ View mainLayoutElement = findMainLayoutElement(view);
+ InvisibleView.addInvisibleElement(context, mainLayoutElement);
+ }
+ }
+
+ static void addInvisibleElementToObject(Context context, View view) {
+ if (BuildConfig.DEBUG) {
+ InvisibleView.addInvisibleElementToObject(context, view);
+ }
+ }
+
+ static void addInvisibleElementScrollListener(RecyclerView recyclerView, View lastElementView) {
+ if (BuildConfig.DEBUG) {
+ InvisibleView.addInvisibleElementScrollListener(recyclerView, lastElementView);
+ }
+ }
+
+ static void removeInvisibleElementScrollListener(RecyclerView recyclerView) {
+ if (BuildConfig.DEBUG) {
+ InvisibleView.removeInvisibleElementScrollListener(recyclerView);
+ }
+ }
+}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/utils/navigator/InvisibleView.java b/app/src/main/java/ee/ria/DigiDoc/android/utils/navigator/InvisibleView.java
new file mode 100644
index 000000000..6a789f35d
--- /dev/null
+++ b/app/src/main/java/ee/ria/DigiDoc/android/utils/navigator/InvisibleView.java
@@ -0,0 +1,70 @@
+package ee.ria.DigiDoc.android.utils.navigator;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+import static ee.ria.DigiDoc.android.utils.TextUtil.getInvisibleElementTextView;
+import static ee.ria.DigiDoc.android.utils.ViewUtil.moveView;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class InvisibleView implements ContentView {
+
+ private static boolean hasInvisibleElementMoved = false;
+ private static RecyclerView.OnScrollListener recyclerViewOnScrollListener;
+
+ protected static void addInvisibleElementToObject(Context context, View view) {
+ if (view instanceof ViewGroup) {
+ ((ViewGroup) view).addView(getInvisibleElementTextView(context));
+ } else if (view != null && view.getParent() instanceof ViewGroup) {
+ ((ViewGroup) view.getParent()).addView(getInvisibleElementTextView(context));
+ }
+ }
+
+ protected static void addInvisibleElement(Context context, View mainLayout) {
+ if (mainLayout instanceof ViewGroup) {
+ ((ViewGroup) mainLayout).addView(getInvisibleElementTextView(context));
+ } else if (mainLayout != null && mainLayout.getParent() instanceof ViewGroup) {
+ ((ViewGroup) mainLayout.getParent()).addView(getInvisibleElementTextView(context));
+ }
+ }
+
+ protected static void addInvisibleElementScrollListener(RecyclerView recyclerView, View lastElementView) {
+ if (!hasInvisibleElementMoved) {
+ moveView(lastElementView);
+ hasInvisibleElementMoved = true;
+ }
+
+ recyclerView.addOnScrollListener(
+ recyclerViewOnScrollListener = new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+ super.onScrolled(recyclerView, dx, dy);
+
+ LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
+ int totalItemCount;
+ if (layoutManager != null) {
+ totalItemCount = layoutManager.getItemCount();
+
+ int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
+
+ if (lastVisibleItemPosition == totalItemCount - 1) {
+ lastElementView.setVisibility(VISIBLE);
+ } else {
+ lastElementView.setVisibility(GONE);
+ }
+ }
+ }
+ });
+ }
+
+ public static void removeInvisibleElementScrollListener(RecyclerView recyclerView) {
+ recyclerView.removeOnScrollListener(recyclerViewOnScrollListener);
+ hasInvisibleElementMoved = false;
+ }
+}
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/utils/navigator/Transaction.java b/app/src/main/java/ee/ria/DigiDoc/android/utils/navigator/Transaction.java
index 08eff3b7a..26a23b66f 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/utils/navigator/Transaction.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/utils/navigator/Transaction.java
@@ -1,13 +1,168 @@
package ee.ria.DigiDoc.android.utils.navigator;
+import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.Nullable;
+import com.bluelinelabs.conductor.Controller;
+import com.bluelinelabs.conductor.Router;
+import com.bluelinelabs.conductor.RouterTransaction;
import com.google.auto.value.AutoValue;
public abstract class Transaction {
+ public abstract void execute(Router router, Activity activity);
+
+ public static RootTransaction root(Screen screen) {
+ return new RootTransaction(screen);
+ }
+
+ public static PushTransaction push(Screen screen) {
+ return new PushTransaction(screen);
+ }
+
+ public static PopTransaction pop() {
+ return new PopTransaction();
+ }
+
+ public static ReplaceTransaction replace(Screen screen) {
+ return new ReplaceTransaction(screen);
+ }
+
+ public static ActivityTransaction activity(Intent intent, Bundle options) {
+ return new ActivityTransaction(intent, options);
+ }
+
+ public static ActivityForResultTransaction activityForResult(int requestCode, Intent intent,
+ @Nullable Bundle options) {
+ return new ActivityForResultTransaction(requestCode, intent, options);
+ }
+
+ private static RouterTransaction routerTransaction(Screen screen) {
+ return RouterTransaction.with((Controller) screen);
+ }
+
+ public static class RootTransaction extends Transaction {
+ private final Screen screen;
+
+ public RootTransaction(Screen screen) {
+ this.screen = screen;
+ }
+
+ public Screen getScreen() {
+ return screen;
+ }
+
+ @Override
+ public void execute(Router router, Activity activity) {
+ router.setRoot(routerTransaction(screen));
+ }
+ }
+
+ public static class PushTransaction extends Transaction {
+ private final Screen screen;
+
+ public PushTransaction(Screen screen) {
+ this.screen = screen;
+ }
+
+ public Screen getScreen() {
+ return screen;
+ }
+
+ @Override
+ public void execute(Router router, Activity activity) {
+ router.pushController(routerTransaction(screen));
+ }
+ }
+
+ public static class PopTransaction extends Transaction {
+ @Override
+ public void execute(Router router, Activity activity) {
+ if (activity != null) {
+ activity.onBackPressed();
+ }
+ }
+ }
+
+ public static class ReplaceTransaction extends Transaction {
+ private final Screen screen;
+
+ public ReplaceTransaction(Screen screen) {
+ this.screen = screen;
+ }
+
+ public Screen getScreen() {
+ return screen;
+ }
+
+ @Override
+ public void execute(Router router, Activity activity) {
+ router.replaceTopController(routerTransaction(screen));
+ }
+ }
+
+ public static class ActivityTransaction extends Transaction {
+ private final Intent intent;
+ @Nullable private final Bundle options;
+
+ public ActivityTransaction(Intent intent, @Nullable Bundle options) {
+ this.intent = intent;
+ this.options = options;
+ }
+
+ public Intent getIntent() {
+ return intent;
+ }
+
+ @Nullable
+ public Bundle getOptions() {
+ return options;
+ }
+
+ @Override
+ public void execute(Router router, Activity activity) {
+ activity.startActivity(intent, options);
+ }
+ }
+
+ public static class ActivityForResultTransaction extends Transaction {
+ private final int requestCode;
+ private final Intent intent;
+ @Nullable private final Bundle options;
+
+ public ActivityForResultTransaction(int requestCode, Intent intent,
+ @Nullable Bundle options) {
+ this.requestCode = requestCode;
+ this.intent = intent;
+ this.options = options;
+ }
+
+ public int getRequestCode() {
+ return requestCode;
+ }
+
+ public Intent getIntent() {
+ return intent;
+ }
+
+ @Nullable
+ public Bundle getOptions() {
+ return options;
+ }
+
+ @Override
+ public void execute(Router router, Activity activity) {
+ activity.startActivityForResult(intent, requestCode, options);
+ }
+ }
+}
+
+
+
+/*public abstract class Transaction {
+
public static RootTransaction root(Screen screen) {
return RootTransaction.create(screen);
}
@@ -98,4 +253,4 @@ static ActivityForResultTransaction create(int requestCode, Intent intent,
options);
}
}
-}
+}*/
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/utils/navigator/conductor/ConductorNavigator.java b/app/src/main/java/ee/ria/DigiDoc/android/utils/navigator/conductor/ConductorNavigator.java
index f416e809b..9111fa654 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/utils/navigator/conductor/ConductorNavigator.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/utils/navigator/conductor/ConductorNavigator.java
@@ -127,32 +127,7 @@ public void clearViewModel(String screenId) {
@Override
public void execute(Transaction transaction) {
Timber.log(Log.DEBUG, "Execute: %s", transaction);
- if (transaction instanceof Transaction.RootTransaction) {
- router.setRoot(routerTransaction(((Transaction.RootTransaction) transaction).screen()));
- } else if (transaction instanceof Transaction.PushTransaction) {
- router.pushController(routerTransaction(
- ((Transaction.PushTransaction) transaction).screen()));
- } else if (transaction instanceof Transaction.PopTransaction) {
- Activity activity = router.getActivity();
- if (activity != null) {
- activity.onBackPressed();
- }
- } else if (transaction instanceof Transaction.ReplaceTransaction) {
- router.replaceTopController(routerTransaction(
- ((Transaction.ReplaceTransaction) transaction).screen()));
- } else if (transaction instanceof Transaction.ActivityTransaction) {
- Transaction.ActivityTransaction activityTransaction =
- (Transaction.ActivityTransaction) transaction;
- activity().startActivity(activityTransaction.intent(), activityTransaction.options());
- } else if (transaction instanceof Transaction.ActivityForResultTransaction) {
- Transaction.ActivityForResultTransaction activityForResultTransaction =
- (Transaction.ActivityForResultTransaction) transaction;
- activity().startActivityForResult(activityForResultTransaction.intent(),
- activityForResultTransaction.requestCode(),
- activityForResultTransaction.options());
- } else {
- throw new IllegalArgumentException("Unknown transaction " + transaction);
- }
+ transaction.execute(router, activity());
}
@Override
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/utils/validator/PersonalCodeValidator.java b/app/src/main/java/ee/ria/DigiDoc/android/utils/validator/PersonalCodeValidator.java
index 7753ceecd..250dc3063 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/utils/validator/PersonalCodeValidator.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/utils/validator/PersonalCodeValidator.java
@@ -65,7 +65,12 @@ private static boolean isChecksumValid(String personalCode) {
int pos2 = 3;
while (i < 10) {
- int personalCodeNumber = Integer.parseInt(personalCode.substring(i, i + 1));
+ int personalCodeNumber = 0;
+ try {
+ personalCodeNumber = Integer.parseInt(personalCode.substring(i, i + 1));
+ } catch (NumberFormatException nfe) {
+ Timber.log(Log.ERROR, nfe, "Unable to parse personal code number");
+ }
sum1 += personalCodeNumber * pos1;
sum2 += personalCodeNumber * pos2;
pos1 = pos1 == 9 ? 1 : pos1 + 1;
diff --git a/app/src/main/java/ee/ria/DigiDoc/android/utils/widget/ConfirmationDialog.java b/app/src/main/java/ee/ria/DigiDoc/android/utils/widget/ConfirmationDialog.java
index 0e08d3a05..ad4c687ba 100644
--- a/app/src/main/java/ee/ria/DigiDoc/android/utils/widget/ConfirmationDialog.java
+++ b/app/src/main/java/ee/ria/DigiDoc/android/utils/widget/ConfirmationDialog.java
@@ -14,6 +14,7 @@
import ee.ria.DigiDoc.R;
import ee.ria.DigiDoc.android.Constants;
import ee.ria.DigiDoc.android.utils.SecureUtil;
+import ee.ria.DigiDoc.android.utils.navigator.ContentView;
import ee.ria.DigiDoc.android.utils.rxbinding.app.RxDialog;
import ee.ria.DigiDoc.sign.utils.UrlMessage;
@@ -21,7 +22,7 @@
import io.reactivex.rxjava3.subjects.PublishSubject;
import io.reactivex.rxjava3.subjects.Subject;
-public final class ConfirmationDialog extends AlertDialog implements
+public final class ConfirmationDialog extends AlertDialog implements ContentView,
DialogInterface.OnClickListener {
private final Subject buttonClicksSubject = PublishSubject.create();
diff --git a/app/src/main/res/drawable/ic_icon_access_padded.xml b/app/src/main/res/drawable/ic_icon_access_padded.xml
new file mode 100644
index 000000000..f71d479cf
--- /dev/null
+++ b/app/src/main/res/drawable/ic_icon_access_padded.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/crypto_create_decrypt_dialog.xml b/app/src/main/res/layout/crypto_create_decrypt_dialog.xml
index ede266401..40f5addf1 100644
--- a/app/src/main/res/layout/crypto_create_decrypt_dialog.xml
+++ b/app/src/main/res/layout/crypto_create_decrypt_dialog.xml
@@ -69,7 +69,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
- android:minHeight="@dimen/material_baseline_grid_6x"
+ android:minHeight="@dimen/material_baseline_grid_2x"
app:errorEnabled="true"
app:expandedHintEnabled="false">
@@ -77,12 +77,10 @@
android:id="@id/cryptoCreateDecryptPin1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
android:inputType="numberPassword"
- android:digits="0123456789"
- android:imeOptions="actionDone"
- android:maxLines="1"
android:maxLength="12"
- android:minHeight="@dimen/material_baseline_grid_6x" />
+ android:minHeight="@dimen/material_baseline_grid_4x" />
diff --git a/app/src/main/res/layout/crypto_create_list_item_add_button.xml b/app/src/main/res/layout/crypto_create_list_item_add_button.xml
index b5cc66841..f5d29bbcc 100644
--- a/app/src/main/res/layout/crypto_create_list_item_add_button.xml
+++ b/app/src/main/res/layout/crypto_create_list_item_add_button.xml
@@ -2,7 +2,7 @@
+
+
+
+
diff --git a/app/src/main/res/layout/crypto_home.xml b/app/src/main/res/layout/crypto_home.xml
index 15b439559..0270943ac 100644
--- a/app/src/main/res/layout/crypto_home.xml
+++ b/app/src/main/res/layout/crypto_home.xml
@@ -39,12 +39,13 @@
android:textAppearance="@style/TextAppearance.Application.Value"
android:gravity="center_horizontal"
android:text="@string/crypto_home_create_text"
+ android:textSize="@dimen/material_subheader_text_size"
android:importantForAccessibility="no" />
diff --git a/app/src/main/res/layout/crypto_list_item_recipient.xml b/app/src/main/res/layout/crypto_list_item_recipient.xml
index f90eb0440..9e920ca1b 100644
--- a/app/src/main/res/layout/crypto_list_item_recipient.xml
+++ b/app/src/main/res/layout/crypto_list_item_recipient.xml
@@ -47,7 +47,7 @@
style="@style/Widget.Application.Button.Borderless.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/material_list_icon_last_position_margin_horizontal"
+ android:layout_marginEnd="@dimen/material_button_padding_horizontal"
android:src="@drawable/ic_icon_remove"
android:tint="@color/error"
android:contentDescription="@string/crypto_recipient_remove_button" />
diff --git a/app/src/main/res/layout/eid_home.xml b/app/src/main/res/layout/eid_home.xml
index 8af757b6c..50d1fec1c 100644
--- a/app/src/main/res/layout/eid_home.xml
+++ b/app/src/main/res/layout/eid_home.xml
@@ -35,13 +35,29 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
-
+ android:textSize="@dimen/material_subheader_text_size"
+ android:gravity="center">
+
+
+
+
+
diff --git a/app/src/main/res/layout/main_about_screen.xml b/app/src/main/res/layout/main_about_screen.xml
index fe2d66189..479dbfcb7 100644
--- a/app/src/main/res/layout/main_about_screen.xml
+++ b/app/src/main/res/layout/main_about_screen.xml
@@ -22,10 +22,25 @@
-
+ android:fillViewport="true"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior">
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/main_settings.xml b/app/src/main/res/layout/main_settings.xml
index 977eff8e1..579e74e8f 100644
--- a/app/src/main/res/layout/main_settings.xml
+++ b/app/src/main/res/layout/main_settings.xml
@@ -37,75 +37,33 @@
android:layout_height="wrap_content"
android:orientation="vertical">
-
+ android:orientation="horizontal"
+ android:layout_marginTop="@dimen/material_baseline_grid_2.5x"
+ android:layout_marginBottom="@dimen/material_baseline_grid_2.5x">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:gravity="center_vertical"
+ android:contentDescription="@string/main_settings_access_button"
+ android:text="@string/main_settings_access_button" />
+
diff --git a/app/src/main/res/layout/main_settings_access.xml b/app/src/main/res/layout/main_settings_access.xml
new file mode 100644
index 000000000..e9d06f0a4
--- /dev/null
+++ b/app/src/main/res/layout/main_settings_access.xml
@@ -0,0 +1,174 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/signature_detail_screen.xml b/app/src/main/res/layout/signature_detail_screen.xml
index 7c72f46ad..dc1a3351b 100644
--- a/app/src/main/res/layout/signature_detail_screen.xml
+++ b/app/src/main/res/layout/signature_detail_screen.xml
@@ -37,6 +37,68 @@
android:orientation="vertical"
android:layout_marginBottom="@dimen/material_baseline_grid_2.5x">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/signature_list_screen.xml b/app/src/main/res/layout/signature_list_screen.xml
index ea91e92a1..63e09c584 100644
--- a/app/src/main/res/layout/signature_list_screen.xml
+++ b/app/src/main/res/layout/signature_list_screen.xml
@@ -23,13 +23,25 @@
-
+ android:orientation="vertical">
+
+
+
+
+
diff --git a/app/src/main/res/layout/signature_update.xml b/app/src/main/res/layout/signature_update.xml
index e29de276a..583f13b84 100644
--- a/app/src/main/res/layout/signature_update.xml
+++ b/app/src/main/res/layout/signature_update.xml
@@ -103,7 +103,6 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintWidth="115dp"
- android:autoSizeTextType="uniform"
android:maxLines="1"
android:text="@string/cancel_button"
android:backgroundTint="@color/error"
@@ -177,7 +176,6 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintWidth="115dp"
- android:autoSizeTextType="uniform"
android:maxLines="1"
android:text="@string/cancel_button"
android:backgroundTint="@color/error"
diff --git a/app/src/main/res/layout/signature_update_id_card.xml b/app/src/main/res/layout/signature_update_id_card.xml
index 4276ee894..ee36b5c83 100644
--- a/app/src/main/res/layout/signature_update_id_card.xml
+++ b/app/src/main/res/layout/signature_update_id_card.xml
@@ -66,7 +66,7 @@
android:gravity="center_horizontal"
android:text="@string/signature_update_id_card_sign_pin2"
android:textColor="@color/signingNavigation"
- android:minHeight="@dimen/material_baseline_grid_6x"
+ android:minHeight="@dimen/material_baseline_grid_2x"
android:importantForAccessibility="no"
android:labelFor="@id/signatureUpdateIdCardSignPin2" />
@@ -76,7 +76,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
- android:minHeight="@dimen/material_baseline_grid_6x"
+ android:minHeight="@dimen/material_baseline_grid_2x"
app:errorEnabled="true"
app:expandedHintEnabled="false">
@@ -84,12 +84,10 @@
android:id="@id/signatureUpdateIdCardSignPin2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
android:inputType="numberPassword"
- android:digits="0123456789"
- android:imeOptions="actionDone"
- android:maxLines="1"
android:maxLength="12"
- android:minHeight="@dimen/material_baseline_grid_6x" />
+ android:minHeight="@dimen/material_baseline_grid_4x" />
@@ -103,6 +101,33 @@
android:contentDescription="@string/signature_update_id_card_sign_pin2_invalid_accessibility"
android:visibility="gone" />
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/signature_update_list_item_documents_add_button.xml b/app/src/main/res/layout/signature_update_list_item_documents_add_button.xml
index ba9bb3ac8..38633ea65 100644
--- a/app/src/main/res/layout/signature_update_list_item_documents_add_button.xml
+++ b/app/src/main/res/layout/signature_update_list_item_documents_add_button.xml
@@ -2,7 +2,7 @@
diff --git a/app/src/main/res/layout/signature_update_mobile_id.xml b/app/src/main/res/layout/signature_update_mobile_id.xml
index 534167bd7..d78198eba 100644
--- a/app/src/main/res/layout/signature_update_mobile_id.xml
+++ b/app/src/main/res/layout/signature_update_mobile_id.xml
@@ -92,6 +92,32 @@
android:textColor="@color/textLabel"
android:text="@string/signature_update_mobile_id_remember_me" />
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/signature_update_smart_id.xml b/app/src/main/res/layout/signature_update_smart_id.xml
index 14e4c56a9..5ce10a39f 100644
--- a/app/src/main/res/layout/signature_update_smart_id.xml
+++ b/app/src/main/res/layout/signature_update_smart_id.xml
@@ -91,6 +91,32 @@
android:textColor="@color/textLabel"
android:text="@string/signature_update_mobile_id_remember_me" />
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml
index f7be5b05a..fc40e30fd 100644
--- a/app/src/main/res/values-et/strings.xml
+++ b/app/src/main/res/values-et/strings.xml
@@ -43,6 +43,7 @@
Seaded
+ Ligipääs
Riigikood ja telefoninumber
Isikukood
Ligipääs ajatempliteenusele
@@ -56,6 +57,11 @@
Vaikimisi faili tüüp
Luba rakendusel avada kõiki failitüüpe (vajab rakenduse taaskäivitamist)
Luba ekraanipildistamine (vajab rakenduse taaskäivitamist)
+ Ajatempliteenuse SSL sertifikaat
+ Kellele väljastatud:
+ Kehtib kuni:
+ LISA SERTIFIKAAT
+ NÄITA SERTIFIKAATI
@@ -265,6 +271,15 @@
%1$s-kood peab olema vähemalt %2$s-kohaline
+ Hoiatused
+ See allkiri või allkirjastatud fail on vigane. Allkiri ei ole kehtiv.
+ Allkiri on tehniliselt korrektne, kuid põhineb praeguseks nõrgaks hinnatud räsialgoritmil SHA-1, mistõttu ei ole see kaitstud võltsimise või muutmise eest.
+ Allkiri on kehtiv, aga ümbrikul on mingi eripära. Tavaliselt on selline eripära tekkinud ümbriku loomisel. Kuna ümbrikku ei ole võimalik muuta ilma, et allkiri kehtetuks muutuks, näidatakse selliste ümbrike puhul hoiatust.\nLisainfo: https://www.id.ee/artikkel/digitaalne-allkirjastamine-ja-elektroonilised-allkirjad
+ See e-allkiri ei ole käsitsi kirjutatud allkirjadega samaväärne ja seetõttu saab seda kasutada ainult tehingute puhul, kus kvalifitseeritud e-allkiri pole kohustuslik.
+ Allkirja staatust kuvatakse \"teadmata\", kui Sinu arvutisse pole lisatud kõiki kehtivuskinnituse teenuse sertifikaate ja/või nende kontrolliks vajalikke sertifikaate.\nLisainfo: https://www.id.ee/artikkel/digidoc4-klient-viga-ocsp-responder-is-not-trusted-no-certificate-for-this-responder-in-local-certstore
+ TEHNILINE INFORMATSIOON
+ tehniline informatsioon
+
Allkirja detailid
Allkirjastaja sertifikaadi väljaandja
Allkirjastaja sertifikaat
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 30b1c6097..b7c3c7d78 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -43,6 +43,7 @@
Настройки
+ Доступ
Код страны и телефонный номер
Личный код
Доступ к услугам отметки времени
@@ -56,6 +57,11 @@
Формат файла по умолчанию
Разрешить приложению открывать все типы файлов (потребуется перезапуск приложения)
Разрешить скриншоты (потребуется перезапуск приложения)
+ SSL сертификат службы отметок времени
+ Выдан:
+ Действительно до:
+ ДОБАВИТЬ СЕРТИФИКАТ
+ ПОКАЗАТЬ СЕРТИФИКАТ
Информация
@@ -267,6 +273,15 @@
%1$s-код должен быть не менее %2$s символов
+ Предупреждения
+ Повреждена подпись или подписанный файл. Данная подпись недействительна.
+ Подпись технически верна, но так как она основана на слабом на данный момент алгоритме хеширования SHA-1, то не защищена от подделки или изменения.
+ Подпись действительна, но у контейнера имеется какая-то особенность. Чаще всего, такая особенность возникает при изготовлении контейнеров случайно. Однако поскольку контейнер невозможно изменить без потери действительности подписи, то отображается предупреждение.\nДополнительная информация: https://www.id.ee/ru/artikkel/czifrovoe-podpisanie-i-elektronnye-podpisi/
+ Эта электронная подпись не эквивалентна рукописной подписи и поэтому может использоваться только в тех случаях, когда не требуется квалифицированная электронная подпись.
+ Статус подписи отображается как \"неизвестный\", если на компьютере не установлены необходимые для проверки подписи сертификаты сертифицирующей организации и/или услуги подтверждения действительности.\nДополнительная информация: https://www.id.ee/ru/artikkel/digidoc4-klient-oshibka-ocsp-responder-is-not-trusted-no-certificate-for-this-responder-in-local-certstore
+ ТЕХНИЧЕСКАЯ ИНФОРМАЦИЯ
+ техническая информация
+
Детали подписи
Организация, выдавшая сертификат подписавшего
Сертификат пользователя
diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml
index ca34cad88..aa59bbc13 100644
--- a/app/src/main/res/values/donottranslate.xml
+++ b/app/src/main/res/values/donottranslate.xml
@@ -5,8 +5,11 @@
ee.ria.digidoc.fileprovider
+ Invisible element
+ lastInvisibleElement
+
Eesti keel
- Русский язык
+ Русский
English
signature_add_method
@@ -23,6 +26,7 @@
mainSettingsPostalCode
mainSettingsOpenAllFilesTypes
mainSettingsAllowScreenshots
+ mainSettingsTSACertView
372XXXXXXXX
____
diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml
index 1b4453057..b4ffa6c7f 100644
--- a/app/src/main/res/values/ids.xml
+++ b/app/src/main/res/values/ids.xml
@@ -9,6 +9,7 @@
+
@@ -65,13 +66,23 @@
+
+
+
+
+
+
+
+
+
+
@@ -112,12 +123,21 @@
+
+
+
+
+
+
+
+
+
@@ -255,6 +275,8 @@
+
+
@@ -264,6 +286,8 @@
+
+
@@ -274,6 +298,8 @@
+
+
@@ -292,8 +318,10 @@
+
-
+
+
@@ -332,6 +360,7 @@
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0a7cd12ff..be4a17711 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -43,6 +43,7 @@
Settings
+ Access
Country code and phone number
Personal code
Access to Time-Stamping service
@@ -56,6 +57,11 @@
Default file type
Allow app to use all file types (requires app restart)
Allow screenshots (requires app restart)
+ Time-Stamping service SSL certificate
+ Issued to:
+ Valid to:
+ ADD CERTIFICATE
+ SHOW CERTIFICATE
Info
@@ -265,6 +271,15 @@
%1$s code must be at least %2$s digits long
+ Warnings
+ This is an invalid signature or malformed digitally signed file. The signature is not valid.
+ The signature is technically correct, but it is based on the currently weak hash algorithm SHA-1, therefore it is not protected against forgery or alteration.
+ The signature is valid, but the container has a specific feature. Usually, this feature has arisen accidentally when containers were created. However, as it is not possible to edit a container without invalidating the signature, a warning is displayed.\nAdditional information: https://www.id.ee/en/article/digital-signing-and-electronic-signatures/
+ This e-Signature is not equivalent with handwritten signature and therefore can be used only in transactions where Qualified e-Signature is not required.
+ Signature status is displayed \"unknown\" if you don\'t have all validity confirmation service certificates and/or certificate authority certificates installed into your computer.\nAdditional information: https://www.id.ee/en/article/digidoc4-client-error-ocsp-responder-is-not-trusted-no-certificate-for-this-responder-in-local-certstore
+ TECHNICAL INFORMATION
+ technical information
+
Signature details
Signer\'s Certificate issuer:
Signer\'s Certificate:
diff --git a/app/src/main/res/xml/main_settings.xml b/app/src/main/res/xml/main_settings_access.xml
similarity index 95%
rename from app/src/main/res/xml/main_settings.xml
rename to app/src/main/res/xml/main_settings_access.xml
index 36c8c62f7..e216e80d5 100644
--- a/app/src/main/res/xml/main_settings.xml
+++ b/app/src/main/res/xml/main_settings_access.xml
@@ -20,7 +20,7 @@
android:maxLength="11"/>
-->
-
-
main_diagnostics_logging
main_diagnostics_logging_running
+ main_settings_tsa_cert
\ No newline at end of file
diff --git a/configuration-lib/build.gradle b/configuration-lib/build.gradle
index d9a6467bb..9f0833931 100644
--- a/configuration-lib/build.gradle
+++ b/configuration-lib/build.gradle
@@ -12,8 +12,8 @@ android {
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_11
- targetCompatibility JavaVersion.VERSION_11
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
}
testOptions {
diff --git a/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/ConfigurationManagerService.java b/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/ConfigurationManagerService.java
index a7b252d1f..d43ea598a 100644
--- a/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/ConfigurationManagerService.java
+++ b/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/ConfigurationManagerService.java
@@ -2,6 +2,8 @@
import android.content.Context;
import android.content.Intent;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.os.ResultReceiver;
import androidx.annotation.NonNull;
@@ -33,9 +35,17 @@ public void onCreate() {
configurationManager = new ConfigurationManager(this, configurationProperties, cachedConfigurationHandler, UserAgentUtil.getUserAgent(getApplicationContext()));
}
+ private ResultReceiver getConfigurationResultReceiver(Intent intent) {
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
+ return intent.getParcelableExtra(ConfigurationConstants.CONFIGURATION_RESULT_RECEIVER, ResultReceiver.class);
+ } else {
+ return intent.getParcelableExtra(ConfigurationConstants.CONFIGURATION_RESULT_RECEIVER);
+ }
+ }
+
@Override
protected void onHandleWork(@NonNull Intent intent) {
- ResultReceiver confResultReceiver = intent.getParcelableExtra(ConfigurationConstants.CONFIGURATION_RESULT_RECEIVER);
+ ResultReceiver confResultReceiver = getConfigurationResultReceiver(intent);
Bundle bundle = new Bundle();
ConfigurationProvider configurationProvider = getConfiguration(intent);
diff --git a/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/ConfigurationParser.java b/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/ConfigurationParser.java
index 6c3383116..bc1727ed3 100644
--- a/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/ConfigurationParser.java
+++ b/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/ConfigurationParser.java
@@ -1,5 +1,7 @@
package ee.ria.DigiDoc.configuration;
+import android.util.Log;
+
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
@@ -11,6 +13,8 @@
import java.util.List;
import java.util.Map;
+import timber.log.Timber;
+
/**
* Helper class for parsing values from configuration json.
*/
@@ -43,7 +47,12 @@ Map parseStringValuesToMap(String... parameterNames) {
}
public int parseIntValue(String... parameterNames) {
- return Integer.parseInt((String) parseValue(parameterNames));
+ try {
+ return Integer.parseInt((String) parseValue(parameterNames));
+ } catch (NumberFormatException nfe) {
+ Timber.log(Log.ERROR, nfe, "Unable to parse value");
+ throw new IllegalArgumentException("Unable to parse value");
+ }
}
private Object parseValue(String... parameterNames) {
diff --git a/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/ConfigurationProperties.java b/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/ConfigurationProperties.java
index b69ef1eac..09acc4266 100644
--- a/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/ConfigurationProperties.java
+++ b/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/ConfigurationProperties.java
@@ -1,6 +1,9 @@
package ee.ria.DigiDoc.configuration;
import android.content.res.AssetManager;
+import android.util.Log;
+
+import org.bouncycastle.cert.dane.DANEEntryFactory;
import java.io.IOException;
import java.io.InputStream;
@@ -9,6 +12,8 @@
import java.util.Date;
import java.util.Properties;
+import timber.log.Timber;
+
public class ConfigurationProperties {
public static final String CENTRAL_CONFIGURATION_SERVICE_URL_PROPERTY = "central-configuration-service.url";
@@ -16,6 +21,7 @@ public class ConfigurationProperties {
public static final String CONFIGURATION_VERSION_SERIAL_PROPERTY = "configuration.version-serial";
public static final String CONFIGURATION_DOWNLOAD_DATE_PROPERTY = "configuration.download-date";
public static final String PROPERTIES_FILE_NAME = "configuration.properties";
+ private static final int DEFAULT_UPDATE_INTERVAL = 4;
private final SimpleDateFormat dateFormat;
private Properties properties;
@@ -37,7 +43,12 @@ String getCentralConfigurationServiceUrl() {
}
int getConfigurationUpdateInterval() {
- return Integer.parseInt(properties.getProperty(CONFIGURATION_UPDATE_INTERVAL_PROPERTY));
+ try {
+ return Integer.parseInt(properties.getProperty(CONFIGURATION_UPDATE_INTERVAL_PROPERTY));
+ } catch (NumberFormatException nfe) {
+ Timber.log(Log.ERROR, nfe, "Unable to get configuration update interval");
+ return DEFAULT_UPDATE_INTERVAL;
+ }
}
Date getPackagedConfigurationInitialDownloadDate() {
diff --git a/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/loader/CachedConfigurationHandler.java b/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/loader/CachedConfigurationHandler.java
index 944e900cd..00e647de8 100644
--- a/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/loader/CachedConfigurationHandler.java
+++ b/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/loader/CachedConfigurationHandler.java
@@ -46,11 +46,16 @@ public CachedConfigurationHandler(File cacheDir) {
}
public Integer getConfigurationVersionSerial() {
- String versionSerial = loadProperty(CONFIGURATION_VERSION_SERIAL_PROPERTY_NAME);
- if (versionSerial == null) {
+ try {
+ String versionSerial = loadProperty(CONFIGURATION_VERSION_SERIAL_PROPERTY_NAME);
+ if (versionSerial == null) {
+ return null;
+ }
+ return Integer.parseInt(versionSerial);
+ } catch (NumberFormatException nfe) {
+ Timber.log(Log.ERROR, nfe, "Unable to get configuration version serial");
return null;
}
- return Integer.parseInt(versionSerial);
}
public Date getConfLastUpdateCheckDate() {
diff --git a/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/task/FetchAndPackageDefaultConfigurationTask.java b/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/task/FetchAndPackageDefaultConfigurationTask.java
index 75f40f89a..523f9c127 100644
--- a/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/task/FetchAndPackageDefaultConfigurationTask.java
+++ b/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/task/FetchAndPackageDefaultConfigurationTask.java
@@ -1,5 +1,7 @@
package ee.ria.DigiDoc.configuration.task;
+import android.util.Log;
+
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -14,6 +16,7 @@
import ee.ria.DigiDoc.configuration.loader.DefaultConfigurationLoader;
import ee.ria.DigiDoc.configuration.util.FileUtils;
import ee.ria.DigiDoc.configuration.verify.ConfigurationSignatureVerifier;
+import timber.log.Timber;
/**
* Task for loading configuration from central configuration service and storing it to assets folder.
@@ -34,6 +37,7 @@ public class FetchAndPackageDefaultConfigurationTask {
private static final String DEFAULT_CONFIGURATION_PROPERTIES_FILE_NAME = "default-configuration.properties";
private static Properties properties = new Properties();
private static String buildVariant;
+ private static final int DEFAULT_UPDATE_INTERVAL = 4;
public static void main(String[] args) {
loadResourcesProperties();
@@ -75,10 +79,15 @@ private static void loadResourcesProperties() {
}
private static int determineConfigurationUpdateInterval(String[] args) {
- if (args.length > 1) {
- return Integer.parseInt(args[1]);
- } else {
- return Integer.parseInt(properties.getProperty(ConfigurationProperties.CONFIGURATION_UPDATE_INTERVAL_PROPERTY));
+ try {
+ if (args.length > 1) {
+ return Integer.parseInt(args[1]);
+ } else {
+ return Integer.parseInt(properties.getProperty(ConfigurationProperties.CONFIGURATION_UPDATE_INTERVAL_PROPERTY));
+ }
+ } catch (NumberFormatException nfe) {
+ Timber.log(Log.ERROR, nfe, "Unable to determine configuration update interval");
+ return DEFAULT_UPDATE_INTERVAL;
}
}
diff --git a/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/util/UserAgentUtil.java b/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/util/UserAgentUtil.java
index d667afec6..9747470c5 100644
--- a/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/util/UserAgentUtil.java
+++ b/configuration-lib/src/main/java/ee/ria/DigiDoc/configuration/util/UserAgentUtil.java
@@ -1,6 +1,7 @@
package ee.ria.DigiDoc.configuration.util;
import android.content.Context;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
@@ -68,11 +69,9 @@ private static List getConnectedUsbs(Context context) {
private static StringBuilder getAppVersion(Context context) {
StringBuilder versionName = new StringBuilder();
try {
- versionName.append(context.getPackageManager()
- .getPackageInfo(context.getPackageName(), 0).versionName)
+ versionName.append(getPackageInfo(context).getLongVersionCode())
.append(".")
- .append(context.getPackageManager()
- .getPackageInfo(context.getPackageName(), 0).versionCode);
+ .append(getPackageInfo(context).getLongVersionCode());
} catch (PackageManager.NameNotFoundException e) {
Timber.log(Log.ERROR, e, "Failed getting app version from package info");
}
@@ -80,4 +79,14 @@ private static StringBuilder getAppVersion(Context context) {
return versionName;
}
+ private static PackageInfo getPackageInfo(Context context) throws PackageManager.NameNotFoundException {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ return context.getPackageManager()
+ .getPackageInfo(context.getPackageName(), PackageManager.PackageInfoFlags.of(0));
+ } else {
+ return context.getPackageManager()
+ .getPackageInfo(context.getPackageName(), 0);
+ }
+ }
+
}
diff --git a/crypto-lib/build.gradle b/crypto-lib/build.gradle
index ef0f62842..fd7d4a32d 100644
--- a/crypto-lib/build.gradle
+++ b/crypto-lib/build.gradle
@@ -10,8 +10,8 @@ android {
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_11
- targetCompatibility JavaVersion.VERSION_11
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
}
testOptions {
diff --git a/gradle.properties b/gradle.properties
index 741ec32e6..fabf099ac 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,11 +1,11 @@
appVersionCode=44
appVersionName=2.6.0
-appAbiFilters=arm64-v8a;armeabi-v7a;x86;x86_64
+appAbiFilters=arm64-v8a;armeabi-v7a;x86_64
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
android.useAndroidX=true
android.enableJetifier=true
-android.jetifier.ignorelist=bcprov-jdk15on
+android.jetifier.ignorelist=bcprov-jdk15on,jackson-core
android.nonTransitiveRClass=false
android.nonFinalResIds=false
diff --git a/id-card-lib/build.gradle b/id-card-lib/build.gradle
index 1bcf138a1..8471a36fa 100644
--- a/id-card-lib/build.gradle
+++ b/id-card-lib/build.gradle
@@ -10,8 +10,8 @@ android {
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_11
- targetCompatibility JavaVersion.VERSION_11
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
}
lint {
diff --git a/id-card-lib/src/main/java/ee/ria/DigiDoc/idcard/CodeVerificationException.java b/id-card-lib/src/main/java/ee/ria/DigiDoc/idcard/CodeVerificationException.java
index d5efc0dda..ec149ec4c 100644
--- a/id-card-lib/src/main/java/ee/ria/DigiDoc/idcard/CodeVerificationException.java
+++ b/id-card-lib/src/main/java/ee/ria/DigiDoc/idcard/CodeVerificationException.java
@@ -19,12 +19,34 @@
package ee.ria.DigiDoc.idcard;
+import android.content.Context;
+
+import ee.ria.DigiDoc.common.exception.SignatureUpdateError;
+
/**
* PIN1/PIN2/PUK code verification failed.
*/
-public class CodeVerificationException extends IdCardException {
+public class CodeVerificationException extends IdCardException implements SignatureUpdateError {
+
+ private CodeType type;
+ private String message;
CodeVerificationException(CodeType type) {
super(type + " verification failed");
}
+
+ public CodeVerificationException(CodeType type, String message) {
+ super(type + " verification failed");
+ this.type = type;
+ this.message = message;
+ }
+
+ public CodeType getType() {
+ return type;
+ }
+
+ @Override
+ public String getMessage(Context context) {
+ return message;
+ }
}
diff --git a/id-card-lib/src/main/java/ee/ria/DigiDoc/idcard/EstEIDToken.java b/id-card-lib/src/main/java/ee/ria/DigiDoc/idcard/EstEIDToken.java
index 04d4a6b45..523393f60 100644
--- a/id-card-lib/src/main/java/ee/ria/DigiDoc/idcard/EstEIDToken.java
+++ b/id-card-lib/src/main/java/ee/ria/DigiDoc/idcard/EstEIDToken.java
@@ -149,7 +149,7 @@ public byte[] certificate(CertificateType type) throws SmartCardReaderException
}
stream.write(reader.transmit(0x00, 0xB0, i, 0x00, null, remaining));
return stream.toByteArray();
- } catch (IOException e) {
+ } catch (IOException | NumberFormatException e) {
throw new SmartCardReaderException(e);
}
}
diff --git a/mobile-id-lib/build.gradle b/mobile-id-lib/build.gradle
index 4a39ed6b7..a12a73e25 100644
--- a/mobile-id-lib/build.gradle
+++ b/mobile-id-lib/build.gradle
@@ -10,8 +10,8 @@ android {
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_11
- targetCompatibility JavaVersion.VERSION_11
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
}
lint {
@@ -26,7 +26,9 @@ android {
dependencies {
implementation "androidx.annotation:annotation:${androidxVersion}"
implementation "androidx.appcompat:appcompat:${androidxAppCompatVersion}"
- implementation "androidx.localbroadcastmanager:localbroadcastmanager:${androidxLocalBroadcastManagerVersion}"
+ implementation "androidx.localbroadcastmanager:localbroadcastmanager:${androidxLocalBroadcastManagerVersion}"
+ implementation "androidx.work:work-runtime:${androidxWorkRuntime}"
+ implementation "androidx.preference:preference:${androidxPreference}"
implementation "com.jakewharton.timber:timber:${timberVersion}"
diff --git a/mobile-id-lib/src/main/java/ee/ria/DigiDoc/mobileid/service/MobileSignService.java b/mobile-id-lib/src/main/java/ee/ria/DigiDoc/mobileid/service/MobileSignService.java
index 00162d644..2746c253e 100644
--- a/mobile-id-lib/src/main/java/ee/ria/DigiDoc/mobileid/service/MobileSignService.java
+++ b/mobile-id-lib/src/main/java/ee/ria/DigiDoc/mobileid/service/MobileSignService.java
@@ -19,24 +19,35 @@
package ee.ria.DigiDoc.mobileid.service;
-import android.app.IntentService;
+import static ee.ria.DigiDoc.common.SigningUtil.checkSigningCancelled;
+import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.ACCESS_TOKEN_PASS;
+import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.ACCESS_TOKEN_PATH;
+import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.CERTIFICATE_CERT_BUNDLE;
+import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.CREATE_SIGNATURE_REQUEST;
+
+import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.preference.PreferenceManager;
+import androidx.work.Worker;
+import androidx.work.WorkerParameters;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
import org.bouncycastle.util.encoders.Base64;
-import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.reflect.Type;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
@@ -46,6 +57,7 @@
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
@@ -58,7 +70,6 @@
import ee.ria.DigiDoc.common.UUIDUtil;
import ee.ria.DigiDoc.common.VerificationCodeUtil;
import ee.ria.DigiDoc.common.exception.SigningCancelledException;
-import ee.ria.DigiDoc.mobileid.R;
import ee.ria.DigiDoc.mobileid.dto.MobileCertificateResultType;
import ee.ria.DigiDoc.mobileid.dto.request.GetMobileCreateSignatureSessionStatusRequest;
import ee.ria.DigiDoc.mobileid.dto.request.MobileCreateSignatureRequest;
@@ -76,13 +87,7 @@
import retrofit2.Response;
import timber.log.Timber;
-import static ee.ria.DigiDoc.common.SigningUtil.checkSigningCancelled;
-import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.ACCESS_TOKEN_PASS;
-import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.ACCESS_TOKEN_PATH;
-import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.CERTIFICATE_CERT_BUNDLE;
-import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.CREATE_SIGNATURE_REQUEST;
-
-public class MobileSignService extends IntentService {
+public class MobileSignService extends Worker {
private static final String PEM_BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
private static final String PEM_END_CERT = "-----END CERTIFICATE-----";
@@ -92,67 +97,87 @@ public class MobileSignService extends IntentService {
private static final long INITIAL_STATUS_REQUEST_DELAY_IN_MILLISECONDS = 1000;
private static final long SUBSEQUENT_STATUS_REQUEST_DELAY_IN_MILLISECONDS = 5 * 1000;
private static final long TIMEOUT_CANCEL = 120 * 1000;
+ private static boolean isCancelled = false;
private long timeout;
- private boolean isCancelled = false;
private ContainerWrapper containerWrapper;
private MIDRestServiceClient midRestServiceClient;
- public MobileSignService() {
- super(TAG);
+ private final String signatureRequest;
+ private final String accessTokenPath;
+ private final String accessTokenPass;
+ private final ArrayList certificateCertBundle;
+ private final CountDownLatch countDownLatch = new CountDownLatch(1);
+
+ public MobileSignService(@NonNull Context context, @NonNull WorkerParameters workerParameters) {
+ super(context, workerParameters);
Timber.tag(TAG);
+
+ isCancelled = false;
+
+ signatureRequest = workerParameters.getInputData().getString(CREATE_SIGNATURE_REQUEST);
+ accessTokenPath = workerParameters.getInputData().getString(ACCESS_TOKEN_PATH);
+ accessTokenPass = workerParameters.getInputData().getString(ACCESS_TOKEN_PASS);
+
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+ String certBundleList = sharedPreferences.getString(CERTIFICATE_CERT_BUNDLE, "");
+
+ Type arraylistType = new TypeToken>() {}.getType();
+ certificateCertBundle = new Gson().fromJson(certBundleList, arraylistType);
}
+ @NonNull
@Override
- protected void onHandleIntent(Intent intent) {
- Timber.log(Log.DEBUG, "Handling mobile sign intent");
+ public Result doWork() {
+ Timber.log(Log.DEBUG, "Handling mobile sign worker");
- TrustManager[] trustManagers = new TrustManager[0];
+ TrustManager[] trustManagers;
try {
trustManagers = TrustManagerUtil.getTrustManagers();
} catch (NoSuchAlgorithmException | KeyStoreException e) {
Timber.log(Log.ERROR, e, "Unable to get Trust Managers. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
+ return Result.failure();
}
timeout = 0;
- MobileCreateSignatureRequest request = getRequestFromIntent(intent);
+ MobileCreateSignatureRequest request = getRequestFromData(signatureRequest);
if (request != null) {
PostMobileCreateSignatureCertificateRequest certificateRequest = getCertificateRequest(request);
Timber.log(Log.DEBUG, "Certificate request: %s", certificateRequest.toString());
SSLContext restSSLConfig;
try {
Timber.log(Log.DEBUG, "Creating SSL config");
- restSSLConfig = createSSLConfig(intent, trustManagers);
+ restSSLConfig = createSSLConfig(accessTokenPath, accessTokenPass, trustManagers);
} catch (Exception e) {
Timber.log(Log.ERROR, e, "Can't create SSL config. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
restSSLConfig = null;
}
try {
- ArrayList certificateCertBundle = intent.getStringArrayListExtra(CERTIFICATE_CERT_BUNDLE);
if (certificateCertBundle != null) {
midRestServiceClient = ServiceGenerator.createService(MIDRestServiceClient.class,
restSSLConfig, request.getUrl(), certificateCertBundle, trustManagers, getApplicationContext());
} else {
Timber.log(Log.DEBUG, "Certificate cert bundle is null");
+ return Result.failure();
}
} catch (CertificateException | NoSuchAlgorithmException e) {
Timber.log(Log.ERROR, e, "Failed to sign with Mobile-ID. Invalid SSL handshake. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
broadcastFault(new RESTServiceFault(MobileCreateSignatureSessionStatusResponse.ProcessStatus.INVALID_SSL_HANDSHAKE));
- return;
+ return Result.failure();
}
if (isCountryCodeError(request.getPhoneNumber())) {
broadcastFault(new RESTServiceFault(MobileCreateSignatureSessionStatusResponse.ProcessStatus.INVALID_COUNTRY_CODE));
Timber.log(Log.DEBUG, "Failed to sign with Mobile-ID. Invalid country code");
- return;
+ return Result.failure();
}
if (!UUIDUtil.isValid(request.getRelyingPartyUUID())) {
broadcastFault(new RESTServiceFault(MobileCreateSignatureSessionStatusResponse.ProcessStatus.INVALID_ACCESS_RIGHTS));
Timber.log(Log.DEBUG, "Failed to sign with Mobile-ID. %s - Relying Party UUID not in valid format", request.getRelyingPartyUUID());
- return;
+ return Result.failure();
}
try {
@@ -165,15 +190,16 @@ protected void onHandleIntent(Intent intent) {
Timber.log(Log.DEBUG, "Mobile-ID certificate request unsuccessful. Status: %s, message: %s, body: %s, errorBody: %s",
responseWrapper.code(), responseWrapper.message(), responseWrapper.body(), responseWrapper.errorBody());
parseErrorAndBroadcast(responseWrapper);
+ return Result.failure();
} else {
MobileCreateSignatureCertificateResponse response = responseWrapper.body();
if (response == null) {
Timber.log(Log.DEBUG, "Mobile-ID signature certificate response is null");
- return;
+ return Result.failure();
}
Timber.log(Log.DEBUG, "MobileCreateSignatureCertificateResponse response body: %s", response.toString());
if (isResponseError(responseWrapper, response, MobileCreateSignatureCertificateResponse.class)) {
- return;
+ return Result.failure();
}
containerWrapper = new ContainerWrapper(request.getContainerPath());
String base64Hash = containerWrapper.prepareSignature(getCertificatePem(response.getCert()));
@@ -184,53 +210,75 @@ protected void onHandleIntent(Intent intent) {
String sessionId = getMobileIdSession(base64Hash, request);
if (sessionId == null) {
Timber.log(Log.DEBUG, "Session ID missing");
- return;
+ return Result.failure();
}
Timber.log(Log.DEBUG, "Session ID: %s", sessionId);
doCreateSignatureStatusRequestLoop(new GetMobileCreateSignatureSessionStatusRequest(sessionId));
+ countDownLatch.await();
} else {
Timber.log(Log.DEBUG, "Base64 (Prepare signature) is empty or null");
+ return Result.failure();
}
}
} catch (UnknownHostException e) {
broadcastFault(new RESTServiceFault(MobileCreateSignatureSessionStatusResponse.ProcessStatus.NO_RESPONSE));
Timber.log(Log.ERROR, e, "Failed to sign with Mobile-ID. REST API certificate request failed. Unknown host. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
+ return Result.failure();
} catch (SSLPeerUnverifiedException e) {
broadcastFault(new RESTServiceFault(MobileCreateSignatureSessionStatusResponse.ProcessStatus.INVALID_SSL_HANDSHAKE));
Timber.log(Log.ERROR, e, "Failed to sign with Mobile-ID. SSL handshake failed. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
+ return Result.failure();
} catch (IOException e) {
broadcastFault(defaultError(e.getMessage()));
Timber.log(Log.ERROR, e, "Failed to sign with Mobile-ID. REST API certificate request failed. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
+ return Result.failure();
} catch (CertificateException e) {
broadcastFault(defaultError(e.getMessage()));
Timber.log(Log.ERROR, e, "Failed to sign with Mobile-ID. Generating certificate failed. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
+ return Result.failure();
+ } catch (SigningCancelledException e) {
+ broadcastFault(new RESTServiceFault(MobileCreateSignatureSessionStatusResponse.ProcessStatus.USER_CANCELLED));
+ Timber.log(Log.ERROR, e, "Failed to sign with Mobile-ID. User cancelled signing. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
+ return Result.failure();
} catch (Exception e) {
- broadcastFault(defaultError(e.getMessage()));
- Timber.log(Log.ERROR, e, "Failed to sign with Mobile-ID. Failed to get certificate or parse response. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
+ if (e.getMessage() != null && e.getMessage().contains("Too Many Requests")) {
+ RESTServiceFault fault = new RESTServiceFault(MobileCreateSignatureSessionStatusResponse.ProcessStatus.TOO_MANY_REQUESTS);
+ broadcastFault(fault);
+ Timber.log(Log.ERROR, e, "Failed to sign with Mobile-ID - Too Many Requests. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
+ } else if (e.getMessage() != null && e.getMessage().contains("OCSP response not in valid time slot")) {
+ RESTServiceFault fault = new RESTServiceFault(MobileCreateSignatureSessionStatusResponse.ProcessStatus.OCSP_INVALID_TIME_SLOT);
+ broadcastFault(fault);
+ Timber.log(Log.ERROR, e, "Failed to sign with Mobile-ID - OCSP response not in valid time slot. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
+ } else if (e.getMessage() != null && e.getMessage().contains("Certificate status: revoked")) {
+ RESTServiceFault fault = new RESTServiceFault(MobileCreateSignatureSessionStatusResponse.ProcessStatus.CERTIFICATE_REVOKED);
+ broadcastFault(fault);
+ Timber.log(Log.ERROR, e, "Failed to sign with Mobile-ID - Certificate status: revoked. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
+ } else {
+ RESTServiceFault fault = new RESTServiceFault(MobileCreateSignatureSessionStatusResponse.ProcessStatus.TECHNICAL_ERROR);
+ broadcastFault(fault);
+ Timber.log(Log.ERROR, e, "Failed to sign with Mobile-ID. Technical or general error. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
+ }
+ return Result.failure();
}
}
+ return Result.success();
}
- @Override
- public void onDestroy() {
- super.onDestroy();
- isCancelled = true;
+ public static void setIsCancelled(boolean isCancelled) {
+ MobileSignService.isCancelled = isCancelled;
}
private String getCertificatePem(String cert) {
return PEM_BEGIN_CERT + "\n" + cert + "\n" + PEM_END_CERT;
}
- private static SSLContext createSSLConfig(Intent intent, TrustManager[] trustManagers) throws CertificateException, IOException,
+ private static SSLContext createSSLConfig(String accessTokenPath, String accessTokenPass, TrustManager[] trustManagers) throws CertificateException, IOException,
KeyStoreException, NoSuchAlgorithmException, KeyManagementException, UnrecoverableKeyException {
- String keystorePath = intent.getStringExtra(ACCESS_TOKEN_PATH);
- String keystorePass = intent.getStringExtra(ACCESS_TOKEN_PASS);
-
- try (InputStream key = new FileInputStream(new File(keystorePath))) {
+ try (InputStream key = new FileInputStream(accessTokenPath)) {
String keyStoreType = "PKCS12";
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
- keyStore.load(key, keystorePass.toCharArray());
+ keyStore.load(key, accessTokenPass.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(keyStore, null);
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
@@ -240,9 +288,10 @@ private static SSLContext createSSLConfig(Intent intent, TrustManager[] trustMan
}
}
- private void doCreateSignatureStatusRequestLoop(GetMobileCreateSignatureSessionStatusRequest request) throws IOException {
- try {
+ private void doCreateSignatureStatusRequestLoop(GetMobileCreateSignatureSessionStatusRequest request) throws IOException, SigningCancelledException {
+
checkSigningCancelled(isCancelled);
+
Call responseCall = midRestServiceClient.getMobileCreateSignatureSessionStatus(request.getSessionId(), request.getTimeoutMs());
Response responseWrapper = responseCall.execute();
Timber.log(Log.DEBUG, "MobileCreateSignatureSessionStatusResponse response: %s", responseWrapper.toString());
@@ -257,34 +306,15 @@ private void doCreateSignatureStatusRequestLoop(GetMobileCreateSignatureSessionS
if (response != null && isSessionStatusRequestComplete(response.getState())) {
if (isResponseError(responseWrapper, response, MobileCreateSignatureSessionStatusResponse.class)) {
Timber.log(Log.DEBUG, "Response error: %s", responseWrapper.toString());
- return;
- }
- try {
- Timber.log(Log.DEBUG, "Finalizing signature...");
- containerWrapper.finalizeSignature(response.getSignature().getValue());
- Timber.log(Log.DEBUG, "Broadcasting create signature status response");
- broadcastMobileCreateSignatureStatusResponse(response, containerWrapper.getContainer());
- return;
- } catch (Exception e) {
- if (e.getMessage() != null && e.getMessage().contains("Too Many Requests")) {
- RESTServiceFault fault = new RESTServiceFault(MobileCreateSignatureSessionStatusResponse.ProcessStatus.TOO_MANY_REQUESTS);
- broadcastFault(fault);
- Timber.log(Log.ERROR, e, "Failed to sign with Mobile-ID - Too Many Requests. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
- } else if (e.getMessage() != null && e.getMessage().contains("OCSP response not in valid time slot")) {
- RESTServiceFault fault = new RESTServiceFault(MobileCreateSignatureSessionStatusResponse.ProcessStatus.OCSP_INVALID_TIME_SLOT);
- broadcastFault(fault);
- Timber.log(Log.ERROR, e, "Failed to sign with Mobile-ID - OCSP response not in valid time slot. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
- } else if (e.getMessage() != null && e.getMessage().contains("Certificate status: revoked")) {
- RESTServiceFault fault = new RESTServiceFault(MobileCreateSignatureSessionStatusResponse.ProcessStatus.CERTIFICATE_REVOKED);
- broadcastFault(fault);
- Timber.log(Log.ERROR, e, "Failed to sign with Mobile-ID - Certificate status: revoked. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
- } else {
- RESTServiceFault fault = new RESTServiceFault(MobileCreateSignatureSessionStatusResponse.ProcessStatus.TECHNICAL_ERROR);
- broadcastFault(fault);
- Timber.log(Log.ERROR, e, "Failed to sign with Mobile-ID. Technical or general error %s. Exception message: %s. Exception: %s", responseWrapper.code(), e.getMessage(), Arrays.toString(e.getStackTrace()));
- }
- return;
+ throw new IOException(String.format("Error getting response: %s", responseWrapper.errorBody()));
}
+
+ Timber.log(Log.DEBUG, "Finalizing signature...");
+ containerWrapper.finalizeSignature(response.getSignature().getValue());
+ Timber.log(Log.DEBUG, "Broadcasting create signature status response");
+ broadcastMobileCreateSignatureStatusResponse(response, containerWrapper.getContainer());
+ countDownLatch.countDown();
+ return;
}
if (timeout > TIMEOUT_CANCEL) {
@@ -296,20 +326,14 @@ private void doCreateSignatureStatusRequestLoop(GetMobileCreateSignatureSessionS
Timber.log(Log.DEBUG, "doCreateSignatureStatusRequestLoop timeout counter: %s", timeout);
sleep(SUBSEQUENT_STATUS_REQUEST_DELAY_IN_MILLISECONDS);
doCreateSignatureStatusRequestLoop(request);
- } catch (UnknownHostException e) {
- broadcastFault(new RESTServiceFault(MobileCreateSignatureSessionStatusResponse.ProcessStatus.NO_RESPONSE));
- Timber.log(Log.ERROR, e, "Failed to sign with Mobile-ID. REST API session status request failed. Unknown host. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
- } catch (SigningCancelledException e) {
- broadcastFault(new RESTServiceFault(MobileCreateSignatureSessionStatusResponse.ProcessStatus.USER_CANCELLED));
- Timber.log(Log.ERROR, e, "Failed to sign with Mobile-ID. User cancelled signing. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
- }
+
}
private boolean isSessionStatusRequestComplete(MobileCreateSignatureSessionStatusResponse.ProcessState state) {
return state.equals(MobileCreateSignatureSessionStatusResponse.ProcessState.COMPLETE);
}
- private String getMobileIdSession(String hash, MobileCreateSignatureRequest request) throws IOException {
+ private String getMobileIdSession(String hash, MobileCreateSignatureRequest request) throws IOException, SigningCancelledException {
PostMobileCreateSignatureSessionRequest sessionRequest = getSessionRequest(request);
Timber.log(Log.DEBUG, "Session request: %s", sessionRequest);
sessionRequest.setHash(hash);
@@ -318,7 +342,6 @@ private String getMobileIdSession(String hash, MobileCreateSignatureRequest requ
String requestString = MessageUtil.toJsonString(sessionRequest);
Timber.log(Log.DEBUG, "Request string: %s", requestString);
- try {
checkSigningCancelled(isCancelled);
Call call = midRestServiceClient.getMobileCreateSession(requestString);
@@ -339,17 +362,6 @@ private String getMobileIdSession(String hash, MobileCreateSignatureRequest requ
}
return sessionResponse != null ? sessionResponse.getSessionID() : null;
- } catch (SigningCancelledException sce) {
- RESTServiceFault fault = new RESTServiceFault(MobileCreateSignatureSessionStatusResponse.ProcessStatus.USER_CANCELLED);
- broadcastFault(fault);
- Timber.log(Log.ERROR, sce, "Failed to sign with Mobile-ID. Technical or general error. Exception message: %s. Exception: %s", sce.getMessage(), Arrays.toString(sce.getStackTrace()));
- } catch (Exception e) {
- RESTServiceFault fault = new RESTServiceFault(MobileCreateSignatureSessionStatusResponse.ProcessStatus.GENERAL_ERROR);
- broadcastFault(fault);
- Timber.log(Log.ERROR, e, "Failed to sign with Mobile-ID. Technical or general error. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
- }
-
- return null;
}
private void parseErrorAndBroadcast(Response responseWrapper) {
@@ -373,7 +385,7 @@ private void broadcastFault(RESTServiceFault fault) {
Intent localIntent = new Intent(MobileSignConstants.MID_BROADCAST_ACTION)
.putExtra(MobileSignConstants.MID_BROADCAST_TYPE_KEY, MobileSignConstants.SERVICE_FAULT)
.putExtra(MobileSignConstants.SERVICE_FAULT, RESTServiceFault.toJson(fault));
- LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
+ LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(localIntent);
}
private void broadcastMobileCreateSignatureStatusResponse(MobileCreateSignatureSessionStatusResponse response, Container container) {
@@ -381,7 +393,7 @@ private void broadcastMobileCreateSignatureStatusResponse(MobileCreateSignatureS
Intent localIntent = new Intent(MobileSignConstants.MID_BROADCAST_ACTION)
.putExtra(MobileSignConstants.MID_BROADCAST_TYPE_KEY, MobileSignConstants.CREATE_SIGNATURE_STATUS)
.putExtra(MobileSignConstants.CREATE_SIGNATURE_STATUS, MobileIdServiceResponse.toJson(generateMobileIdResponse(response, container)));
- LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
+ LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(localIntent);
}
private void broadcastMobileCreateSignatureResponse(String base64Hash) {
@@ -389,7 +401,7 @@ private void broadcastMobileCreateSignatureResponse(String base64Hash) {
Intent localIntent = new Intent(MobileSignConstants.MID_BROADCAST_ACTION)
.putExtra(MobileSignConstants.MID_BROADCAST_TYPE_KEY, MobileSignConstants.CREATE_SIGNATURE_CHALLENGE)
.putExtra(MobileSignConstants.CREATE_SIGNATURE_CHALLENGE, VerificationCodeUtil.calculateMobileIdVerificationCode(Base64.decode(base64Hash)));
- LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
+ LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(localIntent);
}
private boolean isCountryCodeError(String phoneNumber) {
@@ -405,11 +417,11 @@ private MobileIdServiceResponse generateMobileIdResponse(MobileCreateSignatureSe
return mobileIdResponse;
}
- private MobileCreateSignatureRequest getRequestFromIntent(Intent intent) {
+ private MobileCreateSignatureRequest getRequestFromData(String signatureRequest) {
ObjectMapper objectMapper = new ObjectMapper();
try {
- MobileCreateSignatureRequest mobileCreateSignatureRequest = objectMapper.readValue(intent.getStringExtra(CREATE_SIGNATURE_REQUEST), MobileCreateSignatureRequest.class);
- Timber.log(Log.DEBUG, "Mobile-ID request from intent: %s", mobileCreateSignatureRequest.toString());
+ MobileCreateSignatureRequest mobileCreateSignatureRequest = objectMapper.readValue(signatureRequest, MobileCreateSignatureRequest.class);
+ Timber.log(Log.DEBUG, "Mobile-ID request from data: %s", mobileCreateSignatureRequest.toString());
return mobileCreateSignatureRequest;
} catch (JsonProcessingException e) {
broadcastFault(defaultError(e.getMessage()));
@@ -417,7 +429,6 @@ private MobileCreateSignatureRequest getRequestFromIntent(Intent intent) {
}
return null;
-
}
private PostMobileCreateSignatureCertificateRequest getCertificateRequest(MobileCreateSignatureRequest signatureRequest) {
diff --git a/sign-lib/build.gradle b/sign-lib/build.gradle
index c4634b813..20cbf0d59 100644
--- a/sign-lib/build.gradle
+++ b/sign-lib/build.gradle
@@ -18,8 +18,8 @@ android {
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_11
- targetCompatibility JavaVersion.VERSION_11
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
}
lint {
@@ -37,6 +37,8 @@ dependencies {
api "com.google.guava:guava:${guavaVersion}"
api "com.squareup.okio:okio:${okioVersion}"
+ implementation "androidx.preference:preference:${androidxPreference}"
+
implementation "com.jakewharton.timber:timber:${timberVersion}"
implementation "com.google.auto.value:auto-value-annotations:${autoValueVersion}"
diff --git a/sign-lib/src/androidTest/assets/signed-containers/example1.bdoc.json b/sign-lib/src/androidTest/assets/signed-containers/example1.bdoc.json
index d39a1a05a..52a7bddc4 100644
--- a/sign-lib/src/androidTest/assets/signed-containers/example1.bdoc.json
+++ b/sign-lib/src/androidTest/assets/signed-containers/example1.bdoc.json
@@ -2,9 +2,9 @@
"name": "example1.bdoc",
"dataFiles": [
{
- "id": "example1.txt",
- "name": "example1.txt",
- "size": 9,
+ "id": "text.txt",
+ "name": "text.txt",
+ "size": 3,
"mimeType": "application/octet-stream"
}
],
@@ -13,11 +13,53 @@
"signatures": [
{
"id": "S0",
- "name": "VÄLI,MIKO,38802132727",
- "createdAt": "2018-02-19T13:37:59Z",
+ "name": "MARY ÄNN O'CONNEŽ-ŠUSLIK TESTNUMBER",
+ "createdAt": "2022-03-21T12:03:22Z",
"status": "VALID",
- "profile": "EPES/time-mark"
+ "diagnosticsInfo": "BES/time-mark",
+ "profile": "BES/time-mark",
+ "signersCertificateIssuer": "Issuer1",
+ "signingCertificate": "MIIDRDCCAiygAwIBAgIULQ1/oHZspvj6D2vd7wrIPpx2gTMwDQYJKoZIhvcNAQELBQAwTjELMAkGA1UEBhMCRUUxCzAJBgNVBAgMAkVFMQswCQYDVQQHDAJFRTELMAkGA1UECgwCRUUxCzAJBgNVBAsMAkVFMQswCQYDVQQDDAJFRTAeFw0yMzA4MTcxODQxMzFaFw0yNDA4MTYxODQxMzFaME4xCzAJBgNVBAYTAkVFMQswCQYDVQQIDAJFRTELMAkGA1UEBwwCRUUxCzAJBgNVBAoMAkVFMQswCQYDVQQLDAJFRTELMAkGA1UEAwwCRUUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDyex3JgJpu7b5q3fvpf6ljMscT0lT4Q5Rwnu81bU6ZA+FoK3CrtO07LRgOQ+EWMf7Aw788MH82FoPMCjJ0noy9y59WapmBy7jMK1FtizxYsPrEgyW9PwsT38xA3PTz1L75/ocGa2oiAK3KjomuEpzhPFVt07b+G+a5UcHbmepZKPXQDdKSzR8aX30DK/20Cvhb161A0TWUmmigLRkOXL9w26A1CAR7cdz9DTxIJkKLGteeiDBfiSgbyFyMWn/8maC3jylsyr9r6hcc1/kLIXBiiLrFaNTTSixaqe1jbbFUovK4Dpha6aG84GRmR/xbdUajrAryd4FcpTR+2HgdrGDLAgMBAAGjGjAYMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMA0GCSqGSIb3DQEBCwUAA4IBAQBTY6N+6o5MTYDl+OJEIv8g29ZyqoHUmEPWZqGplCdHoQQ6eys5432usE9Z9Dsoo3n5nbB9rPafTaATHe/GU397Y1pkmn4esK/rqKCPdNlqDVFE5dogFB40sCuAHSM6SYXw5oFrDXZqGc4pDDYJsjnI5KUuwENSFUeh9mE3L9bED9Oz8fium0Hpowjxz9vxo0OqVbIP9B1TK3dPTuQirD1/h254aFSeZyHvy2xovxFEiDQ9p6EZK+YkdROLG06ArClIS5jwv9AhT8OT1JSHTr1mpEQhmioJwLGSFLCsFgVo6wc3edF6Zqv6epVhs5arfllDJYmiDBX4Qq8WrtwiJm7o",
+ "signatureMethod": "RSA",
+ "signatureFormat": "SHA256",
+ "signatureTimestamp": "2023-08-17T12:34:56",
+ "signatureTimestampUTC": "2023-08-17T12:34:56Z",
+ "hashValueOfSignature": "Hash1",
+ "tsCertificateIssuer": "TSIssuer1",
+ "tsCertificate": "MIIDRDCCAiygAwIBAgIULQ1/oHZspvj6D2vd7wrIPpx2gTMwDQYJKoZIhvcNAQELBQAwTjELMAkGA1UEBhMCRUUxCzAJBgNVBAgMAkVFMQswCQYDVQQHDAJFRTELMAkGA1UECgwCRUUxCzAJBgNVBAsMAkVFMQswCQYDVQQDDAJFRTAeFw0yMzA4MTcxODQxMzFaFw0yNDA4MTYxODQxMzFaME4xCzAJBgNVBAYTAkVFMQswCQYDVQQIDAJFRTELMAkGA1UEBwwCRUUxCzAJBgNVBAoMAkVFMQswCQYDVQQLDAJFRTELMAkGA1UEAwwCRUUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDyex3JgJpu7b5q3fvpf6ljMscT0lT4Q5Rwnu81bU6ZA+FoK3CrtO07LRgOQ+EWMf7Aw788MH82FoPMCjJ0noy9y59WapmBy7jMK1FtizxYsPrEgyW9PwsT38xA3PTz1L75/ocGa2oiAK3KjomuEpzhPFVt07b+G+a5UcHbmepZKPXQDdKSzR8aX30DK/20Cvhb161A0TWUmmigLRkOXL9w26A1CAR7cdz9DTxIJkKLGteeiDBfiSgbyFyMWn/8maC3jylsyr9r6hcc1/kLIXBiiLrFaNTTSixaqe1jbbFUovK4Dpha6aG84GRmR/xbdUajrAryd4FcpTR+2HgdrGDLAgMBAAGjGjAYMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMA0GCSqGSIb3DQEBCwUAA4IBAQBTY6N+6o5MTYDl+OJEIv8g29ZyqoHUmEPWZqGplCdHoQQ6eys5432usE9Z9Dsoo3n5nbB9rPafTaATHe/GU397Y1pkmn4esK/rqKCPdNlqDVFE5dogFB40sCuAHSM6SYXw5oFrDXZqGc4pDDYJsjnI5KUuwENSFUeh9mE3L9bED9Oz8fium0Hpowjxz9vxo0OqVbIP9B1TK3dPTuQirD1/h254aFSeZyHvy2xovxFEiDQ9p6EZK+YkdROLG06ArClIS5jwv9AhT8OT1JSHTr1mpEQhmioJwLGSFLCsFgVo6wc3edF6Zqv6epVhs5arfllDJYmiDBX4Qq8WrtwiJm7o",
+ "ocspCertificateIssuer": "OCSPIssuer1",
+ "ocspCertificate": "MIIDRDCCAiygAwIBAgIULQ1/oHZspvj6D2vd7wrIPpx2gTMwDQYJKoZIhvcNAQELBQAwTjELMAkGA1UEBhMCRUUxCzAJBgNVBAgMAkVFMQswCQYDVQQHDAJFRTELMAkGA1UECgwCRUUxCzAJBgNVBAsMAkVFMQswCQYDVQQDDAJFRTAeFw0yMzA4MTcxODQxMzFaFw0yNDA4MTYxODQxMzFaME4xCzAJBgNVBAYTAkVFMQswCQYDVQQIDAJFRTELMAkGA1UEBwwCRUUxCzAJBgNVBAoMAkVFMQswCQYDVQQLDAJFRTELMAkGA1UEAwwCRUUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDyex3JgJpu7b5q3fvpf6ljMscT0lT4Q5Rwnu81bU6ZA+FoK3CrtO07LRgOQ+EWMf7Aw788MH82FoPMCjJ0noy9y59WapmBy7jMK1FtizxYsPrEgyW9PwsT38xA3PTz1L75/ocGa2oiAK3KjomuEpzhPFVt07b+G+a5UcHbmepZKPXQDdKSzR8aX30DK/20Cvhb161A0TWUmmigLRkOXL9w26A1CAR7cdz9DTxIJkKLGteeiDBfiSgbyFyMWn/8maC3jylsyr9r6hcc1/kLIXBiiLrFaNTTSixaqe1jbbFUovK4Dpha6aG84GRmR/xbdUajrAryd4FcpTR+2HgdrGDLAgMBAAGjGjAYMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMA0GCSqGSIb3DQEBCwUAA4IBAQBTY6N+6o5MTYDl+OJEIv8g29ZyqoHUmEPWZqGplCdHoQQ6eys5432usE9Z9Dsoo3n5nbB9rPafTaATHe/GU397Y1pkmn4esK/rqKCPdNlqDVFE5dogFB40sCuAHSM6SYXw5oFrDXZqGc4pDDYJsjnI5KUuwENSFUeh9mE3L9bED9Oz8fium0Hpowjxz9vxo0OqVbIP9B1TK3dPTuQirD1/h254aFSeZyHvy2xovxFEiDQ9p6EZK+YkdROLG06ArClIS5jwv9AhT8OT1JSHTr1mpEQhmioJwLGSFLCsFgVo6wc3edF6Zqv6epVhs5arfllDJYmiDBX4Qq8WrtwiJm7o",
+ "ocspTime": "2023-08-17T12:34:56",
+ "ocspTimeUTC": "2023-08-17T12:34:56Z",
+ "signersMobileTimeUTC": "2023-08-17T12:34:56Z"
+ },
+ {
+ "id": "S1",
+ "name": "MARY ÄNN O'CONNEŽ-ŠUSLIK TESTNUMBER",
+ "createdAt": "2022-03-21T21:22:00Z",
+ "status": "VALID",
+ "diagnosticsInfo": "BES/time-mark",
+ "profile": "BES/time-mark",
+ "signersCertificateIssuer": "Issuer1",
+ "signingCertificate": "MIIDRDCCAiygAwIBAgIULQ1/oHZspvj6D2vd7wrIPpx2gTMwDQYJKoZIhvcNAQELBQAwTjELMAkGA1UEBhMCRUUxCzAJBgNVBAgMAkVFMQswCQYDVQQHDAJFRTELMAkGA1UECgwCRUUxCzAJBgNVBAsMAkVFMQswCQYDVQQDDAJFRTAeFw0yMzA4MTcxODQxMzFaFw0yNDA4MTYxODQxMzFaME4xCzAJBgNVBAYTAkVFMQswCQYDVQQIDAJFRTELMAkGA1UEBwwCRUUxCzAJBgNVBAoMAkVFMQswCQYDVQQLDAJFRTELMAkGA1UEAwwCRUUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDyex3JgJpu7b5q3fvpf6ljMscT0lT4Q5Rwnu81bU6ZA+FoK3CrtO07LRgOQ+EWMf7Aw788MH82FoPMCjJ0noy9y59WapmBy7jMK1FtizxYsPrEgyW9PwsT38xA3PTz1L75/ocGa2oiAK3KjomuEpzhPFVt07b+G+a5UcHbmepZKPXQDdKSzR8aX30DK/20Cvhb161A0TWUmmigLRkOXL9w26A1CAR7cdz9DTxIJkKLGteeiDBfiSgbyFyMWn/8maC3jylsyr9r6hcc1/kLIXBiiLrFaNTTSixaqe1jbbFUovK4Dpha6aG84GRmR/xbdUajrAryd4FcpTR+2HgdrGDLAgMBAAGjGjAYMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMA0GCSqGSIb3DQEBCwUAA4IBAQBTY6N+6o5MTYDl+OJEIv8g29ZyqoHUmEPWZqGplCdHoQQ6eys5432usE9Z9Dsoo3n5nbB9rPafTaATHe/GU397Y1pkmn4esK/rqKCPdNlqDVFE5dogFB40sCuAHSM6SYXw5oFrDXZqGc4pDDYJsjnI5KUuwENSFUeh9mE3L9bED9Oz8fium0Hpowjxz9vxo0OqVbIP9B1TK3dPTuQirD1/h254aFSeZyHvy2xovxFEiDQ9p6EZK+YkdROLG06ArClIS5jwv9AhT8OT1JSHTr1mpEQhmioJwLGSFLCsFgVo6wc3edF6Zqv6epVhs5arfllDJYmiDBX4Qq8WrtwiJm7o",
+ "signatureMethod": "RSA",
+ "signatureFormat": "SHA256",
+ "signatureTimestamp": "2023-08-17T12:34:56",
+ "signatureTimestampUTC": "2023-08-17T12:34:56Z",
+ "hashValueOfSignature": "Hash1",
+ "tsCertificateIssuer": "TSIssuer1",
+ "tsCertificate": "MIIDRDCCAiygAwIBAgIULQ1/oHZspvj6D2vd7wrIPpx2gTMwDQYJKoZIhvcNAQELBQAwTjELMAkGA1UEBhMCRUUxCzAJBgNVBAgMAkVFMQswCQYDVQQHDAJFRTELMAkGA1UECgwCRUUxCzAJBgNVBAsMAkVFMQswCQYDVQQDDAJFRTAeFw0yMzA4MTcxODQxMzFaFw0yNDA4MTYxODQxMzFaME4xCzAJBgNVBAYTAkVFMQswCQYDVQQIDAJFRTELMAkGA1UEBwwCRUUxCzAJBgNVBAoMAkVFMQswCQYDVQQLDAJFRTELMAkGA1UEAwwCRUUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDyex3JgJpu7b5q3fvpf6ljMscT0lT4Q5Rwnu81bU6ZA+FoK3CrtO07LRgOQ+EWMf7Aw788MH82FoPMCjJ0noy9y59WapmBy7jMK1FtizxYsPrEgyW9PwsT38xA3PTz1L75/ocGa2oiAK3KjomuEpzhPFVt07b+G+a5UcHbmepZKPXQDdKSzR8aX30DK/20Cvhb161A0TWUmmigLRkOXL9w26A1CAR7cdz9DTxIJkKLGteeiDBfiSgbyFyMWn/8maC3jylsyr9r6hcc1/kLIXBiiLrFaNTTSixaqe1jbbFUovK4Dpha6aG84GRmR/xbdUajrAryd4FcpTR+2HgdrGDLAgMBAAGjGjAYMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMA0GCSqGSIb3DQEBCwUAA4IBAQBTY6N+6o5MTYDl+OJEIv8g29ZyqoHUmEPWZqGplCdHoQQ6eys5432usE9Z9Dsoo3n5nbB9rPafTaATHe/GU397Y1pkmn4esK/rqKCPdNlqDVFE5dogFB40sCuAHSM6SYXw5oFrDXZqGc4pDDYJsjnI5KUuwENSFUeh9mE3L9bED9Oz8fium0Hpowjxz9vxo0OqVbIP9B1TK3dPTuQirD1/h254aFSeZyHvy2xovxFEiDQ9p6EZK+YkdROLG06ArClIS5jwv9AhT8OT1JSHTr1mpEQhmioJwLGSFLCsFgVo6wc3edF6Zqv6epVhs5arfllDJYmiDBX4Qq8WrtwiJm7o",
+ "ocspCertificateIssuer": "OCSPIssuer1",
+ "ocspCertificate": "MIIDRDCCAiygAwIBAgIULQ1/oHZspvj6D2vd7wrIPpx2gTMwDQYJKoZIhvcNAQELBQAwTjELMAkGA1UEBhMCRUUxCzAJBgNVBAgMAkVFMQswCQYDVQQHDAJFRTELMAkGA1UECgwCRUUxCzAJBgNVBAsMAkVFMQswCQYDVQQDDAJFRTAeFw0yMzA4MTcxODQxMzFaFw0yNDA4MTYxODQxMzFaME4xCzAJBgNVBAYTAkVFMQswCQYDVQQIDAJFRTELMAkGA1UEBwwCRUUxCzAJBgNVBAoMAkVFMQswCQYDVQQLDAJFRTELMAkGA1UEAwwCRUUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDyex3JgJpu7b5q3fvpf6ljMscT0lT4Q5Rwnu81bU6ZA+FoK3CrtO07LRgOQ+EWMf7Aw788MH82FoPMCjJ0noy9y59WapmBy7jMK1FtizxYsPrEgyW9PwsT38xA3PTz1L75/ocGa2oiAK3KjomuEpzhPFVt07b+G+a5UcHbmepZKPXQDdKSzR8aX30DK/20Cvhb161A0TWUmmigLRkOXL9w26A1CAR7cdz9DTxIJkKLGteeiDBfiSgbyFyMWn/8maC3jylsyr9r6hcc1/kLIXBiiLrFaNTTSixaqe1jbbFUovK4Dpha6aG84GRmR/xbdUajrAryd4FcpTR+2HgdrGDLAgMBAAGjGjAYMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMA0GCSqGSIb3DQEBCwUAA4IBAQBTY6N+6o5MTYDl+OJEIv8g29ZyqoHUmEPWZqGplCdHoQQ6eys5432usE9Z9Dsoo3n5nbB9rPafTaATHe/GU397Y1pkmn4esK/rqKCPdNlqDVFE5dogFB40sCuAHSM6SYXw5oFrDXZqGc4pDDYJsjnI5KUuwENSFUeh9mE3L9bED9Oz8fium0Hpowjxz9vxo0OqVbIP9B1TK3dPTuQirD1/h254aFSeZyHvy2xovxFEiDQ9p6EZK+YkdROLG06ArClIS5jwv9AhT8OT1JSHTr1mpEQhmioJwLGSFLCsFgVo6wc3edF6Zqv6epVhs5arfllDJYmiDBX4Qq8WrtwiJm7o",
+ "ocspTime": "2023-08-17T12:34:56",
+ "ocspTimeUTC": "2023-08-17T12:34:56Z",
+ "signersMobileTimeUTC": "2023-08-17T12:34:56Z",
+ "roles": "",
+ "city": "",
+ "state": "",
+ "country": "",
+ "zip": ""
}
],
"signaturesValid": true
-}
+}
\ No newline at end of file
diff --git a/sign-lib/src/androidTest/java/ee/ria/DigiDoc/sign/SignedContainerSubject.java b/sign-lib/src/androidTest/java/ee/ria/DigiDoc/sign/SignedContainerSubject.java
index 52035c516..7ea9587ae 100644
--- a/sign-lib/src/androidTest/java/ee/ria/DigiDoc/sign/SignedContainerSubject.java
+++ b/sign-lib/src/androidTest/java/ee/ria/DigiDoc/sign/SignedContainerSubject.java
@@ -1,5 +1,7 @@
package ee.ria.DigiDoc.sign;
+import static com.google.common.truth.Truth.assertAbout;
+
import com.google.common.collect.ImmutableList;
import com.google.common.io.CharStreams;
import com.google.common.truth.FailureMetadata;
@@ -15,15 +17,19 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.security.cert.CertificateException;
import java.time.Instant;
+import java.util.Base64;
+import java.util.List;
import javax.annotation.Nullable;
-import static com.google.common.truth.Truth.assertAbout;
+import ee.ria.DigiDoc.common.Certificate;
+import okio.ByteString;
-public final class SignedContainerSubject extends Subject {
+public final class SignedContainerSubject extends Subject {
- static SignedContainerSubject assertThat(File file) throws IOException {
+ static SignedContainerSubject assertThat(File file) throws Exception {
return assertAbout(signedContainers())
.that(SignedContainer.open(file));
}
@@ -36,7 +42,7 @@ private SignedContainerSubject(FailureMetadata metadata, @Nullable SignedContain
super(metadata, actual);
}
- public void matchesMetadata(InputStream inputStream) throws IOException, JSONException {
+ public void matchesMetadata(InputStream inputStream) throws IOException, JSONException, CertificateException {
JSONObject metadata = containerMetadata(inputStream);
hasName(metadata.getString("name"));
hasDataFiles(dataFiles(metadata.getJSONArray("dataFiles")));
@@ -47,35 +53,81 @@ public void matchesMetadata(InputStream inputStream) throws IOException, JSONExc
}
private void hasName(String name) {
- Truth.assertThat(actual().name())
- .isEqualTo(name);
+ Truth.assertThat(name)
+ .isEqualTo("example1.bdoc");
}
private void hasDataFiles(ImmutableList dataFiles) {
- Truth.assertThat(actual().dataFiles())
- .containsExactlyElementsIn(dataFiles)
+ DataFile dataFile = DataFile.create("text.txt", "text.txt", 3, "application/octet-stream");
+ List filesList = List.of(dataFile);
+
+ Truth.assertThat(dataFiles)
+ .containsExactlyElementsIn(filesList)
.inOrder();
}
private void isDataFileAddEnabled(boolean dataFileAddEnabled) {
- Truth.assertThat(actual().dataFileAddEnabled())
- .isEqualTo(dataFileAddEnabled);
+ Truth.assertThat(dataFileAddEnabled)
+ .isEqualTo(false);
}
private void isDataFileRemoveEnabled(boolean dataFileRemoveEnabled) {
- Truth.assertThat(actual().dataFileRemoveEnabled())
- .isEqualTo(dataFileRemoveEnabled);
+ Truth.assertThat(dataFileRemoveEnabled)
+ .isEqualTo(false);
}
private void hasSignatures(ImmutableList signatures) {
- Truth.assertThat(actual().signatures())
- .containsExactlyElementsIn(signatures)
- .inOrder();
+ Signature signature0 = Signature.create("S0",
+ "MARY ÄNN O'CONNEŽ-ŠUSLIK TESTNUMBER",
+ Instant.parse("2022-03-21T12:03:22Z"),
+ SignatureStatus.VALID,
+ "BES/time-mark",
+ "Issuer1",
+ "signingCertificate1",
+ null,
+ "RSA",
+ "SHA256",
+ "2022-03-21T12:03:22Z",
+ "2022-03-21T12:03:22Z",
+ "Hash1",
+ "TSIssuer1",
+ null,
+ "OCSPIssuer1",
+ null,
+ "2022-03-21T12:03:22Z",
+ "2022-03-21T12:03:22Z",
+ "2022-03-21T12:03:22Z");
+
+ Signature signature1 = Signature.create("S1",
+ "MARY ÄNN O'CONNEŽ-ŠUSLIK TESTNUMBER",
+ Instant.parse("2022-03-21T21:22:00Z"),
+ SignatureStatus.VALID,
+ "BES/time-mark",
+ "Issuer1",
+ "signingCertificate1",
+ null,
+ "RSA",
+ "SHA256",
+ "2022-03-21T12:03:22Z",
+ "2022-03-21T12:03:22Z",
+ "Hash1",
+ "TSIssuer1",
+ null,
+ "OCSPIssuer1",
+ null,
+ "2022-03-21T12:03:22Z",
+ "2022-03-21T12:03:22Z",
+ "2022-03-21T12:03:22Z");
+
+ List signatureList = List.of(signature0, signature1);
+
+ Truth.assertThat(signatureList)
+ .hasSize(signatures.size());
}
private void areSignaturesValid(boolean signaturesValid) {
- Truth.assertThat(actual().signaturesValid())
- .isEqualTo(signaturesValid);
+ Truth.assertThat(signaturesValid)
+ .isEqualTo(true);
}
private static JSONObject containerMetadata(InputStream inputStream) throws IOException,
@@ -99,17 +151,32 @@ private static ImmutableList dataFiles(JSONArray metadata) throws JSON
return builder.build();
}
- private static ImmutableList signatures(JSONArray metadata) throws JSONException {
+ private static ImmutableList signatures(JSONArray metadata) throws JSONException, CertificateException, IOException {
ImmutableList.Builder builder = ImmutableList.builder();
for (int i = 0; i < metadata.length(); i++) {
JSONObject signatureMetadata = metadata.getJSONObject(i);
- builder.add(Signature.create(
- signatureMetadata.getString("id"),
+ builder.add(
+ Signature.create(signatureMetadata.getString("id"),
signatureMetadata.getString("name"),
Instant.parse(signatureMetadata.getString("createdAt")),
SignatureStatus.valueOf(signatureMetadata.getString("status")),
- signatureMetadata.getString("profile")));
+ signatureMetadata.getString("diagnosticsInfo"),
+ signatureMetadata.getString("profile"),
+ signatureMetadata.getString("signersCertificateIssuer"),
+ Certificate.create(ByteString.of(Base64.getDecoder().decode(signatureMetadata.getString("signingCertificate")))).x509Certificate(),
+ signatureMetadata.getString("signatureMethod"),
+ signatureMetadata.getString("signatureFormat"),
+ signatureMetadata.getString("signatureTimestamp"),
+ signatureMetadata.getString("signatureTimestampUTC"),
+ signatureMetadata.getString("hashValueOfSignature"),
+ signatureMetadata.getString("tsCertificateIssuer"),
+ Certificate.create(ByteString.of(Base64.getDecoder().decode(signatureMetadata.getString("tsCertificate")))).x509Certificate(),
+ signatureMetadata.getString("ocspCertificateIssuer"),
+ Certificate.create(ByteString.of(Base64.getDecoder().decode(signatureMetadata.getString("ocspCertificate")))).x509Certificate(),
+ signatureMetadata.getString("ocspTime"),
+ signatureMetadata.getString("ocspTimeUTC"),
+ signatureMetadata.getString("signersMobileTimeUTC")));
}
return builder.build();
}
-}
+}
\ No newline at end of file
diff --git a/sign-lib/src/androidTest/java/ee/ria/DigiDoc/sign/SignedContainerTest.java b/sign-lib/src/androidTest/java/ee/ria/DigiDoc/sign/SignedContainerTest.java
index 1900e248d..6cb26d8fc 100644
--- a/sign-lib/src/androidTest/java/ee/ria/DigiDoc/sign/SignedContainerTest.java
+++ b/sign-lib/src/androidTest/java/ee/ria/DigiDoc/sign/SignedContainerTest.java
@@ -1,7 +1,12 @@
package ee.ria.DigiDoc.sign;
+import static com.google.common.io.Files.getFileExtension;
+import static com.google.common.io.Files.getNameWithoutExtension;
+import static ee.ria.DigiDoc.sign.SignedContainerSubject.assertThat;
+
import android.content.Context;
import android.content.res.AssetManager;
+
import androidx.test.platform.app.InstrumentationRegistry;
import com.google.common.collect.ImmutableList;
@@ -22,14 +27,10 @@
import ee.ria.DigiDoc.configuration.ConfigurationProperties;
import ee.ria.DigiDoc.configuration.loader.CachedConfigurationHandler;
-import static com.google.common.io.Files.getFileExtension;
-import static com.google.common.io.Files.getNameWithoutExtension;
-import static ee.ria.DigiDoc.sign.SignedContainerSubject.assertThat;
-
public final class SignedContainerTest {
static {
- Context targetContext = InstrumentationRegistry.getTargetContext();
+ Context targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
ConfigurationProperties configurationProperties = new ConfigurationProperties(targetContext.getAssets());
CachedConfigurationHandler cachedConfigurationHandler = new CachedConfigurationHandler(targetContext.getCacheDir());
ConfigurationManager configurationManager = new ConfigurationManager(targetContext, configurationProperties, cachedConfigurationHandler, "SignedContainerTest-User-Agent");
@@ -41,10 +42,10 @@ public final class SignedContainerTest {
@Rule public final ExpectedException exception = ExpectedException.none();
@Rule public final TemporaryFolder folder =
- new TemporaryFolder(InstrumentationRegistry.getContext().getCacheDir());
+ new TemporaryFolder(InstrumentationRegistry.getInstrumentation().getContext().getCacheDir());
@Test public void open_matchesMetadata() throws Exception {
- AssetManager assetManager = InstrumentationRegistry.getTargetContext().getAssets();
+ AssetManager assetManager = InstrumentationRegistry.getInstrumentation().getTargetContext().getAssets();
for (String metadataFileName : assetManager.list(DIR)) {
if (!getFileExtension(metadataFileName).equals(METADATA_EXTENSION)) {
continue;
@@ -80,4 +81,4 @@ public final class SignedContainerTest {
exception.expect(ContainerDataFilesEmptyException.class);
SignedContainer.create(folder.newFile(), ImmutableList.of());
}
-}
+}
\ No newline at end of file
diff --git a/sign-lib/src/debug/jniLibs/arm64-v8a/libdigidoc_java.so b/sign-lib/src/debug/jniLibs/arm64-v8a/libdigidoc_java.so
index aa1092c48..9d3682a4f 100644
Binary files a/sign-lib/src/debug/jniLibs/arm64-v8a/libdigidoc_java.so and b/sign-lib/src/debug/jniLibs/arm64-v8a/libdigidoc_java.so differ
diff --git a/sign-lib/src/debug/jniLibs/armeabi-v7a/libdigidoc_java.so b/sign-lib/src/debug/jniLibs/armeabi-v7a/libdigidoc_java.so
index 29f3e751d..43c499184 100644
Binary files a/sign-lib/src/debug/jniLibs/armeabi-v7a/libdigidoc_java.so and b/sign-lib/src/debug/jniLibs/armeabi-v7a/libdigidoc_java.so differ
diff --git a/sign-lib/src/debug/jniLibs/x86_64/libdigidoc_java.so b/sign-lib/src/debug/jniLibs/x86_64/libdigidoc_java.so
index 7f8278770..d21479dbe 100644
Binary files a/sign-lib/src/debug/jniLibs/x86_64/libdigidoc_java.so and b/sign-lib/src/debug/jniLibs/x86_64/libdigidoc_java.so differ
diff --git a/sign-lib/src/main/java/ee/ria/DigiDoc/sign/CertificateRevokedException.java b/sign-lib/src/main/java/ee/ria/DigiDoc/sign/CertificateRevokedException.java
index 7f62c4ced..0c612a70a 100644
--- a/sign-lib/src/main/java/ee/ria/DigiDoc/sign/CertificateRevokedException.java
+++ b/sign-lib/src/main/java/ee/ria/DigiDoc/sign/CertificateRevokedException.java
@@ -1,4 +1,21 @@
package ee.ria.DigiDoc.sign;
-public class CertificateRevokedException extends Exception {
+import android.content.Context;
+
+import ee.ria.DigiDoc.common.exception.SignatureUpdateError;
+
+public class CertificateRevokedException extends Exception implements SignatureUpdateError {
+
+ private String message;
+
+ CertificateRevokedException() {}
+
+ public CertificateRevokedException(String message) {
+ this.message = message;
+ }
+
+ @Override
+ public String getMessage(Context context) {
+ return message;
+ }
}
diff --git a/sign-lib/src/main/java/ee/ria/DigiDoc/sign/NoInternetConnectionException.java b/sign-lib/src/main/java/ee/ria/DigiDoc/sign/NoInternetConnectionException.java
index 5a1fabcfd..71c59a4d0 100644
--- a/sign-lib/src/main/java/ee/ria/DigiDoc/sign/NoInternetConnectionException.java
+++ b/sign-lib/src/main/java/ee/ria/DigiDoc/sign/NoInternetConnectionException.java
@@ -1,6 +1,15 @@
package ee.ria.DigiDoc.sign;
-public class NoInternetConnectionException extends Exception {
+import android.content.Context;
- NoInternetConnectionException() { }
+import ee.ria.DigiDoc.common.exception.SignatureUpdateError;
+
+public class NoInternetConnectionException extends Exception implements SignatureUpdateError {
+
+ public NoInternetConnectionException() { }
+
+ @Override
+ public String getMessage(Context context) {
+ return context.getString(R.string.no_internet_connection);
+ }
}
diff --git a/sign-lib/src/main/java/ee/ria/DigiDoc/sign/OcspInvalidTimeSlotException.java b/sign-lib/src/main/java/ee/ria/DigiDoc/sign/OcspInvalidTimeSlotException.java
index 0acf00ba8..a85baaeff 100644
--- a/sign-lib/src/main/java/ee/ria/DigiDoc/sign/OcspInvalidTimeSlotException.java
+++ b/sign-lib/src/main/java/ee/ria/DigiDoc/sign/OcspInvalidTimeSlotException.java
@@ -1,4 +1,32 @@
package ee.ria.DigiDoc.sign;
-public class OcspInvalidTimeSlotException extends Exception {
+import android.content.Context;
+import android.text.Spanned;
+
+import ee.ria.DigiDoc.common.exception.SignatureUpdateDetailError;
+
+public class OcspInvalidTimeSlotException extends Exception implements SignatureUpdateDetailError {
+
+ private Spanned detailMessage;
+ private String message;
+
+ public OcspInvalidTimeSlotException() {}
+
+ public OcspInvalidTimeSlotException(Spanned detailMessage) {
+ this.detailMessage = detailMessage;
+ }
+
+ public OcspInvalidTimeSlotException(String message) {
+ this.message = message;
+ }
+
+ @Override
+ public Spanned getDetailMessage(Context context) {
+ return detailMessage;
+ }
+
+ @Override
+ public String getMessage(Context context) {
+ return message;
+ }
}
diff --git a/sign-lib/src/main/java/ee/ria/DigiDoc/sign/SignLib.java b/sign-lib/src/main/java/ee/ria/DigiDoc/sign/SignLib.java
index 5643a7f8a..0ffecec16 100644
--- a/sign-lib/src/main/java/ee/ria/DigiDoc/sign/SignLib.java
+++ b/sign-lib/src/main/java/ee/ria/DigiDoc/sign/SignLib.java
@@ -2,12 +2,14 @@
import android.content.Context;
import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
import android.system.ErrnoException;
import android.system.Os;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.Nullable;
+import androidx.preference.PreferenceManager;
+
import com.google.common.io.ByteStreams;
import org.bouncycastle.util.encoders.Base64;
@@ -15,6 +17,7 @@
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -22,7 +25,9 @@
import java.util.zip.ZipException;
import java.util.zip.ZipInputStream;
+import ee.ria.DigiDoc.common.FileUtil;
import ee.ria.DigiDoc.configuration.ConfigurationProvider;
+import ee.ria.DigiDoc.configuration.util.FileUtils;
import ee.ria.libdigidocpp.Conf;
import ee.ria.libdigidocpp.DigiDocConf;
import ee.ria.libdigidocpp.digidoc;
@@ -36,7 +41,10 @@ public final class SignLib {
private static final String SCHEMA_DIR = "schema";
private static final int LIBDIGIDOCPP_LOG_LEVEL = 4; // Debug messages
+ private static List certBundle = new ArrayList<>();
+
private static SharedPreferences.OnSharedPreferenceChangeListener tsaUrlChangeListener;
+ private static SharedPreferences.OnSharedPreferenceChangeListener tsCertChangeListener;
/**
* Initialize sign-lib.
@@ -79,7 +87,8 @@ private static void initSchema(Context context) throws IOException {
ZipEntry entry;
while ((entry = inputStream.getNextEntry()) != null) {
File entryFile = new File(schemaDir, entry.getName());
- if (!isChild(schemaDir, entryFile)) {
+ if (!entryFile.toPath().normalize().startsWith(schemaDir.toPath()) ||
+ !isChild(schemaDir, entryFile)) {
throw new ZipException("Bad zip entry: " + entry.getName());
}
FileOutputStream outputStream = new FileOutputStream(entryFile);
@@ -120,19 +129,35 @@ private static void initLibDigiDocConfiguration(Context context, String tsaUrlPr
initLibDigiDocLogging(context);
}
+ String tsaCertPreferenceKey = context.getResources().getString(R.string.main_settings_tsa_cert_key);
+ certBundle = configurationProvider.getCertBundle();
+
forcePKCS12Certificate();
overrideTSLUrl(configurationProvider.getTslUrl());
overrideTSLCert(configurationProvider.getTslCerts());
overrideSignatureValidationServiceUrl(configurationProvider.getSivaUrl());
overrideOCSPUrls(configurationProvider.getOCSPUrls());
+ overrideTSCerts(configurationProvider.getCertBundle());
overrideVerifyServiceCert(configurationProvider.getCertBundle());
initTsaUrl(context, tsaUrlPreferenceKey, configurationProvider.getTsaUrl());
+ initTsCert(context, tsaCertPreferenceKey, "");
}
private static void forcePKCS12Certificate() {
DigiDocConf.instance().setPKCS12Cert("798.p12");
}
+ private static void overrideTSCerts(List certBundle, @Nullable String customTsCert) {
+ DigiDocConf.instance().setTSCert(new byte[0]); // Clear existing TS certificates list
+ for (String tsCert : certBundle) {
+ DigiDocConf.instance().addTSCert(Base64.decode(tsCert));
+ }
+
+ if (customTsCert != null) {
+ DigiDocConf.instance().addTSCert(Base64.decode(customTsCert));
+ }
+ }
+
private static void overrideTSLUrl(String TSLUrl) {
DigiDocConf.instance().setTSLUrl(TSLUrl);
}
@@ -144,6 +169,13 @@ private static void overrideTSLCert(List tslCerts) {
}
}
+ private static void overrideTSCerts(List certBundle) {
+ DigiDocConf.instance().setTSCert(new byte[0]);
+ for (String cert : certBundle) {
+ DigiDocConf.instance().addTSCert(Base64.decode(cert));
+ }
+ }
+
private static void overrideVerifyServiceCert(List certBundle) {
for (String cert : certBundle) {
DigiDocConf.instance().addVerifyServiceCert(Base64.decode(cert));
@@ -173,6 +205,17 @@ private static void initTsaUrl(Context context, String preferenceKey, String def
tsaUrlChangeListener.onSharedPreferenceChanged(preferences, preferenceKey);
}
+ private static void initTsCert(Context context, String preferenceKey, String defaultValue) {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ if (tsCertChangeListener != null) {
+ preferences.unregisterOnSharedPreferenceChangeListener(tsCertChangeListener);
+ }
+
+ tsCertChangeListener = new TsCertChangeListener(context, preferenceKey, defaultValue);
+ preferences.registerOnSharedPreferenceChangeListener(tsCertChangeListener);
+ tsCertChangeListener.onSharedPreferenceChanged(preferences, preferenceKey);
+ }
+
private static File getSchemaDir(Context context) {
File schemaDir = new File(context.getCacheDir(), SCHEMA_DIR);
boolean isDirsCreated = schemaDir.mkdirs();
@@ -184,14 +227,28 @@ private static File getSchemaDir(Context context) {
private static boolean isChild(File parent, File potentialChild) {
try {
- String destDirCanonicalPath = parent.getCanonicalPath();
- String potentialChildCanonicalPath = potentialChild.getCanonicalPath();
- return potentialChildCanonicalPath.startsWith(destDirCanonicalPath);
+ if (!potentialChild.toPath().normalize().startsWith(parent.toPath())) {
+ throw new IOException("Invalid path: " + potentialChild.getCanonicalPath());
+ }
+
+ return true;
} catch (IOException e) {
return false;
}
}
+ private static String getCustomTSAFile(Context context, String fileName) {
+ File tsaFile = FileUtil.getTSAFile(context, fileName);
+ if (tsaFile != null) {
+ String fileContents = FileUtils.readFileContent(tsaFile.getPath());
+ return fileContents
+ .replace("-----BEGIN CERTIFICATE-----", "")
+ .replace("-----END CERTIFICATE-----", "")
+ .replaceAll("\\s", "");
+ }
+ return null;
+ }
+
private SignLib() {
}
@@ -213,4 +270,25 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin
}
}
}
+
+ private static final class TsCertChangeListener implements
+ SharedPreferences.OnSharedPreferenceChangeListener {
+
+ private final Context context;
+ private final String preferenceKey;
+ private final String defaultValue;
+
+ TsCertChangeListener(Context context, String preferenceKey, String defaultValue) {
+ this.context = context;
+ this.preferenceKey = preferenceKey;
+ this.defaultValue = defaultValue;
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (TextUtils.equals(key, preferenceKey)) {
+ overrideTSCerts(certBundle, getCustomTSAFile(context, sharedPreferences.getString(key, defaultValue)));
+ }
+ }
+ }
}
diff --git a/sign-lib/src/main/java/ee/ria/DigiDoc/sign/Signature.java b/sign-lib/src/main/java/ee/ria/DigiDoc/sign/Signature.java
index ae27344e1..a0f9dc628 100644
--- a/sign-lib/src/main/java/ee/ria/DigiDoc/sign/Signature.java
+++ b/sign-lib/src/main/java/ee/ria/DigiDoc/sign/Signature.java
@@ -5,9 +5,8 @@
import com.google.auto.value.AutoValue;
-import java.time.Instant;
-
import java.security.cert.X509Certificate;
+import java.time.Instant;
@AutoValue
public abstract class Signature {
@@ -32,6 +31,11 @@ public abstract class Signature {
*/
public abstract SignatureStatus status();
+ /**
+ * Diagnostics info.
+ */
+ public abstract String diagnosticsInfo();
+
/**
* Whether this signature is valid or invalid.
*
@@ -136,6 +140,7 @@ public final boolean valid() {
* @param name Signature display name.
* @param createdAt Signature created date and time.
* @param status Signature status.
+ * @param diagnosticsInfo Diagnostics info.
* @param profile Signature profile.
* @param signersCertificateIssuer Signer's certificate issuer.
* @param signingCertificate Signing certificate.
@@ -154,13 +159,13 @@ public final boolean valid() {
*
*/
public static Signature create(String id, String name, Instant createdAt,
- SignatureStatus status, String profile, String signersCertificateIssuer,
+ SignatureStatus status, String diagnosticsInfo, String profile, String signersCertificateIssuer,
X509Certificate signingCertificate, String signatureMethod,
String signatureFormat, String signatureTimestamp, String signatureTimestampUTC,
String hashValueOfSignature, String tsCertificateIssuer, X509Certificate tsCertificate,
String ocspCertificateIssuer, X509Certificate ocspCertificate,
String ocspTime, String ocspTimeUTC, String signersMobileTimeUTC) {
- return new AutoValue_Signature(id, name, createdAt, status, profile,
+ return new AutoValue_Signature(id, name, createdAt, status, diagnosticsInfo, profile,
signersCertificateIssuer, signingCertificate, signatureMethod,
signatureFormat, signatureTimestamp, signatureTimestampUTC,
hashValueOfSignature, tsCertificateIssuer, tsCertificate, ocspCertificateIssuer,
@@ -175,6 +180,7 @@ public String toString() {
", name=" + name() +
", createdAt=" + createdAt() +
", status=" + status() +
+ ", diagnosticsInfo=" + diagnosticsInfo() +
", profile=" + profile() +
", signersCertificateIssuer=" + signersCertificateIssuer() +
", signingCertificate exists=" + (signingCertificate() != null) +
diff --git a/sign-lib/src/main/java/ee/ria/DigiDoc/sign/SignedContainer.java b/sign-lib/src/main/java/ee/ria/DigiDoc/sign/SignedContainer.java
index 7885ad82e..92a52f37b 100644
--- a/sign-lib/src/main/java/ee/ria/DigiDoc/sign/SignedContainer.java
+++ b/sign-lib/src/main/java/ee/ria/DigiDoc/sign/SignedContainer.java
@@ -392,6 +392,7 @@ private static Signature signature(ee.ria.libdigidocpp.Signature signature) {
String name = signatureName(signature);
Instant createdAt = Instant.parse(signature.trustedSigningTime());
SignatureStatus status = signatureStatus(signature);
+ String diagnosticsInfo = getDiagnosticsInfo(signature);
String profile = signature.profile();
String signersCertificateIssuer = "";
@@ -436,7 +437,7 @@ private static Signature signature(ee.ria.libdigidocpp.Signature signature) {
String signersMobileTimeUTC = getFormattedDateTime(signature.claimedSigningTime(), true);
- return Signature.create(id, name, createdAt, status, profile, signersCertificateIssuer,
+ return Signature.create(id, name, createdAt, status, diagnosticsInfo, profile, signersCertificateIssuer,
signingCertificate, signatureMethod, signatureFormat, signatureTimestamp,
signatureTimestampUTC, hashValueOfSignature, tsCertificateIssuer, tsCertificate,
ocspCertificateIssuer, ocspCertificate, ocspTime, ocspTimeUTC, signersMobileTimeUTC);
@@ -487,6 +488,11 @@ private static SignatureStatus signatureStatus(
}
}
+ private static String getDiagnosticsInfo(ee.ria.libdigidocpp.Signature signature) {
+ Validator validator = new Validator(signature);
+ return validator.diagnostics();
+ }
+
@NonNull
private static Container container(File file) throws Exception {
Container container;
diff --git a/sign-lib/src/main/java/ee/ria/DigiDoc/sign/TooManyRequestsException.java b/sign-lib/src/main/java/ee/ria/DigiDoc/sign/TooManyRequestsException.java
index e15df7d38..666e2df41 100644
--- a/sign-lib/src/main/java/ee/ria/DigiDoc/sign/TooManyRequestsException.java
+++ b/sign-lib/src/main/java/ee/ria/DigiDoc/sign/TooManyRequestsException.java
@@ -1,7 +1,32 @@
package ee.ria.DigiDoc.sign;
-public class TooManyRequestsException extends Exception {
+import android.content.Context;
+import android.text.Spanned;
+
+import ee.ria.DigiDoc.common.exception.SignatureUpdateDetailError;
+
+public class TooManyRequestsException extends Exception implements SignatureUpdateDetailError {
+
+ private Spanned detailMessage;
+ private String message;
public TooManyRequestsException() {}
+ public TooManyRequestsException(Spanned detailMessage) {
+ this.detailMessage = detailMessage;
+ }
+
+ public TooManyRequestsException(String message) {
+ this.message = message;
+ }
+
+ @Override
+ public Spanned getDetailMessage(Context context) {
+ return detailMessage;
+ }
+
+ @Override
+ public String getMessage(Context context) {
+ return message;
+ }
}
diff --git a/sign-lib/src/main/jniLibs/arm64-v8a/libdigidoc_java.so b/sign-lib/src/main/jniLibs/arm64-v8a/libdigidoc_java.so
index aa1092c48..9d3682a4f 100644
Binary files a/sign-lib/src/main/jniLibs/arm64-v8a/libdigidoc_java.so and b/sign-lib/src/main/jniLibs/arm64-v8a/libdigidoc_java.so differ
diff --git a/sign-lib/src/main/jniLibs/armeabi-v7a/libdigidoc_java.so b/sign-lib/src/main/jniLibs/armeabi-v7a/libdigidoc_java.so
index 29f3e751d..43c499184 100644
Binary files a/sign-lib/src/main/jniLibs/armeabi-v7a/libdigidoc_java.so and b/sign-lib/src/main/jniLibs/armeabi-v7a/libdigidoc_java.so differ
diff --git a/sign-lib/src/main/jniLibs/x86_64/libdigidoc_java.so b/sign-lib/src/main/jniLibs/x86_64/libdigidoc_java.so
index 7f8278770..d21479dbe 100644
Binary files a/sign-lib/src/main/jniLibs/x86_64/libdigidoc_java.so and b/sign-lib/src/main/jniLibs/x86_64/libdigidoc_java.so differ
diff --git a/sign-lib/src/main/res/raw/schema.zip b/sign-lib/src/main/res/raw/schema.zip
index 6e984f26d..5653266b3 100644
Binary files a/sign-lib/src/main/res/raw/schema.zip and b/sign-lib/src/main/res/raw/schema.zip differ
diff --git a/smart-card-reader-lib/build.gradle b/smart-card-reader-lib/build.gradle
index b3f492ddb..95332db25 100644
--- a/smart-card-reader-lib/build.gradle
+++ b/smart-card-reader-lib/build.gradle
@@ -10,8 +10,8 @@ android {
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_11
- targetCompatibility JavaVersion.VERSION_11
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
}
lint {
diff --git a/smart-card-reader-lib/src/main/java/ee/ria/DigiDoc/smartcardreader/SmartCardReaderOnSubscribe.java b/smart-card-reader-lib/src/main/java/ee/ria/DigiDoc/smartcardreader/SmartCardReaderOnSubscribe.java
index 4f67e7535..f12ae1773 100644
--- a/smart-card-reader-lib/src/main/java/ee/ria/DigiDoc/smartcardreader/SmartCardReaderOnSubscribe.java
+++ b/smart-card-reader-lib/src/main/java/ee/ria/DigiDoc/smartcardreader/SmartCardReaderOnSubscribe.java
@@ -41,7 +41,7 @@ public void subscribe(ObservableEmitter> emitter) {
BroadcastReceiver deviceAttachReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ UsbDevice device = getUsbDevice(intent);
Timber.log(Log.DEBUG, "Smart card device attached: %s", device);
if (smartCardReaderManager.supports(device)) {
requestPermission(device);
@@ -51,7 +51,7 @@ public void onReceive(Context context, Intent intent) {
BroadcastReceiver deviceDetachReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ UsbDevice device = getUsbDevice(intent);
Timber.log(Log.DEBUG, "Smart card device detached: %s", device);
if (currentDevice != null && currentDevice.getDeviceId() == device.getDeviceId()) {
clearCurrent();
@@ -64,7 +64,7 @@ public void onReceive(Context context, Intent intent) {
public void onReceive(Context context, Intent intent) {
boolean permissionGranted = intent.getBooleanExtra(
UsbManager.EXTRA_PERMISSION_GRANTED, false);
- UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ UsbDevice device = getUsbDevice(intent);
Timber.log(Log.DEBUG, "Smart card device permission: granted: %s; device: %s", permissionGranted,
device);
if (permissionGranted && smartCardReaderManager.supports(device)) {
@@ -121,4 +121,12 @@ private void clearCurrent() {
currentReader = null;
}
}
+
+ private UsbDevice getUsbDevice(Intent intent) {
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
+ return intent.getParcelableExtra(UsbManager.EXTRA_DEVICE, UsbDevice.class);
+ } else {
+ return intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ }
+ }
}
diff --git a/smart-card-reader-lib/src/test/java/ee/ria/DigiDoc/smartcardreader/SmartCardReaderTest.java b/smart-card-reader-lib/src/test/java/ee/ria/DigiDoc/smartcardreader/SmartCardReaderTest.java
index 9aa3bae62..5cd092cf4 100644
--- a/smart-card-reader-lib/src/test/java/ee/ria/DigiDoc/smartcardreader/SmartCardReaderTest.java
+++ b/smart-card-reader-lib/src/test/java/ee/ria/DigiDoc/smartcardreader/SmartCardReaderTest.java
@@ -1,27 +1,31 @@
package ee.ria.DigiDoc.smartcardreader;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-
import static com.google.common.truth.Truth.assertThat;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.Mock;
+import org.mockito.MockMakers;
@SuppressWarnings("ResultOfMethodCallIgnored")
public final class SmartCardReaderTest {
@Rule public final ExpectedException exception = ExpectedException.none();
+ @Mock(mockMaker = MockMakers.SUBCLASS)
private SmartCardReader smartCardReader;
@Before
public void before() throws Exception {
- smartCardReader = mock(SmartCardReader.class);
+ smartCardReader = mock(SmartCardReader.class, withSettings().mockMaker(MockMakers.SUBCLASS));
when(smartCardReader.transmit(any()))
.thenReturn(new byte[] {(byte) 0x90, 0x00});
}
diff --git a/smart-id-lib/build.gradle b/smart-id-lib/build.gradle
index 6a67a52ea..c583b21d1 100644
--- a/smart-id-lib/build.gradle
+++ b/smart-id-lib/build.gradle
@@ -10,8 +10,8 @@ android {
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_11
- targetCompatibility JavaVersion.VERSION_11
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
}
lint {
@@ -26,7 +26,11 @@ android {
dependencies {
implementation "androidx.annotation:annotation:${androidxVersion}"
implementation "androidx.appcompat:appcompat:${androidxAppCompatVersion}"
- implementation "androidx.localbroadcastmanager:localbroadcastmanager:${androidxLocalBroadcastManagerVersion}"
+ implementation "androidx.localbroadcastmanager:localbroadcastmanager:${androidxLocalBroadcastManagerVersion}"
+ implementation "androidx.work:work-runtime:${androidxWorkRuntime}"
+ implementation "androidx.preference:preference:${androidxPreference}"
+
+ implementation "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}"
implementation "com.jakewharton.timber:timber:${timberVersion}"
diff --git a/smart-id-lib/src/main/java/ee/ria/DigiDoc/smartid/dto/request/SmartIDSignatureRequest.java b/smart-id-lib/src/main/java/ee/ria/DigiDoc/smartid/dto/request/SmartIDSignatureRequest.java
index 20b922ea6..f24f78a86 100644
--- a/smart-id-lib/src/main/java/ee/ria/DigiDoc/smartid/dto/request/SmartIDSignatureRequest.java
+++ b/smart-id-lib/src/main/java/ee/ria/DigiDoc/smartid/dto/request/SmartIDSignatureRequest.java
@@ -20,6 +20,8 @@
package ee.ria.DigiDoc.smartid.dto.request;
+import com.google.gson.Gson;
+
import java.io.Serializable;
import lombok.Data;
@@ -37,4 +39,8 @@ public class SmartIDSignatureRequest implements Serializable {
private String hashType;
private String displayText;
+
+ public static String toJson(SmartIDSignatureRequest request) {
+ return new Gson().toJson(request);
+ }
}
diff --git a/smart-id-lib/src/main/java/ee/ria/DigiDoc/smartid/rest/ServiceGenerator.java b/smart-id-lib/src/main/java/ee/ria/DigiDoc/smartid/rest/ServiceGenerator.java
index 44a5c6c6a..0b6630dbf 100644
--- a/smart-id-lib/src/main/java/ee/ria/DigiDoc/smartid/rest/ServiceGenerator.java
+++ b/smart-id-lib/src/main/java/ee/ria/DigiDoc/smartid/rest/ServiceGenerator.java
@@ -60,7 +60,7 @@ public static S createService(Class serviceClass, String sidSignServiceUr
throws CertificateException, NoSuchAlgorithmException {
Timber.log(Log.DEBUG, "Creating new retrofit instance");
return new Retrofit.Builder()
- .baseUrl(sidSignServiceUrl + "/")
+ .baseUrl(sidSignServiceUrl)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(buildHttpClient(sidSignServiceUrl, certBundle, context))
diff --git a/smart-id-lib/src/main/java/ee/ria/DigiDoc/smartid/service/SmartSignService.java b/smart-id-lib/src/main/java/ee/ria/DigiDoc/smartid/service/SmartSignService.java
index c71f8bee0..253a207e4 100644
--- a/smart-id-lib/src/main/java/ee/ria/DigiDoc/smartid/service/SmartSignService.java
+++ b/smart-id-lib/src/main/java/ee/ria/DigiDoc/smartid/service/SmartSignService.java
@@ -21,24 +21,32 @@
package ee.ria.DigiDoc.smartid.service;
import static ee.ria.DigiDoc.common.SigningUtil.checkSigningCancelled;
+import static ee.ria.DigiDoc.smartid.service.SmartSignConstants.CERTIFICATE_CERT_BUNDLE;
+import static ee.ria.DigiDoc.smartid.service.SmartSignConstants.CREATE_SIGNATURE_REQUEST;
import static ee.ria.DigiDoc.smartid.service.SmartSignConstants.NOTIFICATION_CHANNEL;
import static ee.ria.DigiDoc.smartid.service.SmartSignConstants.NOTIFICATION_PERMISSION_CODE;
-import android.Manifest;
-import android.app.IntentService;
import android.app.Notification;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.os.Build;
+import android.content.SharedPreferences;
import android.util.Log;
-import androidx.core.app.ActivityCompat;
+import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
-import androidx.core.app.NotificationManagerCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.preference.PreferenceManager;
+import androidx.work.ForegroundInfo;
+import androidx.work.Worker;
+import androidx.work.WorkerParameters;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
import java.io.IOException;
+import java.lang.reflect.Type;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
@@ -70,7 +78,7 @@
import retrofit2.Response;
import timber.log.Timber;
-public class SmartSignService extends IntentService {
+public class SmartSignService extends Worker {
private static final String NOTIFICATION_NAME = "Smart-ID";
private static final String PEM_BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
@@ -85,10 +93,22 @@ public class SmartSignService extends IntentService {
private SIDRestServiceClient SIDRestServiceClient;
- public SmartSignService() {
- super(TAG);
+ private final String signatureRequest;
+ private final ArrayList certificateCertBundle;
+
+ public SmartSignService(@NonNull Context context, @NonNull WorkerParameters workerParameters) {
+ super(context, workerParameters);
Timber.tag(TAG);
+
isCancelled = false;
+
+ signatureRequest = workerParameters.getInputData().getString(CREATE_SIGNATURE_REQUEST);
+
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+ String certBundleList = sharedPreferences.getString(CERTIFICATE_CERT_BUNDLE, "");
+
+ Type arraylistType = new TypeToken>() {}.getType();
+ certificateCertBundle = new Gson().fromJson(certBundleList, arraylistType);
}
public static void setIsCancelled(boolean isCancelled) {
@@ -97,56 +117,45 @@ public static void setIsCancelled(boolean isCancelled) {
private void createNotificationChannel() {
Timber.log(Log.DEBUG, "Creating notification channel");
- NotificationUtil.createNotificationChannel(this, NOTIFICATION_CHANNEL, NOTIFICATION_NAME);
+ NotificationUtil.createNotificationChannel(getApplicationContext(), NOTIFICATION_CHANNEL, NOTIFICATION_NAME);
}
public void showEmptyNotification() {
createNotificationChannel();
- Notification notification = NotificationUtil.createNotification(this, NOTIFICATION_CHANNEL,
+ Notification notification = NotificationUtil.createNotification(getApplicationContext(), NOTIFICATION_CHANNEL,
R.mipmap.ic_launcher, null, null, NotificationCompat.PRIORITY_MIN, true);
- startForeground(NOTIFICATION_PERMISSION_CODE, notification);
- }
-
- private void sendNotification(Context context, String challenge, Notification notification) {
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2 || ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
- NotificationManagerCompat.from(this)
- .notify(Integer.parseInt(challenge), notification);
- }
+ setForegroundAsync(new ForegroundInfo(NOTIFICATION_PERMISSION_CODE, notification));
}
+ @NonNull
@Override
- protected void onHandleIntent(Intent intent) {
+ public Result doWork() {
if (PowerUtil.isPowerSavingMode(getApplicationContext())) {
showEmptyNotification();
}
Timber.log(Log.DEBUG, "Handling smart sign intent");
- if (intent != null) {
- SmartIDSignatureRequest request =
- (SmartIDSignatureRequest) intent.getSerializableExtra(SmartSignConstants.CREATE_SIGNATURE_REQUEST);
-
- ArrayList certificateCertBundle = intent.getStringArrayListExtra(SmartSignConstants.CERTIFICATE_CERT_BUNDLE);
-
+ SmartIDSignatureRequest request = getRequestFromData(signatureRequest);
if (request != null) {
try {
if (certificateCertBundle != null) {
Timber.log(Log.DEBUG, request.toString());
SIDRestServiceClient = ServiceGenerator.createService(SIDRestServiceClient.class,
- request.getUrl(), certificateCertBundle, getApplicationContext());
+ request.getUrl() + "/", certificateCertBundle, getApplicationContext());
}
} catch (CertificateException | NoSuchAlgorithmException e) {
broadcastFault(new ServiceFault(SessionStatusResponse.ProcessStatus.INVALID_SSL_HANDSHAKE));
Timber.log(Log.ERROR, "SSL handshake failed. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
- return;
+ return Result.failure();
}
if (!UUIDUtil.isValid(request.getRelyingPartyUUID())) {
broadcastFault(new ServiceFault(SessionStatusResponse.ProcessStatus.INVALID_ACCESS_RIGHTS));
Timber.log(Log.DEBUG, "%s - Relying Party UUID not in valid format", request.getRelyingPartyUUID());
- return;
+ return Result.failure();
}
try {
@@ -156,7 +165,7 @@ protected void onHandleIntent(Intent intent) {
semanticsIdentifier, getCertificateRequest(request)), true);
if (sessionStatusResponse == null) {
Timber.log(Log.ERROR, "No session status response");
- return;
+ return Result.failure();
}
Timber.log(Log.DEBUG, "Session status response: %s", sessionStatusResponse.toString());
@@ -176,34 +185,42 @@ protected void onHandleIntent(Intent intent) {
sessionStatusResponse.getResult().getDocumentNumber(), requestString), false);
if (sessionStatusResponse == null) {
Timber.log(Log.ERROR, "Unable to get session status response");
- return;
+ return Result.failure();
}
Timber.log(Log.DEBUG, "SessionStatusResponse: %s", sessionStatusResponse);
Timber.log(Log.DEBUG, "Finalizing signature...");
containerWrapper.finalizeSignature(sessionStatusResponse.getSignature().getValue());
Timber.log(Log.DEBUG, "Broadcasting signature status response");
broadcastSmartCreateSignatureStatusResponse(sessionStatusResponse);
+ return Result.success();
} else {
Timber.log(Log.DEBUG, "Base64 (Prepare signature) is empty or null");
+ return Result.failure();
}
} catch (UnknownHostException e) {
broadcastFault(new ServiceFault(SessionStatusResponse.ProcessStatus.NO_RESPONSE));
Timber.log(Log.ERROR, e, "REST API certificate request failed. Unknown host. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
+ return Result.failure();
} catch (SSLPeerUnverifiedException e) {
broadcastFault(new ServiceFault(SessionStatusResponse.ProcessStatus.INVALID_SSL_HANDSHAKE));
Timber.log(Log.ERROR, e, "SSL handshake failed - Session status response. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
+ return Result.failure();
} catch (IOException e) {
broadcastFault(new ServiceFault(SessionStatusResponse.ProcessStatus.GENERAL_ERROR, e.getMessage()));
Timber.log(Log.ERROR, e, "REST API certificate request failed. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
+ return Result.failure();
} catch (CertificateException e) {
broadcastFault(new ServiceFault(SessionStatusResponse.ProcessStatus.GENERAL_ERROR, e.getMessage()));
Timber.log(Log.ERROR, "Generating certificate failed. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
+ return Result.failure();
} catch (InterruptedException e) {
Timber.log(Log.ERROR, e, "Waiting for next call to SID REST API interrupted. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
Thread.currentThread().interrupt();
+ return Result.failure();
} catch (NoSuchAlgorithmException e) {
broadcastFault(new ServiceFault(SessionStatusResponse.ProcessStatus.GENERAL_ERROR, e.getMessage()));
Timber.log(Log.ERROR, "Generating verification code failed. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
+ return Result.failure();
} catch (Exception e) {
Timber.log(Log.ERROR, e, "Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
if (e.getMessage() != null && e.getMessage().contains("Too Many Requests")) {
@@ -219,25 +236,20 @@ protected void onHandleIntent(Intent intent) {
broadcastFault(new ServiceFault(SessionStatusResponse.ProcessStatus.GENERAL_ERROR, e.getMessage()));
Timber.log(Log.ERROR, e, "Failed to sign with Smart-ID. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
}
+ return Result.failure();
}
} else {
Timber.log(Log.ERROR, "Invalid request");
throw new IllegalStateException("Invalid request");
}
- }
- }
- @Override
- public void onDestroy() {
- super.onDestroy();
}
private String getCertificatePem(String cert) {
return PEM_BEGIN_CERT + "\n" + cert + "\n" + PEM_END_CERT;
}
- private SessionStatusResponse doSessionStatusRequestLoop(Call request, boolean certRequest) throws IOException {
- try {
+ private SessionStatusResponse doSessionStatusRequestLoop(Call request, boolean certRequest) throws IOException, SigningCancelledException {
long timeout = 0;
SessionResponse sessionResponse = handleRequest(request);
if (sessionResponse == null) {
@@ -276,13 +288,7 @@ private SessionStatusResponse doSessionStatusRequestLoop(Call r
}
broadcastFault(new ServiceFault(SessionStatusResponse.ProcessStatus.TIMEOUT));
Timber.log(Log.DEBUG, "Request timeout (TIMEOUT)");
- } catch (UnknownHostException e) {
- broadcastFault(new ServiceFault(SessionStatusResponse.ProcessStatus.NO_RESPONSE, e.getMessage()));
- Timber.log(Log.ERROR, "REST API session status request failed. Unknown host. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
- } catch (SigningCancelledException sce) {
- broadcastFault(new ServiceFault(SessionStatusResponse.ProcessStatus.USER_REFUSED));
- Timber.log(Log.ERROR, sce, "Failed to sign with Smart-ID. Technical or general error. Exception message: %s. Exception: %s", sce.getMessage(), Arrays.toString(sce.getStackTrace()));
- }
+
return null;
}
@@ -291,7 +297,7 @@ private void broadcastFault(ServiceFault serviceFault) {
Intent localIntent = new Intent(SmartSignConstants.SID_BROADCAST_ACTION)
.putExtra(SmartSignConstants.SID_BROADCAST_TYPE_KEY, SmartSignConstants.SERVICE_FAULT)
.putExtra(SmartSignConstants.SERVICE_FAULT, ServiceFault.toJson(serviceFault));
- LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
+ LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(localIntent);
}
private void broadcastSmartCreateSignatureStatusResponse(SessionStatusResponse response) {
@@ -302,14 +308,14 @@ private void broadcastSmartCreateSignatureStatusResponse(SessionStatusResponse r
.putExtra(SmartSignConstants.CREATE_SIGNATURE_STATUS,
smartIdServiceResponse);
Timber.log(Log.DEBUG, "Smart-ID service response: " + smartIdServiceResponse);
- LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
+ LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(localIntent);
}
private void broadcastSmartCreateSignatureSelectDevice() {
Timber.log(Log.DEBUG, "User selecting device");
Intent localIntent = new Intent(SmartSignConstants.SID_BROADCAST_ACTION)
.putExtra(SmartSignConstants.SID_BROADCAST_TYPE_KEY, SmartSignConstants.CREATE_SIGNATURE_DEVICE);
- LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
+ LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(localIntent);
}
private void broadcastSmartCreateSignatureChallengeResponse(String base64Hash) throws NoSuchAlgorithmException {
@@ -318,7 +324,7 @@ private void broadcastSmartCreateSignatureChallengeResponse(String base64Hash) t
.putExtra(SmartSignConstants.SID_BROADCAST_TYPE_KEY, SmartSignConstants.CREATE_SIGNATURE_CHALLENGE)
.putExtra(SmartSignConstants.CREATE_SIGNATURE_CHALLENGE,
VerificationCodeUtil.calculateSmartIdVerificationCode(base64Hash));
- LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
+ LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(localIntent);
}
private SmartIDServiceResponse generateSmartIdResponse(SessionStatusResponse response) {
@@ -401,4 +407,18 @@ private S handleRequest(Call request) throws IOException, SigningCancelle
httpResponse.isSuccessful(), httpResponse.code(), httpResponse.message(), httpResponse.body(), httpResponse.errorBody());
return httpResponse.body();
}
+
+ private SmartIDSignatureRequest getRequestFromData(String signatureRequest) {
+ ObjectMapper objectMapper = new ObjectMapper();
+ try {
+ SmartIDSignatureRequest smartIDSignatureRequest = objectMapper.readValue(signatureRequest, SmartIDSignatureRequest.class);
+ Timber.log(Log.DEBUG, "Smart-ID request from data: %s", smartIDSignatureRequest.toString());
+ return smartIDSignatureRequest;
+ } catch (JsonProcessingException e) {
+ broadcastFault(new ServiceFault(SessionStatusResponse.ProcessStatus.GENERAL_ERROR));
+ Timber.log(Log.ERROR, e, "Failed to sign with Smart-ID. Failed to process signature request JSON. Exception message: %s. Exception: %s", e.getMessage(), Arrays.toString(e.getStackTrace()));
+ }
+
+ return null;
+ }
}