Add ability to select file to import

To date the only way to import a file is to hope the exported file
is in the correct location. To remove this requirement, this change
checks if there are any activities available on the system which
can look in the file system. If there are, new options for
importing data will be available.

In addition, the import/export activity's layout was updated,
adding more explanation.

Translation contributions:
Clonewayx: cs
PanderMusubi: nl
pbeckmann: de
Airon90: it
arno-github : fr
This commit is contained in:
Branden Archer
2017-01-16 22:05:14 -05:00
parent 89f649e5ef
commit eeb41376c5
13 changed files with 722 additions and 121 deletions

View File

@@ -1,25 +1,42 @@
package protect.card_locker;
import android.Manifest;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.io.File;
import java.util.List;
public class ImportExportActivity extends AppCompatActivity
{
private static final int PERMISSIONS_EXTERNAL_STORAGE_IMPORT = 1;
private static final int PERMISSIONS_EXTERNAL_STORAGE_EXPORT = 2;
private static final String TAG = "LoyaltyCardLocker";
ImportExportTask importExporter;
private static final int PERMISSIONS_EXTERNAL_STORAGE = 1;
private static final int CHOOSE_EXPORT_FILE = 2;
private ImportExportTask importExporter;
private final File sdcardDir = Environment.getExternalStorageDirectory();
private final File exportFile = new File(sdcardDir, "LoyaltyCardLocker.csv");
@Override
protected void onCreate(Bundle savedInstanceState)
@@ -34,25 +51,20 @@ public class ImportExportActivity extends AppCompatActivity
actionBar.setDisplayHomeAsUpEnabled(true);
}
Button importButton = (Button)findViewById(R.id.importButton);
importButton.setOnClickListener(new View.OnClickListener()
// If the application does not have permissions to external
// storage, as for it now
if (ContextCompat.checkSelfPermission(ImportExportActivity.this,
Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(ImportExportActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
{
@Override
public void onClick(View v)
{
if (ContextCompat.checkSelfPermission(ImportExportActivity.this,
Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
{
startImport();
}
else
{
ActivityCompat.requestPermissions(ImportExportActivity.this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
PERMISSIONS_EXTERNAL_STORAGE_IMPORT);
}
}
});
ActivityCompat.requestPermissions(ImportExportActivity.this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSIONS_EXTERNAL_STORAGE);
}
Button exportButton = (Button)findViewById(R.id.exportButton);
exportButton.setOnClickListener(new View.OnClickListener()
@@ -60,56 +72,120 @@ public class ImportExportActivity extends AppCompatActivity
@Override
public void onClick(View v)
{
if (ContextCompat.checkSelfPermission(ImportExportActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
{
startExport();
}
else
{
ActivityCompat.requestPermissions(ImportExportActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSIONS_EXTERNAL_STORAGE_EXPORT);
}
startExport();
}
});
// Check that there is an activity that can bring up a file chooser
final Intent intentPickAction = new Intent(Intent.ACTION_PICK);
intentPickAction.setData(Uri.parse("file://"));
Button importFilesystem = (Button) findViewById(R.id.importOptionFilesystemButton);
importFilesystem.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
chooseFileWithIntent(intentPickAction);
}
});
if(isCallable(getApplicationContext(), intentPickAction) == false)
{
findViewById(R.id.dividerImportFilesystem).setVisibility(View.GONE);
findViewById(R.id.importOptionFilesystemTitle).setVisibility(View.GONE);
findViewById(R.id.importOptionFilesystemExplanation).setVisibility(View.GONE);
importFilesystem.setVisibility(View.GONE);
}
// Check that there is an application that can find content
final Intent intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT);
intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE);
intentGetContentAction.setType("*/*");
Button importApplication = (Button) findViewById(R.id.importOptionApplicationButton);
importApplication.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
chooseFileWithIntent(intentGetContentAction);
}
});
if(isCallable(getApplicationContext(), intentGetContentAction) == false)
{
findViewById(R.id.dividerImportApplication).setVisibility(View.GONE);
findViewById(R.id.importOptionApplicationTitle).setVisibility(View.GONE);
findViewById(R.id.importOptionApplicationExplanation).setVisibility(View.GONE);
importApplication.setVisibility(View.GONE);
}
// This option, to import from the fixed location, should always be present
Button importButton = (Button)findViewById(R.id.importOptionFixedButton);
importButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
startImport(exportFile);
}
});
}
private void startImport()
private void startImport(File target)
{
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
{
@Override
public void onTaskComplete(boolean success, File file)
{
onImportComplete(success, file);
}
};
importExporter = new ImportExportTask(ImportExportActivity.this,
true, DataFormat.CSV);
true, DataFormat.CSV, target, listener);
importExporter.execute();
}
private void startExport()
{
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
{
@Override
public void onTaskComplete(boolean success, File file)
{
onExportComplete(success, file);
}
};
importExporter = new ImportExportTask(ImportExportActivity.this,
false, DataFormat.CSV);
false, DataFormat.CSV, exportFile, listener);
importExporter.execute();
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults)
{
if(requestCode == PERMISSIONS_EXTERNAL_STORAGE_IMPORT ||
requestCode == PERMISSIONS_EXTERNAL_STORAGE_EXPORT)
if(requestCode == PERMISSIONS_EXTERNAL_STORAGE)
{
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 &&
grantResults[0] == PackageManager.PERMISSION_GRANTED)
boolean success = grantResults.length > 0;
for(int grant : grantResults)
{
// permission was granted.
if(requestCode == PERMISSIONS_EXTERNAL_STORAGE_IMPORT)
if(grant != PackageManager.PERMISSION_GRANTED)
{
startImport();
}
else
{
startExport();
success = false;
}
}
else
if(success == false)
{
// External storage permission rejected, inform user that
// import/export is prevented
@@ -143,4 +219,137 @@ public class ImportExportActivity extends AppCompatActivity
return super.onOptionsItemSelected(item);
}
private void onImportComplete(boolean success, File path)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
if(success)
{
builder.setTitle(R.string.importSuccessfulTitle);
}
else
{
builder.setTitle(R.string.importFailedTitle);
}
int messageId = success ? R.string.importedFrom : R.string.importFailed;
final String template = getResources().getString(messageId);
final String message = String.format(template, path.getAbsolutePath());
builder.setMessage(message);
builder.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
}
});
builder.create().show();
}
private void onExportComplete(boolean success, File path)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
if(success)
{
builder.setTitle(R.string.exportSuccessfulTitle);
}
else
{
builder.setTitle(R.string.exportFailedTitle);
}
int messageId = success ? R.string.exportedTo : R.string.exportFailed;
final String template = getResources().getString(messageId);
final String message = String.format(template, path.getAbsolutePath());
builder.setMessage(message);
builder.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
}
});
builder.create().show();
}
/**
* Determines if there is at least one activity that can perform the given intent
*/
private boolean isCallable(Context context, final Intent intent)
{
PackageManager manager = context.getPackageManager();
if(manager == null)
{
return false;
}
List<ResolveInfo> list = manager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for(ResolveInfo info : list)
{
if(info.activityInfo.exported)
{
// There is one activity which is available to be called
return true;
}
}
return false;
}
private void chooseFileWithIntent(Intent intent)
{
try
{
startActivityForResult(intent, CHOOSE_EXPORT_FILE);
}
catch (ActivityNotFoundException e)
{
Log.e(TAG, "No activity found to handle intent", e);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && requestCode == CHOOSE_EXPORT_FILE)
{
String path = null;
Uri uri = data.getData();
if(uri != null && uri.toString().startsWith("/"))
{
uri = Uri.parse("file://" + uri.toString());
}
if(uri != null)
{
path = uri.getPath();
}
if(path != null)
{
Log.e(TAG, "Starting file import with: " + uri.toString());
startImport(new File(path));
}
else
{
Log.e(TAG, "Fail to make sense of URI returned from activity: " + (uri != null ? uri.toString() : "null"));
}
}
else
{
Log.w(TAG, "Failed onActivityResult(), result=" + resultCode);
}
}
}

