From 622ea3755461ec2aa5f050c2d1a15fadd0721f0f Mon Sep 17 00:00:00 2001 From: amlwin Date: Mon, 6 Oct 2025 11:34:45 +0800 Subject: [PATCH 1/2] Rename .java to .kt --- .../{ImportExportActivity.java => ImportExportActivity.kt} | 0 ...{ImportExportActivityTest.java => ImportExportActivityTest.kt} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename app/src/main/java/protect/card_locker/{ImportExportActivity.java => ImportExportActivity.kt} (100%) rename app/src/test/java/protect/card_locker/{ImportExportActivityTest.java => ImportExportActivityTest.kt} (100%) diff --git a/app/src/main/java/protect/card_locker/ImportExportActivity.java b/app/src/main/java/protect/card_locker/ImportExportActivity.kt similarity index 100% rename from app/src/main/java/protect/card_locker/ImportExportActivity.java rename to app/src/main/java/protect/card_locker/ImportExportActivity.kt diff --git a/app/src/test/java/protect/card_locker/ImportExportActivityTest.java b/app/src/test/java/protect/card_locker/ImportExportActivityTest.kt similarity index 100% rename from app/src/test/java/protect/card_locker/ImportExportActivityTest.java rename to app/src/test/java/protect/card_locker/ImportExportActivityTest.kt From 303b40e572546ddb4c9792dab5eb7554e0fb11f8 Mon Sep 17 00:00:00 2001 From: amlwin Date: Mon, 6 Oct 2025 11:34:45 +0800 Subject: [PATCH 2/2] Convert ImportExportActivity to Kotlin Refactored `ImportExportActivity` and its corresponding test class from Java to Kotlin. The new implementation uses modern Kotlin idioms and syntax while preserving the original functionality. --- .../card_locker/ImportExportActivity.kt | 631 +++++++++--------- .../card_locker/ImportExportActivityTest.kt | 114 ++-- 2 files changed, 387 insertions(+), 358 deletions(-) diff --git a/app/src/main/java/protect/card_locker/ImportExportActivity.kt b/app/src/main/java/protect/card_locker/ImportExportActivity.kt index aeef6d0f5..aa8a2aa13 100644 --- a/app/src/main/java/protect/card_locker/ImportExportActivity.kt +++ b/app/src/main/java/protect/card_locker/ImportExportActivity.kt @@ -1,395 +1,416 @@ -package protect.card_locker; +package protect.card_locker -import android.content.ActivityNotFoundException; -import android.content.DialogInterface; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.text.InputType; -import android.util.Log; -import android.view.MenuItem; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.Button; -import android.widget.EditText; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.Toast; +import android.content.ActivityNotFoundException +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.text.InputType +import android.util.Log +import android.view.MenuItem +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.Button +import android.widget.EditText +import android.widget.FrameLayout +import android.widget.LinearLayout +import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.widget.Toolbar +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.textfield.TextInputLayout +import protect.card_locker.async.TaskHandler +import protect.card_locker.databinding.ImportExportActivityBinding +import protect.card_locker.importexport.DataFormat +import protect.card_locker.importexport.ImportExportResult +import protect.card_locker.importexport.ImportExportResultType +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.Toolbar; +class ImportExportActivity : CatimaAppCompatActivity() { + private lateinit var binding: ImportExportActivityBinding -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.google.android.material.textfield.TextInputLayout; + private var importExporter: ImportExportTask? = null -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; + private var importAlertTitle: String? = null + private var importAlertMessage: String? = null + private var importDataFormat: DataFormat? = null + private var exportPassword: String? = null -import protect.card_locker.async.TaskHandler; -import protect.card_locker.databinding.ImportExportActivityBinding; -import protect.card_locker.importexport.DataFormat; -import protect.card_locker.importexport.ImportExportResult; -import protect.card_locker.importexport.ImportExportResultType; + private lateinit var fileCreateLauncher: ActivityResultLauncher + private lateinit var fileOpenLauncher: ActivityResultLauncher -public class ImportExportActivity extends CatimaAppCompatActivity { - private ImportExportActivityBinding binding; - private static final String TAG = "Catima"; + private val mTasks = TaskHandler() - private ImportExportTask importExporter; + companion object { + private const val TAG = "Catima" + } - private String importAlertTitle; - private String importAlertMessage; - private DataFormat importDataFormat; - private String exportPassword; + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ImportExportActivityBinding.inflate(layoutInflater) + setTitle(R.string.importExport) + setContentView(binding.root) + Utils.applyWindowInsets(binding.root) + val toolbar: Toolbar = binding.toolbar + setSupportActionBar(toolbar) + enableToolbarBackButton() - private ActivityResultLauncher fileCreateLauncher; - private ActivityResultLauncher fileOpenLauncher; - - final private TaskHandler mTasks = new TaskHandler(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - binding = ImportExportActivityBinding.inflate(getLayoutInflater()); - setTitle(R.string.importExport); - setContentView(binding.getRoot()); - Utils.applyWindowInsets(binding.getRoot()); - Toolbar toolbar = binding.toolbar; - setSupportActionBar(toolbar); - enableToolbarBackButton(); - - Intent fileIntent = getIntent(); - if (fileIntent != null && fileIntent.getType() != null) { - chooseImportType(fileIntent.getData()); + val fileIntent = intent + if (fileIntent?.type != null) { + chooseImportType(fileIntent.data) } // would use ActivityResultContracts.CreateDocument() but mime type cannot be set - fileCreateLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { - Intent intent = result.getData(); - if (intent == null) { - Log.e(TAG, "Activity returned NULL data"); - return; - } - Uri uri = intent.getData(); - if (uri == null) { - Log.e(TAG, "Activity returned NULL uri"); - return; - } - // Running this in a thread prevents Android from throwing a NetworkOnMainThreadException for large files - // FIXME: This is still suboptimal, because showing that the export started is delayed until the network request finishes - new Thread() { - @Override - public void run() { + fileCreateLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + val intent = result.data + if (intent == null) { + Log.e(TAG, "Activity returned NULL data") + return@registerForActivityResult + } + val uri = intent.data + if (uri == null) { + Log.e(TAG, "Activity returned NULL uri") + return@registerForActivityResult + } + // Running this in a thread prevents Android from throwing a NetworkOnMainThreadException for large files + // FIXME: This is still suboptimal, because showing that the export started is delayed until the network request finishes + Thread { try { - OutputStream writer = getContentResolver().openOutputStream(uri); - Log.d(TAG, "Starting file export with: " + result); - startExport(writer, uri, exportPassword.toCharArray(), true); - } catch (IOException e) { - Log.e(TAG, "Failed to export file: " + result, e); - onExportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, result.toString()), uri); + val writer = contentResolver.openOutputStream(uri) + Log.d(TAG, "Starting file export with: $result") + startExport(writer, uri, exportPassword?.toCharArray(), true) + } catch (e: IOException) { + Log.e(TAG, "Failed to export file: $result", e) + onExportComplete( + ImportExportResult( + ImportExportResultType.GenericFailure, + result.toString() + ), uri + ) } - } - }.start(); - }); - fileOpenLauncher = registerForActivityResult(new ActivityResultContracts.GetContent(), result -> { - if (result == null) { - Log.e(TAG, "Activity returned NULL data"); - return; + }.start() } - openFileForImport(result, null); - }); - // Check that there is a file manager available - final Intent intentCreateDocumentAction = new Intent(Intent.ACTION_CREATE_DOCUMENT); - intentCreateDocumentAction.addCategory(Intent.CATEGORY_OPENABLE); - intentCreateDocumentAction.setType("application/zip"); - intentCreateDocumentAction.putExtra(Intent.EXTRA_TITLE, "catima.zip"); - - Button exportButton = binding.exportButton; - exportButton.setOnClickListener(v -> { - AlertDialog.Builder builder = new MaterialAlertDialogBuilder(ImportExportActivity.this); - builder.setTitle(R.string.exportPassword); - - FrameLayout container = new FrameLayout(ImportExportActivity.this); - - final TextInputLayout textInputLayout = new TextInputLayout(ImportExportActivity.this); - textInputLayout.setEndIconMode(TextInputLayout.END_ICON_PASSWORD_TOGGLE); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - params.setMargins(50, 10, 50, 0); - textInputLayout.setLayoutParams(params); - - final EditText input = new EditText(ImportExportActivity.this); - input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); - input.setHint(R.string.exportPasswordHint); - - textInputLayout.addView(input); - container.addView(textInputLayout); - builder.setView(container); - builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> { - exportPassword = input.getText().toString(); - try { - fileCreateLauncher.launch(intentCreateDocumentAction); - } catch (ActivityNotFoundException e) { - Toast.makeText(getApplicationContext(), R.string.failedOpeningFileManager, Toast.LENGTH_LONG).show(); - Log.e(TAG, "No activity found to handle intent", e); + fileOpenLauncher = + registerForActivityResult(ActivityResultContracts.GetContent()) { result -> + if (result == null) { + Log.e(TAG, "Activity returned NULL data") + return@registerForActivityResult } - }); - builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel()); - builder.show(); - }); + openFileForImport(result, null) + } // Check that there is a file manager available - Button importFilesystem = binding.importOptionFilesystemButton; - importFilesystem.setOnClickListener(v -> chooseImportType(null)); + val intentCreateDocumentAction = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/zip" + putExtra(Intent.EXTRA_TITLE, "catima.zip") + } + + val exportButton: Button = binding.exportButton + exportButton.setOnClickListener { + val builder = MaterialAlertDialogBuilder(this@ImportExportActivity) + builder.setTitle(R.string.exportPassword) + + val container = FrameLayout(this@ImportExportActivity) + + val textInputLayout = TextInputLayout(this@ImportExportActivity).apply { + endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { + setMargins(50, 10, 50, 0) + } + } + + val input = EditText(this@ImportExportActivity).apply { + inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD + setHint(R.string.exportPasswordHint) + } + + textInputLayout.addView(input) + container.addView(textInputLayout) + builder.setView(container) + builder.setPositiveButton(R.string.ok) { _, _ -> + exportPassword = input.text.toString() + try { + fileCreateLauncher.launch(intentCreateDocumentAction) + } catch (e: ActivityNotFoundException) { + Toast.makeText( + applicationContext, + R.string.failedOpeningFileManager, + Toast.LENGTH_LONG + ).show() + Log.e(TAG, "No activity found to handle intent", e) + } + } + builder.setNegativeButton(R.string.cancel) { dialogInterface, _ -> dialogInterface.cancel() } + builder.show() + } + + // Check that there is a file manager available + val importFilesystem: Button = binding.importOptionFilesystemButton + importFilesystem.setOnClickListener { chooseImportType(null) } // FIXME: The importer/exporter is currently quite broken // To prevent the screen from turning off during import/export and some devices killing Catima as it's no longer foregrounded, force the screen to stay on here - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } - private void openFileForImport(Uri uri, char[] password) { + private fun openFileForImport(uri: Uri, password: CharArray?) { // Running this in a thread prevents Android from throwing a NetworkOnMainThreadException for large files // FIXME: This is still suboptimal, because showing that the import started is delayed until the network request finishes - new Thread() { - @Override - public void run() { - try { - InputStream reader = getContentResolver().openInputStream(uri); - Log.d(TAG, "Starting file import with: " + uri); - startImport(reader, uri, importDataFormat, password, true); - } catch (IOException e) { - Log.e(TAG, "Failed to import file: " + uri, e); - onImportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, e.toString()), uri, importDataFormat); - } + Thread { + try { + val reader = contentResolver.openInputStream(uri) + Log.d(TAG, "Starting file import with: $uri") + startImport(reader, uri, importDataFormat, password, true) + } catch (e: IOException) { + Log.e(TAG, "Failed to import file: $uri", e) + onImportComplete( + ImportExportResult( + ImportExportResultType.GenericFailure, + e.toString() + ), uri, importDataFormat + ) } - }.start(); + }.start() } - private void chooseImportType(@Nullable Uri fileData) { + private fun chooseImportType(fileData: Uri?) { + val betaImportOptions = mutableListOf() + betaImportOptions.add("Fidme") + val importOptions = mutableListOf() - List betaImportOptions = new ArrayList<>(); - betaImportOptions.add("Fidme"); - List importOptions = new ArrayList<>(); - - for (String importOption : getResources().getStringArray(R.array.import_types_array)) { + for (importOption in resources.getStringArray(R.array.import_types_array)) { + var option = importOption if (betaImportOptions.contains(importOption)) { - importOption = importOption + " (BETA)"; + option = "$importOption (BETA)" } - - importOptions.add(importOption); + importOptions.add(option) } - AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); + val builder = MaterialAlertDialogBuilder(this) builder.setTitle(R.string.chooseImportType) - .setItems(importOptions.toArray(new CharSequence[importOptions.size()]), (dialog, which) -> { - switch (which) { - // Catima - case 0: - importAlertTitle = getString(R.string.importCatima); - importAlertMessage = getString(R.string.importCatimaMessage); - importDataFormat = DataFormat.Catima; - break; - // Fidme - case 1: - importAlertTitle = getString(R.string.importFidme); - importAlertMessage = getString(R.string.importFidmeMessage); - importDataFormat = DataFormat.Fidme; - break; - // Loyalty Card Keychain - case 2: - importAlertTitle = getString(R.string.importLoyaltyCardKeychain); - importAlertMessage = getString(R.string.importLoyaltyCardKeychainMessage); - importDataFormat = DataFormat.Catima; - break; - // Voucher Vault - case 3: - importAlertTitle = getString(R.string.importVoucherVault); - importAlertMessage = getString(R.string.importVoucherVaultMessage); - importDataFormat = DataFormat.VoucherVault; - break; - default: - throw new IllegalArgumentException("Unknown DataFormat"); + .setItems(importOptions.toTypedArray()) { _, which -> + when (which) { + // Catima + 0 -> { + importAlertTitle = getString(R.string.importCatima) + importAlertMessage = getString(R.string.importCatimaMessage) + importDataFormat = DataFormat.Catima + } + // Fidme + 1 -> { + importAlertTitle = getString(R.string.importFidme) + importAlertMessage = getString(R.string.importFidmeMessage) + importDataFormat = DataFormat.Fidme + } + // Loyalty Card Keychain + 2 -> { + importAlertTitle = getString(R.string.importLoyaltyCardKeychain) + importAlertMessage = getString(R.string.importLoyaltyCardKeychainMessage) + importDataFormat = DataFormat.Catima + } + // Voucher Vault + 3 -> { + importAlertTitle = getString(R.string.importVoucherVault) + importAlertMessage = getString(R.string.importVoucherVaultMessage) + importDataFormat = DataFormat.VoucherVault } - if (fileData != null) { - openFileForImport(fileData, null); - return; - } + else -> throw IllegalArgumentException("Unknown DataFormat") + } - new MaterialAlertDialogBuilder(this) - .setTitle(importAlertTitle) - .setMessage(importAlertMessage) - .setPositiveButton(R.string.ok, (dialog1, which1) -> { - try { - fileOpenLauncher.launch("*/*"); - } catch (ActivityNotFoundException e) { - Toast.makeText(getApplicationContext(), R.string.failedOpeningFileManager, Toast.LENGTH_LONG).show(); - Log.e(TAG, "No activity found to handle intent", e); - } - }) - .setNegativeButton(R.string.cancel, null) - .show(); - }); - builder.show(); + if (fileData != null) { + openFileForImport(fileData, null) + return@setItems + } + + MaterialAlertDialogBuilder(this) + .setTitle(importAlertTitle) + .setMessage(importAlertMessage) + .setPositiveButton(R.string.ok) { _, _ -> + try { + fileOpenLauncher.launch("*/*") + } catch (e: ActivityNotFoundException) { + Toast.makeText( + applicationContext, + R.string.failedOpeningFileManager, + Toast.LENGTH_LONG + ).show() + Log.e(TAG, "No activity found to handle intent", e) + } + } + .setNegativeButton(R.string.cancel, null) + .show() + } + builder.show() } - private void startImport(final InputStream target, final Uri targetUri, final DataFormat dataFormat, final char[] password, final boolean closeWhenDone) { - mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false); - ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() { - @Override - public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) { - onImportComplete(result, targetUri, dataFormat); - if (closeWhenDone) { - try { - target.close(); - } catch (IOException ioException) { - ioException.printStackTrace(); - } + private fun startImport( + target: InputStream?, + targetUri: Uri, + dataFormat: DataFormat?, + password: CharArray?, + closeWhenDone: Boolean + ) { + mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false) + val listener = ImportExportTask.TaskCompleteListener { result, dataFormat -> + onImportComplete(result, targetUri, dataFormat) + if (closeWhenDone) { + try { + target?.close() + } catch (ioException: IOException) { + ioException.printStackTrace() } } - }; + } - importExporter = new ImportExportTask(ImportExportActivity.this, - dataFormat, target, password, listener); - mTasks.executeTask(TaskHandler.TYPE.IMPORT, importExporter); + importExporter = ImportExportTask( + this@ImportExportActivity, + dataFormat, target, password, listener + ) + mTasks.executeTask(TaskHandler.TYPE.IMPORT, importExporter) } - private void startExport(final OutputStream target, final Uri targetUri, char[] password, final boolean closeWhenDone) { - mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false); - ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() { - @Override - public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) { - onExportComplete(result, targetUri); - if (closeWhenDone) { - try { - target.close(); - } catch (IOException ioException) { - ioException.printStackTrace(); - } + private fun startExport( + target: OutputStream?, + targetUri: Uri, + password: CharArray?, + closeWhenDone: Boolean + ) { + mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false) + val listener = ImportExportTask.TaskCompleteListener { result, dataFormat -> + onExportComplete(result, targetUri) + if (closeWhenDone) { + try { + target?.close() + } catch (ioException: IOException) { + ioException.printStackTrace() } } - }; + } - importExporter = new ImportExportTask(ImportExportActivity.this, - DataFormat.Catima, target, password, listener); - mTasks.executeTask(TaskHandler.TYPE.EXPORT, importExporter); + importExporter = ImportExportTask( + this@ImportExportActivity, + DataFormat.Catima, target, password, listener + ) + mTasks.executeTask(TaskHandler.TYPE.EXPORT, importExporter) } - @Override - protected void onDestroy() { - mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false); - mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false); - super.onDestroy(); + override fun onDestroy() { + mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false) + mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false) + super.onDestroy() } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int id = item.getItemId(); + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val id = item.itemId if (id == android.R.id.home) { - finish(); - return true; + finish() + return true } - return super.onOptionsItemSelected(item); + return super.onOptionsItemSelected(item) } - private void retryWithPassword(DataFormat dataFormat, Uri uri) { - AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); - builder.setTitle(R.string.passwordRequired); + private fun retryWithPassword(dataFormat: DataFormat, uri: Uri) { + val builder = MaterialAlertDialogBuilder(this) + builder.setTitle(R.string.passwordRequired) - FrameLayout container = new FrameLayout(ImportExportActivity.this); + val container = FrameLayout(this@ImportExportActivity) - final TextInputLayout textInputLayout = new TextInputLayout(ImportExportActivity.this); - textInputLayout.setEndIconMode(TextInputLayout.END_ICON_PASSWORD_TOGGLE); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - params.setMargins(50, 10, 50, 0); - textInputLayout.setLayoutParams(params); + val textInputLayout = TextInputLayout(this@ImportExportActivity).apply { + endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { + setMargins(50, 10, 50, 0) + } + } - final EditText input = new EditText(ImportExportActivity.this); - input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); - input.setHint(R.string.exportPasswordHint); + val input = EditText(this@ImportExportActivity).apply { + inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD + setHint(R.string.exportPasswordHint) + } - textInputLayout.addView(input); - container.addView(textInputLayout); - builder.setView(container); + textInputLayout.addView(input) + container.addView(textInputLayout) + builder.setView(container) - builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> { - openFileForImport(uri, input.getText().toString().toCharArray()); - }); - builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel()); + builder.setPositiveButton(R.string.ok) { _, _ -> + openFileForImport(uri, input.text.toString().toCharArray()) + } + builder.setNegativeButton(R.string.cancel) { dialogInterface, _ -> dialogInterface.cancel() } - builder.show(); + builder.show() } - private String buildResultDialogMessage(ImportExportResult result, boolean isImport) { - int messageId; - - if (result.resultType() == ImportExportResultType.Success) { - messageId = isImport ? R.string.importSuccessful : R.string.exportSuccessful; + private fun buildResultDialogMessage(result: ImportExportResult, isImport: Boolean): String { + val messageId = if (result.resultType() == ImportExportResultType.Success) { + if (isImport) R.string.importSuccessful else R.string.exportSuccessful } else { - messageId = isImport ? R.string.importFailed : R.string.exportFailed; + if (isImport) R.string.importFailed else R.string.exportFailed } - StringBuilder messageBuilder = new StringBuilder(getResources().getString(messageId)); + val messageBuilder = StringBuilder(resources.getString(messageId)) if (result.developerDetails() != null) { - messageBuilder.append("\n\n"); - messageBuilder.append(getResources().getString(R.string.include_if_asking_support)); - messageBuilder.append("\n\n"); - messageBuilder.append(result.developerDetails()); + messageBuilder.append("\n\n") + messageBuilder.append(resources.getString(R.string.include_if_asking_support)) + messageBuilder.append("\n\n") + messageBuilder.append(result.developerDetails()) } - return messageBuilder.toString(); + return messageBuilder.toString() } - private void onImportComplete(ImportExportResult result, Uri path, DataFormat dataFormat) { - ImportExportResultType resultType = result.resultType(); + private fun onImportComplete(result: ImportExportResult, path: Uri, dataFormat: DataFormat?) { + val resultType = result.resultType() if (resultType == ImportExportResultType.BadPassword) { - retryWithPassword(dataFormat, path); - return; + retryWithPassword(dataFormat!!, path) + return } - AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); - builder.setTitle(resultType == ImportExportResultType.Success ? R.string.importSuccessfulTitle : R.string.importFailedTitle); - builder.setMessage(buildResultDialogMessage(result, true)); - builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss()); + val builder = MaterialAlertDialogBuilder(this) + builder.setTitle(if (resultType == ImportExportResultType.Success) R.string.importSuccessfulTitle else R.string.importFailedTitle) + builder.setMessage(buildResultDialogMessage(result, true)) + builder.setNeutralButton(R.string.ok) { dialog, _ -> dialog.dismiss() } - builder.create().show(); + builder.create().show() } - private void onExportComplete(ImportExportResult result, final Uri path) { - ImportExportResultType resultType = result.resultType(); + private fun onExportComplete(result: ImportExportResult, path: Uri) { + val resultType = result.resultType() - AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); - builder.setTitle(resultType == ImportExportResultType.Success ? R.string.exportSuccessfulTitle : R.string.exportFailedTitle); - builder.setMessage(buildResultDialogMessage(result, false)); - builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss()); + val builder = MaterialAlertDialogBuilder(this) + builder.setTitle(if (resultType == ImportExportResultType.Success) R.string.exportSuccessfulTitle else R.string.exportFailedTitle) + builder.setMessage(buildResultDialogMessage(result, false)) + builder.setNeutralButton(R.string.ok) { dialog, _ -> dialog.dismiss() } if (resultType == ImportExportResultType.Success) { - final CharSequence sendLabel = ImportExportActivity.this.getResources().getText(R.string.sendLabel); + val sendLabel = this@ImportExportActivity.resources.getText(R.string.sendLabel) - builder.setPositiveButton(sendLabel, (dialog, which) -> { - Intent sendIntent = new Intent(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_STREAM, path); - sendIntent.setType("text/csv"); + builder.setPositiveButton(sendLabel) { dialog, _ -> + val sendIntent = Intent(Intent.ACTION_SEND).apply { + putExtra(Intent.EXTRA_STREAM, path) + type = "text/csv" + // set flag to give temporary permission to external app to use the FileProvider + flags = Intent.FLAG_GRANT_READ_URI_PERMISSION + } - // set flag to give temporary permission to external app to use the FileProvider - sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - - ImportExportActivity.this.startActivity(Intent.createChooser(sendIntent, - sendLabel)); - - dialog.dismiss(); - }); + this@ImportExportActivity.startActivity(Intent.createChooser(sendIntent, sendLabel)) + dialog.dismiss() + } } - builder.create().show(); + builder.create().show() } } \ No newline at end of file diff --git a/app/src/test/java/protect/card_locker/ImportExportActivityTest.kt b/app/src/test/java/protect/card_locker/ImportExportActivityTest.kt index bf5f937e0..ecd5cc240 100644 --- a/app/src/test/java/protect/card_locker/ImportExportActivityTest.kt +++ b/app/src/test/java/protect/card_locker/ImportExportActivityTest.kt @@ -1,69 +1,77 @@ -package protect.card_locker; +package protect.card_locker -import static org.junit.Assert.assertEquals; -import static org.robolectric.Shadows.shadowOf; +import android.app.Activity +import android.content.Intent +import android.content.pm.ActivityInfo +import android.content.pm.ApplicationInfo +import android.content.pm.ResolveInfo +import android.view.View +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.Robolectric +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.Shadows.shadowOf -import android.app.Activity; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.view.View; +@RunWith(RobolectricTestRunner::class) +class ImportExportActivityTest { -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.Robolectric; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -@RunWith(RobolectricTestRunner.class) -public class ImportExportActivityTest { - private void registerIntentHandler(String handler) { + private fun registerIntentHandler(handler: String) { // Add something that will 'handle' the given intent type - PackageManager packageManager = RuntimeEnvironment.application.getPackageManager(); + val packageManager = RuntimeEnvironment.application.packageManager - ResolveInfo info = new ResolveInfo(); - info.isDefault = true; - - ApplicationInfo applicationInfo = new ApplicationInfo(); - applicationInfo.packageName = "does.not.matter"; - info.activityInfo = new ActivityInfo(); - info.activityInfo.applicationInfo = applicationInfo; - info.activityInfo.name = "DoesNotMatter"; - info.activityInfo.exported = true; - - Intent intent = new Intent(handler); - - if (handler.equals(Intent.ACTION_GET_CONTENT)) { - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType("*/*"); + val info = ResolveInfo().apply { + isDefault = true + activityInfo = ActivityInfo().apply { + applicationInfo = ApplicationInfo().apply { + packageName = "does.not.matter" + } + name = "DoesNotMatter" + exported = true + } } - shadowOf(packageManager).addResolveInfoForIntent(intent, info); + val intent = Intent(handler) + + if (handler == Intent.ACTION_GET_CONTENT) { + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.type = "*/*" + } + + shadowOf(packageManager).addResolveInfoForIntent(intent, info) } - private void checkVisibility(Activity activity, int state, int divider, int title, int message, int button) { - View dividerView = activity.findViewById(divider); - View titleView = activity.findViewById(title); - View messageView = activity.findViewById(message); - View buttonView = activity.findViewById(button); + private fun checkVisibility( + activity: Activity, + state: Int, + divider: Int, + title: Int, + message: Int, + button: Int + ) { + val dividerView = activity.findViewById(divider) + val titleView = activity.findViewById(title) + val messageView = activity.findViewById(message) + val buttonView = activity.findViewById(button) - assertEquals(state, dividerView.getVisibility()); - assertEquals(state, titleView.getVisibility()); - assertEquals(state, messageView.getVisibility()); - assertEquals(state, buttonView.getVisibility()); + assertEquals(state, dividerView.visibility) + assertEquals(state, titleView.visibility) + assertEquals(state, messageView.visibility) + assertEquals(state, buttonView.visibility) } @Test - public void testAllOptionsAvailable() { - registerIntentHandler(Intent.ACTION_PICK); - registerIntentHandler(Intent.ACTION_GET_CONTENT); + fun testAllOptionsAvailable() { + registerIntentHandler(Intent.ACTION_PICK) + registerIntentHandler(Intent.ACTION_GET_CONTENT) - Activity activity = Robolectric.setupActivity(ImportExportActivity.class); + val activity = Robolectric.setupActivity(ImportExportActivity::class.java) - checkVisibility(activity, View.VISIBLE, R.id.dividerImportFilesystem, - R.id.importOptionFilesystemTitle, R.id.importOptionFilesystemExplanation, - R.id.importOptionFilesystemButton); + checkVisibility( + activity, View.VISIBLE, R.id.dividerImportFilesystem, + R.id.importOptionFilesystemTitle, R.id.importOptionFilesystemExplanation, + R.id.importOptionFilesystemButton + ) } -} +} \ No newline at end of file