package net.osmand.plus.dashboard;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.StatFs;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import net.osmand.IndexConstants;
import net.osmand.ValueHolder;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandSettings;
import net.osmand.plus.ProgressImplementation;
import net.osmand.plus.R;
import net.osmand.plus.download.DownloadActivity;
import net.osmand.util.Algorithms;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import gnu.trove.list.array.TIntArrayList;
public class DashChooseAppDirFragment {
public static class ChooseAppDirFragment {
public static final int VERSION_DEFAULTLOCATION_CHANGED = 19;
private TextView locationPath;
private TextView locationDesc;
MessageFormat formatGb = new MessageFormat("{0, number,#.##} GB", Locale.US);
private View copyMapsBtn;
private ImageView editBtn;
private View confirmBtn;
private boolean mapsCopied = false;
private TextView warningReadonly;
private int type = -1;
private File selectedFile = new File("/");
private File currentAppFile;
private OsmandSettings settings;
private Activity activity;
private Fragment fragment;
private Dialog dlg;
private static int typeTemp = -1;
private static String selectePathTemp;
public ChooseAppDirFragment(Activity activity, Fragment f) {
this.activity = activity;
this.fragment = f;
}
public ChooseAppDirFragment(Activity activity, Dialog dlg) {
this.activity = activity;
this.dlg = dlg;
}
public void setPermissionDenied() {
typeTemp = -1;
selectePathTemp = null;
}
private String getFreeSpace(File dir) {
if (dir.canRead()) {
StatFs fs = new StatFs(dir.getAbsolutePath());
return formatGb
.format(new Object[] { (float) (fs.getAvailableBlocks()) * fs.getBlockSize() / (1 << 30) });
}
return "";
}
public void updateView() {
if (type == OsmandSettings.EXTERNAL_STORAGE_TYPE_INTERNAL_FILE) {
locationPath.setText(R.string.storage_directory_internal_app);
} else if (type == OsmandSettings.EXTERNAL_STORAGE_TYPE_DEFAULT) {
locationPath.setText(R.string.storage_directory_shared);
} else if (type == OsmandSettings.EXTERNAL_STORAGE_TYPE_EXTERNAL_FILE) {
locationPath.setText(R.string.storage_directory_external);
} else if (type == OsmandSettings.EXTERNAL_STORAGE_TYPE_OBB) {
locationPath.setText(R.string.storage_directory_multiuser);
} else if (type == OsmandSettings.EXTERNAL_STORAGE_TYPE_SPECIFIED) {
locationPath.setText(R.string.storage_directory_manual);
}
locationDesc.setText(selectedFile.getAbsolutePath() + " \u2022 " + getFreeSpace(selectedFile));
boolean copyFiles = !currentAppFile.getAbsolutePath().equals(selectedFile.getAbsolutePath()) && !mapsCopied;
if (copyFiles) {
copyFiles = false;
File[] lf = currentAppFile.listFiles();
if (lf != null) {
for (File f : lf) {
if (f != null) {
if (f.getName().endsWith(IndexConstants.BINARY_MAP_INDEX_EXT)) {
copyFiles = true;
break;
}
}
}
}
}
warningReadonly.setVisibility(copyFiles ? View.VISIBLE : View.GONE);
if (copyFiles) {
if (!OsmandSettings.isWritable(currentAppFile)) {
warningReadonly.setText(activity.getString(R.string.android_19_location_disabled,
currentAppFile.getAbsolutePath()));
} else {
warningReadonly.setText(getString(R.string.application_dir_change_warning3));
}
}
copyMapsBtn.setVisibility(copyFiles ? View.VISIBLE : View.GONE);
}
public View initView(LayoutInflater inflater, ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.dash_storage_type_fragment, container, false);
settings = getMyApplication().getSettings();
locationPath = (TextView) view.findViewById(R.id.location_path);
locationDesc = (TextView) view.findViewById(R.id.location_desc);
warningReadonly = (TextView) view.findViewById(R.id.android_19_location_changed);
currentAppFile = settings.getExternalStorageDirectory();
selectedFile = currentAppFile;
if (settings.getExternalStorageDirectoryTypeV19() >= 0) {
type = settings.getExternalStorageDirectoryTypeV19();
} else {
ValueHolder<Integer> vh = new ValueHolder<Integer>();
settings.getExternalStorageDirectory(vh);
if (vh.value != null && vh.value >= 0) {
type = vh.value;
} else {
type = 0;
}
}
editBtn = (ImageView) view.findViewById(R.id.edit_icon);
copyMapsBtn = view.findViewById(R.id.copy_maps);
confirmBtn = view.findViewById(R.id.confirm);
addListeners();
processPermissionGranted();
updateView();
return view;
}
public String getString(int string) {
return activity.getString(string);
}
@TargetApi(Build.VERSION_CODES.KITKAT)
protected void showSelectDialog19() {
AlertDialog.Builder editalert = new AlertDialog.Builder(activity);
editalert.setTitle(R.string.application_dir);
final List<String> items = new ArrayList<String>();
final List<String> paths = new ArrayList<String>();
final TIntArrayList types = new TIntArrayList();
int selected = -1;
if (type == OsmandSettings.EXTERNAL_STORAGE_TYPE_SPECIFIED) {
items.add(getString(R.string.storage_directory_manual));
paths.add(selectedFile.getAbsolutePath());
types.add(OsmandSettings.EXTERNAL_STORAGE_TYPE_SPECIFIED);
}
File df = settings.getDefaultInternalStorage();
if (type == OsmandSettings.EXTERNAL_STORAGE_TYPE_DEFAULT) {
selected = items.size();
}
items.add(getString(R.string.storage_directory_shared));
paths.add(df.getAbsolutePath());
types.add(OsmandSettings.EXTERNAL_STORAGE_TYPE_DEFAULT);
File[] externals = getMyApplication().getExternalFilesDirs(null);
if (externals != null) {
int i = 1;
for (File external : externals) {
if (external != null) {
if (selectedFile.getAbsolutePath().equals(external.getAbsolutePath())) {
selected = items.size();
}
items.add(getString(R.string.storage_directory_external) + " " + (i++));
paths.add(external.getAbsolutePath());
types.add(OsmandSettings.EXTERNAL_STORAGE_TYPE_EXTERNAL_FILE);
}
}
}
File[] obbDirs = getMyApplication().getObbDirs();
if (obbDirs != null) {
int i = 1;
for (File obb : obbDirs) {
if (obb != null) {
if (selectedFile.getAbsolutePath().equals(obb.getAbsolutePath())) {
selected = items.size();
}
items.add(getString(R.string.storage_directory_multiuser) + " " + (i++));
paths.add(obb.getAbsolutePath());
types.add(OsmandSettings.EXTERNAL_STORAGE_TYPE_OBB);
}
}
}
String pth = settings.getInternalAppPath().getAbsolutePath();
if (selectedFile.getAbsolutePath().equals(pth)) {
selected = items.size();
}
items.add(getString(R.string.storage_directory_internal_app));
paths.add(pth);
types.add(OsmandSettings.EXTERNAL_STORAGE_TYPE_INTERNAL_FILE);
items.add(getString(R.string.storage_directory_manual) + getString(R.string.shared_string_ellipsis));
paths.add("");
types.add(OsmandSettings.EXTERNAL_STORAGE_TYPE_SPECIFIED);
editalert.setSingleChoiceItems(items.toArray(new String[items.size()]), selected,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == items.size() - 1) {
dialog.dismiss();
showOtherDialog();
} else {
if (types.get(which) == OsmandSettings.EXTERNAL_STORAGE_TYPE_DEFAULT
&& !DownloadActivity.hasPermissionToWriteExternalStorage(activity)) {
typeTemp = types.get(which);
selectePathTemp = paths.get(which);
dialog.dismiss();
if (dlg != null) {
dlg.dismiss();
}
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
DownloadActivity.PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
} else {
mapsCopied = false;
type = types.get(which);
selectedFile = new File(paths.get(which));
dialog.dismiss();
updateView();
}
}
}
});
editalert.setNegativeButton(R.string.shared_string_dismiss, null);
editalert.show();
}
private void processPermissionGranted() {
if (typeTemp != -1 && selectePathTemp != null) {
mapsCopied = false;
type = typeTemp;
selectedFile = new File(selectePathTemp);
typeTemp = -1;
selectePathTemp = null;
}
}
public void showOtherDialog() {
AlertDialog.Builder editalert = new AlertDialog.Builder(activity);
editalert.setTitle(R.string.application_dir);
final EditText input = new EditText(activity);
input.setText(selectedFile.getAbsolutePath());
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT);
lp.leftMargin = lp.rightMargin = 5;
lp.bottomMargin = lp.topMargin = 5;
input.setLayoutParams(lp);
settings.getExternalStorageDirectory().getAbsolutePath();
editalert.setView(input);
editalert.setNegativeButton(R.string.shared_string_cancel, null);
editalert.setPositiveButton(R.string.shared_string_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
selectedFile = new File(input.getText().toString());
mapsCopied = false;
updateView();
}
});
editalert.show();
}
private void addListeners() {
editBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
showOtherDialog();
} else {
showSelectDialog19();
}
}
});
copyMapsBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MoveFilesToDifferentDirectory task = new MoveFilesToDifferentDirectory(activity, currentAppFile,
selectedFile) {
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if (result) {
mapsCopied = true;
getMyApplication().getResourceManager().resetStoreDirectory();
} else {
Toast.makeText(activity, R.string.copying_osmand_file_failed,
Toast.LENGTH_SHORT).show();
}
updateView();
}
};
task.execute();
}
});
confirmBtn.setOnClickListener(getConfirmListener());
}
public OnClickListener getConfirmListener() {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean wr = OsmandSettings.isWritable(selectedFile);
if (wr) {
boolean changed = !currentAppFile.getAbsolutePath().equals(selectedFile.getAbsolutePath());
getMyApplication().setExternalStorageDirectory(type, selectedFile.getAbsolutePath());
if (changed) {
successCallback();
reloadData();
}
if (fragment != null && activity instanceof FragmentActivity) {
((FragmentActivity) activity).getSupportFragmentManager().beginTransaction()
.remove(fragment).commit();
}
} else {
Toast.makeText(activity, R.string.specified_directiory_not_writeable,
Toast.LENGTH_LONG).show();
}
if(dlg != null) {
dlg.dismiss();
}
}
};
}
// To be implemented by subclass
protected void successCallback() {}
protected void reloadData() {
new ReloadData(activity, getMyApplication()).execute((Void) null);
}
public OsmandApplication getMyApplication() {
if (activity == null) {
return null;
}
return (OsmandApplication) activity.getApplication();
}
public static HashSet<String> getExternalMounts() {
final HashSet<String> out = new HashSet<String>();
String reg = "(?i).*vold.*(vfat|ntfs|exfat|fat32|ext3|ext4).*rw.*";
String s = "";
try {
final Process process = new ProcessBuilder().command("mount").redirectErrorStream(true).start();
process.waitFor();
final InputStream is = process.getInputStream();
final byte[] buffer = new byte[1024];
while (is.read(buffer) != -1) {
s = s + new String(buffer);
}
is.close();
} catch (final Exception e) {
e.printStackTrace();
}
// parse output
final String[] lines = s.split("\n");
for (String line : lines) {
if (!line.toLowerCase(Locale.US).contains("asec")) {
if (line.matches(reg)) {
String[] parts = line.split(" ");
for (String part : parts) {
if (part.startsWith("/"))
if (!part.toLowerCase(Locale.US).contains("vold"))
out.add(part);
}
}
}
}
return out;
}
public void setDialog(Dialog dlg) {
this.dlg = dlg;
}
}
public static class MoveFilesToDifferentDirectory extends AsyncTask<Void, Void, Boolean> {
private File to;
private Context ctx;
private File from;
protected ProgressImplementation progress;
private Runnable runOnSuccess;
public MoveFilesToDifferentDirectory(Context ctx, File from, File to) {
this.ctx = ctx;
this.from = from;
this.to = to;
}
public void setRunOnSuccess(Runnable runOnSuccess) {
this.runOnSuccess = runOnSuccess;
}
@Override
protected void onPreExecute() {
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) {
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 {
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;
try {
rnm = f.renameTo(t);
} 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);
} 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 Context ctx;
protected ProgressImplementation progress;
private OsmandApplication app;
public ReloadData(Context ctx, OsmandApplication app) {
this.ctx = ctx;
this.app = app;
}
@Override
protected void onPreExecute() {
progress = ProgressImplementation.createProgressDialog(ctx, ctx.getString(R.string.loading_data),
ctx.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;
}
}
}