View File

@@ -16,49 +16,31 @@ import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
class ImportExportTask extends AsyncTask<Void, Void, Void>
class ImportExportTask extends AsyncTask<Void, Void, Boolean>
{
private static final String TAG = "LoyaltyCardLocker";
private static final String TARGET_FILE = "LoyaltyCardLocker.csv";
private Activity activity;
private boolean doImport;
private DataFormat format;
private File target;
private TaskCompleteListener listener;
private ProgressDialog progress;
public ImportExportTask(Activity activity, boolean doImport, DataFormat format)
public ImportExportTask(Activity activity, boolean doImport, DataFormat format, File target,
TaskCompleteListener listener)
{
super();
this.activity = activity;
this.doImport = doImport;
this.format = format;
this.target = target;
this.listener = listener;
}
private void toastWithArg(int stringId, String argument)
private boolean performImport(File importFile, DBHelper db)
{
final String template = activity.getResources().getString(stringId);
final String message = String.format(template, argument);
activity.runOnUiThread(new Runnable()
{
@Override
public void run()
{
Toast.makeText(activity, message, Toast.LENGTH_LONG).show();
}
});
}
private void performImport(File importFile, DBHelper db)
{
if(importFile.exists() == false)
{
toastWithArg(R.string.fileMissing, importFile.getAbsolutePath());
return;
}
boolean result = false;
try
@@ -73,11 +55,12 @@ class ImportExportTask extends AsyncTask<Void, Void, Void>
Log.e(TAG, "Unable to import file", e);
}
int messageId = result ? R.string.importedFrom : R.string.importFailed;
toastWithArg(messageId, importFile.getAbsolutePath());
Log.i(TAG, "Import of '" + importFile.getAbsolutePath() + "' result: " + result);
return result;
}
private void performExport(File exportFile, DBHelper db)
private boolean performExport(File exportFile, DBHelper db)
{
boolean result = false;
@@ -93,8 +76,7 @@ class ImportExportTask extends AsyncTask<Void, Void, Void>
Log.e(TAG, "Unable to export file", e);
}
int messageId = result ? R.string.exportedTo : R.string.exportFailed;
toastWithArg(messageId, exportFile.getAbsolutePath());
return result;
}
protected void onPreExecute()
@@ -114,26 +96,27 @@ class ImportExportTask extends AsyncTask<Void, Void, Void>
progress.show();
}
protected Void doInBackground(Void... nothing)
protected Boolean doInBackground(Void... nothing)
{
final File sdcardDir = Environment.getExternalStorageDirectory();
final File importExportFile = new File(sdcardDir, TARGET_FILE);
final DBHelper db = new DBHelper(activity);
boolean result;
if(doImport)
{
performImport(importExportFile, db);
result = performImport(target, db);
}
else
{
performExport(importExportFile, db);
result = performExport(target, db);
}
return null;
return result;
}
protected void onPostExecute(Void result)
protected void onPostExecute(Boolean result)
{
listener.onTaskComplete(result, target);
progress.dismiss();
Log.i(TAG, (doImport ? "Import" : "Export") + " Complete");
}
@@ -143,4 +126,9 @@ class ImportExportTask extends AsyncTask<Void, Void, Void>
progress.dismiss();
Log.i(TAG, (doImport ? "Import" : "Export") + " Cancelled");
}
interface TaskCompleteListener
{
void onTaskComplete(boolean success, File file);
}
}

