Merge pull request #7645 from osmandapp/DataStorage

Data Storage
This commit is contained in:
max-klaus 2019-10-07 16:27:00 +03:00 committed by GitHub
commit 7ccd8a7e2f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1687 additions and 6 deletions

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/content_padding"
android:paddingRight="@dimen/content_padding">
<View
android:layout_width="match_parent"
android:layout_height="@dimen/content_padding_half" />
<View
android:layout_width="match_parent"
android:layout_height="@dimen/content_padding" />
<include android:id="@+id/btnDontMove"
layout="@layout/bottom_sheet_dialog_button_big" />
<View
android:layout_width="match_parent"
android:layout_height="@dimen/content_padding_half" />
<include android:id="@+id/btnMove"
layout="@layout/bottom_sheet_dialog_button_big" />
<View
android:layout_width="match_parent"
android:layout_height="@dimen/content_padding_half" />
<include android:id="@+id/btnClose"
layout="@layout/bottom_sheet_dialog_button_big" />
<View
android:layout_width="match_parent"
android:layout_height="@dimen/content_padding" />
</LinearLayout>

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:osmand="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_cancel_button_height">
<LinearLayout
android:id="@+id/button_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="UselessParent">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/button_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/content_padding_small"
android:paddingRight="@dimen/content_padding_small"
android:gravity="center_vertical|start"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_medium"
tools:text="Button" />
</LinearLayout>
</LinearLayout>

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:osmand="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_list_item_height"
android:background="@android:drawable/list_selector_background"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/button_icon"
android:layout_width="@dimen/standard_icon_size"
android:layout_height="@dimen/standard_icon_size"
android:layout_gravity="center"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:tint="?attr/active_color_basic"
tools:visibility="visible"
android:src="@drawable/ic_action_plus"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top"
android:orientation="vertical"
android:paddingLeft="@dimen/content_padding"
android:paddingRight="@dimen/content_padding">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/button_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:singleLine="true"
android:textSize="@dimen/default_list_text_size"
android:ellipsize="end"
android:gravity="center_vertical"
android:layout_gravity="center_vertical"
osmand:typeface="@string/font_roboto_medium"
android:textColor="?attr/active_color_basic"
tools:text="Some title" />
</FrameLayout>
</LinearLayout>

View file

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<net.osmand.plus.widgets.OsmandTextFieldBoxes
android:id="@+id/edit_text_otfb"
android:layout_width="match_parent"
android:layout_height="@dimen/dialog_button_ex_height"
android:layout_marginTop="@dimen/text_margin_small"
android:paddingLeft="@dimen/content_padding"
android:paddingRight="@dimen/content_padding"
app:labelText="@string/enter_path_to_folder"
app:primaryColor="?android:textColorPrimary"
app:secondaryColor="?android:textColorSecondary">
<studio.carbonylgroup.textfieldboxes.ExtendedEditText
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:maxLines="1"
android:textColor="?android:textColorPrimary"
tools:text="Folder path ... " />
</net.osmand.plus.widgets.OsmandTextFieldBoxes>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding">
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/subHeaderPadding"
android:layout_marginBottom="@dimen/content_padding"
android:ellipsize="end"
android:linksClickable="true"
android:paddingLeft="@dimen/content_padding"
android:paddingRight="@dimen/content_padding"
android:textColor="?android:textColorSecondary"
android:textSize="@dimen/default_sub_text_size"
tools:text="EditText description" />
</FrameLayout>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="@dimen/content_padding_half"
android:background="?attr/divider_color" />
<include
android:id="@+id/button"
layout="@layout/bottom_sheet_item_btn_with_icon_and_text" />
</LinearLayout>

View file

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:osmand="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:id="@+id/selectable_list_item"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground">
<LinearLayout
android:paddingTop="@dimen/content_padding"
android:paddingBottom="@dimen/content_padding_small"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.AppCompatImageView
android:id="@+id/icon"
android:layout_width="@dimen/standard_icon_size"
android:layout_height="@dimen/standard_icon_size"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:layout_gravity="center"
android:src="@drawable/mm_storage_tank"
tools:tint="?attr/default_icon_color"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_weight="1"
android:paddingLeft="@dimen/content_padding"
android:paddingRight="@dimen/content_padding"
android:layout_height="wrap_content">
<net.osmand.plus.widgets.TextViewEx
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_regular"
android:textColor="?android:textColorPrimary"
tools:text="Internal application memory"/>
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_regular"
android:textColor="?android:textColorSecondary"
tools:text="Free 1,34 • 14,6 / 16 Gb"/>
</LinearLayout>
<RadioButton
android:id="@android:id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="@dimen/content_padding"
android:background="@null"
android:clickable="true"
android:focusable="true" />
</LinearLayout>
<LinearLayout
android:id="@+id/secondPart"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.AppCompatImageView
android:layout_width="@dimen/standard_icon_size"
android:layout_height="@dimen/standard_icon_size"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:layout_gravity="center"
android:visibility="invisible"
android:src="@drawable/mm_storage_tank"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_weight="1"
android:paddingLeft="@dimen/content_padding"
android:layout_height="wrap_content">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/additionalDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/default_desc_text_size"
android:paddingRight="@dimen/content_padding"
osmand:typeface="@string/font_roboto_regular"
android:textColor="?android:textColorSecondary"
tools:text="@string/internal_app_storage_description" />
<FrameLayout
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="@dimen/content_padding">
<View
android:layout_gravity="bottom"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/list_divider"/>
</FrameLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</FrameLayout>

View file

