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.
This commit is contained in:
amlwin
2025-10-06 11:34:45 +08:00
parent 622ea37554
commit 303b40e572
2 changed files with 387 additions and 358 deletions

View File

@@ -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<Intent>
private lateinit var fileOpenLauncher: ActivityResultLauncher<String>
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<Intent> fileCreateLauncher;
private ActivityResultLauncher<String> 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<CharSequence>()
betaImportOptions.add("Fidme")
val importOptions = mutableListOf<CharSequence>()
List<CharSequence> betaImportOptions = new ArrayList<>();
betaImportOptions.add("Fidme");
List<CharSequence> 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()
}
}

View File

@@ -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<View>(divider)
val titleView = activity.findViewById<View>(title)
val messageView = activity.findViewById<View>(message)
val buttonView = activity.findViewById<View>(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
)
}
}
}