View File

@@ -19,36 +19,149 @@
</android.support.design.widget.AppBarLayout>
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
android:textSize="20sp"
android:layout_gravity="center"
android:text="@string/importExportHelp"/>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout android:orientation="horizontal"
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingBottom="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/importName"
android:layout_weight="1.0"
android:id="@+id/importButton"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/exportName"
android:layout_weight="1.0"
android:id="@+id/exportButton"/>
</LinearLayout>
android:textSize="@dimen/text_size_medium"
android:layout_gravity="center"
android:text="@string/importExportHelp"/>
</LinearLayout>
<View
android:id="@+id/dividerExport"
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_margin="16dp"
android:background="?android:attr/listDivider"/>
<TextView
android:id="@+id/exportOptionTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/text_size_large"
android:text="@string/exportName"/>
<TextView
android:id="@+id/exportOptionExplanation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/text_size_medium"
android:text="@string/exportOptionExplanation"/>
<Button
android:id="@+id/exportButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:text="@string/exportName" />
<View
android:id="@+id/dividerImportFilesystem"
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_margin="16dp"
android:background="?android:attr/listDivider"/>
<TextView
android:id="@+id/importOptionFilesystemTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/text_size_large"
android:text="@string/importOptionFilesystemTitle"/>
<TextView
android:id="@+id/importOptionFilesystemExplanation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/text_size_medium"
android:text="@string/importOptionFilesystemExplanation"/>
<Button
android:id="@+id/importOptionFilesystemButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:text="@string/importOptionFilesystemButton" />
<View
android:id="@+id/dividerImportApplication"
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_margin="16dp"
android:background="?android:attr/listDivider"/>
<TextView
android:id="@+id/importOptionApplicationTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/text_size_large"
android:text="@string/importOptionApplicationTitle"/>
<TextView
android:id="@+id/importOptionApplicationExplanation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/text_size_medium"
android:text="@string/importOptionApplicationExplanation"/>
<Button
android:id="@+id/importOptionApplicationButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:text="Use external application" />
<View
android:id="@+id/dividerImportFixed"
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_margin="16dp"
android:background="?android:attr/listDivider"/>
<TextView
android:id="@+id/importOptionFixedTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/text_size_large"
android:text="@string/importOptionFixedTitle"/>
<TextView
android:id="@+id/importOptionFixedExplanation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/text_size_medium"
android:text="@string/importOptionFixedExplanation"/>
<Button
android:id="@+id/importOptionFixedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:text="@string/importOptionFixedButton" />
</LinearLayout>
</ScrollView>
</android.support.design.widget.CoordinatorLayout>