@ -11,6 +11,16 @@
Thx - Hardy Thx - Hardy
--> -->
<string name="change_data_storage_full_description">Move OsmAnd data files to the new destination?\n%1$s > %2$s</string>
<string name="data_storage_preference_summary">%1$s • Used %2$s GB</string>
<string name="data_storage_space_description">Free %1$s • %2$s / %3$s GB</string>
<string name="enter_path_to_folder">Enter path to the folder</string>
<string name="shared_string_select_folder">Select folder</string>
<string name="paste_Osmand_data_folder_path">Paste path to the folder with OsmAnd data</string>
<string name="change_osmand_data_folder_question">Change OsmAnd data folder?</string>
<string name="move_maps_to_new_destination">Move to the new destination</string>
<string name="internal_app_storage_description">Internal storage, hiden from user and other apps, so no one except OsmAnd cant get access to your data</string>
<string name="change_data_storage_folder">Change data storage folder</string>
<string name="download_detailed_map">Download detailed %s map, to view this area.</string> <string name="download_detailed_map">Download detailed %s map, to view this area.</string>
<string name="shared_string_by_default">By default</string> <string name="shared_string_by_default">By default</string>
<string name="plugins_settings">Plugin settings</string> <string name="plugins_settings">Plugin settings</string>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/application_dir">
<PreferenceCategory
android:key="application_profiles"
android:layout="@layout/preference_category_with_descr"
android:title="@string/change_data_storage_folder" />
</PreferenceScreen>

View file

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

View file

