Merge pull request #10731 from osmandapp/DataStorageRefactoring

Data storage refactoring
This commit is contained in:
Vitaliy 2021-02-02 12:47:16 +02:00 committed by GitHub
commit dedea0c45e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 905 additions and 784 deletions

View file

@ -9,7 +9,8 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/card_row_min_height">
android:layout_height="wrap_content"
android:minHeight="@dimen/card_row_min_height">
<androidx.appcompat.widget.AppCompatImageView
android:id="@android:id/icon"
@ -31,11 +32,15 @@
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:paddingLeft="@dimen/content_padding"
android:paddingStart="@dimen/content_padding"
android:paddingRight="0dp"
android:paddingEnd="0dp"
android:paddingTop="@dimen/content_padding_half"
android:paddingBottom="@dimen/content_padding_half"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:text="Internal application memory"
android:paddingStart="@dimen/content_padding" />
tools:text="Internal application memory" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/memory"

View file

@ -12,6 +12,7 @@
-->
<string name="hillshade_slope_contour_lines">Hillshade / Slope / Contour lines</string>
<string name="toast_select_edits_for_upload">Select edits for upload</string>
<string name="uploaded_count">Uploaded %1$d of %2$d</string>
<string name="uploading_count">Uploading %1$d of %2$d</string>

View file

@ -14,10 +14,10 @@
android:title="@string/shared_string_maps"/>
<Preference
android:key="contour_lines_and_hillshade_memory"
android:key="terrain_memory_used"
android:layout="@layout/data_storage_memory_used_item"
android:icon="@drawable/ic_map"
android:title="@string/contour_lines_and_hillshade"/>
android:title="@string/hillshade_slope_contour_lines"/>
<Preference
android:key="tracks_memory_used"

View file

@ -22,7 +22,7 @@
android:layout="@layout/preference_with_descr"
android:persistent="false"
android:title="@string/application_dir"
app:fragment="net.osmand.plus.settings.fragments.DataStorageFragment"
app:fragment="net.osmand.plus.settings.datastorage.DataStorageFragment"
tools:icon="@drawable/ic_action_folder" />
<Preference

View file

@ -139,7 +139,7 @@ import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.settings.fragments.BaseSettingsFragment;
import net.osmand.plus.settings.fragments.BaseSettingsFragment.SettingsScreenType;
import net.osmand.plus.settings.fragments.ConfigureProfileFragment;
import net.osmand.plus.settings.fragments.DataStorageFragment;
import net.osmand.plus.settings.datastorage.DataStorageFragment;
import net.osmand.plus.track.TrackAppearanceFragment;
import net.osmand.plus.track.TrackMenuFragment;
import net.osmand.plus.views.AddGpxPointBottomSheetHelper.NewGpxPoint;

View file