View File

@@ -38,7 +38,6 @@
<string name="importExport">Import/Export</string>
<string name="importName">Import</string>
<string name="exportName">Export</string>
<string name="importExportHelp">Data jsou importována do/z LoyaltyCardLocker.csv na externí úložišti.</string>
<string name="importedFrom">Importováno z: %1$s</string>
<string name="exportedTo">Exportováno do: %1$s</string>
<string name="fileMissing">Doubor chybí: %1$s</string>
@@ -60,5 +59,19 @@
<string name="enterBarcodeInstructions">Zadejte hodnotu čárového kódu a potm vyberte kód, který představuje čárový kód, který je na kartě.</string>
<string name="copy_to_clipboard_toast">ID karty zkopírováno do schránky</string>
<string name="importExportHelp">Zálohování dat vám umožní přesunout vaše uložené karty na jiné zařízení.</string>
<string name="importSuccessfulTitle">Import proběhl úspěšně</string>
<string name="importFailedTitle">Import selhal</string>
<string name="exportSuccessfulTitle">Export proběhl úspěšně</string>
<string name="exportFailedTitle">Export selhal</string>
<string name="exportOptionExplanation">Data jsou zapsána do kořenové složky externího uložiště.</string>
<string name="importOptionFilesystemTitle">Import ze souborového systému</string>
<string name="importOptionFilesystemExplanation">Vyberte konkrétní soubor v uložišti.</string>
<string name="importOptionFilesystemButton">Ze souborového systému</string>
<string name="importOptionApplicationTitle">Import ze souborového systému</string>
<string name="importOptionApplicationExplanation">K otevření souboru použije externí aplikaci jako Dropbox, Google Drive, nebo vámi preferovaný prohlížeč souborů.</string>
<string name="importOptionApplicationButton">Použít externí aplikaci</string>
<string name="importOptionFixedTitle">Import z umístění exportu</string>
<string name="importOptionFixedExplanation">Import ze stejné složky souborového systému do níž se zapisuje při exportu.</string>
<string name="importOptionFixedButton">Použít složku exportu</string>
</resources>

View File

@@ -45,7 +45,22 @@
<string name="debug_version_fmt">Version: <xliff:g id="version">%s</xliff:g></string>
<string name="enterBarcodeInstructions">Füge die Kundennummer ein, anschließend wähle die korrekte Barcodeart aus.</string>
<string name="app_revision_fmt">Versions Information: <xliff:g id="app_revision_url">%s</xliff:g></string>
<string name="importExportHelp">Daten werden aus LoyalityCardLocker.csv aus dem externen Speicher im-/exportiert.</string>
<string name="importing">Importiere…</string>
<string name="exporting">Exportiere…</string>
<string name="importExportHelp">Gesicherte Daten ermöglichen das Verschieben der Kundenkarten auf ein anderes Gerät.</string>
<string name="importSuccessfulTitle">Import erfolgreich</string>
<string name="importFailedTitle">Import fehlgeschlagen</string>
<string name="exportSuccessfulTitle">Export erfolgreich</string>
<string name="exportFailedTitle">Export fehlgeschlagen</string>
<string name="exportOptionExplanation">Die Datei wird ins Wurzelverzeichnis des externen Speichers geschrieben.</string>
<string name="importOptionFilesystemTitle">Importiere vom Dateisystem</string>
<string name="importOptionFilesystemExplanation">Wähle eine Datei im Speicher aus.</string>
<string name="importOptionFilesystemButton">Vom Dateisystem</string>
<string name="importOptionApplicationTitle">Importiere vom Dateisystem</string>
<string name="importOptionApplicationExplanation">Wähle eine Datei aus einer App wie Dropbox, Google Drive, oder deinem bevorzugten Dateisystem aus.</string>
<string name="importOptionApplicationButton">Nutze eine externe App</string>
<string name="importOptionFixedTitle">Importiere vom Export Ort</string>
<string name="importOptionFixedExplanation">Importiere von derselben Stelle, wo die exportiere Datei liegen würde.</string>
<string name="importOptionFixedButton">Verwende die exportierte Stelle</string>
</resources>