@ -22,6 +22,7 @@ import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.StatFs;
import android.support.annotation.AttrRes; import android.support.annotation.AttrRes;
import android.support.annotation.ColorInt; import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes; import android.support.annotation.ColorRes;
@ -587,6 +588,28 @@ public class AndroidUtils {
} }
} }
public static float getFreeSpaceGb(File dir) {
if (dir.canRead()) {
StatFs fs = new StatFs(dir.getAbsolutePath());
return (float) (fs.getBlockSize()) * fs.getAvailableBlocks() / (1 << 30);
}
return -1;
}
public static float getTotalSpaceGb(File dir) {
if (dir.canRead()) {
return (float) (dir.getTotalSpace()) / (1 << 30);
}
return -1;
}
public static float getUsedSpaceGb(File dir) {
if (dir.canRead()) {
return getTotalSpaceGb(dir) - getFreeSpaceGb(dir);
}
return -1;
}
public static CharSequence getStyledString(CharSequence baseString, CharSequence stringToInsertAndStyle, public static CharSequence getStyledString(CharSequence baseString, CharSequence stringToInsertAndStyle,
CharacterStyle baseStyle, CharacterStyle replaceStyle) { CharacterStyle baseStyle, CharacterStyle replaceStyle) {
int indexOfPlaceholder = baseString.toString().indexOf(STRING_PLACEHOLDER); int indexOfPlaceholder = baseString.toString().indexOf(STRING_PLACEHOLDER);

View file

@ -39,6 +39,7 @@ public class UiUtilities {
private static final int ORIENTATION_90 = 3; private static final int ORIENTATION_90 = 3;
private static final int ORIENTATION_270 = 1; private static final int ORIENTATION_270 = 1;
private static final int ORIENTATION_180 = 2; private static final int ORIENTATION_180 = 2;
private static final int INVALID_ID = -1;
public enum DialogButtonType { public enum DialogButtonType {
PRIMARY, PRIMARY,
@ -324,35 +325,49 @@ public class UiUtilities {
} }
public static void setupDialogButton(boolean nightMode, View buttonView, DialogButtonType buttonType, CharSequence buttonText) { public static void setupDialogButton(boolean nightMode, View buttonView, DialogButtonType buttonType, CharSequence buttonText) {
setupDialogButton(nightMode, buttonView, buttonType, buttonText, INVALID_ID);
}
public static void setupDialogButton(boolean nightMode, View buttonView, DialogButtonType buttonType, CharSequence buttonText, int iconResId) {
Context ctx = buttonView.getContext(); Context ctx = buttonView.getContext();
TextViewEx buttonTextView = (TextViewEx) buttonView.findViewById(R.id.button_text); TextViewEx buttonTextView = (TextViewEx) buttonView.findViewById(R.id.button_text);
boolean v21 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; boolean v21 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
View buttonContainer = buttonView.findViewById(R.id.button_container); View buttonContainer = buttonView.findViewById(R.id.button_container);
int textAndIconColorResId = INVALID_ID;
switch (buttonType) { switch (buttonType) {
case PRIMARY: case PRIMARY:
if (v21) { if (v21) {
AndroidUtils.setBackground(ctx, buttonContainer, nightMode, R.drawable.ripple_solid_light, R.drawable.ripple_solid_dark); AndroidUtils.setBackground(ctx, buttonContainer, nightMode, R.drawable.ripple_solid_light, R.drawable.ripple_solid_dark);
} }
AndroidUtils.setBackground(ctx, buttonView, nightMode, R.drawable.dlg_btn_primary_light, R.drawable.dlg_btn_primary_dark); AndroidUtils.setBackground(ctx, buttonView, nightMode, R.drawable.dlg_btn_primary_light, R.drawable.dlg_btn_primary_dark);
buttonTextView.setTextColor(ContextCompat.getColorStateList(ctx, nightMode ? R.color.dlg_btn_primary_text_dark : R.color.dlg_btn_primary_text_light)); textAndIconColorResId = nightMode ? R.color.dlg_btn_primary_text_dark : R.color.dlg_btn_primary_text_light;
break; break;
case SECONDARY: case SECONDARY:
if (v21) { if (v21) {
AndroidUtils.setBackground(ctx, buttonContainer, nightMode, R.drawable.ripple_solid_light, R.drawable.ripple_solid_dark); AndroidUtils.setBackground(ctx, buttonContainer, nightMode, R.drawable.ripple_solid_light, R.drawable.ripple_solid_dark);
} }
AndroidUtils.setBackground(ctx, buttonView, nightMode, R.drawable.dlg_btn_secondary_light, R.drawable.dlg_btn_secondary_dark); AndroidUtils.setBackground(ctx, buttonView, nightMode, R.drawable.dlg_btn_secondary_light, R.drawable.dlg_btn_secondary_dark);
buttonTextView.setTextColor(ContextCompat.getColorStateList(ctx, nightMode ? R.color.dlg_btn_secondary_text_dark : R.color.dlg_btn_secondary_text_light)); textAndIconColorResId = nightMode ? R.color.dlg_btn_secondary_text_dark : R.color.dlg_btn_secondary_text_light;
break; break;
case STROKED: case STROKED:
if (v21) { if (v21) {
AndroidUtils.setBackground(ctx, buttonContainer, nightMode, R.drawable.ripple_light, R.drawable.ripple_dark); AndroidUtils.setBackground(ctx, buttonContainer, nightMode, R.drawable.ripple_light, R.drawable.ripple_dark);
} }
AndroidUtils.setBackground(ctx, buttonView, nightMode, R.drawable.dlg_btn_stroked_light, R.drawable.dlg_btn_stroked_dark); AndroidUtils.setBackground(ctx, buttonView, nightMode, R.drawable.dlg_btn_stroked_light, R.drawable.dlg_btn_stroked_dark);
buttonTextView.setTextColor(ContextCompat.getColorStateList(ctx, nightMode ? R.color.dlg_btn_secondary_text_dark : R.color.dlg_btn_secondary_text_light)); textAndIconColorResId = nightMode ? R.color.dlg_btn_secondary_text_dark : R.color.dlg_btn_secondary_text_light;
break; break;
} }
buttonTextView.setText(buttonText); if (textAndIconColorResId != INVALID_ID) {
buttonTextView.setEnabled(buttonView.isEnabled()); ColorStateList colorStateList = ContextCompat.getColorStateList(ctx, textAndIconColorResId);
buttonTextView.setText(buttonText);
buttonTextView.setTextColor(colorStateList);
buttonTextView.setEnabled(buttonView.isEnabled());
if (iconResId != INVALID_ID) {
Drawable icon = tintDrawable(ContextCompat.getDrawable(ctx, iconResId), ContextCompat.getColor(ctx, textAndIconColorResId));
buttonTextView.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
buttonTextView.setCompoundDrawablePadding(AndroidUtils.dpToPx(ctx, ctx.getResources().getDimension(R.dimen.content_padding_half)));
}
}
} }
public static Context getThemedContext(Context context, boolean nightMode) { public static Context getThemedContext(Context context, boolean nightMode) {

View file

@ -93,7 +93,8 @@ public abstract class BaseSettingsFragment extends PreferenceFragmentCompat impl
VOICE_ANNOUNCES(VoiceAnnouncesFragment.class.getName(), true, R.xml.voice_announces, R.layout.profile_preference_toolbar_with_switch), VOICE_ANNOUNCES(VoiceAnnouncesFragment.class.getName(), true, R.xml.voice_announces, R.layout.profile_preference_toolbar_with_switch),
VEHICLE_PARAMETERS(VehicleParametersFragment.class.getName(), true, R.xml.vehicle_parameters, R.layout.profile_preference_toolbar), VEHICLE_PARAMETERS(VehicleParametersFragment.class.getName(), true, R.xml.vehicle_parameters, R.layout.profile_preference_toolbar),
MAP_DURING_NAVIGATION(MapDuringNavigationFragment.class.getName(), true, R.xml.map_during_navigation, R.layout.profile_preference_toolbar), MAP_DURING_NAVIGATION(MapDuringNavigationFragment.class.getName(), true, R.xml.map_during_navigation, R.layout.profile_preference_toolbar),
TURN_SCREEN_ON(TurnScreenOnFragment.class.getName(), true, R.xml.turn_screen_on, R.layout.profile_preference_toolbar_with_switch); TURN_SCREEN_ON(TurnScreenOnFragment.class.getName(), true, R.xml.turn_screen_on, R.layout.profile_preference_toolbar_with_switch),
DATA_STORAGE(DataStorageFragment.class.getName(), false, R.xml.data_storage, R.layout.global_preference_toolbar);
public final String fragmentName; public final String fragmentName;
public final boolean profileDependent; public final boolean profileDependent;

View file

@ -0,0 +1,566 @@
package net.osmand.plus.settings;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.preference.CheckBoxPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceScreen;
import android.support.v7.preference.PreferenceViewHolder;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import net.osmand.AndroidUtils;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandSettings;
import net.osmand.plus.ProgressImplementation;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
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 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.text.MessageFormat;
import java.util.ArrayList;
import java.util.Locale;
import static net.osmand.plus.settings.DataStorageItemsHolder.INTERNAL_STORAGE;
import static net.osmand.plus.settings.DataStorageItemsHolder.MANUALLY_SPECIFIED;
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.PATH_CHANGED;
import static net.osmand.plus.settings.bottomsheets.SelectFolderBottomSheet.NEW_PATH;
public class DataStorageFragment extends BaseSettingsFragment {
private final static String CHANGE_DIRECTORY_BUTTON = "change_directory";
private ArrayList<DataStorageMenuItem> menuItems;
private ArrayList<CheckBoxPreference> dataStorageRadioButtonsGroup;
private Preference changeButton;
private DataStorageMenuItem currentDataStorage;
private String tmpManuallySpecifiedPath;
private DataStorageItemsHolder itemsHolder;
private OsmandApplication app;
private OsmandActionBarActivity activity;
private OsmandSettings settings;
@Override
protected void setupPreferences() {
app = getMyApplication();
activity = getMyActivity();
PreferenceScreen screen = getPreferenceScreen();
if (screen == null || app == null || activity == null) {
return;
}
settings = app.getSettings();
itemsHolder = DataStorageItemsHolder.refreshInfo(app);
menuItems = itemsHolder.getStorageItems();
dataStorageRadioButtonsGroup = new ArrayList<>();
for (DataStorageMenuItem item : menuItems) {
CheckBoxPreference preference = new CheckBoxPreference(activity);
preference.setKey(item.getKey());
preference.setTitle(item.getTitle());
preference.setLayoutResource(R.layout.data_storage_list_item);
screen.addPreference(preference);
dataStorageRadioButtonsGroup.add(preference);
}
currentDataStorage = itemsHolder.getCurrentStorage();
changeButton = new Preference(app);
changeButton.setKey(CHANGE_DIRECTORY_BUTTON);
changeButton.setLayoutResource(R.layout.bottom_sheet_item_btn_with_icon_and_text);
screen.addPreference(changeButton);
updateView(currentDataStorage.getKey());
}
@Override
public boolean onPreferenceClick(Preference preference) {
if (CHANGE_DIRECTORY_BUTTON.equals(preference.getKey())) {
showFolderSelectionDialog();
return false;
}
return super.onPreferenceClick(preference);
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
super.onPreferenceChange(preference, newValue);
if (newValue instanceof Bundle) {
//results from BottomSheets
Bundle resultData = (Bundle) newValue;
if (resultData.containsKey(ChangeDataStorageBottomSheet.TAG)) {
boolean moveMaps = resultData.getBoolean(MOVE_DATA);
DataStorageMenuItem newDataStorage = resultData.getParcelable(CHOSEN_DIRECTORY);
if (newDataStorage != null) {
if (tmpManuallySpecifiedPath != null) {
String directory = tmpManuallySpecifiedPath;
tmpManuallySpecifiedPath = null;
newDataStorage.setDirectory(directory);
}
if (moveMaps) {
moveData(currentDataStorage, newDataStorage);
} else {
confirm(app, activity, newDataStorage, false);
}
}
} else if (resultData.containsKey(SelectFolderBottomSheet.TAG)) {
boolean pathChanged = resultData.getBoolean(PATH_CHANGED);
if (pathChanged) {
tmpManuallySpecifiedPath = resultData.getString(NEW_PATH);
if (tmpManuallySpecifiedPath != null) {
DataStorageMenuItem manuallySpecified = null;
try {
manuallySpecified = (DataStorageMenuItem) itemsHolder.getManuallySpecified().clone();
manuallySpecified.setDirectory(tmpManuallySpecifiedPath);
} catch (CloneNotSupportedException e) {
return false;
}
ChangeDataStorageBottomSheet.showInstance(getFragmentManager(), MANUALLY_SPECIFIED,
currentDataStorage, manuallySpecified, this, false);
}
}
}
} else {
//show necessary dialog
String key = preference.getKey();
if (key != null) {
DataStorageMenuItem newDataStorage = itemsHolder.getStorage(key);
if (newDataStorage != null) {
if (!currentDataStorage.getKey().equals(newDataStorage.getKey())) {
if (newDataStorage.getType() == OsmandSettings.EXTERNAL_STORAGE_TYPE_DEFAULT
&& !DownloadActivity.hasPermissionToWriteExternalStorage(activity)) {
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
DownloadActivity.PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
} else if (key.equals(MANUALLY_SPECIFIED)) {
showFolderSelectionDialog();
} else {
ChangeDataStorageBottomSheet.showInstance(getFragmentManager(), key,
currentDataStorage, newDataStorage, DataStorageFragment.this, false);
}
}
}
}
}
return false;
}
@Override
protected void onBindPreferenceViewHolder(Preference preference, PreferenceViewHolder holder) {
super.onBindPreferenceViewHolder(preference, holder);
String key = preference.getKey();
if (key == null) {
return;
}
View itemView = holder.itemView;
if (preference instanceof CheckBoxPreference) {
DataStorageMenuItem item = itemsHolder.getStorage(key);
if (item != null) {
TextView tvTitle = itemView.findViewById(android.R.id.title);
TextView tvSummary = itemView.findViewById(R.id.summary);
TextView tvAdditionalDescription = itemView.findViewById(R.id.additionalDescription);
ImageView ivIcon = itemView.findViewById(R.id.icon);
View divider = itemView.findViewById(R.id.divider);
View secondPart = itemView.findViewById(R.id.secondPart);
tvTitle.setText(item.getTitle());
String currentKey = item.getKey();
boolean isCurrent = currentDataStorage.getKey().equals(currentKey);
int defaultIconColor = isNightMode() ? R.color.icon_color_default_dark : R.color.icon_color_default_light;
int chosenIconColor = isNightMode() ? R.color.icon_color_osmand_dark : R.color.icon_color_osmand_light;
Drawable icon = app.getUIUtilities().getIcon(item.getIconResId(),
isCurrent ? chosenIconColor : defaultIconColor);
ivIcon.setImageDrawable(icon);
if (currentKey.equals(MANUALLY_SPECIFIED)) {
tvSummary.setText(item.getDirectory());
secondPart.setVisibility(View.GONE);
tvAdditionalDescription.setVisibility(View.GONE);
divider.setVisibility(View.GONE);
} else {
tvAdditionalDescription.setVisibility(View.VISIBLE);
divider.setVisibility(View.VISIBLE);
secondPart.setVisibility(View.VISIBLE);
String space = getSpaceDescription(item.getDirectory());
tvSummary.setText(space);
if (currentKey.equals(INTERNAL_STORAGE)) {
tvAdditionalDescription.setText(item.getDescription());
} else {
tvAdditionalDescription.setText(item.getDirectory());
}
}
}
} else if (key.equals(CHANGE_DIRECTORY_BUTTON)) {
ImageView icon = itemView.findViewById(R.id.button_icon);
TextView title = itemView.findViewById(R.id.button_text);
int colorResId = isNightMode() ? R.color.active_color_primary_dark : R.color.active_color_primary_light;
int color = ContextCompat.getColor(app, colorResId);
Drawable drawable = UiUtilities.getColoredSelectableDrawable(app, color, 0.3f);
AndroidUtils.setBackground(itemView, drawable);
icon.setVisibility(View.INVISIBLE);
title.setText(R.string.shared_string_change);
}
}
private void updateView(String key) {
//selection set up
for (CheckBoxPreference preference : dataStorageRadioButtonsGroup) {
String preferenceKey = preference.getKey();
boolean checked = preferenceKey != null && preferenceKey.equals(key);
preference.setChecked(checked);
}
boolean visible = key.equals(MANUALLY_SPECIFIED);
changeButton.setVisible(visible);
}
private void showFolderSelectionDialog() {
DataStorageMenuItem manuallySpecified = itemsHolder.getManuallySpecified();
if (manuallySpecified != null) {
SelectFolderBottomSheet.showInstance(getFragmentManager(), manuallySpecified.getKey(),
manuallySpecified.getDirectory(), DataStorageFragment.this,
getString(R.string.storage_directory_manual), getString(R.string.paste_Osmand_data_folder_path),
getString(R.string.shared_string_select_folder), false);
}
}
private void moveData(final DataStorageMenuItem currentStorage, final DataStorageMenuItem newStorage) {
File fromDirectory = new File(currentStorage.getDirectory());
File toDirectory = new File(newStorage.getDirectory());
@SuppressLint("StaticFieldLeak")
MoveFilesToDifferentDirectory task = new MoveFilesToDifferentDirectory(activity, fromDirectory, toDirectory) {
private MessageFormat formatMb = new MessageFormat("{0, number,##.#} MB", Locale.US);
@NonNull
private String getFormattedSize(long sizeBytes) {
int size = (int) ((sizeBytes + 512) >> 10);
if (size >= 0) {
if (size > 100) {
return formatMb.format(new Object[]{(float) size / (1 << 10)});
} else {
return size + " kB";
}
}
return "";
}
private void showResultsDialog() {
StringBuilder sb = new StringBuilder();
Context ctx = activity.get();
if (ctx == null) {
return;
}
int moved = getMovedCount();
int copied = getCopiedCount();
int failed = getFailedCount();
sb.append(ctx.getString(R.string.files_moved, moved, getFormattedSize(getMovedSize()))).append("\n");
if (copied > 0) {
sb.append(ctx.getString(R.string.files_copied, copied, getFormattedSize(getCopiedSize()))).append("\n");
}
if (failed > 0) {
sb.append(ctx.getString(R.string.files_failed, failed, getFormattedSize(getFailedSize()))).append("\n");
}
if (copied > 0 || failed > 0) {
int count = copied + failed;
sb.append(ctx.getString(R.string.files_present, count, getFormattedSize(getCopiedSize() + getFailedSize()), newStorage.getDirectory()));
}
AlertDialog.Builder bld = new AlertDialog.Builder(ctx);
bld.setMessage(sb.toString());
bld.setPositiveButton(R.string.shared_string_restart, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
confirm(app, activity.get(), newStorage, true);
}
});
bld.show();
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
OsmandActionBarActivity a = this.activity.get();
if (a == null) {
return;
}
OsmandApplication app = a.getMyApplication();
if (result) {
app.getResourceManager().resetStoreDirectory();
// immediately proceed with change (to not loose where maps are currently located)
if (getCopiedCount() > 0 || getFailedCount() > 0) {
showResultsDialog();
} else {
confirm(app, a, newStorage, false);
}
} else {
showResultsDialog();
Toast.makeText(a, R.string.copying_osmand_file_failed,
Toast.LENGTH_SHORT).show();
}
}
};
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void confirm(OsmandApplication app, OsmandActionBarActivity activity, DataStorageMenuItem newStorageDirectory, boolean silentRestart) {
String newDirectory = newStorageDirectory.getDirectory();
int type = newStorageDirectory.getType();
File newDirectoryFile = new File(newDirectory);
boolean wr = OsmandSettings.isWritable(newDirectoryFile);
if (wr) {
app.setExternalStorageDirectory(type, newDirectory);
reloadData();
if (silentRestart) {
android.os.Process.killProcess(android.os.Process.myPid());
} else {
app.restartApp(activity);
}
} else {
Toast.makeText(activity, R.string.specified_directiory_not_writeable,
Toast.LENGTH_LONG).show();
}
updateAllSettings();
}
private String getSpaceDescription(String path) {
File dir = new File(path);
File dirParent = dir.getParentFile();
while (!dir.exists() && dirParent != null) {
dir = dir.getParentFile();
dirParent = dir.getParentFile();
}
if (dir.exists()) {
DecimalFormat formatter = new DecimalFormat("#.##");
return String.format(getString(R.string.data_storage_space_description),
formatter.format(AndroidUtils.getFreeSpaceGb(dir)),
formatter.format(AndroidUtils.getUsedSpaceGb(dir)),
formatter.format(AndroidUtils.getTotalSpaceGb(dir)));
}
return "";
}
protected void reloadData() {
new ReloadData(activity, getMyApplication()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
}
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;
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, R.string.shared_string_io_error, 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) {
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,200 @@
package net.osmand.plus.settings;
import android.os.Build;
import net.osmand.ValueHolder;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandSettings;
import net.osmand.plus.R;
import java.io.File;
import java.util.ArrayList;
public class DataStorageItemsHolder {
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";
private ArrayList<DataStorageMenuItem> menuItems;
private DataStorageMenuItem currentDataStorage;
private DataStorageMenuItem manuallySpecified;
private int currentStorageType;
private String currentStoragePath;
private OsmandApplication app;
private OsmandSettings settings;
private DataStorageItemsHolder(OsmandApplication app) {
this.app = app;
this.settings = app.getSettings();
prepareData();
}
public static DataStorageItemsHolder refreshInfo(OsmandApplication app) {
return new DataStorageItemsHolder(app);
}
private void prepareData() {
if (app == null) {
return;
}
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();
menuItems = new ArrayList<>();
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()
.buildKey(INTERNAL_STORAGE)
.buildTitle(getString(R.string.storage_directory_internal_app))
.buildDirectory(path)
.buildDescription(getString(R.string.internal_app_storage_description))
.buildType(OsmandSettings.EXTERNAL_STORAGE_TYPE_INTERNAL_FILE)
.buildIconResId(iconId)
.build();
addItem(internalStorageItem);
//shared_storage
dir = settings.getDefaultInternalStorage();
path = dir.getAbsolutePath();
iconId = R.drawable.ic_action_phone;
DataStorageMenuItem sharedStorageItem = DataStorageMenuItem.builder()
.buildKey(SHARED_STORAGE)
.buildTitle(getString(R.string.storage_directory_shared))
.buildDirectory(path)
.buildType(OsmandSettings.EXTERNAL_STORAGE_TYPE_DEFAULT)
.buildIconResId(iconId)
.build();
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()
.buildKey(EXTERNAL_STORAGE + i)
.buildTitle(getString(R.string.storage_directory_external) + " " + i)
.buildDirectory(path)
.buildType(OsmandSettings.EXTERNAL_STORAGE_TYPE_EXTERNAL_FILE)
.buildIconResId(iconId)
.build();
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()
.buildKey(MULTIUSER_STORAGE + i)
.buildTitle(getString(R.string.storage_directory_multiuser) + " " + i)
.buildDirectory(path)
.buildType(OsmandSettings.EXTERNAL_STORAGE_TYPE_OBB)
.buildIconResId(iconId)
.build();
addItem(multiuserStorageItem);
}
}
}
}
//manually specified storage
manuallySpecified = DataStorageMenuItem.builder()
.buildKey(MANUALLY_SPECIFIED)
.buildTitle(getString(R.string.storage_directory_manual))
.buildDirectory(currentStoragePath)
.buildType(OsmandSettings.EXTERNAL_STORAGE_TYPE_SPECIFIED)
.buildIconResId(R.drawable.ic_action_folder)
.build();
menuItems.add(manuallySpecified);
if (currentDataStorage == null) {
currentDataStorage = manuallySpecified;
}
}
private String getString(int resId) {
return app.getString(resId);
}
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;
}
}

View file

@ -0,0 +1,169 @@
package net.osmand.plus.settings;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.IdRes;
public class DataStorageMenuItem implements Parcelable, Cloneable {
private String key;
private int type;
private String title;
private String description;
private String directory;
@IdRes
private int iconResId;
private DataStorageMenuItem(String key, int type, String title, String description,
String directory, int iconResId) {
this.key = key;
this.type = type;
this.title = title;
this.description = description;
this.directory = directory;
this.iconResId = iconResId;
}
private DataStorageMenuItem(Parcel in) {
key = in.readString();
type = in.readInt();
title = in.readString();
description = in.readString();
directory = in.readString();
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
public String getDirectory() {
return directory;
}
public int getIconResId() {
return iconResId;
}
public String getKey() {
return key;
}
public int getType() {
return type;
}
public void setKey(String key) {
this.key = key;
}
public void setType(int type) {
this.type = type;
}
public void setTitle(String title) {
this.title = title;
}
public void setDescription(String description) {
this.description = description;
}
public void setDirectory(String directory) {
this.directory = directory;
}
public void setIconResId(int iconResId) {
this.iconResId = iconResId;
}
public static DataStorageMenuItemBuilder builder() {
return new DataStorageMenuItemBuilder();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(key);
dest.writeInt(type);
dest.writeString(title);
dest.writeString(description);
dest.writeString(directory);
}
public static final Parcelable.Creator<DataStorageMenuItem> CREATOR = new Parcelable.Creator<DataStorageMenuItem>() {
@Override
public DataStorageMenuItem createFromParcel(Parcel source) {
return new DataStorageMenuItem(source);
}
@Override
public DataStorageMenuItem[] newArray(int size) {
return new DataStorageMenuItem[size];
}
};
public static class DataStorageMenuItemBuilder {
private String key;
private int type;
private String title;
private String description;
private String directory;
@IdRes
private int iconResId;
public DataStorageMenuItemBuilder buildKey(String key) {
this.key = key;
return this;
}
public DataStorageMenuItemBuilder buildType(int type) {
this.type = type;
return this;
}
public DataStorageMenuItemBuilder buildTitle(String title) {
this.title = title;
return this;
}
public DataStorageMenuItemBuilder buildDescription(String description) {
this.description = description;
return this;
}
public DataStorageMenuItemBuilder buildDirectory(String directory) {
this.directory = directory;
return this;
}
public DataStorageMenuItemBuilder buildIconResId(int iconResId) {
this.iconResId = iconResId;
return this;
}
public DataStorageMenuItem build() {
return new DataStorageMenuItem(key, type, title, description, directory, iconResId);
}
}
@Override
public Object clone() throws CloneNotSupportedException {
return DataStorageMenuItem.builder()
.buildKey(this.key)
.buildTitle(this.title)
.buildDescription(this.description)
.buildDirectory(this.directory)
.buildType(this.type)
.buildIconResId(this.iconResId)
.build();
}
}

View file

@ -7,6 +7,7 @@ import android.support.v7.preference.Preference;
import android.support.v7.preference.SwitchPreferenceCompat; import android.support.v7.preference.SwitchPreferenceCompat;
import android.util.Pair; import android.util.Pair;
import net.osmand.AndroidUtils;
import net.osmand.plus.ApplicationMode; import net.osmand.plus.ApplicationMode;
import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandSettings; import net.osmand.plus.OsmandSettings;
@ -16,6 +17,9 @@ import net.osmand.plus.dialogs.SendAnalyticsBottomSheetDialogFragment;
import net.osmand.plus.settings.preferences.ListPreferenceEx; import net.osmand.plus.settings.preferences.ListPreferenceEx;
import net.osmand.plus.settings.preferences.SwitchPreferenceEx; import net.osmand.plus.settings.preferences.SwitchPreferenceEx;
import java.io.File;
import java.text.DecimalFormat;
public class GlobalSettingsFragment extends BaseSettingsFragment implements SendAnalyticsBottomSheetDialogFragment.OnSendAnalyticsPrefsUpdate, OnPreferenceChanged { public class GlobalSettingsFragment extends BaseSettingsFragment implements SendAnalyticsBottomSheetDialogFragment.OnSendAnalyticsPrefsUpdate, OnPreferenceChanged {
@ -137,6 +141,14 @@ public class GlobalSettingsFragment extends BaseSettingsFragment implements Send
Preference externalStorageDir = (Preference) findPreference(OsmandSettings.EXTERNAL_STORAGE_DIR); Preference externalStorageDir = (Preference) findPreference(OsmandSettings.EXTERNAL_STORAGE_DIR);
externalStorageDir.setIcon(getContentIcon(R.drawable.ic_action_folder)); externalStorageDir.setIcon(getContentIcon(R.drawable.ic_action_folder));
DataStorageItemsHolder holder = DataStorageItemsHolder.refreshInfo(app);
DataStorageMenuItem currentStorage = holder.getCurrentStorage();
File dir = new File(currentStorage.getDirectory());
DecimalFormat formatter = new DecimalFormat("#.##");
String summary = String.format(getString(R.string.data_storage_preference_summary),
currentStorage.getTitle(),
formatter.format(AndroidUtils.getUsedSpaceGb(dir)));
externalStorageDir.setSummary(summary);
} }
private void setupSendAnonymousDataPref() { private void setupSendAnonymousDataPref() {

View file

@ -0,0 +1,180 @@
package net.osmand.plus.settings.bottomsheets;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.view.View;
import net.osmand.PlatformUtil;
import net.osmand.plus.OsmandSettings;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
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.BaseSettingsFragment;
import net.osmand.plus.settings.DataStorageMenuItem;
import org.apache.commons.logging.Log;
import java.io.File;
import static net.osmand.plus.settings.DataStorageItemsHolder.MANUALLY_SPECIFIED;
public class ChangeDataStorageBottomSheet extends BasePreferenceBottomSheet {
public static final String TAG = "ChangeDataStorageBottomSheet";
private static final Log LOG = PlatformUtil.getLog(ChangeDataStorageBottomSheet.class);
private final static String CURRENT_DIRECTORY = "current_directory";
private final static String NEW_DIRECTORY = "new_directory";
public final static String MOVE_DATA = "move_data";
public final static String CHOSEN_DIRECTORY = "chosen_storage";
private DataStorageMenuItem currentDirectory;
private DataStorageMenuItem newDirectory;
@Override
public void createMenuItems(Bundle savedInstanceState) {
Context ctx = getContext();
if (savedInstanceState != null) {
currentDirectory = savedInstanceState.getParcelable(CURRENT_DIRECTORY);
newDirectory = savedInstanceState.getParcelable(NEW_DIRECTORY);
}
if (ctx == null || currentDirectory == null || newDirectory == null) {
return;
}
items.add(new TitleItem(getString(R.string.change_osmand_data_folder_question)));
int textColorPrimary = nightMode ? R.color.text_color_primary_dark : R.color.text_color_primary_light;
int activeColor = nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light;
CharSequence desc = null;
File currentStorageFile = new File(currentDirectory.getDirectory());
if ((!OsmandSettings.isWritable(currentStorageFile))) {
desc = String.format(getString(R.string.android_19_location_disabled), currentStorageFile.getAbsoluteFile());
} else {
String from = currentDirectory.getKey().equals(MANUALLY_SPECIFIED) ? currentDirectory.getDirectory() : currentDirectory.getTitle();
String to = newDirectory.getKey().equals(MANUALLY_SPECIFIED) ? newDirectory.getDirectory() : newDirectory.getTitle();
String fullDescription = String.format(getString(R.string.change_data_storage_full_description), from, to);
SpannableStringBuilder coloredDescription = new SpannableStringBuilder(
fullDescription);
int startIndexFrom = fullDescription.indexOf(from);
int endIndexFrom = startIndexFrom + from.length();
int startIndexTo = fullDescription.indexOf(to);
int endIndexTo = startIndexTo + to.length();
coloredDescription.setSpan(new ForegroundColorSpan(
ContextCompat.getColor(ctx, activeColor)),
startIndexFrom, endIndexFrom, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
coloredDescription.setSpan(new ForegroundColorSpan(
ContextCompat.getColor(ctx, activeColor)),
startIndexTo, endIndexTo, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
desc = coloredDescription;
}
BottomSheetItemWithDescription description = (BottomSheetItemWithDescription) new BottomSheetItemWithDescription.Builder()
.setDescription(desc)
.setDescriptionColorId(textColorPrimary)
.setLayoutId(R.layout.bottom_sheet_item_description_long)
.create();
items.add(description);
//buttons
View mainView = View.inflate(ctx, R.layout.bottom_sheet_change_data_storage, null);
View btnDontMoveView = mainView.findViewById(R.id.btnDontMove);
btnDontMoveView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
positiveButtonsClick(false);
}
});
UiUtilities.setupDialogButton(nightMode, btnDontMoveView, UiUtilities.DialogButtonType.SECONDARY, getString(R.string.dont_move_maps), currentDirectory.getIconResId());
View btnMoveView = mainView.findViewById(R.id.btnMove);
btnMoveView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
positiveButtonsClick(true);
}
});
UiUtilities.setupDialogButton(nightMode, btnMoveView, UiUtilities.DialogButtonType.PRIMARY, getString(R.string.move_maps_to_new_destination), R.drawable.ic_action_folder_move);
View btnCloseView = mainView.findViewById(R.id.btnClose);
btnCloseView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
UiUtilities.setupDialogButton(nightMode, btnCloseView, UiUtilities.DialogButtonType.SECONDARY, getString(R.string.shared_string_cancel), R.drawable.ic_action_undo_dark);
BaseBottomSheetItem baseItem = new BaseBottomSheetItem.Builder()
.setCustomView(mainView)
.create();
items.add(baseItem);
}
public void setCurrentDirectory(DataStorageMenuItem currentDirectory) {
this.currentDirectory = currentDirectory;
}
public void setNewDirectory(DataStorageMenuItem newDirectory) {
this.newDirectory = newDirectory;
}
private void positiveButtonsClick(boolean moveData) {
Bundle bundle = new Bundle();
bundle.putBoolean(TAG, true);
bundle.putParcelable(CHOSEN_DIRECTORY, newDirectory);
bundle.putBoolean(MOVE_DATA, moveData);
Fragment fragment = getTargetFragment();
if (fragment instanceof BaseSettingsFragment) {
((BaseSettingsFragment) fragment).onPreferenceChange(getPreference(), bundle);
}
dismiss();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(CURRENT_DIRECTORY, currentDirectory);
outState.putParcelable(NEW_DIRECTORY, newDirectory);
}
@Override
protected boolean hideButtonsContainer() {
return true;
}
public static boolean showInstance(FragmentManager fm, String prefId, DataStorageMenuItem currentDirectory,
DataStorageMenuItem newDirectory, Fragment target, boolean usedOnMap) {
try {
if (fm.findFragmentByTag(TAG) == null) {
Bundle args = new Bundle();
args.putString(PREFERENCE_ID, prefId);
ChangeDataStorageBottomSheet fragment = new ChangeDataStorageBottomSheet();
fragment.setCurrentDirectory(currentDirectory);
fragment.setNewDirectory(newDirectory);
fragment.setTargetFragment(target, 0);
fragment.setUsedOnMap(usedOnMap);
fragment.show(fm, TAG);
return true;
}
} catch (RuntimeException e) {
LOG.error(e.getMessage());
}
return false;
}
}

View file

@ -0,0 +1,198 @@
package net.osmand.plus.settings.bottomsheets;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import net.osmand.PlatformUtil;
import net.osmand.plus.R;
import net.osmand.plus.base.bottomsheetmenu.BaseBottomSheetItem;
import net.osmand.plus.base.bottomsheetmenu.simpleitems.TitleItem;
import net.osmand.plus.settings.BaseSettingsFragment;
import org.apache.commons.logging.Log;
import static android.view.View.GONE;
public class SelectFolderBottomSheet extends BasePreferenceBottomSheet {
public static final String TAG = "SelectFolderBottomSheet";
private static final Log LOG = PlatformUtil.getLog(SelectFolderBottomSheet.class);
private static final int CHOOSE_FOLDER_REQUEST_CODE = 0;
private static final String EDIT_TEXT_PREFERENCE_KEY = "edit_text_preference_key";
private static final String DIALOG_TITLE = "dialog_title";
private static final String DESCRIPTION = "description";
private static final String BTN_TITLE = "btn_title";
private static final String ET_WAS_FOCUSED = "edit_text_was_focused";
public static final String NEW_PATH = "path";
public static final String PATH_CHANGED = "changed";
private EditText editText;
private String currentPath;
private String dialogTitle;
private String btnTitle;
private String description;
private boolean etWasFocused;
@Override
public void createMenuItems(Bundle savedInstanceState) {
final Context ctx = getContext();
String text = null;
if (savedInstanceState != null) {
String folderPath = savedInstanceState.getString(NEW_PATH);
if (folderPath != null) {
currentPath = folderPath;
}
text = savedInstanceState.getString(EDIT_TEXT_PREFERENCE_KEY);
dialogTitle = savedInstanceState.getString(DIALOG_TITLE);
description = savedInstanceState.getString(DESCRIPTION);
btnTitle = savedInstanceState.getString(BTN_TITLE);
etWasFocused = savedInstanceState.getBoolean(ET_WAS_FOCUSED);
}
if (ctx == null || currentPath == null) {
return;
}
if (dialogTitle != null) {
items.add(new TitleItem(dialogTitle));
}
View mainView = View.inflate(ctx, R.layout.bottom_sheet_select_folder, null);
TextView tvDescription = mainView.findViewById(R.id.description);
TextView tvBtnTitle = mainView.findViewById(R.id.title);
editText = mainView.findViewById(R.id.text);
View divider = mainView.findViewById(R.id.divider);
View btnOpenChoseDialog = mainView.findViewById(R.id.button);
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// if (btnTitle != null) {
// tvBtnTitle.setText(btnTitle);
// int colorResId = nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light;
// int color = ContextCompat.getColor(ctx, colorResId);
// Drawable drawable = UiUtilities.getColoredSelectableDrawable(ctx, color, 0.3f);
// AndroidUtils.setBackground(btnOpenChoseDialog, drawable);
// btnOpenChoseDialog.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// openDocumentTree();
// }
// });
// }
// } else {
divider.setVisibility(GONE);
btnOpenChoseDialog.setVisibility(GONE);
// }
if (text != null) {
editText.setText(text);
}
if (description != null) {
tvDescription.setText(description);
}
editText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus && !etWasFocused) {
etWasFocused = true;
editText.setText(currentPath);
}
}
});
BaseBottomSheetItem baseItem = new BaseBottomSheetItem.Builder()
.setCustomView(mainView)
.create();
items.add(baseItem);
}
public static boolean showInstance(FragmentManager fm, String prefId, String currentPath, Fragment target,
String dialogTitle, String description, String btnTitle, boolean usedOnMap) {
try {
if (fm.findFragmentByTag(TAG) == null) {
Bundle args = new Bundle();
args.putString(PREFERENCE_ID, prefId);
SelectFolderBottomSheet fragment = new SelectFolderBottomSheet();
fragment.setCurrentPath(currentPath);
fragment.setTargetFragment(target, 0);
fragment.setDialogTitle(dialogTitle);
fragment.setDescription(description);
fragment.setBtnTitle(btnTitle);
fragment.setUsedOnMap(usedOnMap);
fragment.show(fm, TAG);
}
return true;
} catch (RuntimeException e) {
return false;
}
}
@Override
protected int getDismissButtonTextId() {
return R.string.shared_string_close;
}
@Override
protected int getRightBottomButtonTextId() {
return R.string.shared_string_apply;
}
@Override
protected void onRightBottomButtonClick() {
Fragment fragment = getTargetFragment();
if (fragment instanceof BaseSettingsFragment) {
String newPath = editText.getText().toString();
if (!newPath.equals("")) {
boolean pathChanged = !newPath.equals(currentPath);
Bundle bundle = new Bundle();
bundle.putBoolean(TAG, true);
bundle.putString(NEW_PATH, newPath);
bundle.putBoolean(PATH_CHANGED, pathChanged);
((BaseSettingsFragment) fragment).onPreferenceChange(getPreference(), bundle);
}
}
dismiss();
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(EDIT_TEXT_PREFERENCE_KEY, editText.getText().toString());
outState.putString(NEW_PATH, currentPath);
outState.putString(DIALOG_TITLE, dialogTitle);
outState.putString(DESCRIPTION, description);
outState.putString(BTN_TITLE, btnTitle);
outState.putBoolean(ET_WAS_FOCUSED, etWasFocused);
}
public void setCurrentPath(String currentPath) {
this.currentPath = currentPath;
}
public void setDialogTitle(String dialogTitle) {
this.dialogTitle = dialogTitle;
}
public void setDescription(String description) {
this.description = description;
}
public void setBtnTitle(String btnTitle) {
this.btnTitle = btnTitle;
}
}