Loading app/src/main/java/app/fedilab/android/activities/LoginActivity.java +3 −17 Original line number Diff line number Diff line Loading @@ -54,9 +54,9 @@ public class LoginActivity extends BaseActivity { public static Account.API apiLogin; public static String currentInstanceLogin, client_idLogin, client_secretLogin, softwareLogin; private final int PICK_IMPORT = 5557; public static boolean requestedAdmin; @SuppressLint("ApplySharedPref") public void proceedLogin(Activity activity, Account account) { new Thread(() -> { Loading Loading @@ -154,6 +154,8 @@ public class LoginActivity extends BaseActivity { //The activity handles a redirect URI, it will extract token code and will proceed to authentication //That happens when the user wants to use an external browser manageItent(getIntent()); } Loading @@ -176,7 +178,6 @@ public class LoginActivity extends BaseActivity { // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_proxy) { Intent intent = new Intent(LoginActivity.this, ProxyActivity.class); startActivity(intent); Loading @@ -188,20 +189,5 @@ public class LoginActivity extends BaseActivity { return super.onOptionsItemSelected(item); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == PICK_IMPORT && resultCode == RESULT_OK) { if (data == null || data.getData() == null) { Toasty.error(LoginActivity.this, getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show(); return; } // String filename = Helper.getFilePathFromURI(LoginActivity.this, data.getData()); // Sqlite.importDB(LoginActivity.this, filename); } else { Toasty.error(LoginActivity.this, getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show(); } } } No newline at end of file app/src/main/java/app/fedilab/android/helper/Helper.java +49 −1 Original line number Diff line number Diff line Loading @@ -312,7 +312,7 @@ public class Helper { public static final String INTENT_SEND_MODIFIED_IMAGE = "INTENT_SEND_MODIFIED_IMAGE"; public static final String INTENT_COMPOSE_ERROR_MESSAGE = "INTENT_COMPOSE_ERROR_MESSAGE"; public static final String TEMP_MEDIA_DIRECTORY = "TEMP_MEDIA_DIRECTORY"; public static final String TEMP_EXPORT_DATA = "TEMP_EXPORT_DATA"; public static final int EXTERNAL_STORAGE_REQUEST_CODE = 84; public static final int EXTERNAL_STORAGE_REQUEST_CODE_MEDIA_SAVE = 85; Loading Loading @@ -1276,6 +1276,50 @@ public class Helper { }).start(); } public static void createFileFromUri(Context context, Uri uri, OnFileCopied callBack) { new Thread(() -> { InputStream selectedFileInputStream; File file = null; try { String uriFullPath = uri.getPath(); String[] uriFullPathStr = uriFullPath.split(":"); String fullPath = uriFullPath; if (uriFullPathStr.length > 1) { fullPath = uriFullPathStr[1]; } final String fileName = Helper.dateFileToString(context, new Date()) + ".zip"; selectedFileInputStream = context.getContentResolver().openInputStream(uri); if (selectedFileInputStream != null) { final File certCacheDir = new File(context.getCacheDir(), TEMP_EXPORT_DATA); boolean isCertCacheDirExists = certCacheDir.exists(); if (!isCertCacheDirExists) { isCertCacheDirExists = certCacheDir.mkdirs(); } if (isCertCacheDirExists) { String filePath = certCacheDir.getAbsolutePath() + "/" + fileName; file = new File(filePath); OutputStream selectedFileOutPutStream = new FileOutputStream(filePath); byte[] buffer = new byte[1024]; int length; while ((length = selectedFileInputStream.read(buffer)) > 0) { selectedFileOutPutStream.write(buffer, 0, length); } selectedFileOutPutStream.flush(); selectedFileOutPutStream.close(); } selectedFileInputStream.close(); } } catch (Exception e) { e.printStackTrace(); } Handler mainHandler = new Handler(Looper.getMainLooper()); File finalFile = file; Runnable myRunnable = () -> callBack.onFileCopied(finalFile); mainHandler.post(myRunnable); }).start(); } public static void createAttachmentFromPAth(String path, OnAttachmentCopied callBack) { new Thread(() -> { Attachment attachment = new Attachment(); Loading Loading @@ -1954,6 +1998,10 @@ public class Helper { void onAttachmentCopied(Attachment attachment); } public interface OnFileCopied { void onFileCopied(File file); } public static void addMutedAccount(app.fedilab.android.client.entities.api.Account target) { if (MainActivity.filteredAccounts == null) { MainActivity.filteredAccounts = new ArrayList<>(); Loading app/src/main/java/app/fedilab/android/helper/SettingsStorage.java→app/src/main/java/app/fedilab/android/helper/ZipHelper.java +294 −0 Original line number Diff line number Diff line Loading @@ -14,8 +14,11 @@ package app.fedilab.android.helper; * You should have received a copy of the GNU General Public License along with Fedilab; if not, * see <http://www.gnu.org/licenses>. */ import static app.fedilab.android.BaseMainActivity.currentAccount; import static app.fedilab.android.helper.LogoHelper.getMainLogo; import static app.fedilab.android.sqlite.Sqlite.DB_NAME; import static app.fedilab.android.sqlite.Sqlite.db; import android.annotation.SuppressLint; import android.content.Context; Loading @@ -24,30 +27,167 @@ import android.content.SharedPreferences; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.widget.Toast; import androidx.preference.PreferenceManager; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.nio.channels.FileChannel; import java.util.Date; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import app.fedilab.android.R; import es.dmoral.toasty.Toasty; public class ZipHelper { final static int BUFFER_SIZE = 2048; public static void exportData(Context context) throws IOException { String suffix = Helper.dateFileToString(context, new Date()); String fileName = "Fedilab_data_export_" + suffix + ".zip"; String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); String zipFile = filePath + "/" + fileName; BufferedInputStream origin; try (ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)))) { byte[] data = new byte[BUFFER_SIZE]; String settingsPath = storeSettings(context, suffix); if (settingsPath != null) { FileInputStream fi = new FileInputStream(settingsPath); origin = new BufferedInputStream(fi, BUFFER_SIZE); try { ZipEntry entry = new ZipEntry(settingsPath.substring(settingsPath.lastIndexOf("/") + 1)); out.putNextEntry(entry); int count; while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1) { out.write(data, 0, count); } } finally { origin.close(); } //noinspection ResultOfMethodCallIgnored new File(settingsPath).delete(); } else { Toasty.error(context, context.getString(R.string.toast_error), Toasty.LENGTH_SHORT).show(); return; } String dbPath = exportDB(context, suffix); if (dbPath != null) { FileInputStream fi = new FileInputStream(dbPath); origin = new BufferedInputStream(fi, BUFFER_SIZE); try { ZipEntry entry = new ZipEntry(dbPath.substring(dbPath.lastIndexOf("/") + 1)); out.putNextEntry(entry); int count; while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1) { out.write(data, 0, count); } } finally { origin.close(); } //noinspection ResultOfMethodCallIgnored new File(dbPath).delete(); } else { Toasty.error(context, context.getString(R.string.toast_error), Toasty.LENGTH_SHORT).show(); return; } String message = context.getString(R.string.data_export_settings_success); Intent intentOpen = new Intent(); intentOpen.setAction(android.content.Intent.ACTION_VIEW); Uri uri = Uri.parse("file://" + zipFile); intentOpen.setDataAndType(uri, "application/zip"); String title = context.getString(R.string.data_export_settings); Helper.notify_user(context, currentAccount, intentOpen, BitmapFactory.decodeResource(context.getResources(), getMainLogo(context)), Helper.NotifType.BACKUP, title, message); } } //From https://stackoverflow.com/a/10864463 public class SettingsStorage { @SuppressLint("UnspecifiedImmutableFlag") public static void importData(Context context, File file) { new Thread(() -> { try { int size; byte[] buffer = new byte[BUFFER_SIZE]; String uriFullPath = file.getAbsolutePath(); String[] uriFullPathStr = uriFullPath.split(":"); String fullPath = uriFullPath; if (uriFullPathStr.length > 1) { fullPath = uriFullPathStr[1]; } fullPath = fullPath.replace(".zip", ""); File f = new File(fullPath); if (!f.isDirectory()) { //noinspection ResultOfMethodCallIgnored f.mkdirs(); } boolean successful = true; try (ZipInputStream zin = new ZipInputStream(new FileInputStream(fullPath + ".zip"))) { ZipEntry ze; while ((ze = zin.getNextEntry()) != null) { if (!successful) { break; } String path = fullPath + ze.getName(); File unzipFile = new File(path); FileOutputStream out = new FileOutputStream(unzipFile, false); BufferedOutputStream fout = new BufferedOutputStream(out, BUFFER_SIZE); try { while ((size = zin.read(buffer, 0, BUFFER_SIZE)) != -1) { fout.write(buffer, 0, size); } public static boolean saveSharedPreferencesToFile(Context context) { zin.closeEntry(); } finally { fout.flush(); fout.close(); } if (ze.getName().contains("settings")) { successful = restoreSettings(context, Uri.fromFile(new File(path))); } else if (ze.getName().contains("database")) { successful = importDB(context, path); } else { break; } } } Handler mainHandler = new Handler(Looper.getMainLooper()); boolean finalSuccessful = successful; Runnable myRunnable = () -> { if (finalSuccessful) { Helper.restart(context); } else { Toasty.error(context, context.getString(R.string.toast_error), Toast.LENGTH_LONG).show(); } }; mainHandler.post(myRunnable); } catch (Exception e) { e.printStackTrace(); } }).start(); } private static String storeSettings(Context context, String suffix) { boolean res = false; ObjectOutputStream output = null; String fileName = "Fedilab_settings_export_" + Helper.dateFileToString(context, new Date()) + ".txt"; String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); String fileName = "Fedilab_settings_export_" + suffix + ".fedilab"; String filePath = context.getCacheDir().getAbsolutePath(); String fullPath = filePath + "/" + fileName; File dst = new File(fullPath); try { Loading @@ -55,14 +195,6 @@ public class SettingsStorage { SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); output.writeObject(sharedpreferences.getAll()); res = true; String message = context.getString(R.string.data_export_settings_success); Intent intentOpen = new Intent(); intentOpen.setAction(android.content.Intent.ACTION_VIEW); Uri uri = Uri.parse("file://" + fullPath); intentOpen.setDataAndType(uri, "text/txt"); String title = context.getString(R.string.data_export_settings); Helper.notify_user(context, currentAccount, intentOpen, BitmapFactory.decodeResource(context.getResources(), getMainLogo(context)), Helper.NotifType.BACKUP, title, message); } catch (IOException e) { e.printStackTrace(); } finally { Loading @@ -75,12 +207,12 @@ public class SettingsStorage { ex.printStackTrace(); } } return res; return res ? fullPath : null; } @SuppressLint("ApplySharedPref") @SuppressWarnings({"unchecked", "UnnecessaryUnboxing"}) public static boolean loadSharedPreferencesFromFile(Context context, Uri srcUri) { @SuppressWarnings("UnnecessaryUnboxing") private static boolean restoreSettings(Context context, Uri srcUri) { boolean res = false; ObjectInputStream input = null; try { Loading @@ -88,23 +220,11 @@ public class SettingsStorage { SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor prefEdit = sharedpreferences.edit(); prefEdit.clear(); //noinspection unchecked Map<String, ?> entries = (Map<String, ?>) input.readObject(); for (Map.Entry<String, ?> entry : entries.entrySet()) { Object v = entry.getValue(); String key = entry.getKey(); //We skip some values if (key.compareTo(Helper.PREF_USER_ID) == 0) { continue; } if (key.compareTo(Helper.PREF_INSTANCE) == 0) { continue; } if (key.compareTo(Helper.PREF_USER_INSTANCE) == 0) { continue; } if (key.compareTo(Helper.PREF_USER_TOKEN) == 0) { continue; } if (v instanceof Boolean) prefEdit.putBoolean(key, ((Boolean) v).booleanValue()); else if (v instanceof Float) Loading @@ -116,6 +236,7 @@ public class SettingsStorage { else if (v instanceof String) prefEdit.putString(key, ((String) v)); } prefEdit.commit(); res = true; } catch (IOException | ClassNotFoundException e) { Loading @@ -131,4 +252,43 @@ public class SettingsStorage { } return res; } private static String exportDB(Context context, String suffix) { try { String fileName = "Fedilab_database_export_" + suffix + ".fedilab"; String filePath = context.getCacheDir().getAbsolutePath(); String fullPath = filePath + "/" + fileName; File dbSource = context.getDatabasePath(DB_NAME); File dbDest = new File(fullPath); FileChannel src = new FileInputStream(dbSource).getChannel(); FileChannel dst = new FileOutputStream(dbDest).getChannel(); dst.transferFrom(src, 0, src.size()); src.close(); dst.close(); return fullPath; } catch (Exception e) { e.printStackTrace(); return null; } } private static boolean importDB(Context context, String backupDBPath) { try { if (db != null) { db.close(); } File dbDest = context.getDatabasePath(DB_NAME); File dbSource = new File(backupDBPath); FileChannel src = new FileInputStream(dbSource).getChannel(); FileChannel dst = new FileOutputStream(dbDest).getChannel(); dst.transferFrom(src, 0, src.size()); src.close(); dst.close(); return true; } catch (Exception e) { e.printStackTrace(); } return false; } } app/src/main/java/app/fedilab/android/ui/fragment/login/FragmentLoginMain.java +70 −4 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ package app.fedilab.android.ui.fragment.login; * see <http://www.gnu.org/licenses>. */ import static android.app.Activity.RESULT_OK; import static app.fedilab.android.activities.LoginActivity.apiLogin; import static app.fedilab.android.activities.LoginActivity.client_idLogin; import static app.fedilab.android.activities.LoginActivity.client_secretLogin; Loading @@ -22,8 +23,9 @@ import static app.fedilab.android.activities.LoginActivity.currentInstanceLogin; import static app.fedilab.android.activities.LoginActivity.requestedAdmin; import static app.fedilab.android.activities.LoginActivity.softwareLogin; import android.Manifest; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.text.Editable; Loading @@ -36,11 +38,13 @@ import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.appcompat.widget.PopupMenu; import androidx.core.app.ActivityCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.preference.PreferenceManager; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; Loading @@ -55,6 +59,7 @@ import app.fedilab.android.client.entities.app.InstanceSocial; import app.fedilab.android.databinding.FragmentLoginMainBinding; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MastodonHelper; import app.fedilab.android.helper.ZipHelper; import app.fedilab.android.viewmodel.mastodon.AppsVM; import app.fedilab.android.viewmodel.mastodon.InstanceSocialVM; import app.fedilab.android.viewmodel.mastodon.NodeInfoVM; Loading @@ -65,7 +70,9 @@ public class FragmentLoginMain extends Fragment { private FragmentLoginMainBinding binding; private boolean searchInstanceRunning = false; private String oldSearch; private static final int REQUEST_CODE = 5412; private final int PICK_IMPORT = 5557; private ActivityResultLauncher<String> permissionLauncher; public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Loading @@ -76,6 +83,24 @@ public class FragmentLoginMain extends Fragment { InstanceSocialVM instanceSocialVM = new ViewModelProvider(FragmentLoginMain.this).get(InstanceSocialVM.class); binding.menuIcon.setOnClickListener(this::showMenu); binding.loginInstance.setOnItemClickListener((parent, view, position, id) -> oldSearch = parent.getItemAtPosition(position).toString().trim()); permissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { if (isGranted) { Intent openFileIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); openFileIntent.addCategory(Intent.CATEGORY_OPENABLE); openFileIntent.setType("application/zip"); String[] mimeTypes = new String[]{"application/zip"}; openFileIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); //noinspection deprecation startActivityForResult( Intent.createChooser( openFileIntent, getString(R.string.load_settings)), PICK_IMPORT); } else { ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE); } }); binding.loginInstance.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { Loading Loading @@ -184,7 +209,6 @@ public class FragmentLoginMain extends Fragment { MenuInflater menuInflater = popupMenu.getMenuInflater(); menuInflater.inflate(R.menu.main_login, popupMenu.getMenu()); MenuItem adminTabItem = popupMenu.getMenu().findItem(R.id.action_request_admin); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); adminTabItem.setChecked(requestedAdmin); popupMenu.setOnMenuItemClickListener(item -> { int itemId = item.getItemId(); Loading @@ -208,6 +232,8 @@ public class FragmentLoginMain extends Fragment { return false; } }); } else if (itemId == R.id.action_import_data) { permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE); } return false; }); Loading Loading @@ -258,4 +284,44 @@ public class FragmentLoginMain extends Fragment { }); } @SuppressWarnings("deprecation") @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == PICK_IMPORT && resultCode == RESULT_OK) { if (data == null || data.getData() == null) { Toasty.error(requireActivity(), getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show(); return; } Helper.createFileFromUri(requireActivity(), data.getData(), file -> ZipHelper.importData(requireActivity(), file)); } else { Toasty.error(requireActivity(), getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show(); } } @SuppressWarnings("deprecation") @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Intent openFileIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); openFileIntent.addCategory(Intent.CATEGORY_OPENABLE); openFileIntent.setType("application/zip"); String[] mimeTypes = new String[]{"application/zip"}; openFileIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); startActivityForResult( Intent.createChooser( openFileIntent, getString(R.string.load_settings)), PICK_IMPORT); } else { Toasty.error(requireActivity(), getString(R.string.permission_missing), Toasty.LENGTH_SHORT).show(); } } } } No newline at end of file app/src/main/java/app/fedilab/android/ui/fragment/settings/FragmentSettingsCategories.java +27 −8 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
app/src/main/java/app/fedilab/android/activities/LoginActivity.java +3 −17 Original line number Diff line number Diff line Loading @@ -54,9 +54,9 @@ public class LoginActivity extends BaseActivity { public static Account.API apiLogin; public static String currentInstanceLogin, client_idLogin, client_secretLogin, softwareLogin; private final int PICK_IMPORT = 5557; public static boolean requestedAdmin; @SuppressLint("ApplySharedPref") public void proceedLogin(Activity activity, Account account) { new Thread(() -> { Loading Loading @@ -154,6 +154,8 @@ public class LoginActivity extends BaseActivity { //The activity handles a redirect URI, it will extract token code and will proceed to authentication //That happens when the user wants to use an external browser manageItent(getIntent()); } Loading @@ -176,7 +178,6 @@ public class LoginActivity extends BaseActivity { // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_proxy) { Intent intent = new Intent(LoginActivity.this, ProxyActivity.class); startActivity(intent); Loading @@ -188,20 +189,5 @@ public class LoginActivity extends BaseActivity { return super.onOptionsItemSelected(item); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == PICK_IMPORT && resultCode == RESULT_OK) { if (data == null || data.getData() == null) { Toasty.error(LoginActivity.this, getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show(); return; } // String filename = Helper.getFilePathFromURI(LoginActivity.this, data.getData()); // Sqlite.importDB(LoginActivity.this, filename); } else { Toasty.error(LoginActivity.this, getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show(); } } } No newline at end of file
app/src/main/java/app/fedilab/android/helper/Helper.java +49 −1 Original line number Diff line number Diff line Loading @@ -312,7 +312,7 @@ public class Helper { public static final String INTENT_SEND_MODIFIED_IMAGE = "INTENT_SEND_MODIFIED_IMAGE"; public static final String INTENT_COMPOSE_ERROR_MESSAGE = "INTENT_COMPOSE_ERROR_MESSAGE"; public static final String TEMP_MEDIA_DIRECTORY = "TEMP_MEDIA_DIRECTORY"; public static final String TEMP_EXPORT_DATA = "TEMP_EXPORT_DATA"; public static final int EXTERNAL_STORAGE_REQUEST_CODE = 84; public static final int EXTERNAL_STORAGE_REQUEST_CODE_MEDIA_SAVE = 85; Loading Loading @@ -1276,6 +1276,50 @@ public class Helper { }).start(); } public static void createFileFromUri(Context context, Uri uri, OnFileCopied callBack) { new Thread(() -> { InputStream selectedFileInputStream; File file = null; try { String uriFullPath = uri.getPath(); String[] uriFullPathStr = uriFullPath.split(":"); String fullPath = uriFullPath; if (uriFullPathStr.length > 1) { fullPath = uriFullPathStr[1]; } final String fileName = Helper.dateFileToString(context, new Date()) + ".zip"; selectedFileInputStream = context.getContentResolver().openInputStream(uri); if (selectedFileInputStream != null) { final File certCacheDir = new File(context.getCacheDir(), TEMP_EXPORT_DATA); boolean isCertCacheDirExists = certCacheDir.exists(); if (!isCertCacheDirExists) { isCertCacheDirExists = certCacheDir.mkdirs(); } if (isCertCacheDirExists) { String filePath = certCacheDir.getAbsolutePath() + "/" + fileName; file = new File(filePath); OutputStream selectedFileOutPutStream = new FileOutputStream(filePath); byte[] buffer = new byte[1024]; int length; while ((length = selectedFileInputStream.read(buffer)) > 0) { selectedFileOutPutStream.write(buffer, 0, length); } selectedFileOutPutStream.flush(); selectedFileOutPutStream.close(); } selectedFileInputStream.close(); } } catch (Exception e) { e.printStackTrace(); } Handler mainHandler = new Handler(Looper.getMainLooper()); File finalFile = file; Runnable myRunnable = () -> callBack.onFileCopied(finalFile); mainHandler.post(myRunnable); }).start(); } public static void createAttachmentFromPAth(String path, OnAttachmentCopied callBack) { new Thread(() -> { Attachment attachment = new Attachment(); Loading Loading @@ -1954,6 +1998,10 @@ public class Helper { void onAttachmentCopied(Attachment attachment); } public interface OnFileCopied { void onFileCopied(File file); } public static void addMutedAccount(app.fedilab.android.client.entities.api.Account target) { if (MainActivity.filteredAccounts == null) { MainActivity.filteredAccounts = new ArrayList<>(); Loading
app/src/main/java/app/fedilab/android/helper/SettingsStorage.java→app/src/main/java/app/fedilab/android/helper/ZipHelper.java +294 −0 Original line number Diff line number Diff line Loading @@ -14,8 +14,11 @@ package app.fedilab.android.helper; * You should have received a copy of the GNU General Public License along with Fedilab; if not, * see <http://www.gnu.org/licenses>. */ import static app.fedilab.android.BaseMainActivity.currentAccount; import static app.fedilab.android.helper.LogoHelper.getMainLogo; import static app.fedilab.android.sqlite.Sqlite.DB_NAME; import static app.fedilab.android.sqlite.Sqlite.db; import android.annotation.SuppressLint; import android.content.Context; Loading @@ -24,30 +27,167 @@ import android.content.SharedPreferences; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.widget.Toast; import androidx.preference.PreferenceManager; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.nio.channels.FileChannel; import java.util.Date; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import app.fedilab.android.R; import es.dmoral.toasty.Toasty; public class ZipHelper { final static int BUFFER_SIZE = 2048; public static void exportData(Context context) throws IOException { String suffix = Helper.dateFileToString(context, new Date()); String fileName = "Fedilab_data_export_" + suffix + ".zip"; String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); String zipFile = filePath + "/" + fileName; BufferedInputStream origin; try (ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)))) { byte[] data = new byte[BUFFER_SIZE]; String settingsPath = storeSettings(context, suffix); if (settingsPath != null) { FileInputStream fi = new FileInputStream(settingsPath); origin = new BufferedInputStream(fi, BUFFER_SIZE); try { ZipEntry entry = new ZipEntry(settingsPath.substring(settingsPath.lastIndexOf("/") + 1)); out.putNextEntry(entry); int count; while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1) { out.write(data, 0, count); } } finally { origin.close(); } //noinspection ResultOfMethodCallIgnored new File(settingsPath).delete(); } else { Toasty.error(context, context.getString(R.string.toast_error), Toasty.LENGTH_SHORT).show(); return; } String dbPath = exportDB(context, suffix); if (dbPath != null) { FileInputStream fi = new FileInputStream(dbPath); origin = new BufferedInputStream(fi, BUFFER_SIZE); try { ZipEntry entry = new ZipEntry(dbPath.substring(dbPath.lastIndexOf("/") + 1)); out.putNextEntry(entry); int count; while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1) { out.write(data, 0, count); } } finally { origin.close(); } //noinspection ResultOfMethodCallIgnored new File(dbPath).delete(); } else { Toasty.error(context, context.getString(R.string.toast_error), Toasty.LENGTH_SHORT).show(); return; } String message = context.getString(R.string.data_export_settings_success); Intent intentOpen = new Intent(); intentOpen.setAction(android.content.Intent.ACTION_VIEW); Uri uri = Uri.parse("file://" + zipFile); intentOpen.setDataAndType(uri, "application/zip"); String title = context.getString(R.string.data_export_settings); Helper.notify_user(context, currentAccount, intentOpen, BitmapFactory.decodeResource(context.getResources(), getMainLogo(context)), Helper.NotifType.BACKUP, title, message); } } //From https://stackoverflow.com/a/10864463 public class SettingsStorage { @SuppressLint("UnspecifiedImmutableFlag") public static void importData(Context context, File file) { new Thread(() -> { try { int size; byte[] buffer = new byte[BUFFER_SIZE]; String uriFullPath = file.getAbsolutePath(); String[] uriFullPathStr = uriFullPath.split(":"); String fullPath = uriFullPath; if (uriFullPathStr.length > 1) { fullPath = uriFullPathStr[1]; } fullPath = fullPath.replace(".zip", ""); File f = new File(fullPath); if (!f.isDirectory()) { //noinspection ResultOfMethodCallIgnored f.mkdirs(); } boolean successful = true; try (ZipInputStream zin = new ZipInputStream(new FileInputStream(fullPath + ".zip"))) { ZipEntry ze; while ((ze = zin.getNextEntry()) != null) { if (!successful) { break; } String path = fullPath + ze.getName(); File unzipFile = new File(path); FileOutputStream out = new FileOutputStream(unzipFile, false); BufferedOutputStream fout = new BufferedOutputStream(out, BUFFER_SIZE); try { while ((size = zin.read(buffer, 0, BUFFER_SIZE)) != -1) { fout.write(buffer, 0, size); } public static boolean saveSharedPreferencesToFile(Context context) { zin.closeEntry(); } finally { fout.flush(); fout.close(); } if (ze.getName().contains("settings")) { successful = restoreSettings(context, Uri.fromFile(new File(path))); } else if (ze.getName().contains("database")) { successful = importDB(context, path); } else { break; } } } Handler mainHandler = new Handler(Looper.getMainLooper()); boolean finalSuccessful = successful; Runnable myRunnable = () -> { if (finalSuccessful) { Helper.restart(context); } else { Toasty.error(context, context.getString(R.string.toast_error), Toast.LENGTH_LONG).show(); } }; mainHandler.post(myRunnable); } catch (Exception e) { e.printStackTrace(); } }).start(); } private static String storeSettings(Context context, String suffix) { boolean res = false; ObjectOutputStream output = null; String fileName = "Fedilab_settings_export_" + Helper.dateFileToString(context, new Date()) + ".txt"; String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); String fileName = "Fedilab_settings_export_" + suffix + ".fedilab"; String filePath = context.getCacheDir().getAbsolutePath(); String fullPath = filePath + "/" + fileName; File dst = new File(fullPath); try { Loading @@ -55,14 +195,6 @@ public class SettingsStorage { SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); output.writeObject(sharedpreferences.getAll()); res = true; String message = context.getString(R.string.data_export_settings_success); Intent intentOpen = new Intent(); intentOpen.setAction(android.content.Intent.ACTION_VIEW); Uri uri = Uri.parse("file://" + fullPath); intentOpen.setDataAndType(uri, "text/txt"); String title = context.getString(R.string.data_export_settings); Helper.notify_user(context, currentAccount, intentOpen, BitmapFactory.decodeResource(context.getResources(), getMainLogo(context)), Helper.NotifType.BACKUP, title, message); } catch (IOException e) { e.printStackTrace(); } finally { Loading @@ -75,12 +207,12 @@ public class SettingsStorage { ex.printStackTrace(); } } return res; return res ? fullPath : null; } @SuppressLint("ApplySharedPref") @SuppressWarnings({"unchecked", "UnnecessaryUnboxing"}) public static boolean loadSharedPreferencesFromFile(Context context, Uri srcUri) { @SuppressWarnings("UnnecessaryUnboxing") private static boolean restoreSettings(Context context, Uri srcUri) { boolean res = false; ObjectInputStream input = null; try { Loading @@ -88,23 +220,11 @@ public class SettingsStorage { SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor prefEdit = sharedpreferences.edit(); prefEdit.clear(); //noinspection unchecked Map<String, ?> entries = (Map<String, ?>) input.readObject(); for (Map.Entry<String, ?> entry : entries.entrySet()) { Object v = entry.getValue(); String key = entry.getKey(); //We skip some values if (key.compareTo(Helper.PREF_USER_ID) == 0) { continue; } if (key.compareTo(Helper.PREF_INSTANCE) == 0) { continue; } if (key.compareTo(Helper.PREF_USER_INSTANCE) == 0) { continue; } if (key.compareTo(Helper.PREF_USER_TOKEN) == 0) { continue; } if (v instanceof Boolean) prefEdit.putBoolean(key, ((Boolean) v).booleanValue()); else if (v instanceof Float) Loading @@ -116,6 +236,7 @@ public class SettingsStorage { else if (v instanceof String) prefEdit.putString(key, ((String) v)); } prefEdit.commit(); res = true; } catch (IOException | ClassNotFoundException e) { Loading @@ -131,4 +252,43 @@ public class SettingsStorage { } return res; } private static String exportDB(Context context, String suffix) { try { String fileName = "Fedilab_database_export_" + suffix + ".fedilab"; String filePath = context.getCacheDir().getAbsolutePath(); String fullPath = filePath + "/" + fileName; File dbSource = context.getDatabasePath(DB_NAME); File dbDest = new File(fullPath); FileChannel src = new FileInputStream(dbSource).getChannel(); FileChannel dst = new FileOutputStream(dbDest).getChannel(); dst.transferFrom(src, 0, src.size()); src.close(); dst.close(); return fullPath; } catch (Exception e) { e.printStackTrace(); return null; } } private static boolean importDB(Context context, String backupDBPath) { try { if (db != null) { db.close(); } File dbDest = context.getDatabasePath(DB_NAME); File dbSource = new File(backupDBPath); FileChannel src = new FileInputStream(dbSource).getChannel(); FileChannel dst = new FileOutputStream(dbDest).getChannel(); dst.transferFrom(src, 0, src.size()); src.close(); dst.close(); return true; } catch (Exception e) { e.printStackTrace(); } return false; } }
app/src/main/java/app/fedilab/android/ui/fragment/login/FragmentLoginMain.java +70 −4 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ package app.fedilab.android.ui.fragment.login; * see <http://www.gnu.org/licenses>. */ import static android.app.Activity.RESULT_OK; import static app.fedilab.android.activities.LoginActivity.apiLogin; import static app.fedilab.android.activities.LoginActivity.client_idLogin; import static app.fedilab.android.activities.LoginActivity.client_secretLogin; Loading @@ -22,8 +23,9 @@ import static app.fedilab.android.activities.LoginActivity.currentInstanceLogin; import static app.fedilab.android.activities.LoginActivity.requestedAdmin; import static app.fedilab.android.activities.LoginActivity.softwareLogin; import android.Manifest; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.text.Editable; Loading @@ -36,11 +38,13 @@ import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.appcompat.widget.PopupMenu; import androidx.core.app.ActivityCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.preference.PreferenceManager; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; Loading @@ -55,6 +59,7 @@ import app.fedilab.android.client.entities.app.InstanceSocial; import app.fedilab.android.databinding.FragmentLoginMainBinding; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MastodonHelper; import app.fedilab.android.helper.ZipHelper; import app.fedilab.android.viewmodel.mastodon.AppsVM; import app.fedilab.android.viewmodel.mastodon.InstanceSocialVM; import app.fedilab.android.viewmodel.mastodon.NodeInfoVM; Loading @@ -65,7 +70,9 @@ public class FragmentLoginMain extends Fragment { private FragmentLoginMainBinding binding; private boolean searchInstanceRunning = false; private String oldSearch; private static final int REQUEST_CODE = 5412; private final int PICK_IMPORT = 5557; private ActivityResultLauncher<String> permissionLauncher; public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Loading @@ -76,6 +83,24 @@ public class FragmentLoginMain extends Fragment { InstanceSocialVM instanceSocialVM = new ViewModelProvider(FragmentLoginMain.this).get(InstanceSocialVM.class); binding.menuIcon.setOnClickListener(this::showMenu); binding.loginInstance.setOnItemClickListener((parent, view, position, id) -> oldSearch = parent.getItemAtPosition(position).toString().trim()); permissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { if (isGranted) { Intent openFileIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); openFileIntent.addCategory(Intent.CATEGORY_OPENABLE); openFileIntent.setType("application/zip"); String[] mimeTypes = new String[]{"application/zip"}; openFileIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); //noinspection deprecation startActivityForResult( Intent.createChooser( openFileIntent, getString(R.string.load_settings)), PICK_IMPORT); } else { ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE); } }); binding.loginInstance.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { Loading Loading @@ -184,7 +209,6 @@ public class FragmentLoginMain extends Fragment { MenuInflater menuInflater = popupMenu.getMenuInflater(); menuInflater.inflate(R.menu.main_login, popupMenu.getMenu()); MenuItem adminTabItem = popupMenu.getMenu().findItem(R.id.action_request_admin); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); adminTabItem.setChecked(requestedAdmin); popupMenu.setOnMenuItemClickListener(item -> { int itemId = item.getItemId(); Loading @@ -208,6 +232,8 @@ public class FragmentLoginMain extends Fragment { return false; } }); } else if (itemId == R.id.action_import_data) { permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE); } return false; }); Loading Loading @@ -258,4 +284,44 @@ public class FragmentLoginMain extends Fragment { }); } @SuppressWarnings("deprecation") @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == PICK_IMPORT && resultCode == RESULT_OK) { if (data == null || data.getData() == null) { Toasty.error(requireActivity(), getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show(); return; } Helper.createFileFromUri(requireActivity(), data.getData(), file -> ZipHelper.importData(requireActivity(), file)); } else { Toasty.error(requireActivity(), getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show(); } } @SuppressWarnings("deprecation") @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Intent openFileIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); openFileIntent.addCategory(Intent.CATEGORY_OPENABLE); openFileIntent.setType("application/zip"); String[] mimeTypes = new String[]{"application/zip"}; openFileIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); startActivityForResult( Intent.createChooser( openFileIntent, getString(R.string.load_settings)), PICK_IMPORT); } else { Toasty.error(requireActivity(), getString(R.string.permission_missing), Toasty.LENGTH_SHORT).show(); } } } } No newline at end of file
app/src/main/java/app/fedilab/android/ui/fragment/settings/FragmentSettingsCategories.java +27 −8 File changed.Preview size limit exceeded, changes collapsed. Show changes