aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ime/app/src/main/AndroidManifest.xml15
-rw-r--r--ime/app/src/main/java/com/anysoftkeyboard/prefs/GlobalPrefsBackup.java20
-rw-r--r--ime/app/src/main/java/com/anysoftkeyboard/ui/FileExplorerCreate.java194
-rw-r--r--ime/app/src/main/java/com/anysoftkeyboard/ui/FileExplorerRestore.java133
-rw-r--r--ime/app/src/main/java/com/anysoftkeyboard/ui/settings/MainFragment.java167
-rw-r--r--ime/app/src/main/res/drawable-hdpi/ic_menu_add.pngbin0 -> 135 bytes
-rw-r--r--ime/app/src/main/res/drawable-hdpi/ic_menu_refresh.pngbin0 -> 707 bytes
-rw-r--r--ime/app/src/main/res/drawable-mdpi/ic_menu_add.pngbin0 -> 117 bytes
-rw-r--r--ime/app/src/main/res/drawable-mdpi/ic_menu_refresh.pngbin0 -> 448 bytes
-rw-r--r--ime/app/src/main/res/drawable-xhdpi/ic_menu_add.pngbin0 -> 148 bytes
-rw-r--r--ime/app/src/main/res/drawable-xhdpi/ic_menu_refresh.pngbin0 -> 915 bytes
-rw-r--r--ime/app/src/main/res/drawable-xxhdpi/ic_menu_add.pngbin0 -> 206 bytes
-rw-r--r--ime/app/src/main/res/drawable-xxhdpi/ic_menu_refresh.pngbin0 -> 1541 bytes
-rw-r--r--ime/app/src/main/res/drawable-xxxhdpi/ic_menu_add.pngbin0 -> 251 bytes
-rw-r--r--ime/app/src/main/res/drawable-xxxhdpi/ic_menu_refresh.pngbin0 -> 2070 bytes
-rw-r--r--ime/app/src/main/res/layout/file_explorer_create_main_ui.xml33
-rw-r--r--ime/app/src/main/res/layout/file_explorer_restore_main_ui.xml12
-rw-r--r--ime/app/src/main/res/layout/file_explorer_single_item.xml9
-rw-r--r--ime/app/src/main/res/menu/file_explorer_create_menu.xml10
-rw-r--r--ime/app/src/main/res/values/strings.xml14
-rw-r--r--ime/app/src/main/res/values/styles.xml13
-rw-r--r--ime/app/src/test/java/com/anysoftkeyboard/prefs/GlobalPrefsBackupTest.java39
-rw-r--r--ime/base/src/main/java/com/anysoftkeyboard/utils/XmlWriter.java5
-rw-r--r--ime/prefs/src/main/java/com/anysoftkeyboard/prefs/backup/PrefsXmlStorage.java31
24 files changed, 650 insertions, 45 deletions
diff --git a/ime/app/src/main/AndroidManifest.xml b/ime/app/src/main/AndroidManifest.xml
index a68ef5fb3..56f8a8a84 100644
--- a/ime/app/src/main/AndroidManifest.xml
+++ b/ime/app/src/main/AndroidManifest.xml
@@ -119,6 +119,21 @@
android:label="@string/ime_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
+
+ <!-- File explorer only when android version < 4.4 -->
+ <activity
+ android:name="com.anysoftkeyboard.ui.FileExplorerCreate"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/ime_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:theme="@style/Theme.AskFileExplorer" />
+
+ <activity
+ android:name="com.anysoftkeyboard.ui.FileExplorerRestore"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/ime_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:theme="@style/Theme.AskFileExplorer" />
</application>
</manifest>
diff --git a/ime/app/src/main/java/com/anysoftkeyboard/prefs/GlobalPrefsBackup.java b/ime/app/src/main/java/com/anysoftkeyboard/prefs/GlobalPrefsBackup.java
index fde93b79f..b9e9e136d 100644
--- a/ime/app/src/main/java/com/anysoftkeyboard/prefs/GlobalPrefsBackup.java
+++ b/ime/app/src/main/java/com/anysoftkeyboard/prefs/GlobalPrefsBackup.java
@@ -6,6 +6,7 @@ import android.support.annotation.StringRes;
import android.support.annotation.VisibleForTesting;
import android.support.v4.util.Pair;
import android.support.v7.preference.PreferenceManager;
+import com.anysoftkeyboard.base.utils.Logger;
import com.anysoftkeyboard.dictionaries.ExternalDictionaryFactory;
import com.anysoftkeyboard.dictionaries.prefsprovider.UserDictionaryPrefsProvider;
import com.anysoftkeyboard.dictionaries.sqlite.AbbreviationsDictionary;
@@ -28,6 +29,8 @@ import java.util.Map;
public class GlobalPrefsBackup {
@VisibleForTesting static final String GLOBAL_BACKUP_FILENAME = "AnySoftKeyboardPrefs.xml";
+ private static File customFilename = null;
+
public static List<ProviderDetails> getAllPrefsProviders(@NonNull Context context) {
return Arrays.asList(
new ProviderDetails(
@@ -49,6 +52,7 @@ public class GlobalPrefsBackup {
}
private static Boolean backupProvider(PrefsProvider provider, PrefsRoot prefsRoot) {
+ Logger.d("backupProvider", "BackupProvider is called");
final PrefsRoot providerRoot = provider.getPrefsRoot();
prefsRoot
.createChild()
@@ -106,7 +110,7 @@ public class GlobalPrefsBackup {
}
@NonNull
- private static Observable<ProviderDetails> doIt(
+ public static Observable<ProviderDetails> doIt(
Pair<List<ProviderDetails>, Boolean[]> enabledProviders,
Function<PrefsXmlStorage, PrefsRoot> prefsRootFactory,
BiConsumer<PrefsProvider, PrefsRoot> providerAction,
@@ -133,8 +137,20 @@ public class GlobalPrefsBackup {
prefsRoot -> prefsRootFinalizer.accept(storage, prefsRoot));
}
+ public static void updateCustomFilename(File filename) {
+ customFilename = filename;
+ }
+
public static File getBackupFile() {
- return AnyApplication.getBackupFile(GLOBAL_BACKUP_FILENAME);
+ File tempFilename;
+
+ if (customFilename == null) return AnyApplication.getBackupFile(GLOBAL_BACKUP_FILENAME);
+ else {
+ // We reset the customFilename
+ tempFilename = customFilename;
+ customFilename = null;
+ return tempFilename;
+ }
}
public static class ProviderDetails {
diff --git a/ime/app/src/main/java/com/anysoftkeyboard/ui/FileExplorerCreate.java b/ime/app/src/main/java/com/anysoftkeyboard/ui/FileExplorerCreate.java
new file mode 100644
index 000000000..88edf257e
--- /dev/null
+++ b/ime/app/src/main/java/com/anysoftkeyboard/ui/FileExplorerCreate.java
@@ -0,0 +1,194 @@
+package com.anysoftkeyboard.ui;
+
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.Environment;
+import android.support.v4.util.Pair;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageButton;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.anysoftkeyboard.base.utils.Logger;
+import com.anysoftkeyboard.prefs.GlobalPrefsBackup;
+import com.anysoftkeyboard.rx.RxSchedulers;
+import com.anysoftkeyboard.ui.settings.MainFragment;
+import com.menny.android.anysoftkeyboard.R;
+import io.reactivex.disposables.Disposable;
+import java.io.File;
+import net.evendanan.pixel.RxProgressDialog;
+
+public class FileExplorerCreate extends AppCompatActivity {
+ private ListView mListViewFiles;
+ private File mCurrentFolder;
+ private File mBasePath;
+
+ public void listFile(File basePath) {
+ File[] files = basePath.listFiles();
+ ArrayAdapter<File> adapter =
+ new ArrayAdapter<File>(this, R.layout.file_explorer_single_item, files);
+ mListViewFiles.setAdapter(adapter);
+
+ // Set onclickListener for all element of listView
+ mListViewFiles.setOnItemClickListener(
+ new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(
+ AdapterView<?> parent, View view, int position, long id) {
+ Object o = mListViewFiles.getItemAtPosition(position);
+ if (new File(o.toString()).isDirectory()) {
+ mCurrentFolder = new File(o.toString());
+ setTitle(o.toString());
+ listFile(mCurrentFolder);
+ } else if (new File(o.toString()).isFile())
+ create_builder(new File(o.toString()));
+ }
+ });
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (!mCurrentFolder.equals(mBasePath)) {
+ int sep = mCurrentFolder.toString().lastIndexOf("/");
+ setTitle(mCurrentFolder.toString().substring(0, sep));
+ mCurrentFolder = new File(mCurrentFolder.toString().substring(0, sep));
+ listFile(mCurrentFolder);
+ } else finish();
+ }
+
+ public void emptyFilenameError() {
+ AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
+ alertDialogBuilder.setMessage(R.string.file_explorer_filename_empty);
+ alertDialogBuilder.setPositiveButton(android.R.string.ok, null);
+ AlertDialog dialog = alertDialogBuilder.create();
+ dialog.show();
+ }
+
+ public Disposable launch_backup(String fileOutput) {
+ return RxProgressDialog.create(
+ new Pair<>(MainFragment.supportedProviders, MainFragment.checked),
+ this,
+ getText(R.string.take_a_while_progress_message),
+ R.layout.progress_window)
+ .subscribeOn(RxSchedulers.background())
+ .flatMap(GlobalPrefsBackup::backup)
+ .observeOn(RxSchedulers.mainThread())
+ .subscribe(
+ providerDetails ->
+ Logger.i(
+ "FileExplorerCreate",
+ "Finished backing up %s",
+ providerDetails.provider.providerId()),
+ e -> {
+ Logger.w(
+ "FileExplorerCreate",
+ e,
+ "Failed to do operation due to %s",
+ e.getMessage());
+ Toast.makeText(
+ getApplicationContext(),
+ this.getString(R.string.file_explorer_backup_failed),
+ Toast.LENGTH_LONG)
+ .show();
+ },
+ () ->
+ Toast.makeText(
+ getApplicationContext(),
+ this.getString(
+ R.string
+ .file_explorer_backup_success)
+ + fileOutput,
+ Toast.LENGTH_LONG)
+ .show());
+ }
+
+ public void create_builder(File outputFile) {
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.file_explorer_alert_title)
+ .setMessage(R.string.file_explorer_backup_alert_message)
+ .setPositiveButton(
+ android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ launch_backup(outputFile.toString());
+ finish();
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.file_explorer_create_menu, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.file_explorer_menu_add_folder:
+ new File(mCurrentFolder.toString() + "/askBackup").mkdir();
+ Toast.makeText(
+ getApplicationContext(),
+ "Folder askBackup has been created at " + mCurrentFolder.toString(),
+ Toast.LENGTH_LONG)
+ .show();
+ listFile(mCurrentFolder);
+ return true;
+ case R.id.file_explorer_menu_refresh:
+ listFile(mCurrentFolder);
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.file_explorer_create_main_ui);
+
+ TextView filenameTextView = (TextView) findViewById(R.id.file_explorer_filename);
+ ImageButton filenameButton = (ImageButton) findViewById(R.id.file_explorer_filename_button);
+ mListViewFiles = (ListView) findViewById(R.id.file_explorer_list_view);
+
+ mBasePath = Environment.getExternalStorageDirectory();
+
+ mCurrentFolder = mBasePath;
+
+ setTitle(mBasePath.toString());
+
+ listFile(mBasePath);
+
+ filenameButton.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (filenameTextView.length() > 0) {
+ final File fileOutput =
+ new File(
+ mCurrentFolder
+ + "/"
+ + filenameTextView.getText().toString()
+ + ".xml");
+
+ GlobalPrefsBackup.updateCustomFilename(fileOutput);
+ if (fileOutput.exists()) create_builder(fileOutput);
+ else {
+ launch_backup(fileOutput.toString());
+ finish();
+ }
+ } else emptyFilenameError();
+ }
+ });
+ }
+}
diff --git a/ime/app/src/main/java/com/anysoftkeyboard/ui/FileExplorerRestore.java b/ime/app/src/main/java/com/anysoftkeyboard/ui/FileExplorerRestore.java
new file mode 100644
index 000000000..3b84cd422
--- /dev/null
+++ b/ime/app/src/main/java/com/anysoftkeyboard/ui/FileExplorerRestore.java
@@ -0,0 +1,133 @@
+package com.anysoftkeyboard.ui;
+
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.Environment;
+import android.support.v4.util.Pair;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.Toast;
+import com.anysoftkeyboard.base.utils.Logger;
+import com.anysoftkeyboard.prefs.GlobalPrefsBackup;
+import com.anysoftkeyboard.rx.RxSchedulers;
+import com.anysoftkeyboard.ui.settings.MainFragment;
+import com.menny.android.anysoftkeyboard.R;
+import io.reactivex.disposables.Disposable;
+import java.io.File;
+import net.evendanan.pixel.RxProgressDialog;
+
+public class FileExplorerRestore extends AppCompatActivity {
+ private ListView mListViewFiles;
+ private File mBasePath;
+ private File mCurrentFolder;
+
+ private Disposable launch_restore(String fileName) {
+ return RxProgressDialog.create(
+ new Pair<>(MainFragment.supportedProviders, MainFragment.checked),
+ this,
+ getText(R.string.take_a_while_progress_message),
+ R.layout.progress_window)
+ .subscribeOn(RxSchedulers.background())
+ .flatMap(GlobalPrefsBackup::restore)
+ .observeOn(RxSchedulers.mainThread())
+ .subscribe(
+ providerDetails ->
+ Logger.i(
+ "FileExplorerRestore",
+ "Finished restore up %s",
+ providerDetails.provider.providerId()),
+ e -> {
+ Logger.w(
+ "FileExplorerRestore",
+ e,
+ "Failed to do operation due to %s",
+ e.getMessage());
+ Toast.makeText(
+ getApplicationContext(),
+ this.getString(R.string.file_explorer_restore_failed),
+ Toast.LENGTH_LONG)
+ .show();
+ },
+ () ->
+ Toast.makeText(
+ getApplicationContext(),
+ this.getString(
+ R.string
+ .file_explorer_restore_success)
+ + fileName,
+ Toast.LENGTH_LONG)
+ .show());
+ }
+
+ public void create_builder(File fileOutput) {
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.file_explorer_alert_title)
+ .setMessage(R.string.file_explorer_restore_alert_message)
+ .setPositiveButton(
+ android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ GlobalPrefsBackup.updateCustomFilename(fileOutput);
+ launch_restore(fileOutput.toString());
+ finish();
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show();
+ }
+
+ public void listFile(File basePath) {
+ File[] files = basePath.listFiles();
+ ArrayAdapter<File> adapter =
+ new ArrayAdapter<File>(this, R.layout.file_explorer_single_item, files);
+ mListViewFiles.setAdapter(adapter);
+
+ // Set onclickListener for all element of listView
+ mListViewFiles.setOnItemClickListener(
+ new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(
+ AdapterView<?> parent, View view, int position, long id) {
+ Object o = mListViewFiles.getItemAtPosition(position);
+ if (new File(o.toString()).isDirectory()) {
+ mCurrentFolder = new File(o.toString());
+ setTitle(o.toString());
+ listFile(mCurrentFolder);
+ } else if (new File(o.toString()).isFile())
+ create_builder(new File(o.toString()));
+ }
+ });
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (!mCurrentFolder.equals(mBasePath)) {
+ int sep = mCurrentFolder.toString().lastIndexOf("/");
+ setTitle(mCurrentFolder.toString().substring(0, sep));
+ mCurrentFolder = new File(mCurrentFolder.toString().substring(0, sep));
+ listFile(mCurrentFolder);
+ } else finish();
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.file_explorer_restore_main_ui);
+
+ mListViewFiles = (ListView) findViewById(R.id.file_explorer_list_view);
+
+ mBasePath = Environment.getExternalStorageDirectory();
+
+ mCurrentFolder = mBasePath;
+
+ setTitle(mBasePath.toString());
+
+ listFile(mBasePath);
+ }
+}
diff --git a/ime/app/src/main/java/com/anysoftkeyboard/ui/settings/MainFragment.java b/ime/app/src/main/java/com/anysoftkeyboard/ui/settings/MainFragment.java
index 265d4d9a5..7a40a1e6d 100644
--- a/ime/app/src/main/java/com/anysoftkeyboard/ui/settings/MainFragment.java
+++ b/ime/app/src/main/java/com/anysoftkeyboard/ui/settings/MainFragment.java
@@ -1,8 +1,11 @@
package com.anysoftkeyboard.ui.settings;
import android.Manifest;
+import android.app.Activity;
import android.content.ActivityNotFoundException;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
@@ -28,13 +31,17 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
+import android.widget.Toast;
import com.anysoftkeyboard.PermissionsRequestCodes;
import com.anysoftkeyboard.base.utils.Logger;
import com.anysoftkeyboard.keyboards.AnyKeyboard;
import com.anysoftkeyboard.keyboards.Keyboard;
import com.anysoftkeyboard.keyboards.views.DemoAnyKeyboardView;
import com.anysoftkeyboard.prefs.GlobalPrefsBackup;
+import com.anysoftkeyboard.prefs.backup.PrefsXmlStorage;
import com.anysoftkeyboard.rx.RxSchedulers;
+import com.anysoftkeyboard.ui.FileExplorerCreate;
+import com.anysoftkeyboard.ui.FileExplorerRestore;
import com.anysoftkeyboard.ui.settings.setup.SetUpKeyboardWizardFragment;
import com.anysoftkeyboard.ui.settings.setup.SetupSupport;
import com.anysoftkeyboard.ui.tutorials.ChangeLogFragment;
@@ -47,6 +54,9 @@ import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.Disposables;
import io.reactivex.functions.Function;
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.List;
import net.evendanan.chauffeur.lib.FragmentChauffeurActivity;
@@ -63,12 +73,21 @@ public class MainFragment extends Fragment {
static final int DIALOG_SAVE_FAILED = 11;
static final int DIALOG_LOAD_SUCCESS = 20;
static final int DIALOG_LOAD_FAILED = 21;
+ static int successDialog;
+ static int failedDialog;
+ public static List<GlobalPrefsBackup.ProviderDetails> supportedProviders;
+ public static Boolean[] checked;
+ static Function<
+ Pair<List<GlobalPrefsBackup.ProviderDetails>, Boolean[]>,
+ ObservableSource<GlobalPrefsBackup.ProviderDetails>>
+ action;
private final boolean mTestingBuild;
private AnimationDrawable mNotConfiguredAnimation = null;
@NonNull private Disposable mPaletteDisposable = Disposables.empty();
private DemoAnyKeyboardView mDemoAnyKeyboardView;
+ public int modeBackupRestore;
private GeneralDialogController mDialogController;
@NonNull private CompositeDisposable mDisposable = new CompositeDisposable();
@@ -350,16 +369,15 @@ public class MainFragment extends Fragment {
private void onBackupRestoreDialogRequired(AlertDialog.Builder builder, int optionId) {
final int actionString;
- final Function<
- Pair<List<GlobalPrefsBackup.ProviderDetails>, Boolean[]>,
- ObservableSource<GlobalPrefsBackup.ProviderDetails>>
- action;
- final int successDialog;
- final int failedDialog;
+ final int choosePathString = R.string.word_editor_action_choose_path;
+
+ final String actionCustomPath;
+ modeBackupRestore = optionId;
switch (optionId) {
case R.id.backup_prefs:
action = GlobalPrefsBackup::backup;
actionString = R.string.word_editor_action_backup_words;
+ actionCustomPath = Intent.ACTION_CREATE_DOCUMENT;
builder.setTitle(R.string.pick_prefs_providers_to_backup);
successDialog = DIALOG_SAVE_SUCCESS;
failedDialog = DIALOG_SAVE_FAILED;
@@ -367,6 +385,7 @@ public class MainFragment extends Fragment {
case R.id.restore_prefs:
action = GlobalPrefsBackup::restore;
actionString = R.string.word_editor_action_restore_words;
+ actionCustomPath = Intent.ACTION_GET_CONTENT;
builder.setTitle(R.string.pick_prefs_providers_to_restore);
successDialog = DIALOG_LOAD_SUCCESS;
failedDialog = DIALOG_LOAD_FAILED;
@@ -376,11 +395,10 @@ public class MainFragment extends Fragment {
"The option-id " + optionId + " is not supported here.");
}
- final List<GlobalPrefsBackup.ProviderDetails> supportedProviders =
- GlobalPrefsBackup.getAllPrefsProviders(getContext());
+ supportedProviders = GlobalPrefsBackup.getAllPrefsProviders(getContext());
final CharSequence[] providersTitles = new CharSequence[supportedProviders.size()];
final boolean[] initialChecked = new boolean[supportedProviders.size()];
- final Boolean[] checked = new Boolean[supportedProviders.size()];
+ checked = new Boolean[supportedProviders.size()];
for (int providerIndex = 0; providerIndex < supportedProviders.size(); providerIndex++) {
// starting with everything checked
@@ -399,38 +417,111 @@ public class MainFragment extends Fragment {
mDisposable.dispose();
mDisposable = new CompositeDisposable();
- mDisposable.add(
- RxProgressDialog.create(
- new Pair<>(supportedProviders, checked),
- getActivity(),
- getText(R.string.take_a_while_progress_message),
- R.layout.progress_window)
- .subscribeOn(RxSchedulers.background())
- .flatMap(action)
- .observeOn(RxSchedulers.mainThread())
- .subscribe(
- providerDetails ->
- Logger.i(
- "MainFragment",
- "Finished backing up %s",
- providerDetails.provider.providerId()),
- e -> {
- Logger.w(
- "MainFragment",
- e,
- "Failed to do operation due to %s",
- e.getMessage());
- mDialogController.showDialog(
- failedDialog, e.getMessage());
- },
- () ->
- mDialogController.showDialog(
- successDialog,
- GlobalPrefsBackup.getBackupFile()
- .getAbsolutePath())));
+ mDisposable.add(launchBackupRestore(0, null));
+ });
+ builder.setNeutralButton(
+ choosePathString,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ Intent dataToFileChooser = new Intent();
+ dataToFileChooser.setType("text/xml");
+ dataToFileChooser.setAction(actionCustomPath);
+ dataToFileChooser.putExtra("checked", checked);
+ try {
+ startActivityForResult(dataToFileChooser, 1);
+ } catch (ActivityNotFoundException e) {
+ Logger.e(TAG, "Could not launch the custom path activity");
+ Toast.makeText(
+ getActivity().getApplicationContext(),
+ R.string.toast_error_custom_path_backup,
+ Toast.LENGTH_LONG)
+ .show();
+ }
+
+ } else {
+ Intent intent = null;
+ if (optionId == R.id.backup_prefs) {
+ intent = new Intent(getContext(), FileExplorerCreate.class);
+ } else if (optionId == R.id.restore_prefs) {
+ intent = new Intent(getContext(), FileExplorerRestore.class);
+ }
+ startActivity(intent);
+ }
+ }
});
}
+ private Disposable launchBackupRestore(int custom, Uri customUri) {
+ File filePath;
+ if (custom == 1) filePath = new File(customUri.getPath());
+ else filePath = GlobalPrefsBackup.getBackupFile();
+
+ return RxProgressDialog.create(
+ new Pair<>(supportedProviders, checked),
+ getActivity(),
+ getText(R.string.take_a_while_progress_message),
+ R.layout.progress_window)
+ .subscribeOn(RxSchedulers.background())
+ .flatMap(action)
+ .observeOn(RxSchedulers.mainThread())
+ .subscribe(
+ providerDetails ->
+ Logger.i(
+ "MainFragment",
+ "Finished backing up %s",
+ providerDetails.provider.providerId()),
+ e -> {
+ Logger.w(
+ "MainFragment",
+ e,
+ "Failed to do operation due to %s",
+ e.getMessage());
+ mDialogController.showDialog(failedDialog, e.getMessage());
+ },
+ () -> mDialogController.showDialog(successDialog, filePath));
+ }
+
+ public static void launchRestoreCustomFileData(InputStream inputStream) {
+ PrefsXmlStorage.prefsXmlStorageCustomPath(inputStream);
+ }
+
+ public static void launchBackupCustomFileData(OutputStream outputStream) {
+ PrefsXmlStorage.prefsXmlBackupCustomPath(outputStream);
+ }
+
+ // This function is if launched when selecting neutral button of the main Fragment
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if (requestCode == 1
+ && resultCode == Activity.RESULT_OK
+ && data != null
+ && data.getDataString() != null) {
+
+ ContentResolver resolver = getContext().getContentResolver();
+ Logger.d(TAG, "Resolver " + resolver.getType(data.getData()));
+ try {
+ // Actually, it is not a good idea to convert URI into filepath.
+ // For more informations, see:
+ // https://commonsware.com/blog/2016/03/15/how-consume-content-uri.html
+ if (modeBackupRestore == R.id.restore_prefs) {
+ Logger.d(TAG, "Launching Restore at uri " + data.getData());
+ launchRestoreCustomFileData(resolver.openInputStream(data.getData()));
+ } else if (modeBackupRestore == R.id.backup_prefs) {
+ Logger.d(TAG, "Launching Backup at uri " + data.getData());
+ launchBackupCustomFileData(resolver.openOutputStream(data.getData()));
+ }
+ launchBackupRestore(1, data.getData());
+ } catch (Exception e) {
+ e.printStackTrace();
+ Logger.d(TAG, "Error when getting inputStream on onActivityResult");
+ }
+ }
+ }
+
private static class StoragePermissionRequest
extends PermissionsRequest.PermissionsRequestBase {
diff --git a/ime/app/src/main/res/drawable-hdpi/ic_menu_add.png b/ime/app/src/main/res/drawable-hdpi/ic_menu_add.png
new file mode 100644
index 000000000..9375e30cd
--- /dev/null
+++ b/ime/app/src/main/res/drawable-hdpi/ic_menu_add.png
Binary files differ
diff --git a/ime/app/src/main/res/drawable-hdpi/ic_menu_refresh.png b/ime/app/src/main/res/drawable-hdpi/ic_menu_refresh.png
new file mode 100644
index 000000000..7b58598fe
--- /dev/null
+++ b/ime/app/src/main/res/drawable-hdpi/ic_menu_refresh.png
Binary files differ
diff --git a/ime/app/src/main/res/drawable-mdpi/ic_menu_add.png b/ime/app/src/main/res/drawable-mdpi/ic_menu_add.png
new file mode 100644
index 000000000..f3bf34a97
--- /dev/null
+++ b/ime/app/src/main/res/drawable-mdpi/ic_menu_add.png
Binary files differ
diff --git a/ime/app/src/main/res/drawable-mdpi/ic_menu_refresh.png b/ime/app/src/main/res/drawable-mdpi/ic_menu_refresh.png
new file mode 100644
index 000000000..4b9605ebe
--- /dev/null
+++ b/ime/app/src/main/res/drawable-mdpi/ic_menu_refresh.png
Binary files differ
diff --git a/ime/app/src/main/res/drawable-xhdpi/ic_menu_add.png b/ime/app/src/main/res/drawable-xhdpi/ic_menu_add.png
new file mode 100644
index 000000000..fd8e0d8ca
--- /dev/null
+++ b/ime/app/src/main/res/drawable-xhdpi/ic_menu_add.png
Binary files differ
diff --git a/ime/app/src/main/res/drawable-xhdpi/ic_menu_refresh.png b/ime/app/src/main/res/drawable-xhdpi/ic_menu_refresh.png
new file mode 100644
index 000000000..2d5addc8d
--- /dev/null
+++ b/ime/app/src/main/res/drawable-xhdpi/ic_menu_refresh.png
Binary files differ
diff --git a/ime/app/src/main/res/drawable-xxhdpi/ic_menu_add.png b/ime/app/src/main/res/drawable-xxhdpi/ic_menu_add.png
new file mode 100644
index 000000000..e4380b31e
--- /dev/null
+++ b/ime/app/src/main/res/drawable-xxhdpi/ic_menu_add.png
Binary files differ
diff --git a/ime/app/src/main/res/drawable-xxhdpi/ic_menu_refresh.png b/ime/app/src/main/res/drawable-xxhdpi/ic_menu_refresh.png
new file mode 100644
index 000000000..8df39b56a
--- /dev/null
+++ b/ime/app/src/main/res/drawable-xxhdpi/ic_menu_refresh.png
Binary files differ
diff --git a/ime/app/src/main/res/drawable-xxxhdpi/ic_menu_add.png b/ime/app/src/main/res/drawable-xxxhdpi/ic_menu_add.png
new file mode 100644
index 000000000..405a672b7
--- /dev/null
+++ b/ime/app/src/main/res/drawable-xxxhdpi/ic_menu_add.png
Binary files differ
diff --git a/ime/app/src/main/res/drawable-xxxhdpi/ic_menu_refresh.png b/ime/app/src/main/res/drawable-xxxhdpi/ic_menu_refresh.png
new file mode 100644
index 000000000..4c0269eea
--- /dev/null
+++ b/ime/app/src/main/res/drawable-xxxhdpi/ic_menu_refresh.png
Binary files differ
diff --git a/ime/app/src/main/res/layout/file_explorer_create_main_ui.xml b/ime/app/src/main/res/layout/file_explorer_create_main_ui.xml
new file mode 100644
index 000000000..ccd244b51
--- /dev/null
+++ b/ime/app/src/main/res/layout/file_explorer_create_main_ui.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ListView
+ android:id="@+id/file_explorer_list_view"
+ android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1"
+ android:clipToPadding="false">
+ </ListView>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="48dp">
+
+ <EditText
+ android:id="@+id/file_explorer_filename"
+ android:layout_width="250dp"
+ android:layout_height="wrap_content"
+ android:ems="10"
+ android:inputType="textPersonName"
+ android:hint="@string/file_explorer_filename" />
+
+ <ImageButton
+ android:id="@+id/file_explorer_filename_button"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:srcCompat="@android:drawable/ic_menu_send" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/ime/app/src/main/res/layout/file_explorer_restore_main_ui.xml b/ime/app/src/main/res/layout/file_explorer_restore_main_ui.xml
new file mode 100644
index 000000000..b90c06e77
--- /dev/null
+++ b/ime/app/src/main/res/layout/file_explorer_restore_main_ui.xml
@@ -0,0 +1,12 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ListView
+ android:id="@+id/file_explorer_list_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false">
+ </ListView>
+</LinearLayout>
diff --git a/ime/app/src/main/res/layout/file_explorer_single_item.xml b/ime/app/src/main/res/layout/file_explorer_single_item.xml
new file mode 100644
index 000000000..7d0922da1
--- /dev/null
+++ b/ime/app/src/main/res/layout/file_explorer_single_item.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/label"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:padding="10dp"
+ android:textSize="16dp"
+ android:textColor="@android:color/black">
+</TextView>
diff --git a/ime/app/src/main/res/menu/file_explorer_create_menu.xml b/ime/app/src/main/res/menu/file_explorer_create_menu.xml
new file mode 100644
index 000000000..03f62e65f
--- /dev/null
+++ b/ime/app/src/main/res/menu/file_explorer_create_menu.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <item android:id="@+id/file_explorer_menu_add_folder" android:icon="@drawable/ic_menu_add"
+ android:title="@string/file_explorer_menu_add_folder" app:showAsAction="ifRoom|never" />
+
+ <item android:id="@+id/file_explorer_menu_refresh" android:icon="@drawable/ic_menu_refresh"
+ android:title="@string/file_explorer_menu_add_folder" app:showAsAction="ifRoom|never" />
+</menu>
diff --git a/ime/app/src/main/res/values/strings.xml b/ime/app/src/main/res/values/strings.xml
index 4e8fb950f..b5a83e655 100644
--- a/ime/app/src/main/res/values/strings.xml
+++ b/ime/app/src/main/res/values/strings.xml
@@ -735,10 +735,21 @@
<string name="enter_word_hint">Type the new word here</string>
<string name="enter_abbreviation_hint">Abbreviation</string>
<string name="enter_abbreviation_target_hint">Full sentence</string>
+ <string name="toast_error_custom_path_backup">Error: Any contextual app could be open</string>
+ <string name="file_explorer_menu_add_folder">Create folder</string>
+ <string name="file_explorer_filename">Backup filename&#8230;</string>
+ <string name="file_explorer_filename_empty">Filename cannot be empty&#8230;</string>
+ <string name="file_explorer_alert_title">Select this file ?</string>
+ <string name="file_explorer_restore_alert_message">Do you want to restore this file ?</string>
+ <string name="file_explorer_backup_alert_message">This file will be overwritten.</string>
- <string name="about_additional_software_licenses">Additional Software Licenses</string>
+ <string name="file_explorer_backup_success">Your data have been saved to\u0020</string>
+ <string name="file_explorer_backup_failed">Your data have failed to ba saved.</string>
+ <string name="file_explorer_restore_success">Your data have been restored from\u0020</string>
+ <string name="file_explorer_restore_failed">Your data have failed to be restored.</string>
+ <string name="about_additional_software_licenses">Additional Software Licenses</string>
<string name="setup_wizard_step_one_title">Enable AnySoftKeyboard</string>
<string name="setup_wizard_step_one_details">In this step, you\'ll need to enable <i>
@@ -902,6 +913,7 @@
<string name="prefs_providers_backed_up_to">You data was successfully backed-up to %s</string>
<string name="prefs_providers_failed_restore_due_to">Failed to restore your data due to %s</string>
<string name="prefs_providers_restored_to">You data was successfully restored from %s</string>
+ <string name="word_editor_action_choose_path">Choose custom path</string>
<string name="allow_layouts_to_provide_generic_rows">Keyboards can provide generic rows</string>
<string name="allow_layouts_to_provide_generic_rows_off_summary">Always use selected generic rows.</string>
diff --git a/ime/app/src/main/res/values/styles.xml b/ime/app/src/main/res/values/styles.xml
index 2cbdeadbe..0c457a2d0 100644
--- a/ime/app/src/main/res/values/styles.xml
+++ b/ime/app/src/main/res/values/styles.xml
@@ -60,6 +60,19 @@
<item name="android:textAppearanceLarge">@style/Ask.Text.Large</item>
</style>
+ <style name="Theme.AskFileExplorer"
+ parent="Theme.AppCompat.Light.DarkActionBar">
+ <item name="android:textColorLink">@color/text_color_link</item>
+ <item name="colorAccent">@color/app_accent</item>
+ <!-- I'm going to use the keyboard's background, so no need for double drawing -->
+ <item name="android:imeFullscreenBackground">@null</item>
+ <item name="preferenceTheme">@style/Theme.AskPrefs</item>
+
+ <item name="colorPrimary">@color/app_color_primary</item>
+ <item name="colorPrimaryDark">@color/app_color_primary_dark</item>
+
+ </style>
+
<style name="Theme.AskApp.NoTitle"
parent="Theme.AskApp">
<item name="windowNoTitle">true</item>
diff --git a/ime/app/src/test/java/com/anysoftkeyboard/prefs/GlobalPrefsBackupTest.java b/ime/app/src/test/java/com/anysoftkeyboard/prefs/GlobalPrefsBackupTest.java
index 14866cd15..2fb5b5cad 100644
--- a/ime/app/src/test/java/com/anysoftkeyboard/prefs/GlobalPrefsBackupTest.java
+++ b/ime/app/src/test/java/com/anysoftkeyboard/prefs/GlobalPrefsBackupTest.java
@@ -2,6 +2,7 @@ package com.anysoftkeyboard.prefs;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import android.os.Environment;
import android.support.annotation.Nullable;
import android.support.v4.util.Pair;
import com.anysoftkeyboard.AnySoftKeyboardRobolectricTestRunner;
@@ -11,6 +12,7 @@ import com.anysoftkeyboard.prefs.backup.PrefsRoot;
import com.anysoftkeyboard.test.TestUtils;
import com.menny.android.anysoftkeyboard.AnyApplication;
import com.menny.android.anysoftkeyboard.R;
+import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -53,6 +55,43 @@ public class GlobalPrefsBackupTest {
}
@Test
+ public void testBackupRestoreCustomPath() throws Exception {
+ File customFile = new File(Environment.getExternalStorageDirectory() + "/testBackup.xml");
+ final FakePrefsProvider fakePrefsProvider = new FakePrefsProvider("id1");
+ List<GlobalPrefsBackup.ProviderDetails> fakeDetails =
+ Collections.singletonList(
+ new GlobalPrefsBackup.ProviderDetails(
+ fakePrefsProvider, R.string.pop_text_type_title));
+
+ final AtomicReference<List<GlobalPrefsBackup.ProviderDetails>> hits =
+ new AtomicReference<>(new ArrayList<>());
+
+ GlobalPrefsBackup.updateCustomFilename(customFile);
+
+ GlobalPrefsBackup.backup(Pair.create(fakeDetails, new Boolean[] {true}))
+ .blockingSubscribe(p -> hits.get().add(p));
+
+ Assert.assertEquals(1, hits.get().size());
+ Assert.assertSame(fakePrefsProvider, hits.get().get(0).provider);
+
+ hits.get().clear();
+
+ Assert.assertTrue(customFile.exists());
+ Assert.assertTrue(customFile.length() > 0);
+
+ Assert.assertNull(fakePrefsProvider.storedPrefsRoot);
+
+ GlobalPrefsBackup.updateCustomFilename(customFile);
+
+ GlobalPrefsBackup.restore(Pair.create(fakeDetails, new Boolean[] {true}))
+ .blockingSubscribe(p -> hits.get().add(p));
+
+ Assert.assertEquals(1, hits.get().size());
+ Assert.assertSame(fakePrefsProvider, hits.get().get(0).provider);
+ Assert.assertNotNull(fakePrefsProvider.storedPrefsRoot);
+ }
+
+ @Test
public void testGetAllPrefsProviders() {
final List<GlobalPrefsBackup.ProviderDetails> allPrefsProviders =
GlobalPrefsBackup.getAllPrefsProviders(getApplicationContext());
diff --git a/ime/base/src/main/java/com/anysoftkeyboard/utils/XmlWriter.java b/ime/base/src/main/java/com/anysoftkeyboard/utils/XmlWriter.java
index 20999528b..96a36de8e 100644
--- a/ime/base/src/main/java/com/anysoftkeyboard/utils/XmlWriter.java
+++ b/ime/base/src/main/java/com/anysoftkeyboard/utils/XmlWriter.java
@@ -21,6 +21,7 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InvalidObjectException;
+import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayDeque;
@@ -67,6 +68,10 @@ public class XmlWriter {
true);
}
+ public XmlWriter(OutputStream outputFileStream) throws IOException {
+ this(new OutputStreamWriter(outputFileStream, Charsets.UTF8), true, 0, true);
+ }
+
/**
* Begin to output an entity.
*
diff --git a/ime/prefs/src/main/java/com/anysoftkeyboard/prefs/backup/PrefsXmlStorage.java b/ime/prefs/src/main/java/com/anysoftkeyboard/prefs/backup/PrefsXmlStorage.java
index df16320fc..c4f5af55a 100644
--- a/ime/prefs/src/main/java/com/anysoftkeyboard/prefs/backup/PrefsXmlStorage.java
+++ b/ime/prefs/src/main/java/com/anysoftkeyboard/prefs/backup/PrefsXmlStorage.java
@@ -5,6 +5,8 @@ import com.anysoftkeyboard.utils.XmlWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
@@ -18,20 +20,36 @@ import org.xml.sax.helpers.DefaultHandler;
public class PrefsXmlStorage {
private final File mStorageFile;
+ private static InputStream mStorageFileStream;
+ private static OutputStream mBackupFileStream;
public PrefsXmlStorage(File storageFile) {
mStorageFile = storageFile;
}
+ public static void prefsXmlStorageCustomPath(InputStream is) {
+ mStorageFileStream = is;
+ }
+
+ public static void prefsXmlBackupCustomPath(OutputStream is) {
+ mBackupFileStream = is;
+ }
+
public void store(PrefsRoot prefsRoot) throws Exception {
final File targetFolder = mStorageFile.getParentFile();
// parent folder may be null in case the file is on the root folder.
- if (targetFolder != null && !targetFolder.exists() && !targetFolder.mkdirs()) {
+ if (mBackupFileStream == null
+ && targetFolder != null
+ && !targetFolder.exists()
+ && !targetFolder.mkdirs()) {
throw new IOException("Failed to of storage folder " + targetFolder.getAbsolutePath());
}
+ final XmlWriter output;
// https://github.com/menny/Java-very-tiny-XmlWriter/blob/master/XmlWriter.java
- final XmlWriter output = new XmlWriter(mStorageFile);
+ if (mBackupFileStream == null) output = new XmlWriter(mStorageFile);
+ else output = new XmlWriter(mBackupFileStream);
+
try {
output.writeEntity("AnySoftKeyboardPrefs")
.writeAttribute("version", Integer.toString(prefsRoot.getVersion()));
@@ -77,8 +95,13 @@ public class PrefsXmlStorage {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
final PrefsXmlParser prefsXmlParser = new PrefsXmlParser();
- try (FileInputStream fileInputStream = new FileInputStream(mStorageFile)) {
- parser.parse(fileInputStream, prefsXmlParser);
+ if (mStorageFileStream == null) {
+ try (FileInputStream fileInputStream = new FileInputStream(mStorageFile)) {
+ parser.parse(fileInputStream, prefsXmlParser);
+ }
+ } else {
+ Logger.d("PrefsXmlStorage", "Loaded settings from custom file path");
+ parser.parse(mStorageFileStream, prefsXmlParser);
}
return prefsXmlParser.getParsedRoot();
}