@ -19,13 +19,13 @@ import net.osmand.plus.base.bottomsheetmenu.BaseBottomSheetItem;
import net.osmand.plus.base.bottomsheetmenu.BottomSheetItemWithDescription;
import net.osmand.plus.base.bottomsheetmenu.simpleitems.TitleItem;
import net.osmand.plus.settings.fragments.BaseSettingsFragment;
import net.osmand.plus.settings.fragments.DataStorageMenuItem;
import net.osmand.plus.settings.datastorage.item.StorageItem;
import org.apache.commons.logging.Log;
import java.io.File;
import static net.osmand.plus.settings.fragments.DataStorageHelper.MANUALLY_SPECIFIED;
import static net.osmand.plus.settings.datastorage.DataStorageHelper.MANUALLY_SPECIFIED;
public class ChangeDataStorageBottomSheet extends BasePreferenceBottomSheet {
@ -39,8 +39,8 @@ public class ChangeDataStorageBottomSheet extends BasePreferenceBottomSheet {
public final static String MOVE_DATA = "move_data";
public final static String CHOSEN_DIRECTORY = "chosen_storage";
private DataStorageMenuItem currentDirectory;
private DataStorageMenuItem newDirectory;
private StorageItem currentDirectory;
private StorageItem newDirectory;
@Override
public void createMenuItems(Bundle savedInstanceState) {
@ -126,11 +126,11 @@ public class ChangeDataStorageBottomSheet extends BasePreferenceBottomSheet {
items.add(baseItem);
}
public void setCurrentDirectory(DataStorageMenuItem currentDirectory) {
public void setCurrentDirectory(StorageItem currentDirectory) {
this.currentDirectory = currentDirectory;
}
public void setNewDirectory(DataStorageMenuItem newDirectory) {
public void setNewDirectory(StorageItem newDirectory) {
this.newDirectory = newDirectory;
}
@ -158,8 +158,8 @@ public class ChangeDataStorageBottomSheet extends BasePreferenceBottomSheet {
return true;
}
public static boolean showInstance(FragmentManager fm, String prefId, DataStorageMenuItem currentDirectory,
DataStorageMenuItem newDirectory, Fragment target, boolean usedOnMap) {
public static boolean showInstance(FragmentManager fm, String prefId, StorageItem currentDirectory,
StorageItem newDirectory, Fragment target, boolean usedOnMap) {
try {
if (fm.findFragmentByTag(TAG) == null) {
Bundle args = new Bundle();

View file

@ -1,8 +1,7 @@
package net.osmand.plus.settings.fragments;
package net.osmand.plus.settings.datastorage;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
@ -26,7 +25,6 @@ import net.osmand.AndroidUtils;
import net.osmand.FileUtils;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.ProgressImplementation;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.activities.MapActivity;
@ -34,20 +32,21 @@ import net.osmand.plus.activities.OsmandActionBarActivity;
import net.osmand.plus.download.DownloadActivity;
import net.osmand.plus.settings.bottomsheets.ChangeDataStorageBottomSheet;
import net.osmand.plus.settings.bottomsheets.SelectFolderBottomSheet;
import net.osmand.util.Algorithms;
import net.osmand.plus.settings.datastorage.item.MemoryItem;
import net.osmand.plus.settings.datastorage.item.StorageItem;
import net.osmand.plus.settings.datastorage.task.MoveFilesTask;
import net.osmand.plus.settings.datastorage.task.RefreshUsedMemoryTask;
import net.osmand.plus.settings.datastorage.task.ReloadDataTask;
import net.osmand.plus.settings.fragments.BaseSettingsFragment;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.text.DecimalFormat;
import java.util.ArrayList;
import static net.osmand.plus.settings.fragments.DataStorageHelper.INTERNAL_STORAGE;
import static net.osmand.plus.settings.fragments.DataStorageHelper.MANUALLY_SPECIFIED;
import static net.osmand.plus.settings.fragments.DataStorageHelper.OTHER_MEMORY;
import static net.osmand.plus.settings.fragments.DataStorageHelper.TILES_MEMORY;
import static net.osmand.plus.settings.datastorage.DataStorageHelper.INTERNAL_STORAGE;
import static net.osmand.plus.settings.datastorage.DataStorageHelper.MANUALLY_SPECIFIED;
import static net.osmand.plus.settings.datastorage.DataStorageHelper.OTHER_MEMORY;
import static net.osmand.plus.settings.datastorage.DataStorageHelper.TILES_MEMORY;
import static net.osmand.plus.settings.bottomsheets.ChangeDataStorageBottomSheet.CHOSEN_DIRECTORY;
import static net.osmand.plus.settings.bottomsheets.ChangeDataStorageBottomSheet.MOVE_DATA;
import static net.osmand.plus.settings.bottomsheets.SelectFolderBottomSheet.NEW_PATH;
@ -60,17 +59,17 @@ public class DataStorageFragment extends BaseSettingsFragment implements DataSto
private final static String CHANGE_DIRECTORY_BUTTON = "change_directory";
private final static String OSMAND_USAGE = "osmand_usage";
private ArrayList<DataStorageMenuItem> menuItems;
private ArrayList<DataStorageMemoryItem> memoryItems;
private ArrayList<StorageItem> storageItems;
private ArrayList<MemoryItem> memoryItems;
private ArrayList<CheckBoxPreference> dataStorageRadioButtonsGroup;
private Preference changeButton;
private DataStorageMenuItem currentDataStorage;
private StorageItem currentDataStorage;
private String tmpManuallySpecifiedPath;
private DataStorageHelper dataStorageHelper;
private boolean calculateTilesBtnPressed;
private DataStorageHelper.RefreshMemoryUsedInfo calculateMemoryTask;
private DataStorageHelper.RefreshMemoryUsedInfo calculateTilesMemoryTask;
private RefreshUsedMemoryTask calculateMemoryTask;
private RefreshUsedMemoryTask calculateTilesMemoryTask;
private OsmandApplication app;
private OsmandActionBarActivity activity;
@ -95,11 +94,11 @@ public class DataStorageFragment extends BaseSettingsFragment implements DataSto
return;
}
menuItems = dataStorageHelper.getStorageItems();
storageItems = dataStorageHelper.getStorageItems();
memoryItems = dataStorageHelper.getMemoryInfoItems();
dataStorageRadioButtonsGroup = new ArrayList<>();
for (DataStorageMenuItem item : menuItems) {
for (StorageItem item : storageItems) {
CheckBoxPreference preference = new CheckBoxPreference(activity);
preference.setKey(item.getKey());
preference.setTitle(item.getTitle());
@ -136,7 +135,7 @@ public class DataStorageFragment extends BaseSettingsFragment implements DataSto
Bundle resultData = (Bundle) newValue;
if (resultData.containsKey(ChangeDataStorageBottomSheet.TAG)) {
boolean moveMaps = resultData.getBoolean(MOVE_DATA);
DataStorageMenuItem newDataStorage = resultData.getParcelable(CHOSEN_DIRECTORY);
StorageItem newDataStorage = resultData.getParcelable(CHOSEN_DIRECTORY);
if (newDataStorage != null) {
if (tmpManuallySpecifiedPath != null) {
String directory = tmpManuallySpecifiedPath;
@ -154,9 +153,9 @@ public class DataStorageFragment extends BaseSettingsFragment implements DataSto
if (pathChanged) {
tmpManuallySpecifiedPath = resultData.getString(NEW_PATH);
if (tmpManuallySpecifiedPath != null) {
DataStorageMenuItem manuallySpecified = null;
StorageItem manuallySpecified = null;
try {
manuallySpecified = (DataStorageMenuItem) dataStorageHelper.getManuallySpecified().clone();
manuallySpecified = (StorageItem) dataStorageHelper.getManuallySpecified().clone();
manuallySpecified.setDirectory(tmpManuallySpecifiedPath);
} catch (CloneNotSupportedException e) {
return false;
@ -170,7 +169,7 @@ public class DataStorageFragment extends BaseSettingsFragment implements DataSto
//show necessary dialog
String key = preference.getKey();
if (key != null) {
DataStorageMenuItem newDataStorage = dataStorageHelper.getStorage(key);
StorageItem newDataStorage = dataStorageHelper.getStorage(key);
if (newDataStorage != null) {
if (!currentDataStorage.getKey().equals(newDataStorage.getKey())) {
if (newDataStorage.getType() == OsmandSettings.EXTERNAL_STORAGE_TYPE_DEFAULT
@ -212,7 +211,7 @@ public class DataStorageFragment extends BaseSettingsFragment implements DataSto
final View itemView = holder.itemView;
if (preference instanceof CheckBoxPreference) {
DataStorageMenuItem item = dataStorageHelper.getStorage(key);
StorageItem item = dataStorageHelper.getStorage(key);
if (item != null) {
TextView tvTitle = itemView.findViewById(android.R.id.title);
TextView tvSummary = itemView.findViewById(R.id.summary);
@ -267,7 +266,7 @@ public class DataStorageFragment extends BaseSettingsFragment implements DataSto
TextView tvSummary = itemView.findViewById(R.id.summary);
tvSummary.setText(DataStorageHelper.getFormattedMemoryInfo(totalUsageBytes, memoryUnitsFormats));
} else {
for (DataStorageMemoryItem mi : memoryItems) {
for (MemoryItem mi : memoryItems) {
if (key.equals(mi.getKey())) {
TextView tvMemory = itemView.findViewById(R.id.memory);
String summary = "";
@ -326,7 +325,7 @@ public class DataStorageFragment extends BaseSettingsFragment implements DataSto
}
private void showFolderSelectionDialog() {
DataStorageMenuItem manuallySpecified = dataStorageHelper.getManuallySpecified();
StorageItem manuallySpecified = dataStorageHelper.getManuallySpecified();
if (manuallySpecified != null) {
SelectFolderBottomSheet.showInstance(getFragmentManager(), manuallySpecified.getKey(),
manuallySpecified.getDirectory(), DataStorageFragment.this,
@ -335,11 +334,11 @@ public class DataStorageFragment extends BaseSettingsFragment implements DataSto
}
}
private void moveData(final DataStorageMenuItem currentStorage, final DataStorageMenuItem newStorage) {
private void moveData(final StorageItem currentStorage, final StorageItem newStorage) {
File fromDirectory = new File(currentStorage.getDirectory());
File toDirectory = new File(newStorage.getDirectory());
@SuppressLint("StaticFieldLeak")
MoveFilesToDifferentDirectory task = new MoveFilesToDifferentDirectory(activity, fromDirectory, toDirectory) {
MoveFilesTask task = new MoveFilesTask(activity, fromDirectory, toDirectory) {
@NonNull
@ -405,7 +404,7 @@ public class DataStorageFragment extends BaseSettingsFragment implements DataSto
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void confirm(OsmandApplication app, OsmandActionBarActivity activity, DataStorageMenuItem newStorageDirectory, boolean silentRestart) {
private void confirm(OsmandApplication app, OsmandActionBarActivity activity, StorageItem newStorageDirectory, boolean silentRestart) {
String newDirectory = newStorageDirectory.getDirectory();
int type = newStorageDirectory.getType();
File newDirectoryFile = new File(newDirectory);
@ -454,7 +453,7 @@ public class DataStorageFragment extends BaseSettingsFragment implements DataSto
}
protected void reloadData() {
new ReloadData(activity, app).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
new ReloadDataTask(activity, app).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
}
@Override
@ -463,204 +462,10 @@ public class DataStorageFragment extends BaseSettingsFragment implements DataSto
}
@Override
public void onFinishUpdating(String taskKey) {
public void onFinishUpdating(String tag) {
updateAllSettings();
if (taskKey != null && taskKey.equals(TILES_MEMORY)) {
if (tag != null && tag.equals(TILES_MEMORY)) {
app.getSettings().OSMAND_USAGE_SPACE.set(dataStorageHelper.getTotalUsedBytes());
}
}
public static class MoveFilesToDifferentDirectory extends AsyncTask<Void, Void, Boolean> {
protected WeakReference<OsmandActionBarActivity> activity;
private WeakReference<Context> context;
private File from;
private File to;
protected ProgressImplementation progress;
private Runnable runOnSuccess;
private int movedCount;
private long movedSize;
private int copiedCount;
private long copiedSize;
private int failedCount;
private long failedSize;
private String exceptionMessage;
public MoveFilesToDifferentDirectory(OsmandActionBarActivity activity, File from, File to) {
this.activity = new WeakReference<>(activity);
this.context = new WeakReference<>((Context) activity);
this.from = from;
this.to = to;
}
public void setRunOnSuccess(Runnable runOnSuccess) {
this.runOnSuccess = runOnSuccess;
}
public int getMovedCount() {
return movedCount;
}
public int getCopiedCount() {
return copiedCount;
}
public int getFailedCount() {
return failedCount;
}
public long getMovedSize() {
return movedSize;
}
public long getCopiedSize() {
return copiedSize;
}
public long getFailedSize() {
return failedSize;
}
@Override
protected void onPreExecute() {
Context ctx = context.get();
if (context == null) {
return;
}
movedCount = 0;
copiedCount = 0;
failedCount = 0;
progress = ProgressImplementation.createProgressDialog(
ctx, ctx.getString(R.string.copying_osmand_files),
ctx.getString(R.string.copying_osmand_files_descr, to.getPath()),
ProgressDialog.STYLE_HORIZONTAL);
}
@Override
protected void onPostExecute(Boolean result) {
Context ctx = context.get();
if (ctx == null) {
return;
}
if (result != null) {
if (result.booleanValue() && runOnSuccess != null) {
runOnSuccess.run();
} else if (!result.booleanValue()) {
Toast.makeText(ctx, ctx.getString(R.string.shared_string_io_error) + ": " + exceptionMessage, Toast.LENGTH_LONG).show();
}
}
try {
if (progress.getDialog().isShowing()) {
progress.getDialog().dismiss();
}
} catch (Exception e) {
//ignored
}
}
private void movingFiles(File f, File t, int depth) throws IOException {
Context ctx = context.get();
if (ctx == null) {
return;
}
if (depth <= 2) {
progress.startTask(ctx.getString(R.string.copying_osmand_one_file_descr, t.getName()), -1);
}
if (f.isDirectory()) {
t.mkdirs();
File[] lf = f.listFiles();
if (lf != null) {
for (int i = 0; i < lf.length; i++) {
if (lf[i] != null) {
movingFiles(lf[i], new File(t, lf[i].getName()), depth + 1);
}
}
}
f.delete();
} else if (f.isFile()) {
if (t.exists()) {
Algorithms.removeAllFiles(t);
}
boolean rnm = false;
long fileSize = f.length();
try {
rnm = f.renameTo(t);
movedCount++;
movedSize += fileSize;
} catch (RuntimeException e) {
}
if (!rnm) {
FileInputStream fin = new FileInputStream(f);
FileOutputStream fout = new FileOutputStream(t);
try {
progress.startTask(ctx.getString(R.string.copying_osmand_one_file_descr, t.getName()), (int) (f.length() / 1024));
Algorithms.streamCopy(fin, fout, progress, 1024);
copiedCount++;
copiedSize += fileSize;
} catch (IOException e) {
failedCount++;
failedSize += fileSize;
} finally {
fin.close();
fout.close();
}
f.delete();
}
}
if (depth <= 2) {
progress.finishTask();
}
}
@Override
protected Boolean doInBackground(Void... params) {
to.mkdirs();
try {
movingFiles(from, to, 0);
} catch (IOException e) {
exceptionMessage = e.getMessage();
return false;
}
return true;
}
}
public static class ReloadData extends AsyncTask<Void, Void, Boolean> {
private WeakReference<Context> ctx;
protected ProgressImplementation progress;
private OsmandApplication app;
public ReloadData(Context ctx, OsmandApplication app) {
this.ctx = new WeakReference<>(ctx);
this.app = app;
}
@Override
protected void onPreExecute() {
Context c = ctx.get();
if (c == null) {
return;
}
progress = ProgressImplementation.createProgressDialog(c, c.getString(R.string.loading_data),
c.getString(R.string.loading_data), ProgressDialog.STYLE_HORIZONTAL);
}
@Override
protected void onPostExecute(Boolean result) {
try {
if (progress.getDialog().isShowing()) {
progress.getDialog().dismiss();
}
} catch (Exception e) {
//ignored
}
}
@Override
protected Boolean doInBackground(Void... params) {
app.getResourceManager().reloadIndexes(progress, new ArrayList<String>());
return true;
}
}
}

View file

@ -0,0 +1,321 @@
package net.osmand.plus.settings.datastorage;
import android.os.Build;
import androidx.annotation.NonNull;
import net.osmand.IndexConstants;
import net.osmand.ValueHolder;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.R;
import net.osmand.plus.settings.datastorage.item.DirectoryItem;
import net.osmand.plus.settings.datastorage.item.DirectoryItem.CheckingType;
import net.osmand.plus.settings.datastorage.item.MemoryItem;
import net.osmand.plus.settings.datastorage.item.StorageItem;
import net.osmand.plus.settings.datastorage.task.RefreshUsedMemoryTask;
import java.io.File;
import java.text.DecimalFormat;
import java.util.ArrayList;
import static net.osmand.IndexConstants.AV_INDEX_DIR;
import static net.osmand.IndexConstants.BACKUP_INDEX_DIR;
import static net.osmand.IndexConstants.GPX_INDEX_DIR;
import static net.osmand.IndexConstants.MAPS_PATH;
import static net.osmand.IndexConstants.ROADS_INDEX_DIR;
import static net.osmand.IndexConstants.SRTM_INDEX_DIR;
import static net.osmand.IndexConstants.TILES_INDEX_DIR;
import static net.osmand.IndexConstants.WIKIVOYAGE_INDEX_DIR;
import static net.osmand.IndexConstants.WIKI_INDEX_DIR;
import static net.osmand.plus.settings.datastorage.item.DirectoryItem.CheckingType.EXTENSIONS;
import static net.osmand.plus.settings.datastorage.item.DirectoryItem.CheckingType.PREFIX;
public class DataStorageHelper {
public final static String INTERNAL_STORAGE = "internal_storage";
public final static String EXTERNAL_STORAGE = "external_storage";
public final static String SHARED_STORAGE = "shared_storage";
public final static String MULTIUSER_STORAGE = "multiuser_storage";
public final static String MANUALLY_SPECIFIED = "manually_specified";
public final static String MAPS_MEMORY = "maps_memory_used";
public final static String TERRAIN_MEMORY = "terrain_memory_used";
public final static String TRACKS_MEMORY = "tracks_memory_used";
public final static String NOTES_MEMORY = "notes_memory_used";
public final static String TILES_MEMORY = "tiles_memory_used";
public final static String OTHER_MEMORY = "other_memory_used";
private OsmandApplication app;
private ArrayList<StorageItem> storageItems = new ArrayList<>();
private StorageItem currentDataStorage;
private StorageItem manuallySpecified;
private ArrayList<MemoryItem> memoryItems = new ArrayList<>();
private MemoryItem mapsMemory;
private MemoryItem terrainMemory;
private MemoryItem tracksMemory;
private MemoryItem notesMemory;
private MemoryItem tilesMemory;
private MemoryItem otherMemory;
private int currentStorageType;
private String currentStoragePath;
public DataStorageHelper(@NonNull OsmandApplication app) {
this.app = app;
prepareData();
}
private void prepareData() {
initStorageItems();
initUsedMemoryItems();
}
private void initStorageItems() {
OsmandSettings settings = app.getSettings();
if (settings.getExternalStorageDirectoryTypeV19() >= 0) {
currentStorageType = settings.getExternalStorageDirectoryTypeV19();
} else {
ValueHolder<Integer> vh = new ValueHolder<Integer>();
if (vh.value != null && vh.value >= 0) {
currentStorageType = vh.value;
} else {
currentStorageType = 0;
}
}
currentStoragePath = settings.getExternalStorageDirectory().getAbsolutePath();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//internal storage
String path = settings.getInternalAppPath().getAbsolutePath();
File dir = new File(path);
int iconId = R.drawable.ic_action_phone;
StorageItem internalStorageItem = StorageItem.builder()
.setKey(INTERNAL_STORAGE)
.setTitle(app.getString(R.string.storage_directory_internal_app))
.setDirectory(path)
.setDescription(app.getString(R.string.internal_app_storage_description))
.setType(OsmandSettings.EXTERNAL_STORAGE_TYPE_INTERNAL_FILE)
.setIconResId(iconId)
.createItem();
addItem(internalStorageItem);
//shared storage
dir = settings.getDefaultInternalStorage();
path = dir.getAbsolutePath();
iconId = R.drawable.ic_action_phone;
StorageItem sharedStorageItem = StorageItem.builder()
.setKey(SHARED_STORAGE)
.setTitle(app.getString(R.string.storage_directory_shared))
.setDirectory(path)
.setType(OsmandSettings.EXTERNAL_STORAGE_TYPE_DEFAULT)
.setIconResId(iconId)
.createItem();
addItem(sharedStorageItem);
//external storage
File[] externals = app.getExternalFilesDirs(null);
if (externals != null) {
int i = 0;
for (File external : externals) {
if (external != null) {
++i;
dir = external;
path = dir.getAbsolutePath();
iconId = getIconForStorageType(dir);
StorageItem externalStorageItem = StorageItem.builder()
.setKey(EXTERNAL_STORAGE + i)
.setTitle(app.getString(R.string.storage_directory_external) + " " + i)
.setDirectory(path)
.setType(OsmandSettings.EXTERNAL_STORAGE_TYPE_EXTERNAL_FILE)
.setIconResId(iconId)
.createItem();
addItem(externalStorageItem);
}
}
}
//multi user storage
File[] obbDirs = app.getObbDirs();
if (obbDirs != null) {
int i = 0;
for (File obb : obbDirs) {
if (obb != null) {
++i;
dir = obb;
path = dir.getAbsolutePath();
iconId = getIconForStorageType(dir);
StorageItem multiuserStorageItem = StorageItem.builder()
.setKey(MULTIUSER_STORAGE + i)
.setTitle(app.getString(R.string.storage_directory_multiuser) + " " + i)
.setDirectory(path)
.setType(OsmandSettings.EXTERNAL_STORAGE_TYPE_OBB)
.setIconResId(iconId)
.createItem();
addItem(multiuserStorageItem);
}
}
}
}
//manually specified storage
manuallySpecified = StorageItem.builder()
.setKey(MANUALLY_SPECIFIED)
.setTitle(app.getString(R.string.storage_directory_manual))
.setDirectory(currentStoragePath)
.setType(OsmandSettings.EXTERNAL_STORAGE_TYPE_SPECIFIED)
.setIconResId(R.drawable.ic_action_folder)
.createItem();
storageItems.add(manuallySpecified);
if (currentDataStorage == null) {
currentDataStorage = manuallySpecified;
}
}
private void initUsedMemoryItems() {
mapsMemory = MemoryItem.builder()
.setKey(MAPS_MEMORY)
.setExtensions(IndexConstants.BINARY_MAP_INDEX_EXT)
.setDirectories(
createDirectory(MAPS_PATH, false, EXTENSIONS, true),
createDirectory(ROADS_INDEX_DIR, true, EXTENSIONS, true),
createDirectory(WIKI_INDEX_DIR, true, EXTENSIONS, true),
createDirectory(WIKIVOYAGE_INDEX_DIR, true, EXTENSIONS, true),
createDirectory(BACKUP_INDEX_DIR, true, EXTENSIONS, true))
.createItem();
memoryItems.add(mapsMemory);
terrainMemory = MemoryItem.builder()
.setKey(TERRAIN_MEMORY)
.setExtensions(IndexConstants.BINARY_SRTM_MAP_INDEX_EXT)
.setDirectories(
createDirectory(SRTM_INDEX_DIR, true, EXTENSIONS, true),
createDirectory(TILES_INDEX_DIR, false, PREFIX, false))
.setPrefixes("Hillshade", "Slope")
.createItem();
memoryItems.add(terrainMemory);
tracksMemory = MemoryItem.builder()
.setKey(TRACKS_MEMORY)
// .setExtensions(IndexConstants.GPX_FILE_EXT, ".gpx.bz2")
.setDirectories(
createDirectory(GPX_INDEX_DIR, true, EXTENSIONS, true))
.createItem();
memoryItems.add(tracksMemory);
notesMemory = MemoryItem.builder()
.setKey(NOTES_MEMORY)
// .setExtensions("")
.setDirectories(
createDirectory(AV_INDEX_DIR, true, EXTENSIONS, true))
.createItem();
memoryItems.add(notesMemory);
tilesMemory = MemoryItem.builder()
.setKey(TILES_MEMORY)
// .setExtensions("")
.setDirectories(
createDirectory(TILES_INDEX_DIR, true, EXTENSIONS, true))
.createItem();
memoryItems.add(tilesMemory);
otherMemory = MemoryItem.builder()
.setKey(OTHER_MEMORY)
.createItem();
memoryItems.add(otherMemory);
}
public ArrayList<StorageItem> getStorageItems() {
return storageItems;
}
private int getIconForStorageType(File dir) {
return R.drawable.ic_action_folder;
}
public StorageItem getCurrentStorage() {
return currentDataStorage;
}
private void addItem(StorageItem item) {
if (currentStorageType == item.getType() && currentStoragePath.equals(item.getDirectory())) {
currentDataStorage = item;
}
storageItems.add(item);
}
public StorageItem getManuallySpecified() {
return manuallySpecified;
}
public StorageItem getStorage(String key) {
if (storageItems != null && key != null) {
for (StorageItem storageItem : storageItems) {
if (key.equals(storageItem.getKey())) {
return storageItem;
}
}
}
return null;
}
public ArrayList<MemoryItem> getMemoryInfoItems() {
return memoryItems;
}
public RefreshUsedMemoryTask calculateMemoryUsedInfo(UpdateMemoryInfoUIAdapter uiAdapter) {
File rootDir = new File(currentStoragePath);
RefreshUsedMemoryTask task = new RefreshUsedMemoryTask(uiAdapter, otherMemory, rootDir, null, null, OTHER_MEMORY);
task.execute(mapsMemory, terrainMemory, tracksMemory, notesMemory);
return task;
}
public RefreshUsedMemoryTask calculateTilesMemoryUsed(UpdateMemoryInfoUIAdapter listener) {
File rootDir = new File(tilesMemory.getDirectories()[0].getAbsolutePath());
RefreshUsedMemoryTask task = new RefreshUsedMemoryTask(listener, otherMemory, rootDir, null, terrainMemory.getPrefixes(), TILES_MEMORY);
task.execute(tilesMemory);
return task;
}
public long getTotalUsedBytes() {
long total = 0;
if (memoryItems != null && memoryItems.size() > 0) {
for (MemoryItem mi : memoryItems) {
total += mi.getUsedMemoryBytes();
}
return total;
}
return -1;
}
public DirectoryItem createDirectory(@NonNull String relativePath,
boolean processInternalDirectories,
CheckingType checkingType,
boolean addUnmatchedToOtherMemory) {
String path = app.getAppPath(relativePath).getAbsolutePath();
return new DirectoryItem(path, processInternalDirectories, checkingType, addUnmatchedToOtherMemory);
}
public static String getFormattedMemoryInfo(long bytes, String[] formatStrings) {
int type = 0;
double memory = (double) bytes / 1024;
while (memory > 1024 && type < formatStrings.length) {
++type;
memory = memory / 1024;
}
String formattedUsed = new DecimalFormat("#.##").format(memory);
return String.format(formatStrings[type], formattedUsed);
}
public interface UpdateMemoryInfoUIAdapter {
void onMemoryInfoUpdate();
void onFinishUpdating(String tag);
}
}

View file

@ -0,0 +1,40 @@
package net.osmand.plus.settings.datastorage.item;
public class DirectoryItem {
private final String absolutePath;
private final boolean processInternalDirectories;
private final CheckingType checkingType;
private final boolean addUnmatchedToOtherMemory;
public enum CheckingType {
EXTENSIONS,
PREFIX
}
public DirectoryItem(String absolutePath,
boolean processInternalDirectories,
CheckingType checkingType,
boolean addUnmatchedToOtherMemory) {
this.absolutePath = absolutePath;
this.processInternalDirectories = processInternalDirectories;
this.checkingType = checkingType;
this.addUnmatchedToOtherMemory = addUnmatchedToOtherMemory;
}
public String getAbsolutePath() {
return absolutePath;
}
public boolean shouldProcessInternalDirectories() {
return processInternalDirectories;
}
public CheckingType getCheckingType() {
return checkingType;
}
public boolean shouldAddUnmatchedToOtherMemory() {
return addUnmatchedToOtherMemory;
}
}

View file

@ -1,16 +1,18 @@
package net.osmand.plus.settings.fragments;
package net.osmand.plus.settings.datastorage.item;
public class DataStorageMemoryItem {
public final static int EXTENSIONS = 0;
public final static int PREFIX = 1;
public class MemoryItem {
private String key;
private String[] extensions;
private String[] prefixes;
private Directory[] directories;
private final String[] extensions;
private final String[] prefixes;
private final DirectoryItem[] directories;
private long usedMemoryBytes;
private DataStorageMemoryItem(String key, String[] extensions, String[] prefixes, long usedMemoryBytes, Directory[] directories) {
private MemoryItem(String key,
String[] extensions,
String[] prefixes,
long usedMemoryBytes,
DirectoryItem[] directories) {
this.key = key;
this.extensions = extensions;
this.prefixes = prefixes;
@ -42,7 +44,7 @@ public class DataStorageMemoryItem {
return prefixes;
}
public Directory[] getDirectories() {
public DirectoryItem[] getDirectories() {
return directories;
}
@ -54,7 +56,7 @@ public class DataStorageMemoryItem {
private String key;
private String[] extensions;
private String[] prefixes;
private Directory[] directories;
private DirectoryItem[] directories;
private long usedMemoryBytes;
public DataStorageMemoryItemBuilder setKey(String key) {
@ -72,7 +74,7 @@ public class DataStorageMemoryItem {
return this;
}
public DataStorageMemoryItemBuilder setDirectories(Directory ... directories) {
public DataStorageMemoryItemBuilder setDirectories(DirectoryItem... directories) {
this.directories = directories;
return this;
}
@ -82,38 +84,8 @@ public class DataStorageMemoryItem {
return this;
}
public DataStorageMemoryItem createItem() {
return new DataStorageMemoryItem(key, extensions, prefixes, usedMemoryBytes, directories);
}
}
public static class Directory {
private String absolutePath;
private boolean goDeeper;
private int checkingType;
private boolean skipOther;
public Directory(String absolutePath, boolean goDeeper, int checkingType, boolean skipOther) {
this.absolutePath = absolutePath;
this.goDeeper = goDeeper;
this.checkingType = checkingType;
this.skipOther = skipOther;
}
public String getAbsolutePath() {
return absolutePath;
}
public boolean isGoDeeper() {
return goDeeper;
}
public int getCheckingType() {
return checkingType;
}
public boolean isSkipOther() {
return skipOther;
public MemoryItem createItem() {
return new MemoryItem(key, extensions, prefixes, usedMemoryBytes, directories);
}
}
}

View file

@ -1,11 +1,11 @@
package net.osmand.plus.settings.fragments;
package net.osmand.plus.settings.datastorage.item;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.IdRes;
public class DataStorageMenuItem implements Parcelable, Cloneable {
public class StorageItem implements Parcelable, Cloneable {
private String key;
private int type;
@ -15,8 +15,12 @@ public class DataStorageMenuItem implements Parcelable, Cloneable {
@IdRes
private int iconResId;
private DataStorageMenuItem(String key, int type, String title, String description,
String directory, int iconResId) {
private StorageItem(String key,
int type,
String title,
String description,
String directory,
int iconResId) {
this.key = key;
this.type = type;
this.title = title;
@ -25,7 +29,7 @@ public class DataStorageMenuItem implements Parcelable, Cloneable {
this.iconResId = iconResId;
}
private DataStorageMenuItem(Parcel in) {
private StorageItem(Parcel in) {
key = in.readString();
type = in.readInt();
title = in.readString();
@ -99,16 +103,16 @@ public class DataStorageMenuItem implements Parcelable, Cloneable {
dest.writeString(directory);
}
public static final Parcelable.Creator<DataStorageMenuItem> CREATOR = new Parcelable.Creator<DataStorageMenuItem>() {
public static final Parcelable.Creator<StorageItem> CREATOR = new Parcelable.Creator<StorageItem>() {
@Override
public DataStorageMenuItem createFromParcel(Parcel source) {
return new DataStorageMenuItem(source);
public StorageItem createFromParcel(Parcel source) {
return new StorageItem(source);
}
@Override
public DataStorageMenuItem[] newArray(int size) {
return new DataStorageMenuItem[size];
public StorageItem[] newArray(int size) {
return new StorageItem[size];
}
};
@ -151,14 +155,14 @@ public class DataStorageMenuItem implements Parcelable, Cloneable {
return this;
}
public DataStorageMenuItem createItem() {
return new DataStorageMenuItem(key, type, title, description, directory, iconResId);
public StorageItem createItem() {
return new StorageItem(key, type, title, description, directory, iconResId);
}
}
@Override
public Object clone() throws CloneNotSupportedException {
return DataStorageMenuItem.builder()
return StorageItem.builder()
.setKey(this.key)
.setTitle(this.title)
.setDescription(this.description)

View file

@ -0,0 +1,173 @@
package net.osmand.plus.settings.datastorage.task;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.widget.Toast;
import net.osmand.plus.ProgressImplementation;
import net.osmand.plus.R;
import net.osmand.plus.activities.OsmandActionBarActivity;
import net.osmand.util.Algorithms;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
public class MoveFilesTask extends AsyncTask<Void, Void, Boolean> {
protected WeakReference<OsmandActionBarActivity> activity;
private WeakReference<Context> context;
private File from;
private File to;
protected ProgressImplementation progress;
private Runnable runOnSuccess;
private int movedCount;
private long movedSize;
private int copiedCount;
private long copiedSize;
private int failedCount;
private long failedSize;
private String exceptionMessage;
public MoveFilesTask(OsmandActionBarActivity activity, File from, File to) {
this.activity = new WeakReference<>(activity);
this.context = new WeakReference<>((Context) activity);
this.from = from;
this.to = to;
}
public void setRunOnSuccess(Runnable runOnSuccess) {
this.runOnSuccess = runOnSuccess;
}
public int getMovedCount() {
return movedCount;
}
public int getCopiedCount() {
return copiedCount;
}
public int getFailedCount() {
return failedCount;
}
public long getMovedSize() {
return movedSize;
}
public long getCopiedSize() {
return copiedSize;
}
public long getFailedSize() {
return failedSize;
}
@Override
protected void onPreExecute() {
Context ctx = context.get();
if (context == null) {
return;
}
movedCount = 0;
copiedCount = 0;
failedCount = 0;
progress = ProgressImplementation.createProgressDialog(
ctx, ctx.getString(R.string.copying_osmand_files),
ctx.getString(R.string.copying_osmand_files_descr, to.getPath()),
ProgressDialog.STYLE_HORIZONTAL);
}
@Override
protected void onPostExecute(Boolean result) {
Context ctx = context.get();
if (ctx == null) {
return;
}
if (result != null) {
if (result.booleanValue() && runOnSuccess != null) {
runOnSuccess.run();
} else if (!result.booleanValue()) {
Toast.makeText(ctx, ctx.getString(R.string.shared_string_io_error) + ": " + exceptionMessage, Toast.LENGTH_LONG).show();
}
}
try {
if (progress.getDialog().isShowing()) {
progress.getDialog().dismiss();
}
} catch (Exception e) {
//ignored
}
}
private void movingFiles(File f, File t, int depth) throws IOException {
Context ctx = context.get();
if (ctx == null) {
return;
}
if (depth <= 2) {
progress.startTask(ctx.getString(R.string.copying_osmand_one_file_descr, t.getName()), -1);
}
if (f.isDirectory()) {
t.mkdirs();
File[] lf = f.listFiles();
if (lf != null) {
for (int i = 0; i < lf.length; i++) {
if (lf[i] != null) {
movingFiles(lf[i], new File(t, lf[i].getName()), depth + 1);
}
}
}
f.delete();
} else if (f.isFile()) {
if (t.exists()) {
Algorithms.removeAllFiles(t);
}
boolean rnm = false;
long fileSize = f.length();
try {
rnm = f.renameTo(t);
movedCount++;
movedSize += fileSize;
} catch (RuntimeException e) {
}
if (!rnm) {
FileInputStream fin = new FileInputStream(f);
FileOutputStream fout = new FileOutputStream(t);
try {
progress.startTask(ctx.getString(R.string.copying_osmand_one_file_descr, t.getName()), (int) (f.length() / 1024));
Algorithms.streamCopy(fin, fout, progress, 1024);
copiedCount++;
copiedSize += fileSize;
} catch (IOException e) {
failedCount++;
failedSize += fileSize;
} finally {
fin.close();
fout.close();
}
f.delete();
}
}
if (depth <= 2) {
progress.finishTask();
}
}
@Override
protected Boolean doInBackground(Void... params) {
to.mkdirs();
try {
movingFiles(from, to, 0);
} catch (IOException e) {
exceptionMessage = e.getMessage();
return false;
}
return true;
}
}

View file

@ -0,0 +1,232 @@
package net.osmand.plus.settings.datastorage.task;
import android.os.AsyncTask;
import androidx.annotation.NonNull;
import net.osmand.plus.settings.datastorage.DataStorageHelper.UpdateMemoryInfoUIAdapter;
import net.osmand.plus.settings.datastorage.item.DirectoryItem;
import net.osmand.plus.settings.datastorage.item.DirectoryItem.CheckingType;
import net.osmand.plus.settings.datastorage.item.MemoryItem;
import java.io.File;
import static net.osmand.plus.settings.datastorage.DataStorageFragment.UI_REFRESH_TIME_MS;
import static net.osmand.util.Algorithms.objectEquals;
public class RefreshUsedMemoryTask extends AsyncTask<MemoryItem, Void, Void> {
private final UpdateMemoryInfoUIAdapter uiAdapter;
private final File root;
private final MemoryItem otherMemoryItem;
private final String[] directoriesToSkip;
private final String[] filePrefixesToSkip;
private final String tag;
private long lastRefreshTime;
public RefreshUsedMemoryTask(UpdateMemoryInfoUIAdapter uiAdapter,
MemoryItem otherMemoryItem,
File root,
String[] directoriesToSkip,
String[] filePrefixesToSkip,
String tag) {
this.uiAdapter = uiAdapter;
this.otherMemoryItem = otherMemoryItem;
this.root = root;
this.directoriesToSkip = directoriesToSkip;
this.filePrefixesToSkip = filePrefixesToSkip;
this.tag = tag;
}
@Override
protected Void doInBackground(MemoryItem... items) {
lastRefreshTime = System.currentTimeMillis();
if (root.canRead()) {
calculateMultiTypes(root, items);
}
return null;
}
private void calculateMultiTypes(File rootDir,
MemoryItem... items) {
File[] subFiles = rootDir.listFiles();
if (subFiles != null) {
for (File file : subFiles) {
if (isCancelled()) break;
if (file.isDirectory()) {
if (!shouldSkipDirectory(file)) {
processDirectory(file, items);
}
} else if (file.isFile()) {
if (!shouldSkipFile(file)) {
processFile(rootDir, file, items);
}
}
refreshUI();
}
}
}
private boolean shouldSkipDirectory(@NonNull File dir) {
if (directoriesToSkip != null) {
for (String dirToSkipPath : directoriesToSkip) {
String dirPath = dir.getAbsolutePath();
if (objectEquals(dirPath, dirToSkipPath)) {
return true;
}
}
}
return false;
}
private boolean shouldSkipFile(@NonNull File file) {
if (filePrefixesToSkip != null) {
String fileName = file.getName().toLowerCase();
for (String prefixToAvoid : filePrefixesToSkip) {
String prefix = prefixToAvoid.toLowerCase();
if (fileName.startsWith(prefix)) {
return true;
}
}
}
return false;
}
private void processDirectory(@NonNull File directory,
@NonNull MemoryItem... items) {
String directoryPath = directory.getAbsolutePath();
for (MemoryItem memoryItem : items) {
DirectoryItem[] targetDirectories = memoryItem.getDirectories();
if (targetDirectories != null) {
for (DirectoryItem dir : targetDirectories) {
String allowedDirPath = dir.getAbsolutePath();
boolean isPerfectlyMatch = objectEquals(directoryPath, allowedDirPath);
boolean isParentDirectory = !isPerfectlyMatch && (directoryPath.startsWith(allowedDirPath));
boolean isMatchDirectory = isPerfectlyMatch || isParentDirectory;
if (isPerfectlyMatch) {
calculateMultiTypes(directory, items);
return;
} else if (isParentDirectory && dir.shouldProcessInternalDirectories()) {
calculateMultiTypes(directory, items);
return;
} else if (isMatchDirectory && !dir.shouldAddUnmatchedToOtherMemory()) {
return;
}
}
}
}
// Current directory did not match to any type
otherMemoryItem.addBytes(calculateFolderSize(directory));
}
private void processFile(@NonNull File rootDir,
@NonNull File file,
@NonNull MemoryItem... items) {
for (MemoryItem item : items) {
DirectoryItem[] targetDirectories = item.getDirectories();
if (targetDirectories == null) continue;
String rootDirPath = rootDir.getAbsolutePath();
for (DirectoryItem targetDirectory : targetDirectories) {
String allowedDirPath = targetDirectory.getAbsolutePath();
boolean processInternal = targetDirectory.shouldProcessInternalDirectories();
if (objectEquals(rootDirPath, allowedDirPath)
|| (rootDirPath.startsWith(allowedDirPath) && processInternal)) {
CheckingType checkingType = targetDirectory.getCheckingType();
switch (checkingType) {
case EXTENSIONS: {
if (isSuitableExtension(file, item)) {
item.addBytes(file.length());
return;
}
break;
}
case PREFIX: {
if (isSuitablePrefix(file, item)) {
item.addBytes(file.length());
return;
}
break;
}
}
if (!targetDirectory.shouldAddUnmatchedToOtherMemory()) {
return;
}
}
}
}
// Current file did not match any type
otherMemoryItem.addBytes(file.length());
}
private boolean isSuitableExtension(@NonNull File file,
@NonNull MemoryItem item) {
String[] extensions = item.getExtensions();
if (extensions != null) {
for (String extension : extensions) {
if (file.getAbsolutePath().endsWith(extension)) {
return true;
}
}
}
return extensions == null;
}
private boolean isSuitablePrefix(@NonNull File file,
@NonNull MemoryItem item) {
String[] prefixes = item.getPrefixes();
if (prefixes != null) {
for (String prefix : prefixes) {
if (file.getName().toLowerCase().startsWith(prefix.toLowerCase())) {
return true;
}
}
}
return prefixes == null;
}
private long calculateFolderSize(@NonNull File dir) {
long bytes = 0;
if (dir.isDirectory()) {
File[] files = dir.listFiles();
if (files == null) return 0;
for (File file : files) {
if (isCancelled()) {
break;
}
if (file.isDirectory()) {
bytes += calculateFolderSize(file);
} else if (file.isFile()) {
bytes += file.length();
}
}
}
return bytes;
}
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
if (uiAdapter != null) {
uiAdapter.onMemoryInfoUpdate();
}
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
if (uiAdapter != null) {
uiAdapter.onFinishUpdating(tag);
}
}
private void refreshUI() {
long currentTime = System.currentTimeMillis();
if ((currentTime - lastRefreshTime) > UI_REFRESH_TIME_MS) {
lastRefreshTime = currentTime;
publishProgress();
}
}
}

View file

@ -0,0 +1,50 @@
package net.osmand.plus.settings.datastorage.task;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.ProgressImplementation;
import net.osmand.plus.R;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
public class ReloadDataTask extends AsyncTask<Void, Void, Boolean> {
private WeakReference<Context> ctx;
protected ProgressImplementation progress;
private OsmandApplication app;
public ReloadDataTask(Context ctx, OsmandApplication app) {
this.ctx = new WeakReference<>(ctx);
this.app = app;
}
@Override
protected void onPreExecute() {
Context c = ctx.get();
if (c == null) {
return;
}
progress = ProgressImplementation.createProgressDialog(c, c.getString(R.string.loading_data),
c.getString(R.string.loading_data), ProgressDialog.STYLE_HORIZONTAL);
}
@Override
protected void onPostExecute(Boolean result) {
try {
if (progress.getDialog().isShowing()) {
progress.getDialog().dismiss();
}
} catch (Exception e) {
//ignored
}
}
@Override
protected Boolean doInBackground(Void... params) {
app.getResourceManager().reloadIndexes(progress, new ArrayList<String>());
return true;
}
}

View file

@ -73,6 +73,7 @@ import net.osmand.plus.settings.bottomsheets.ChangeGeneralProfilesPrefBottomShee
import net.osmand.plus.settings.bottomsheets.EditTextPreferenceBottomSheet;
import net.osmand.plus.settings.bottomsheets.MultiSelectPreferencesBottomSheet;
import net.osmand.plus.settings.bottomsheets.SingleSelectPreferenceBottomSheet;
import net.osmand.plus.settings.datastorage.DataStorageFragment;
import net.osmand.plus.settings.preferences.ListPreferenceEx;
import net.osmand.plus.settings.preferences.MultiSelectBooleanPreference;
import net.osmand.plus.settings.preferences.SwitchPreferenceEx;

View file

@ -1,485 +0,0 @@
package net.osmand.plus.settings.fragments;
import android.os.AsyncTask;
import android.os.Build;
import net.osmand.IndexConstants;
import net.osmand.ValueHolder;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.R;
import java.io.File;
import java.text.DecimalFormat;
import java.util.ArrayList;
import static net.osmand.plus.settings.fragments.DataStorageFragment.UI_REFRESH_TIME_MS;
import static net.osmand.plus.settings.fragments.DataStorageMemoryItem.Directory;
import static net.osmand.plus.settings.fragments.DataStorageMemoryItem.EXTENSIONS;
import static net.osmand.plus.settings.fragments.DataStorageMemoryItem.PREFIX;
public class DataStorageHelper {
public final static String INTERNAL_STORAGE = "internal_storage";
public final static String EXTERNAL_STORAGE = "external_storage";
public final static String SHARED_STORAGE = "shared_storage";
public final static String MULTIUSER_STORAGE = "multiuser_storage";
public final static String MANUALLY_SPECIFIED = "manually_specified";
public final static String MAPS_MEMORY = "maps_memory_used";
public final static String SRTM_AND_HILLSHADE_MEMORY = "contour_lines_and_hillshade_memory";
public final static String TRACKS_MEMORY = "tracks_memory_used";
public final static String NOTES_MEMORY = "notes_memory_used";
public final static String TILES_MEMORY = "tiles_memory_used";
public final static String OTHER_MEMORY = "other_memory_used";
private ArrayList<DataStorageMenuItem> menuItems = new ArrayList<>();
private DataStorageMenuItem currentDataStorage;
private DataStorageMenuItem manuallySpecified;
private ArrayList<DataStorageMemoryItem> memoryItems = new ArrayList<>();
private DataStorageMemoryItem mapsMemory;
private DataStorageMemoryItem srtmAndHillshadeMemory;
private DataStorageMemoryItem tracksMemory;
private DataStorageMemoryItem notesMemory;
private DataStorageMemoryItem tilesMemory;
private DataStorageMemoryItem otherMemory;
private int currentStorageType;
private String currentStoragePath;
public DataStorageHelper(OsmandApplication app) {
prepareData(app);
}
private void prepareData(OsmandApplication app) {
if (app == null) {
return;
}
OsmandSettings settings = app.getSettings();
if (settings.getExternalStorageDirectoryTypeV19() >= 0) {
currentStorageType = settings.getExternalStorageDirectoryTypeV19();
} else {
ValueHolder<Integer> vh = new ValueHolder<Integer>();
if (vh.value != null && vh.value >= 0) {
currentStorageType = vh.value;
} else {
currentStorageType = 0;
}
}
currentStoragePath = settings.getExternalStorageDirectory().getAbsolutePath();
String path;
File dir;
int iconId;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//internal storage
path = settings.getInternalAppPath().getAbsolutePath();
dir = new File(path);
iconId = R.drawable.ic_action_phone;
DataStorageMenuItem internalStorageItem = DataStorageMenuItem.builder()
.setKey(INTERNAL_STORAGE)
.setTitle(app.getString(R.string.storage_directory_internal_app))
.setDirectory(path)
.setDescription(app.getString(R.string.internal_app_storage_description))
.setType(OsmandSettings.EXTERNAL_STORAGE_TYPE_INTERNAL_FILE)
.setIconResId(iconId)
.createItem();
addItem(internalStorageItem);
//shared_storage
dir = settings.getDefaultInternalStorage();
path = dir.getAbsolutePath();
iconId = R.drawable.ic_action_phone;
DataStorageMenuItem sharedStorageItem = DataStorageMenuItem.builder()
.setKey(SHARED_STORAGE)
.setTitle(app.getString(R.string.storage_directory_shared))
.setDirectory(path)
.setType(OsmandSettings.EXTERNAL_STORAGE_TYPE_DEFAULT)
.setIconResId(iconId)
.createItem();
addItem(sharedStorageItem);
//external storage
File[] externals = app.getExternalFilesDirs(null);
if (externals != null) {
int i = 0;
for (File external : externals) {
if (external != null) {
++i;
dir = external;
path = dir.getAbsolutePath();
iconId = getIconForStorageType(dir);
DataStorageMenuItem externalStorageItem = DataStorageMenuItem.builder()
.setKey(EXTERNAL_STORAGE + i)
.setTitle(app.getString(R.string.storage_directory_external) + " " + i)
.setDirectory(path)
.setType(OsmandSettings.EXTERNAL_STORAGE_TYPE_EXTERNAL_FILE)
.setIconResId(iconId)
.createItem();
addItem(externalStorageItem);
}
}
}
//multi user storage
File[] obbDirs = app.getObbDirs();
if (obbDirs != null) {
int i = 0;
for (File obb : obbDirs) {
if (obb != null) {
++i;
dir = obb;
path = dir.getAbsolutePath();
iconId = getIconForStorageType(dir);
DataStorageMenuItem multiuserStorageItem = DataStorageMenuItem.builder()
.setKey(MULTIUSER_STORAGE + i)
.setTitle(app.getString(R.string.storage_directory_multiuser) + " " + i)
.setDirectory(path)
.setType(OsmandSettings.EXTERNAL_STORAGE_TYPE_OBB)
.setIconResId(iconId)
.createItem();
addItem(multiuserStorageItem);
}
}
}
}
//manually specified storage
manuallySpecified = DataStorageMenuItem.builder()
.setKey(MANUALLY_SPECIFIED)
.setTitle(app.getString(R.string.storage_directory_manual))
.setDirectory(currentStoragePath)
.setType(OsmandSettings.EXTERNAL_STORAGE_TYPE_SPECIFIED)
.setIconResId(R.drawable.ic_action_folder)
.createItem();
menuItems.add(manuallySpecified);
if (currentDataStorage == null) {
currentDataStorage = manuallySpecified;
}
initMemoryUsed(app);
}
private void initMemoryUsed(OsmandApplication app) {
mapsMemory = DataStorageMemoryItem.builder()
.setKey(MAPS_MEMORY)
.setExtensions(IndexConstants.BINARY_MAP_INDEX_EXT)
.setDirectories(
new Directory(app.getAppPath(IndexConstants.MAPS_PATH).getAbsolutePath(), false, EXTENSIONS, false),
new Directory(app.getAppPath(IndexConstants.ROADS_INDEX_DIR).getAbsolutePath(), true, EXTENSIONS, false),
new Directory(app.getAppPath(IndexConstants.WIKI_INDEX_DIR).getAbsolutePath(), true, EXTENSIONS, false),
new Directory(app.getAppPath(IndexConstants.WIKIVOYAGE_INDEX_DIR).getAbsolutePath(), true, EXTENSIONS, false),
new Directory(app.getAppPath(IndexConstants.BACKUP_INDEX_DIR).getAbsolutePath(), true, EXTENSIONS, false))
.createItem();
memoryItems.add(mapsMemory);
srtmAndHillshadeMemory = DataStorageMemoryItem.builder()
.setKey(SRTM_AND_HILLSHADE_MEMORY)
.setExtensions(IndexConstants.BINARY_SRTM_MAP_INDEX_EXT)
.setDirectories(
new Directory(app.getAppPath(IndexConstants.SRTM_INDEX_DIR).getAbsolutePath(), true, EXTENSIONS, false),
new Directory(app.getAppPath(IndexConstants.TILES_INDEX_DIR).getAbsolutePath(), false, PREFIX, true))
.setPrefixes("Hillshade")
.createItem();
memoryItems.add(srtmAndHillshadeMemory);
tracksMemory = DataStorageMemoryItem.builder()
.setKey(TRACKS_MEMORY)
// .setExtensions(IndexConstants.GPX_FILE_EXT, ".gpx.bz2")
.setDirectories(
new Directory(app.getAppPath(IndexConstants.GPX_INDEX_DIR).getAbsolutePath(), true, EXTENSIONS, false))
.createItem();
memoryItems.add(tracksMemory);
notesMemory = DataStorageMemoryItem.builder()
.setKey(NOTES_MEMORY)
// .setExtensions("")
.setDirectories(
new Directory(app.getAppPath(IndexConstants.AV_INDEX_DIR).getAbsolutePath(), true, EXTENSIONS, false))
.createItem();
memoryItems.add(notesMemory);
tilesMemory = DataStorageMemoryItem.builder()
.setKey(TILES_MEMORY)
// .setExtensions("")
.setDirectories(
new Directory(app.getAppPath(IndexConstants.TILES_INDEX_DIR).getAbsolutePath(), true, EXTENSIONS, false))
.createItem();
memoryItems.add(tilesMemory);
otherMemory = DataStorageMemoryItem.builder()
.setKey(OTHER_MEMORY)
.createItem();
memoryItems.add(otherMemory);
}
public ArrayList<DataStorageMenuItem> getStorageItems() {
return menuItems;
}
private int getIconForStorageType(File dir) {
return R.drawable.ic_action_folder;
}
public DataStorageMenuItem getCurrentStorage() {
return currentDataStorage;
}
private void addItem(DataStorageMenuItem item) {
if (currentStorageType == item.getType() && currentStoragePath.equals(item.getDirectory())) {
currentDataStorage = item;
}
menuItems.add(item);
}
public DataStorageMenuItem getManuallySpecified() {
return manuallySpecified;
}
public DataStorageMenuItem getStorage(String key) {
if (menuItems != null && key != null) {
for (DataStorageMenuItem menuItem : menuItems) {
if (key.equals(menuItem.getKey())) {
return menuItem;
}
}
}
return null;
}
public int getCurrentType() {
return currentStorageType;
}
public String getCurrentPath() {
return currentStoragePath;
}
public ArrayList<DataStorageMemoryItem> getMemoryInfoItems() {
return memoryItems;
}
public RefreshMemoryUsedInfo calculateMemoryUsedInfo(UpdateMemoryInfoUIAdapter listener) {
File rootDir = new File(currentStoragePath);
RefreshMemoryUsedInfo task = new RefreshMemoryUsedInfo(listener, otherMemory, rootDir, null, null, OTHER_MEMORY);
task.execute(mapsMemory, srtmAndHillshadeMemory, tracksMemory, notesMemory);
return task;
}
public RefreshMemoryUsedInfo calculateTilesMemoryUsed(UpdateMemoryInfoUIAdapter listener) {
File rootDir = new File(tilesMemory.getDirectories()[0].getAbsolutePath());
RefreshMemoryUsedInfo task = new RefreshMemoryUsedInfo(listener, otherMemory, rootDir, null, srtmAndHillshadeMemory.getPrefixes(), TILES_MEMORY);
task.execute(tilesMemory);
return task;
}
public static class RefreshMemoryUsedInfo extends AsyncTask<DataStorageMemoryItem, Void, Void> {
private UpdateMemoryInfoUIAdapter listener;
private File rootDir;
private DataStorageMemoryItem otherMemory;
private String[] directoriesToAvoid;
private String[] prefixesToAvoid;
private String taskKey;
private long lastRefreshTime;
public RefreshMemoryUsedInfo(UpdateMemoryInfoUIAdapter listener, DataStorageMemoryItem otherMemory, File rootDir, String[] directoriesToAvoid, String[] prefixesToAvoid, String taskKey) {
this.listener = listener;
this.otherMemory = otherMemory;
this.rootDir = rootDir;
this.directoriesToAvoid = directoriesToAvoid;
this.prefixesToAvoid = prefixesToAvoid;
this.taskKey = taskKey;
}
@Override
protected Void doInBackground(DataStorageMemoryItem... items) {
lastRefreshTime = System.currentTimeMillis();
if (rootDir.canRead()) {
calculateMultiTypes(rootDir, items);
}
return null;
}
private void calculateMultiTypes(File rootDir, DataStorageMemoryItem... items) {
File[] subFiles = rootDir.listFiles();
for (File file : subFiles) {
if (isCancelled()) {
break;
}
nextFile : {
if (file.isDirectory()) {
//check current directory should be avoid
if (directoriesToAvoid != null) {
for (String directoryToAvoid : directoriesToAvoid) {
if (file.getAbsolutePath().equals(directoryToAvoid)) {
break nextFile;
}
}
}
//check current directory matched items type
for (DataStorageMemoryItem item : items) {
Directory[] directories = item.getDirectories();
if (directories == null) {
continue;
}
for (Directory dir : directories) {
if (file.getAbsolutePath().equals(dir.getAbsolutePath())
|| (file.getAbsolutePath().startsWith(dir.getAbsolutePath()))) {
if (dir.isGoDeeper()) {
calculateMultiTypes(file, items);
break nextFile;
} else if (dir.isSkipOther()) {
break nextFile;
}
}
}
}
//current directory did not match to any type
otherMemory.addBytes(getDirectorySize(file));
} else if (file.isFile()) {
//check current file should be avoid
if (prefixesToAvoid != null) {
for (String prefixToAvoid : prefixesToAvoid) {
if (file.getName().toLowerCase().startsWith(prefixToAvoid.toLowerCase())) {
break nextFile;
}
}
}
//check current file matched items type
for (DataStorageMemoryItem item : items) {
Directory[] directories = item.getDirectories();
if (directories == null) {
continue;
}
for (Directory dir : directories) {
if (rootDir.getAbsolutePath().equals(dir.getAbsolutePath())
|| (rootDir.getAbsolutePath().startsWith(dir.getAbsolutePath()) && dir.isGoDeeper())) {
int checkingType = dir.getCheckingType();
switch (checkingType) {
case EXTENSIONS : {
String[] extensions = item.getExtensions();
if (extensions != null) {
for (String extension : extensions) {
if (file.getAbsolutePath().endsWith(extension)) {
item.addBytes(file.length());
break nextFile;
}
}
} else {
item.addBytes(file.length());
break nextFile;
}
break ;
}
case PREFIX : {
String[] prefixes = item.getPrefixes();
if (prefixes != null) {
for (String prefix : prefixes) {
if (file.getName().toLowerCase().startsWith(prefix.toLowerCase())) {
item.addBytes(file.length());
break nextFile;
}
}
} else {
item.addBytes(file.length());
break nextFile;
}
break ;
}
}
if (dir.isSkipOther()) {
break nextFile;
}
}
}
}
//current file did not match any type
otherMemory.addBytes(file.length());
}
}
refreshUI();
}
}
private long getDirectorySize(File dir) {
long bytes = 0;
if (dir.isDirectory()) {
File[] files = dir.listFiles();
for (File file : files) {
if (isCancelled()) {
break;
}
if (file.isDirectory()) {
bytes += getDirectorySize(file);
} else if (file.isFile()) {
bytes += file.length();
}
}
}
return bytes;
}
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
if (listener != null) {
listener.onMemoryInfoUpdate();
}
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
if (listener != null) {
listener.onFinishUpdating(taskKey);
}
}
private void refreshUI() {
long currentTime = System.currentTimeMillis();
if ((currentTime - lastRefreshTime) > UI_REFRESH_TIME_MS) {
lastRefreshTime = currentTime;
publishProgress();
}
}
}
public long getTotalUsedBytes() {
long total = 0;
if (memoryItems != null && memoryItems.size() > 0) {
for (DataStorageMemoryItem mi : memoryItems) {
total += mi.getUsedMemoryBytes();
}
return total;
}
return -1;
}
public static String getFormattedMemoryInfo(long bytes, String[] formatStrings) {
int type = 0;
double memory = (double) bytes / 1024;
while (memory > 1024 && type < formatStrings.length) {
++type;
memory = memory / 1024;
}
String formattedUsed = new DecimalFormat("#.##").format(memory);
return String.format(formatStrings[type], formattedUsed);
}
public interface UpdateMemoryInfoUIAdapter {
void onMemoryInfoUpdate();
void onFinishUpdating(String taskKey);
}
}

View file

@ -22,6 +22,8 @@ import net.osmand.plus.profiles.SelectProfileBottomSheet.DialogMode;
import net.osmand.plus.profiles.SelectProfileBottomSheet.OnSelectProfileCallback;
import net.osmand.plus.settings.backend.ApplicationMode;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.settings.datastorage.DataStorageHelper;
import net.osmand.plus.settings.datastorage.item.StorageItem;
import net.osmand.plus.settings.preferences.ListPreferenceEx;
import net.osmand.plus.settings.preferences.SwitchPreferenceEx;
@ -181,7 +183,7 @@ public class GlobalSettingsFragment extends BaseSettingsFragment
externalStorageDir.setIcon(getActiveIcon(R.drawable.ic_action_folder));
DataStorageHelper holder = new DataStorageHelper(app);
DataStorageMenuItem currentStorage = holder.getCurrentStorage();
StorageItem currentStorage = holder.getCurrentStorage();
long totalUsed = app.getSettings().OSMAND_USAGE_SPACE.get();
if (totalUsed > 0) {
String[] usedMemoryFormats = new String[] {