View File

@@ -38,7 +38,6 @@
<string name="importExport">Importer/Exporter</string>
<string name="importName">Importer</string>
<string name="exportName">Exporter</string>
<string name="importExportHelp">Les informations des cartes sont importées depuis/exportées vers le fichier LoyaltyCardLocker.csv sur le stockage externe</string>
<string name="importedFrom">Importé depuis : %1$s</string>
<string name="exportedTo">Exporté vers : %1$s</string>
<string name="fileMissing">Fichier manquant : %1$s</string>
@@ -61,4 +60,19 @@
<string name="copy_to_clipboard_toast">Numéro de carte copié dans le presse-papier</string>
<string name="importExportHelp">Exporter vos données vous permet de récupérer vos cartes sur un autre appareil.</string>
<string name="importSuccessfulTitle">Importé avec succès</string>
<string name="importFailedTitle">Échec de l\'import</string>
<string name="exportSuccessfulTitle">Exporté avec succès</string>
<string name="exportFailedTitle">Échec de l\'export</string>
<string name="exportOptionExplanation">Les données sont sauvegardées à la racine du stockage externe.</string>
<string name="importOptionFilesystemTitle">Importer depuis le système de fichiers.</string>
<string name="importOptionFilesystemExplanation">Choisissez le fichier à importer.</string>
<string name="importOptionFilesystemButton">Système de fichiers</string>
<string name="importOptionApplicationTitle">Importer depuis le système de fichiers</string>
<string name="importOptionApplicationExplanation">Utilisez une application externe comme Dropbox, Google Drive, ou votre gestionnaire de fichiers favori pour ouvrir un fichier.</string>
<string name="importOptionApplicationButton">Application externe</string>
<string name="importOptionFixedTitle">Importer depuis le même emplacement que pour l\'export</string>
<string name="importOptionFixedExplanation">Importe les données depuis le même emplacement que celui défini pour l\'export.</string>
<string name="importOptionFixedButton">Utiliser l\'emplacement de l\'export</string>
</resources>

View File

@@ -32,7 +32,6 @@
<string name="importExport">Importa/Esporta</string>
<string name="importName">Importa</string>
<string name="exportName">Esporta</string>
<string name="importExportHelp">I dati sono stati importati in o esportati dal file LoyaltyCardLocker.csv sulla memoria esterna</string>
<string name="importedFrom">Importato da: %1$s</string>
<string name="exportedTo">Esportato in: %1$s</string>
<string name="fileMissing">File mancante: %1$s</string>
@@ -57,4 +56,19 @@
<string name="confirm">Conferma</string>
<string name="deleteTitle">Rimuovi carta fedeltà</string>
<string name="deleteConfirmation">Conferma che vuoi eliminare questa carta.</string>
<string name="importExportHelp">Fare il backup dei dati ti permette di spostare le tue tessere da un dispositivo ad un altro.</string>
<string name="importSuccessfulTitle">Importazione avvenuta con successo</string>
<string name="importFailedTitle">Importazione fallita</string>
<string name="exportSuccessfulTitle">Esportazione avvenuta con successo</string>
<string name="exportFailedTitle">Esportazione fallita</string>
<string name="exportOptionExplanation">I dati sono stati scritti nella cartella principale della memoria esterna.</string>
<string name="importOptionFilesystemTitle">Importa dal file system</string>
<string name="importOptionFilesystemExplanation">Scegli un file dal file system.</string>
<string name="importOptionFilesystemButton">Dal file system</string>
<string name="importOptionApplicationTitle">Importa dal file system</string>
<string name="importOptionApplicationExplanation">Usa un\'applicazione esterna come Dropbox, Google Drive o il tuo file manager preferito per aprire il file.</string>
<string name="importOptionApplicationButton">Usa un\'applicazione esterna</string>
<string name="importOptionFixedTitle">Importa da un altro posto</string>
<string name="importOptionFixedExplanation">Importa dallo stesso posto del file system dove si è esportato.</string>
<string name="importOptionFixedButton">Usa luogo dell\'esportazione</string>
</resources>

View File

@@ -38,7 +38,6 @@
<string name="importExport">Importuoti/Exportuoti</string>
<string name="importName">Importuoti</string>
<string name="exportName">Exportuoti</string>
<string name="importExportHelp">Duomenys importuoti į/eksportruoti iš LoyaltyCardLocker.csv išorinėje atmintyje</string>
<string name="importedFrom">Importuota iš: %1$s</string>
<string name="exportedTo">Eksportuota į: %1$s</string>
<string name="fileMissing">Failas nerastas: %1$s</string>
@@ -61,4 +60,19 @@
<string name="copy_to_clipboard_toast">Kortelės ID nukopijuota į iškarpinę</string>
<!-- needs translated --><string name="importExportHelp">Backed up data can allow you to move your cards to another device.</string>
<!-- needs translated --><string name="importSuccessfulTitle">Import successful</string>
<!-- needs translated --><string name="importFailedTitle">Import failed</string>
<!-- needs translated --><string name="exportSuccessfulTitle">Export successful</string>
<!-- needs translated --><string name="exportFailedTitle">Export failed</string>
<!-- needs translated --><string name="exportOptionExplanation">Data is written to the top directory in external storage.</string>
<!-- needs translated --><string name="importOptionFilesystemTitle">Import from filesystem</string>
<!-- needs translated --><string name="importOptionFilesystemExplanation">Choose a specific file from the filesystem.</string>
<!-- needs translated --><string name="importOptionFilesystemButton">From filesystem</string>
<!-- needs translated --><string name="importOptionApplicationTitle">Import from filesystem</string>
<!-- needs translated --><string name="importOptionApplicationExplanation">Use an external application like Dropbox, Google Drive, or your favorite file manager to open a file.</string>
<!-- needs translated --><string name="importOptionApplicationButton">Use external application</string>
<!-- needs translated --><string name="importOptionFixedTitle">Import from export location</string>
<!-- needs translated --><string name="importOptionFixedExplanation">Import from the same location on the filesystem that is written to on export.</string>
<!-- needs translated --><string name="importOptionFixedButton">Use export location</string>
</resources>

View File

@@ -32,7 +32,6 @@
<string name="importExport">Importeer/Exporteer</string>
<string name="importName">Importeer</string>
<string name="exportName">Exporteer</string>
<string name="importExportHelp">Data is geïmporteerd van of geëxporteerd naar LoyaltyCardLocker.csv op externe opslag</string>
<string name="importedFrom">Geïmporteerd van: %1$s</string>
<string name="exportedTo">Geëxporteerd naar: %1$s</string>
<string name="fileMissing">Bestand niet gevonden: %1$s</string>
@@ -57,4 +56,20 @@
<string name="confirm">Bevestig</string>
<string name="deleteConfirmation">Bevestig deze kaart te verwijderen.</string>
<string name="deleteTitle">Verwijder kaart</string>
<string name="importExportHelp">Data die is geback-upt maakt het mogelijk om je klantenkaarten naar een ander apparaat te verplaatsen.</string>
<string name="importSuccessfulTitle">Importeren succesvol</string>
<string name="importFailedTitle">Importeren mislukte</string>
<string name="exportSuccessfulTitle">Exporteren succesvol</string>
<string name="exportFailedTitle">Exporteren mislukt</string>
<string name="exportOptionExplanation">Data is weggeschreven naar de hoogste map in externe opslag.</string>
<string name="importOptionFilesystemTitle">Importeer van filesysteem</string>
<string name="importOptionFilesystemExplanation">Kies een specifiek bestand van het filesysteem.</string>
<string name="importOptionFilesystemButton">Van filesysteem</string>
<string name="importOptionApplicationTitle">Importeer van filesysteem</string>
<string name="importOptionApplicationExplanation">Gebruik een externe applicatie zoals Dropbox, Google Drive of je favoriete bestandsbeheer om een bestand te openen.</string>
<string name="importOptionApplicationButton">Gebruik externe applicatie</string>
<string name="importOptionFixedTitle">Importeer van exporteerlocatie</string>
<string name="importOptionFixedExplanation">Importeer van zelfde locatie op het filesysteem waar tijdens exporteren naar geschreven is.</string>
<string name="importOptionFixedButton">gebruik exporteerlocaite</string>
</resources>

View File

@@ -7,4 +7,8 @@
<dimen name="no_data_padding">22dp</dimen>
<dimen name="barcode_disp_height">200dp</dimen>
<dimen name="text_size_medium">18sp</dimen>
<dimen name="text_size_large">22sp</dimen>
</resources>

View File

@@ -39,15 +39,29 @@
<string name="importExport">Import/Export</string>
<string name="importName">Import</string>
<string name="exportName">Export</string>
<string name="importExportHelp">Data is imported to/exported from LoyaltyCardLocker.csv on external storage</string>
<string name="importExportHelp">Backed up data can allow you to move your cards to another device.</string>
<string name="importedFrom">Imported from: %1$s</string>
<string name="exportedTo">Exported to: %1$s</string>
<string name="fileMissing">File missing: %1$s</string>
<string name="importSuccessfulTitle">Import successful</string>
<string name="importFailedTitle">Import failed</string>
<string name="importFailed">Failed to import: %1$s</string>
<string name="exportSuccessfulTitle">Export successful</string>
<string name="exportFailedTitle">Export failed</string>
<string name="exportFailed">Failed to export: %1$s</string>
<string name="importing">Importing&#8230;</string>
<string name="exporting">Exporting&#8230;</string>
<string name="noExternalStoragePermissionError">Unable to import or export cards without the external storage permission</string>
<string name="exportOptionExplanation">Data is written to the top directory in external storage.</string>
<string name="importOptionFilesystemTitle">Import from filesystem</string>
<string name="importOptionFilesystemExplanation">Choose a specific file from the filesystem.</string>
<string name="importOptionFilesystemButton">From filesystem</string>
<string name="importOptionApplicationTitle">Import from filesystem</string>
<string name="importOptionApplicationExplanation">Use an external application like Dropbox, Google Drive, or your favorite file manager to open a file.</string>
<string name="importOptionApplicationButton">Use external application</string>
<string name="importOptionFixedTitle">Import from export location</string>
<string name="importOptionFixedExplanation">Import from the same location on the filesystem that is written to on export.</string>
<string name="importOptionFixedButton">Use export location</string>
<string name="about">About</string>
<string name="app_copyright_fmt">Copyright 2016-<xliff:g>%s</xliff:g> Branden Archer</string>

View File

@@ -0,0 +1,154 @@
package protect.card_locker;
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.net.Uri;
import android.view.View;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.res.builder.RobolectricPackageManager;
import static org.robolectric.Shadows.shadowOf;
import static org.junit.Assert.assertEquals;
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 17)
public class ImportExportActivityTest
{
private void registerIntentHandler(String handler)
{
// Add something that will 'handle' the given intent type
RobolectricPackageManager packageManager = (RobolectricPackageManager) shadowOf(
RuntimeEnvironment.application).getPackageManager();
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_PICK))
{
intent.setData(Uri.parse("file://"));
}
if(handler.equals(Intent.ACTION_GET_CONTENT))
{
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
}
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);
assertEquals(state, dividerView.getVisibility());
assertEquals(state, titleView.getVisibility());
assertEquals(state, messageView.getVisibility());
assertEquals(state, buttonView.getVisibility());
}
@Test
public void testImportFilesystemOption()
{
for(boolean isInstalled : new Boolean[]{false, true})
{
int visibility = isInstalled ? View.VISIBLE : View.GONE;
if(isInstalled)
{
registerIntentHandler(Intent.ACTION_PICK);
}
Activity activity = Robolectric.setupActivity(ImportExportActivity.class);
checkVisibility(activity, visibility, R.id.dividerImportFilesystem,
R.id.importOptionFilesystemTitle, R.id.importOptionFilesystemExplanation,
R.id.importOptionFilesystemButton);
// Should always be gone, as its provider is never installed
checkVisibility(activity, View.GONE, R.id.dividerImportApplication,
R.id.importOptionApplicationTitle, R.id.importOptionApplicationExplanation,
R.id.importOptionApplicationButton);
// Import from file system should always be present
checkVisibility(activity, View.VISIBLE, R.id.dividerImportFixed,
R.id.importOptionFixedTitle, R.id.importOptionFixedExplanation,
R.id.importOptionFixedButton);
}
}
@Test
public void testImportApplicationOption()
{
for(boolean isInstalled : new Boolean[]{false, true})
{
int visibility = isInstalled ? View.VISIBLE : View.GONE;
if(isInstalled)
{
registerIntentHandler(Intent.ACTION_GET_CONTENT);
}
Activity activity = Robolectric.setupActivity(ImportExportActivity.class);
checkVisibility(activity, visibility, R.id.dividerImportApplication,
R.id.importOptionApplicationTitle, R.id.importOptionApplicationExplanation,
R.id.importOptionApplicationButton);
// Should always be gone, as its provider is never installed
checkVisibility(activity, View.GONE, R.id.dividerImportFilesystem,
R.id.importOptionFilesystemTitle, R.id.importOptionFilesystemExplanation,
R.id.importOptionFilesystemButton);
// Import from file system should always be present
checkVisibility(activity, View.VISIBLE, R.id.dividerImportFixed,
R.id.importOptionFixedTitle, R.id.importOptionFixedExplanation,
R.id.importOptionFixedButton);
}
}
@Test
public void testAllOptionsAvailable()
{
registerIntentHandler(Intent.ACTION_PICK);
registerIntentHandler(Intent.ACTION_GET_CONTENT);
Activity activity = Robolectric.setupActivity(ImportExportActivity.class);
checkVisibility(activity, View.VISIBLE, R.id.dividerImportApplication,
R.id.importOptionApplicationTitle, R.id.importOptionApplicationExplanation,
R.id.importOptionApplicationButton);
checkVisibility(activity, View.VISIBLE, R.id.dividerImportFilesystem,
R.id.importOptionFilesystemTitle, R.id.importOptionFilesystemExplanation,
R.id.importOptionFilesystemButton);
checkVisibility(activity, View.VISIBLE, R.id.dividerImportFixed,
R.id.importOptionFixedTitle, R.id.importOptionFixedExplanation,
R.id.importOptionFixedButton);
}
}

View File

@@ -3,6 +3,7 @@ package protect.card_locker;
import android.app.Activity;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Environment;
import com.google.zxing.BarcodeFormat;
@@ -15,12 +16,14 @@ import org.robolectric.annotation.Config;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Calendar;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@RunWith(RobolectricGradleTestRunner.class)
@@ -205,32 +208,63 @@ public class ImportExportTest
}
}
class TestTaskCompleteListener implements ImportExportTask.TaskCompleteListener
{
Boolean success;
File file;
public void onTaskComplete(boolean success, File file)
{
this.success = success;
this.file = file;
}
}
@Test
public void useImportExportTask()
{
final int NUM_CARDS = 10;
final File sdcardDir = Environment.getExternalStorageDirectory();
final File exportFile = new File(sdcardDir, "LoyaltyCardLocker.csv");
for(DataFormat format : DataFormat.values())
{
addLoyaltyCards(NUM_CARDS);
// Export to whatever the default location is
ImportExportTask task = new ImportExportTask(activity, false, format);
TestTaskCompleteListener listener = new TestTaskCompleteListener();
// Export to the file
ImportExportTask task = new ImportExportTask(activity, false, format, exportFile, listener);
task.execute();
// Actually run the task to completion
Robolectric.flushBackgroundThreadScheduler();
// Check that the listener was executed
assertNotNull(listener.success);
assertEquals(true, listener.success);
assertNotNull(listener.file);
assertEquals(exportFile, listener.file);
clearDatabase();
// Import everything back from the default location
task = new ImportExportTask(activity, true, format);
listener = new TestTaskCompleteListener();
task = new ImportExportTask(activity, true, format, exportFile, listener);
task.execute();
// Actually run the task to completion
Robolectric.flushBackgroundThreadScheduler();
// Check that the listener was executed
assertNotNull(listener.success);
assertEquals(true, listener.success);
assertNotNull(listener.file);
assertEquals(exportFile, listener.file);
assertEquals(NUM_CARDS, db.getLoyaltyCardCount());
checkLoyaltyCards();