diff --git a/android/jni/com/mapswithme/maps/Framework.cpp b/android/jni/com/mapswithme/maps/Framework.cpp index 63186264a0..96b149d46b 100644 --- a/android/jni/com/mapswithme/maps/Framework.cpp +++ b/android/jni/com/mapswithme/maps/Framework.cpp @@ -4,6 +4,7 @@ #include "../core/jni_helper.hpp" #include "../core/render_context.hpp" +#include "../platform/Platform.hpp" #include "../../../../../map/framework.hpp" #include "../../../../../map/measurement_utils.hpp" @@ -39,10 +40,7 @@ android::Framework * g_framework = 0; namespace android { - void Framework::CallRepaint() - { - //LOG(LINFO, ("Calling Repaint")); - } + void Framework::CallRepaint() {} Framework::Framework() : m_mask(0), @@ -54,12 +52,6 @@ namespace android g_framework = this; m_videoTimer = new VideoTimer(bind(&Framework::CallRepaint, this)); - - /* - size_t const measurementsCount = 5; - m_sensors[0].SetCount(measurementsCount); - m_sensors[1].SetCount(measurementsCount); - */ } Framework::~Framework() @@ -89,9 +81,7 @@ namespace android void Framework::UpdateCompassSensor(int ind, float * arr) { - //LOG ( LINFO, ("Sensors before, C++: ", arr[0], arr[1], arr[2]) ); m_sensors[ind].Next(arr); - //LOG ( LINFO, ("Sensors after, C++: ", arr[0], arr[1], arr[2]) ); } void Framework::DeleteRenderPolicy() @@ -501,7 +491,7 @@ namespace android { ASSERT ( out.empty(), () ); - Platform const & pl = GetPlatform(); + ::Platform const & pl = GetPlatform(); vector v; m_work.GetLocalMaps(v); @@ -949,4 +939,57 @@ extern "C" g_framework->Invalidate(); } + JNIEXPORT jstring JNICALL + Java_com_mapswithme_maps_Framework_nativeGetBookmarkDir(JNIEnv * env, jclass thiz) + { + return jni::ToJavaString(env, GetPlatform().SettingsDir().c_str()); + } + + JNIEXPORT jstring JNICALL + Java_com_mapswithme_maps_Framework_nativeGetWritableDir(JNIEnv * env, jclass thiz) + { + return jni::ToJavaString(env, GetPlatform().WritableDir().c_str()); + } + + JNIEXPORT jstring JNICALL + Java_com_mapswithme_maps_Framework_nativeGetSettingsDir(JNIEnv * env, jclass thiz) + { + return jni::ToJavaString(env, GetPlatform().SettingsDir().c_str()); + } + + JNIEXPORT jobjectArray JNICALL + Java_com_mapswithme_maps_Framework_nativeGetMovablefilesExt(JNIEnv * env, jclass thiz) + { + jstring fakeString = jni::ToJavaString(env, ""); + jclass stringClass = env->GetObjectClass(fakeString); + + string exts[] = { DATA_FILE_EXTENSION, FONT_FILE_EXTENSION}; + jobjectArray resultArray = env->NewObjectArray(ARRAY_SIZE(exts), stringClass, NULL); + + for (int i = 0; i < ARRAY_SIZE(exts); ++i) + env->SetObjectArrayElement(resultArray, i, jni::ToJavaString(env, exts[i])); + + return resultArray; + } + + JNIEXPORT jstring JNICALL + Java_com_mapswithme_maps_Framework_nativeGetBookmarksExt(JNIEnv * env, jclass thiz) + { + return jni::ToJavaString(env, BOOKMARKS_FILE_EXTENSION); + } + + JNIEXPORT void JNICALL + Java_com_mapswithme_maps_Framework_nativeSetWritableDir(JNIEnv * env, jclass thiz, jstring _newPath) + { + string newPath = jni::ToNativeString(env, _newPath); + g_framework->RemoveLocalMaps(); + android::Platform::Instance().SetStoragePath(newPath); + g_framework->AddLocalMaps(); + } + + JNIEXPORT void JNICALL + Java_com_mapswithme_maps_Framework_nativeLoadbookmarks(JNIEnv * env, jclass thiz) + { + g_framework->NativeFramework()->LoadBookmarks(); + } } diff --git a/android/jni/com/mapswithme/maps/StoragePathManager.cpp b/android/jni/com/mapswithme/maps/StoragePathManager.cpp index 972a48c93f..68669fa7b6 100644 --- a/android/jni/com/mapswithme/maps/StoragePathManager.cpp +++ b/android/jni/com/mapswithme/maps/StoragePathManager.cpp @@ -1,158 +1,19 @@ -#include "../platform/Platform.hpp" #include "../core/jni_helper.hpp" -#include "Framework.hpp" +#include "../platform/Platform.hpp" #include "../../../../../map/bookmark.hpp" -#include "../../../../../base/stl_add.hpp" -#include "../../../../../coding/file_name_utils.hpp" -#include "../../../../../coding/internal/file_data.hpp" -#include "../../../../../std/set.hpp" #include "../../../../../std/string.hpp" -#include "../../../../../std/algorithm.hpp" -namespace -{ - struct PathInserter - { - public: - PathInserter(string const & dirPath, set & set) - : m_dirPath(dirPath) - , m_set(set) - { - if (m_dirPath[m_dirPath.size() - 1] != '/') - m_dirPath = m_dirPath + "/"; - } - - void operator() (string const & name) - { - m_set.insert(m_dirPath + name); - } - - private: - string m_dirPath; - set & m_set; - }; -} extern "C" { JNIEXPORT jstring JNICALL -Java_com_mapswithme_util_StoragePathManager_nativeGetBookmarkDir(JNIEnv * env, jclass thiz) +Java_com_mapswithme_util_StoragePathManager_nativeGenerateUniqueBookmarkName(JNIEnv * env, jclass thiz, jstring _baseName) { - return jni::ToJavaString(env, GetPlatform().SettingsDir().c_str()); -} - -JNIEXPORT jstring JNICALL -Java_com_mapswithme_util_StoragePathManager_nativeGetWritableDir(JNIEnv * env, jclass thiz) -{ - return jni::ToJavaString(env, GetPlatform().WritableDir().c_str()); -} - -JNIEXPORT jstring JNICALL -Java_com_mapswithme_util_StoragePathManager_nativeGetSettingsDir(JNIEnv * env, jclass thiz) -{ - return jni::ToJavaString(env, GetPlatform().SettingsDir().c_str()); -} - -JNIEXPORT jboolean JNICALL -Java_com_mapswithme_util_StoragePathManager_nativeSetStoragePath(JNIEnv * env, jobject thiz, - jstring s) -{ - string const from = GetPlatform().WritableDir(); - string const to = jni::ToNativeString(env, s); - - // Remove all maps from container. - g_framework->RemoveLocalMaps(); - - // Get files to copy. - Platform & pl = GetPlatform(); - - // Get regexp like this: (\.mwm$|\.ttf$) - string const regexp = "(" "\\"DATA_FILE_EXTENSION"$" "|" - "\\"FONT_FILE_EXTENSION"$" ")"; - Platform::FilesList files; - pl.GetFilesByRegExp(from, regexp, files); - - // Copy all needed files. - for (size_t i = 0; i < files.size(); ++i) - if (!my::CopyFileX(from + files[i], to + files[i])) - { - // Do the undo - delete all previously copied files. - for (size_t j = 0; j <= i; ++j) - { - string const path = to + files[j]; - VERIFY ( my::DeleteFileX(path), (path) ); - } - return false; - } - - // Set new storage path. - android::Platform::Instance().SetStoragePath(to); - - // Add all maps again. - g_framework->AddLocalMaps(); - - // Reload bookmarks again - g_framework->NativeFramework()->LoadBookmarks(); - return true; -} - -JNIEXPORT jboolean JNICALL -Java_com_mapswithme_util_StoragePathManager_nativeMoveBookmarks(JNIEnv * env, jclass thiz, jobjectArray pathArray, jlong storageAvSize) -{ - set fullBookmarkSet; - Platform & pl = GetPlatform(); - string const settingsDir = pl.SettingsDir(); - string const writableDir = pl.WritableDir(); - if (writableDir != settingsDir) - { - Platform::FilesList list; - pl.GetFilesByExt(writableDir, BOOKMARKS_FILE_EXTENSION, list); - for_each(list.begin(), list.end(), PathInserter(writableDir, fullBookmarkSet)); - } - - int const arraySize = env->GetArrayLength(pathArray); - for (int i = 0; i < arraySize; ++i) - { - jstring jPath = (jstring)env->GetObjectArrayElement(pathArray, i); - string path = jni::ToNativeString(env, jPath); - - if (path != settingsDir) - { - Platform::FilesList list; - pl.GetFilesByExt(path, BOOKMARKS_FILE_EXTENSION, list); - for_each(list.begin(), list.end(), PathInserter(path, fullBookmarkSet)); - } - } - - typedef set::const_iterator fileIt; - - uint64_t fullSize = 0; - for (fileIt it = fullBookmarkSet.begin(); it != fullBookmarkSet.end(); ++it) - { - uint64_t size = 0; - my::GetFileSize(*it, size); - fullSize += size; - } - - if (storageAvSize < fullSize) - return false; - - for (fileIt it = fullBookmarkSet.begin(); it != fullBookmarkSet.end(); ++it) - { - string const oldFilePath = *it; - string fileName = oldFilePath; - my::GetNameFromFullPath(fileName); - my::GetNameWithoutExt(fileName); - string const newFilePath = BookmarkCategory::GenerateUniqueFileName(settingsDir, fileName); - - if (my::CopyFileX(oldFilePath, newFilePath)) - my::DeleteFileX(oldFilePath); - } - - g_framework->NativeFramework()->LoadBookmarks(); - return true; + string baseName = jni::ToNativeString(env, _baseName); + string bookmarkFileName = BookmarkCategory::GenerateUniqueFileName(GetPlatform().SettingsDir(), baseName); + return jni::ToJavaString(env, bookmarkFileName); } } diff --git a/android/jni/com/mapswithme/maps/settings/StoragePathActivity.cpp b/android/jni/com/mapswithme/maps/settings/StoragePathActivity.cpp index 1240611dc8..08d4881187 100644 --- a/android/jni/com/mapswithme/maps/settings/StoragePathActivity.cpp +++ b/android/jni/com/mapswithme/maps/settings/StoragePathActivity.cpp @@ -1,20 +1,9 @@ #include "../Framework.hpp" - #include "../../core/jni_helper.hpp" -#include "../../platform/Platform.hpp" - -#include "../../../../../../coding/internal/file_data.hpp" - extern "C" { - JNIEXPORT jstring JNICALL - Java_com_mapswithme_maps_settings_StoragePathActivity_nativeGetStoragePath(JNIEnv * env, jobject thiz) - { - return jni::ToJavaString(env, android::Platform::Instance().GetStoragePathPrefix()); - } - JNIEXPORT jboolean JNICALL Java_com_mapswithme_maps_settings_SettingsActivity_isDownloadingActive(JNIEnv * env, jobject thiz) { diff --git a/android/res/xml/preferences.xml b/android/res/xml/preferences.xml index 6e06487c32..922bafa11a 100644 --- a/android/res/xml/preferences.xml +++ b/android/res/xml/preferences.xml @@ -4,31 +4,37 @@ + android:title="@string/maps_storage" + android:order="0"/> + android:title="@string/measurement_units" + android:order="1"/> + android:title="@string/allow_statistics" + android:order="2"/> + android:title="@string/pref_zoom_title" + android:order="3"/> + android:title="@string/yopme_pref_title" + android:order="98"/> + android:title="@string/about_menu_title" + android:order="99"/> \ No newline at end of file diff --git a/android/src/com/mapswithme/maps/Framework.java b/android/src/com/mapswithme/maps/Framework.java index db88d5673a..a1a0b460b9 100644 --- a/android/src/com/mapswithme/maps/Framework.java +++ b/android/src/com/mapswithme/maps/Framework.java @@ -22,6 +22,41 @@ public class Framework public void onAdditionalLayerActivated(long index); public void onDismiss(); } + + static public String GetSettingsDir() + { + return nativeGetSettingsDir(); + } + + static public String GetBookmarksDir() + { + return nativeGetBookmarkDir(); + } + + static public String GetWritableDir() + { + return nativeGetWritableDir(); + } + + static public String GetBookmarkFileExt() + { + return nativeGetBookmarksExt(); + } + + static public String[] GetMovableFilesExt() + { + return nativeGetMovablefilesExt(); + } + + static public void SetWritableDir(String newPath) + { + nativeSetWritableDir(newPath); + } + + static public void ReloadBookmarks() + { + nativeLoadbookmarks(); + } // Interface @@ -135,4 +170,12 @@ public class Framework public native static void cleanSearchLayerOnMap(); public native static void invalidate(); public native static String getPoiTypeNameForLatLon(double lat, double lon); + + private native static String[] nativeGetMovablefilesExt(); + private native static String nativeGetBookmarksExt(); + private native static String nativeGetBookmarkDir(); + private native static String nativeGetSettingsDir(); + private native static String nativeGetWritableDir(); + private native static void nativeSetWritableDir(String newPath); + private native static void nativeLoadbookmarks(); } diff --git a/android/src/com/mapswithme/maps/MWMActivity.java b/android/src/com/mapswithme/maps/MWMActivity.java index 33450900ae..e1d68e9278 100644 --- a/android/src/com/mapswithme/maps/MWMActivity.java +++ b/android/src/com/mapswithme/maps/MWMActivity.java @@ -12,7 +12,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnKeyListener; import android.content.Intent; -import android.content.IntentFilter; import android.content.res.Configuration; import android.location.Location; import android.net.Uri; @@ -118,6 +117,11 @@ public class MWMActivity extends NvEventQueueActivity { return mApplication.getLocationState(); } + + private StoragePathManager GetPathManager() + { + return mApplication.GetPathManager(); + } private void startLocation() { @@ -337,11 +341,12 @@ public class MWMActivity extends NvEventQueueActivity final String KitKatMigrationCompleted = "KitKatMigrationCompleted"; final boolean kmlMoved = MWMApplication.get().nativeGetBoolean(KmlMovedFlag, false); final boolean mapsCpy = MWMApplication.get().nativeGetBoolean(KitKatMigrationCompleted, false); + StoragePathManager pathManager = mApplication.GetPathManager(); if (!kmlMoved) { - if (StoragePathManager.MoveBookmarks()) - MWMApplication.get().nativeSetBoolean(KmlMovedFlag, true); + if (pathManager.MoveBookmarks()) + mApplication.nativeSetBoolean(KmlMovedFlag, true); else { ShowAlertDlg(R.string.bookmark_move_fail); @@ -356,12 +361,17 @@ public class MWMActivity extends NvEventQueueActivity @Override public void MoveFilesFinished(String newPath) { - MWMApplication.get().nativeSetBoolean(KitKatMigrationCompleted, true); + mApplication.nativeSetBoolean(KitKatMigrationCompleted, true); ShowAlertDlg(R.string.kitkat_migrate_ok); } + + @Override + public void MoveFilesFailed() + { + ShowAlertDlg(R.string.kitkat_migrate_failed); + } }; - if (StoragePathManager.CheckWritableDir(this, listener) == false) - ShowAlertDlg(R.string.kitkat_migrate_failed); + pathManager.CheckWritableDir(this, listener); } } @@ -1129,19 +1139,7 @@ public class MWMActivity extends NvEventQueueActivity } }; - final IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_MEDIA_MOUNTED); - filter.addAction(Intent.ACTION_MEDIA_REMOVED); - filter.addAction(Intent.ACTION_MEDIA_EJECT); - filter.addAction(Intent.ACTION_MEDIA_SHARED); - filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); - filter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL); - filter.addAction(Intent.ACTION_MEDIA_UNMOUNTABLE); - filter.addAction(Intent.ACTION_MEDIA_CHECKING); - filter.addAction(Intent.ACTION_MEDIA_NOFS); - filter.addDataScheme("file"); - registerReceiver(m_externalStorageReceiver, filter); - + GetPathManager().StartExtStorageWatching(this, m_externalStorageReceiver); updateExternalStorageState(); } @@ -1211,11 +1209,8 @@ public class MWMActivity extends NvEventQueueActivity private void stopWatchingExternalStorage() { - if (m_externalStorageReceiver != null) - { - unregisterReceiver(m_externalStorageReceiver); - m_externalStorageReceiver = null; - } + GetPathManager().StopExtStorageWatching(); + m_externalStorageReceiver = null; } diff --git a/android/src/com/mapswithme/maps/MWMApplication.java b/android/src/com/mapswithme/maps/MWMApplication.java index e8e16f82c7..7fc47199df 100644 --- a/android/src/com/mapswithme/maps/MWMApplication.java +++ b/android/src/com/mapswithme/maps/MWMApplication.java @@ -21,6 +21,7 @@ import com.mapswithme.maps.guides.GuideInfo; import com.mapswithme.maps.guides.GuidesUtils; import com.mapswithme.maps.location.LocationService; import com.mapswithme.util.FbUtil; +import com.mapswithme.util.StoragePathManager; import com.mapswithme.util.Utils; import com.mapswithme.util.log.Logger; import com.mapswithme.util.log.SimpleLogger; @@ -34,7 +35,7 @@ public class MWMApplication extends android.app.Application implements MapStorag private static MWMApplication mSelf; - + private StoragePathManager m_pathManager = new StoragePathManager(); private LocationService m_location = null; private LocationState m_locationState = null; private MapStorage m_storage = null; @@ -101,6 +102,11 @@ public class MWMApplication extends android.app.Application implements MapStorag } } } + + public StoragePathManager GetPathManager() + { + return m_pathManager; + } @Override public void onCountryProgress(Index idx, long current, long total) diff --git a/android/src/com/mapswithme/maps/settings/SettingsActivity.java b/android/src/com/mapswithme/maps/settings/SettingsActivity.java index b902fa157e..f4541c80e4 100644 --- a/android/src/com/mapswithme/maps/settings/SettingsActivity.java +++ b/android/src/com/mapswithme/maps/settings/SettingsActivity.java @@ -4,6 +4,7 @@ import android.annotation.SuppressLint; import android.app.ActionBar; import android.app.Activity; import android.app.AlertDialog; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -16,6 +17,7 @@ import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceActivity; +import android.preference.PreferenceScreen; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; @@ -34,8 +36,8 @@ import com.mapswithme.util.statistics.Statistics; public class SettingsActivity extends PreferenceActivity { private final static String ZOOM_BUTTON_ENABLED = "ZoomButtonsEnabled"; - - private native boolean isDownloadingActive(); + private BroadcastReceiver m_receiver = null; + private Preference m_storagePreference = null; @SuppressLint("NewApi") @SuppressWarnings("deprecation") @@ -44,7 +46,6 @@ public class SettingsActivity extends PreferenceActivity { super.onCreate(savedInstanceState); - if (Utils.apiEqualOrGreaterThan(11)) { // http://stackoverflow.com/questions/6867076/getactionbar-returns-null @@ -137,8 +138,23 @@ public class SettingsActivity extends PreferenceActivity return true; } }); + + m_storagePreference = findPreference(getString(R.string.pref_storage_activity)); yotaSetup(); + storagePathSetup(); + } + + @SuppressWarnings("deprecation") + private void storagePathSetup() + { + PreferenceScreen screen = getPreferenceScreen(); + if (Yota.isYota()) + screen.removePreference(m_storagePreference); + else if (MWMApplication.get().GetPathManager().HasMoreThanOnceStorage()) + screen.addPreference(m_storagePreference); + else + screen.removePreference(m_storagePreference); } @SuppressWarnings("deprecation") @@ -158,9 +174,6 @@ public class SettingsActivity extends PreferenceActivity return true; } }); - // we dont allow to change maps location - getPreferenceScreen() - .removePreference(findPreference(getString(R.string.pref_storage_activity))); } } @@ -179,6 +192,29 @@ public class SettingsActivity extends PreferenceActivity Statistics.INSTANCE.stopActivity(this); } + + @Override + protected void onResume() + { + super.onResume(); + m_receiver = new BroadcastReceiver() + { + @Override + public void onReceive(Context context, Intent intent) + { + storagePathSetup(); + } + }; + MWMApplication.get().GetPathManager().StartExtStorageWatching(this, m_receiver); + } + + @Override + protected void onPause() + { + super.onPause(); + MWMApplication.get().GetPathManager().StopExtStorageWatching(); + m_receiver = null; + } @Override public boolean onOptionsItemSelected(MenuItem item) @@ -287,4 +323,6 @@ public class SettingsActivity extends PreferenceActivity myWebView.loadUrl(url); } + + private native boolean isDownloadingActive(); } diff --git a/android/src/com/mapswithme/maps/settings/StoragePathActivity.java b/android/src/com/mapswithme/maps/settings/StoragePathActivity.java index 6317380543..7b4821fa00 100644 --- a/android/src/com/mapswithme/maps/settings/StoragePathActivity.java +++ b/android/src/com/mapswithme/maps/settings/StoragePathActivity.java @@ -21,6 +21,7 @@ import android.widget.CheckedTextView; import android.widget.ListView; import android.widget.TextView; +import com.mapswithme.maps.MWMApplication; import com.mapswithme.maps.R; import com.mapswithme.maps.base.MapsWithMeBaseListActivity; import com.mapswithme.util.StoragePathManager; @@ -31,240 +32,9 @@ public class StoragePathActivity extends MapsWithMeBaseListActivity { private static String TAG = "StoragePathActivity"; - /// ListView adapter - private static class StoragePathAdapter extends BaseAdapter + private StoragePathManager.StoragePathAdapter getAdapter() { - private static String TAG = "StoragePathAdapter"; - - /// @name Different row types. - //@{ - private static final int TYPE_HEADER = 0; - private static final int TYPE_ITEM = 1; - private static final int TYPES_COUNT = 2; - //@} - - private final LayoutInflater m_inflater; - private final Activity m_context; - - private String m_currPath; - private final String m_defPath; - private long m_sizeNeeded; - - private final int m_listItemHeight; - - public StoragePathAdapter(Activity context, String currPath, String defPath) - { - m_context = context; - m_inflater = m_context.getLayoutInflater(); - - m_currPath = currPath; - m_defPath = defPath; - - m_listItemHeight = (int)Utils.getAttributeDimension(context, android.R.attr.listPreferredItemHeight); - } - - @SuppressLint("DefaultLocale") - private String getSizeString(long size) - { - final String arrS[] = { "Kb", "Mb", "Gb" }; - - long current = 1024; - int i = 0; - for (; i < arrS.length; ++i) - { - final long bound = 1024 * current; - if (size < bound) - break; - else - current = bound; - } - - // left 1 digit after the comma and add postfix string - return String.format("%.1f %s", (double)size / (double)current, arrS[i]); - } - - private List m_items = new ArrayList(); - private int m_current = -1; - - private boolean isAvailable(int index) - { - assert(index >= 0 && index < m_items.size()); - return ((m_current != index) && (m_items.get(index).m_size >= m_sizeNeeded)); - } - - private int findItemByPath(String path) - { - for (int i = 0; i < m_items.size(); ++i) - if (m_items.get(i).m_path.equals(path)) - return i; - return -1; - } - - public void updateList() - { - m_sizeNeeded = StoragePathManager.getDirSize(m_currPath); - m_items = StoragePathManager.GetStorages(m_context, m_currPath, m_defPath); - - // Find index of the current path. - m_current = findItemByPath(m_currPath); - assert(m_current != -1); - - notifyDataSetChanged(); - } - - private static int HEADERS_COUNT = 1; - - @Override - public int getItemViewType(int position) - { - return (position == 0 ? TYPE_HEADER : TYPE_ITEM); - } - - @Override - public int getViewTypeCount() - { - return TYPES_COUNT; - } - - @Override - public int getCount() - { - return (m_items != null ? m_items.size() + HEADERS_COUNT : HEADERS_COUNT); - } - - @Override - public StoragePathManager.StorageItem getItem(int position) - { - return (position == 0 ? null : m_items.get(getIndexFromPos(position))); - } - - @Override - public long getItemId(int position) - { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) - { - // 1. It's a strange thing, but when I tried to use setClickable, - // all the views become nonclickable. - // 2. I call setMinimumHeight(listPreferredItemHeight) - // because standard item's height is unknown. - - switch (getItemViewType(position)) - { - case TYPE_HEADER: - { - if (convertView == null) - { - convertView = m_inflater.inflate(android.R.layout.simple_list_item_1, null); - convertView.setMinimumHeight(m_listItemHeight); - } - - final TextView v = (TextView) convertView; - v.setText(m_context.getString(R.string.maps) + ": " + getSizeString(m_sizeNeeded)); - break; - } - - case TYPE_ITEM: - { - final int index = getIndexFromPos(position); - final StoragePathManager.StorageItem item = m_items.get(index); - - if (convertView == null) - { - convertView = m_inflater.inflate(android.R.layout.simple_list_item_single_choice, null); - convertView.setMinimumHeight(m_listItemHeight); - } - - final CheckedTextView v = (CheckedTextView) convertView; - v.setText(item.m_path + ": " + getSizeString(item.m_size)); - v.setChecked(index == m_current); - v.setEnabled((index == m_current) || isAvailable(index)); - break; - } - } - - return convertView; - } - - private int getIndexFromPos(int position) - { - final int index = position - HEADERS_COUNT; - assert(index >= 0 && index < m_items.size()); - return index; - } - - private String getFullPath(int index) - { - assert(index >= 0 && index < m_items.size()); - return StoragePathManager.getFullPath(m_items.get(index)); - } - - private void doUpdateAfterMove(String path) - { - m_currPath = path; - - updateList(); - } - - public void onListItemClick(final int position) - { - final int index = getIndexFromPos(position); - if (isAvailable(index)) - { - final String path = getFullPath(index); - - final File f = new File(path); - if (!f.exists() && !f.mkdirs()) - { - Log.e(TAG, "Can't create directory: " + path); - return; - } - - new AlertDialog.Builder(m_context) - .setCancelable(false) - .setTitle(R.string.move_maps) - .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dlg, int which) - { - Log.i(TAG, "Transfer data to storage: " + path); - - StoragePathManager.StorageItem oldItem = null; - if (m_current != -1) - oldItem = m_items.get(m_current); - StoragePathManager.SetStoragePath(m_context, new StoragePathManager.SetStoragePathListener() - { - @Override - public void MoveFilesFinished(String newPath) - { - doUpdateAfterMove(newPath); - } - }, m_items.get(index), oldItem); - - dlg.dismiss(); - } - }) - .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dlg, int which) - { - dlg.dismiss(); - } - }) - .create() - .show(); - } - } - } - - private StoragePathAdapter getAdapter() - { - return (StoragePathAdapter) getListView().getAdapter(); + return (StoragePathManager.StoragePathAdapter)getListView().getAdapter(); } @Override @@ -272,18 +42,7 @@ public class StoragePathActivity extends MapsWithMeBaseListActivity { super.onCreate(savedInstanceState); - final String currPath = nativeGetStoragePath(); - final String defPath = Environment.getExternalStorageDirectory().getAbsolutePath(); - Log.i(TAG, "Current and Default maps pathes: " + currPath + "; " + defPath); - - setListAdapter(new StoragePathAdapter(this, currPath, defPath)); - } - - @Override - protected void onStart() - { - super.onStart(); - getAdapter().updateList(); + setListAdapter(MWMApplication.get().GetPathManager().GetAdapter()); } @Override @@ -291,8 +50,20 @@ public class StoragePathActivity extends MapsWithMeBaseListActivity { // Do not process clicks on header items. if (position != 0) - getAdapter().onListItemClick(position); + getAdapter().onItemClick(position); + } + + @Override + protected void onResume() + { + super.onResume(); + MWMApplication.get().GetPathManager().StartExtStorageWatching(this, null); + } + + @Override + protected void onPause() + { + super.onPause(); + MWMApplication.get().GetPathManager().StopExtStorageWatching(); } - - private native String nativeGetStoragePath(); } diff --git a/android/src/com/mapswithme/util/StoragePathManager.java b/android/src/com/mapswithme/util/StoragePathManager.java index ffcb3abcf9..242fabee0e 100644 --- a/android/src/com/mapswithme/util/StoragePathManager.java +++ b/android/src/com/mapswithme/util/StoragePathManager.java @@ -2,41 +2,62 @@ package com.mapswithme.util; import java.io.BufferedReader; import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; +import java.lang.reflect.Array; +import java.nio.channels.FileChannel; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashSet; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; +import java.util.List; +import java.util.Set; +import com.mapswithme.maps.Framework; import com.mapswithme.maps.R; -import com.mapswithme.maps.settings.StoragePathActivity; import android.annotation.SuppressLint; import android.app.Activity; +import android.app.AlertDialog; import android.app.ProgressDialog; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; import android.os.AsyncTask; +import android.os.Environment; import android.os.StatFs; import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.CheckedTextView; +import android.widget.TextView; public class StoragePathManager { - private static String TAG = "StoragePathManager"; - private static String MWM_DIR_POSTFIX = "/MapsWithMe/"; - public static class StorageItem { - public String m_path; - public long m_size; + public final String m_path; + public final long m_size; + + StorageItem(String path, long size) + { + m_path = path; + m_size = size; + } @Override public boolean equals(Object o) { if (o == this) return true; if (o == null) return false; - return m_size == ((StorageItem)o).m_size; + StorageItem other = (StorageItem)o; + return m_size == other.m_size || m_path.equals(other.m_size); } @Override @@ -46,21 +67,271 @@ public class StoragePathManager } } - static public ArrayList GetStorages(Context context, String currentPath, String defPath) + public interface OnStorageItemClickListener + { + void onItemClick(int position); + } + + public interface SetStoragePathListener + { + void MoveFilesFinished(String newPath); + void MoveFilesFailed(); + } + + public static class StoragePathAdapter extends BaseAdapter + implements OnStorageItemClickListener + { + @Override + public int getCount(){ return 0; } + + @Override + public Object getItem(int position) { return null; } + + @Override + public long getItemId(int position) { return 0; } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { return null; } + + @Override + public void onItemClick(int position) {} + }; + + /// ListView adapter + private class StoragePathAdapterImpl extends StoragePathAdapter + { + private static final String TAG = "StoragePathAdapter"; + + /// @name Different row types. + //@{ + private static final int TYPE_HEADER = 0; + private static final int TYPE_ITEM = 1; + private static final int HEADERS_COUNT = 1; + private static final int TYPES_COUNT = 2; + //@} + + private final LayoutInflater m_inflater; + private final Activity m_context; + private final int m_listItemHeight; + + private List m_items = null; + private int m_current = -1; + private long m_sizeNeeded; + + public StoragePathAdapterImpl(Activity context) + { + m_context = context; + m_inflater = m_context.getLayoutInflater(); + + m_listItemHeight = (int)Utils.getAttributeDimension(context, android.R.attr.listPreferredItemHeight); + } + + @Override + public int getItemViewType(int position) + { + return (position == 0 ? TYPE_HEADER : TYPE_ITEM); + } + + @Override + public int getViewTypeCount() + { + return TYPES_COUNT; + } + + @Override + public int getCount() + { + return (m_items != null ? m_items.size() + HEADERS_COUNT : HEADERS_COUNT); + } + + @Override + public StorageItem getItem(int position) + { + return (position == 0 ? null : m_items.get(getIndexFromPos(position))); + } + + @Override + public long getItemId(int position) + { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) + { + // 1. It's a strange thing, but when I tried to use setClickable, + // all the views become nonclickable. + // 2. I call setMinimumHeight(listPreferredItemHeight) + // because standard item's height is unknown. + + switch (getItemViewType(position)) + { + case TYPE_HEADER: + { + if (convertView == null) + { + convertView = m_inflater.inflate(android.R.layout.simple_list_item_1, null); + convertView.setMinimumHeight(m_listItemHeight); + } + + final TextView v = (TextView) convertView; + v.setText(m_context.getString(R.string.maps) + ": " + getSizeString(m_sizeNeeded)); + break; + } + + case TYPE_ITEM: + { + final int index = getIndexFromPos(position); + final StorageItem item = m_items.get(index); + + if (convertView == null) + { + convertView = m_inflater.inflate(android.R.layout.simple_list_item_single_choice, null); + convertView.setMinimumHeight(m_listItemHeight); + } + + final CheckedTextView v = (CheckedTextView) convertView; + v.setText(item.m_path + ": " + getSizeString(item.m_size)); + v.setChecked(index == m_current); + v.setEnabled((index == m_current) || isAvailable(index)); + break; + } + } + + return convertView; + } + + @Override + public void onItemClick(int position) + { + final int index = getIndexFromPos(position); + if (isAvailable(index)) + onStorageItemClick(index); + } + + public void updateList(ArrayList items, int currentItemIndex, long dirSize) + { + m_sizeNeeded = dirSize; + m_items = items; + m_current = currentItemIndex; + + notifyDataSetChanged(); + } + + @SuppressLint("DefaultLocale") + private String getSizeString(long size) + { + final String arrS[] = { "Kb", "Mb", "Gb" }; + + long current = 1024; + int i = 0; + for (; i < arrS.length; ++i) + { + final long bound = 1024 * current; + if (size < bound) + break; + else + current = bound; + } + + // left 1 digit after the comma and add postfix string + return String.format("%.1f %s", (double)size / (double)current, arrS[i]); + } + + private boolean isAvailable(int index) + { + assert(index >= 0 && index < m_items.size()); + return ((m_current != index) && (m_items.get(index).m_size >= m_sizeNeeded)); + } + + private int getIndexFromPos(int position) + { + final int index = position - HEADERS_COUNT; + assert(index >= 0 && index < m_items.size()); + return index; + } + } + + private static String TAG = "StoragePathManager"; + private static String MWM_DIR_POSTFIX = "/MapsWithMe/"; + + private BroadcastReceiver m_externalListener; + private BroadcastReceiver m_internalListener; + private Activity m_context = null; + private ArrayList m_items = null; + private StoragePathAdapterImpl m_adapter = null; + private int m_currentItemIndex = -1; + + public void StartExtStorageWatching(Activity context, BroadcastReceiver listener) + { + m_context = context; + m_externalListener = listener; + + m_internalListener = new BroadcastReceiver() + { + @Override + public void onReceive(Context context, Intent intent) + { + if (m_externalListener != null) + m_externalListener.onReceive(context, intent); + + UpdateExternalStorages(); + } + }; + + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_MEDIA_MOUNTED); + filter.addAction(Intent.ACTION_MEDIA_REMOVED); + filter.addAction(Intent.ACTION_MEDIA_EJECT); + filter.addAction(Intent.ACTION_MEDIA_SHARED); + filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); + filter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL); + filter.addAction(Intent.ACTION_MEDIA_UNMOUNTABLE); + filter.addAction(Intent.ACTION_MEDIA_CHECKING); + filter.addAction(Intent.ACTION_MEDIA_NOFS); + filter.addDataScheme("file"); + + m_context.registerReceiver(m_internalListener, filter); + UpdateExternalStorages(); + } + + public StoragePathAdapter GetAdapter() + { + if (m_adapter == null) + { + m_adapter = new StoragePathAdapterImpl(m_context); + UpdateExternalStorages(); + } + + return m_adapter; + } + + public void StopExtStorageWatching() + { + if (m_internalListener != null) + { + m_context.unregisterReceiver(m_internalListener); + m_internalListener = null; + m_externalListener = null; + m_adapter = null; + } + } + + public boolean HasMoreThanOnceStorage() + { + return m_items.size() > 1; + } + + public void UpdateExternalStorages() { ArrayList pathes = new ArrayList(); - parseMountFile("/etc/vold.conf", VOLD_MODE, pathes); - parseMountFile("/etc/vold.fstab", VOLD_MODE, pathes); - parseMountFile("/system/etc/vold.fstab", VOLD_MODE, pathes); - parseMountFile("/proc/mounts", MOUNTS_MODE, pathes); - if (Utils.apiEqualOrGreaterThan(android.os.Build.VERSION_CODES.KITKAT)) { - File[] files = context.getExternalFilesDirs(null); + File[] files = m_context.getExternalFilesDirs(null); if (files != null) { - File primaryStorageDir = context.getExternalFilesDir(null); + File primaryStorageDir = m_context.getExternalFilesDir(null); for(File f : files) { // On kitkat and Greater we ignore private folder on primary storage @@ -72,31 +343,43 @@ public class StoragePathManager } } - pathes.add(currentPath); - pathes.add(defPath); + ParseMountFile("/etc/vold.conf", VOLD_MODE, pathes); + ParseMountFile("/etc/vold.fstab", VOLD_MODE, pathes); + ParseMountFile("/system/etc/vold.fstab", VOLD_MODE, pathes); + ParseMountFile("/proc/mounts", MOUNTS_MODE, pathes); ArrayList items = new ArrayList(); for (String path : pathes) - addStorage(path, items); + AddStorage(path, items); - return new ArrayList(new LinkedHashSet(items)); - } - - static public String getFullPath(StorageItem item) - { - return item.m_path + MWM_DIR_POSTFIX; + AddStorage(Environment.getExternalStorageDirectory().getAbsolutePath(), items); + + String writableDir = GetWritableDirRoot(); + + StorageItem currentItem = AddStorage(writableDir, items); + LinkedHashSet itemsSet = new LinkedHashSet(items); + if (currentItem != null) + itemsSet.remove(currentItem); + + m_items = new ArrayList(itemsSet); + if (currentItem != null) + { + m_items.add(0, currentItem); + m_currentItemIndex = m_items.indexOf(currentItem); + } + else + m_currentItemIndex = -1; + if (m_adapter != null) + m_adapter.updateList(m_items, m_currentItemIndex, GetMWMDirSize()); } /// @name Assume that MapsWithMe folder doesn't have inner folders and symbolic links. //@{ - static public long getDirSize(String basePath) + public long GetMWMDirSize() { - return getDirSizeImpl(basePath + MWM_DIR_POSTFIX); - } - - static private long getDirSizeImpl(String path) - { - final File dir = new File(path); + String writableDir = Framework.GetWritableDir(); + + final File dir = new File(writableDir); assert(dir.exists()); assert(dir.isDirectory()); @@ -106,19 +389,20 @@ public class StoragePathManager assert(f.isFile()); size += f.length(); } - - return (size + 1024*1024); + + return size; } + //@} - static public boolean MoveBookmarks() + public boolean MoveBookmarks() { ArrayList pathes = new ArrayList(); if (Utils.apiEqualOrGreaterThan(android.os.Build.VERSION_CODES.KITKAT)) { - parseMountFile("/etc/vold.conf", VOLD_MODE, pathes); - parseMountFile("/etc/vold.fstab", VOLD_MODE, pathes); - parseMountFile("/system/etc/vold.fstab", VOLD_MODE, pathes); - parseMountFile("/proc/mounts", MOUNTS_MODE, pathes); + ParseMountFile("/etc/vold.conf", VOLD_MODE, pathes); + ParseMountFile("/etc/vold.fstab", VOLD_MODE, pathes); + ParseMountFile("/system/etc/vold.fstab", VOLD_MODE, pathes); + ParseMountFile("/proc/mounts", MOUNTS_MODE, pathes); } ArrayList approvedPathes = new ArrayList(); @@ -129,69 +413,297 @@ public class StoragePathManager if (f.exists() || f.canRead() || f.isDirectory()) approvedPathes.add(mwmPath); } - String tmp[] = approvedPathes.toArray(new String[approvedPathes.size()]); - return nativeMoveBookmarks(tmp, getFreeBytesAtPath(nativeGetBookmarkDir())); + final String settingsDir = Framework.GetSettingsDir(); + final String writableDir = Framework.GetWritableDir(); + final String bookmarkDir = Framework.GetBookmarksDir(); + final String bookmarkFileExt = Framework.GetBookmarkFileExt(); + + LinkedHashSet bookmarks = new LinkedHashSet(); + if (!settingsDir.equals(writableDir)) + approvedPathes.add(writableDir); + + for (String path : approvedPathes) + { + if (!path.equals(settingsDir)) + AccamulateFiles(path, bookmarkFileExt, bookmarks); + } + + long bookmarksSize = 0; + for (File f : bookmarks) + bookmarksSize += f.length(); + + if (GetFreeBytesAtPath(bookmarkDir) < bookmarksSize) + return false; + + for (File f : bookmarks) + { + String name = f.getName(); + name = name.replace(bookmarkFileExt, ""); + name = nativeGenerateUniqueBookmarkName(name); + try + { + copyFile(f, new File(name)); + f.delete(); + } + catch(IOException e) + { + return false; + } + } + + Framework.ReloadBookmarks(); + + return true; } - static public boolean CheckWritableDir(Context context, SetStoragePathListener listener) + private void AccamulateFiles(final String dirPath, final String filesExtension, Set result) + { + File f = new File(dirPath); + File[] bookmarks = f.listFiles(new FileFilter() + { + @Override + public boolean accept(File pathname) + { + return pathname.getName().endsWith(filesExtension); + } + }); + + result.addAll(Arrays.asList(bookmarks)); + } + + private void onStorageItemClick(int index) + { + final StorageItem oldItem = (m_currentItemIndex != -1) ? m_items.get(m_currentItemIndex) : null; + final StorageItem item = m_items.get(index); + final String path = GetItemFullPath(item); + + final File f = new File(path); + if (!f.exists() && !f.mkdirs()) + { + Log.e(TAG, "Can't create directory: " + path); + return; + } + + new AlertDialog.Builder(m_context) + .setCancelable(false) + .setTitle(R.string.move_maps) + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dlg, int which) + { + SetStoragePathImpl(m_context, new StoragePathManager.SetStoragePathListener() + { + @Override + public void MoveFilesFinished(String newPath) + { + UpdateExternalStorages(); + } + + @Override + public void MoveFilesFailed() + { + UpdateExternalStorages(); + } + }, item, oldItem, R.string.wait_several_minutes); + + dlg.dismiss(); + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dlg, int which) + { + dlg.dismiss(); + } + }) + .create() + .show(); + } + + public void CheckWritableDir(Context context, SetStoragePathListener listener) { if (Utils.apiLowerThan(android.os.Build.VERSION_CODES.KITKAT)) - return true; + return; - final String settingsDir = nativeGetSettingsDir(); - final String writableDir = nativeGetWritableDir(); + final String settingsDir = Framework.GetSettingsDir(); + final String writableDir = Framework.GetWritableDir(); if (settingsDir.equals(writableDir)) - return true; + return; - if (isDirWritable(writableDir)) - return true; + if (IsDirWritable(writableDir)) + return; - final ArrayList items = GetStorages(context, writableDir.replace(MWM_DIR_POSTFIX, ""), - settingsDir.replace(MWM_DIR_POSTFIX, "")); - final long size = getDirSizeImpl(writableDir); - for (StorageItem item : items) + final long size = GetMWMDirSize(); + for (StorageItem item : m_items) { if (item.m_size > size) { - SetStoragePathImpl(context, listener, item, null, R.string.kitkat_optimization_in_progress); - return true; + SetStoragePathImpl(context, listener, item, new StorageItem(GetWritableDirRoot(), 0), R.string.kitkat_optimization_in_progress); + return; } } - return false; + + listener.MoveFilesFailed(); } - public interface SetStoragePathListener - { - void MoveFilesFinished(String newPath); - } - - static public void SetStoragePath(Context context, SetStoragePathListener listener, StorageItem newStorage, StorageItem oldStorage) - { - SetStoragePathImpl(context, listener, newStorage, oldStorage, R.string.wait_several_minutes); - } - - static private void SetStoragePathImpl(Context context, SetStoragePathListener listener, - StorageItem newStorage, StorageItem oldStorage, - int messageId) + private void SetStoragePathImpl(Context context, SetStoragePathListener listener, + StorageItem newStorage, StorageItem oldStorage, int messageId) { MoveFilesTask task = new MoveFilesTask(context, listener, newStorage, oldStorage, messageId); task.execute(""); } - static private boolean isDirWritable(String path) + static private boolean DoMoveMaps(StorageItem newStorage, StorageItem oldStorage) { - final File f = new File(path + "/testDir"); - f.mkdir(); - // we can't only call canWrite, because on KitKat (Samsung S4) this return true - // for sdcard but actually it's read only - if (f.exists()) + String fullOldPath = GetItemFullPath(oldStorage); + String fullNewPath = GetItemFullPath(newStorage); + File oldDir = new File(fullOldPath); + File newDir = new File(fullNewPath); + if (!newDir.exists()) + newDir.mkdir(); + + assert(IsDirWritable(fullNewPath)); + assert(newDir.isDirectory()); + assert(oldDir.isDirectory()); + + final String[] extensions = Framework.GetMovableFilesExt(); + + File[] internalFiles = oldDir.listFiles(new FileFilter() { - f.delete(); - return true; + + @Override + public boolean accept(File pathname) + { + for (String postfix : extensions) + { + if (pathname.getName().endsWith(postfix)) + return true; + } + return false; + } + }); + + try + { + for (File moveFile : internalFiles) + copyFile(moveFile, new File(fullNewPath + moveFile.getName())); + } + catch (IOException e) + { + for (File moveFile : internalFiles) + new File(fullNewPath + moveFile.getName()).delete(); + return false; } - return false; + Framework.SetWritableDir(fullNewPath); + + for (File moveFile : internalFiles) + moveFile.delete(); + + return true; + } + + private static void copyFile(File source, File dest) throws IOException + { + FileChannel inputChannel = null; + FileChannel outputChannel = null; + try + { + inputChannel = new FileInputStream(source).getChannel(); + outputChannel = new FileOutputStream(dest).getChannel(); + outputChannel.transferFrom(inputChannel, 0, inputChannel.size()); + } + finally + { + inputChannel.close(); + outputChannel.close(); + } + } + + private class MoveFilesTask extends AsyncTask + { + private final ProgressDialog m_dlg; + private final StorageItem m_newStorage; + private final StorageItem m_oldStorage; + private final SetStoragePathListener m_listener; + + public MoveFilesTask(Context context, SetStoragePathListener listener, + StorageItem newStorage, StorageItem oldStorage, int messageID) + { + m_newStorage = newStorage; + m_oldStorage = oldStorage; + m_listener = listener; + + m_dlg = new ProgressDialog(context); + m_dlg.setMessage(context.getString(messageID)); + m_dlg.setProgressStyle(ProgressDialog.STYLE_SPINNER); + m_dlg.setIndeterminate(true); + m_dlg.setCancelable(false); + } + + @Override + protected void onPreExecute() + { + m_dlg.show(); + } + + @Override + protected Boolean doInBackground(String... params) + { + return DoMoveMaps(m_newStorage, m_oldStorage); + } + + @Override + protected void onPostExecute(Boolean result) + { + // Using dummy try-catch because of the following: + // http://stackoverflow.com/questions/2745061/java-lang-illegalargumentexception-view-not-attached-to-window-manager + try + { + m_dlg.dismiss(); + } + catch (final Exception e) + { + } + + if (result) + m_listener.MoveFilesFinished(m_newStorage.m_path); + else + m_listener.MoveFilesFailed(); + + UpdateExternalStorages(); + } + } + + static private StorageItem AddStorage(String path, ArrayList items) + { + try + { + final File f = new File(path + "/"); + if (f.exists() && f.isDirectory() && f.canWrite()) + { + if (!IsDirWritable(path)) + return null; + + final long size = GetFreeBytesAtPath(path); + + final StorageItem item = new StorageItem(path, size); + items.add(item); + return item; + } + else + Log.i(TAG, "File error for storage: " + path); + } + catch (final IllegalArgumentException ex) + { + // Suppress exceptions for unavailable storages. + Log.i(TAG, "StatFs error for storage: " + path); + } + + return null; } private static int VOLD_MODE = 1; @@ -200,7 +712,7 @@ public class StoragePathManager // http://stackoverflow.com/questions/8151779/find-sd-card-volume-label-on-android // http://stackoverflow.com/questions/5694933/find-an-external-sd-card-location // http://stackoverflow.com/questions/14212969/file-canwrite-returns-false-on-some-devices-although-write-external-storage-pe - static private void parseMountFile(String file, int mode, ArrayList pathes) + static private void ParseMountFile(String file, int mode, ArrayList pathes) { Log.i(TAG, "Parsing " + file); @@ -257,46 +769,9 @@ public class StoragePathManager } } - private static boolean addStorage(String path, ArrayList items) - { - try - { - final File f = new File(path + "/"); - if (f.exists() && f.isDirectory() && f.canWrite()) - { - if (!isDirWritable(path)) - return false; - - for (StorageItem item : items) - { - if (item.m_path.equals(path)) - return true; - } - - final long size = getFreeBytesAtPath(path); - - final StorageItem item = new StorageItem(); - item.m_path = path; - item.m_size = size; - - items.add(item); - return true; - } - else - Log.i(TAG, "File error for storage: " + path); - } - catch (final IllegalArgumentException ex) - { - // Suppress exceptions for unavailable storages. - Log.i(TAG, "StatFs error for storage: " + path); - } - - return false; - } - @SuppressWarnings("deprecation") @SuppressLint("NewApi") - static private long getFreeBytesAtPath(String path) + static private long GetFreeBytesAtPath(String path) { final StatFs stat = new StatFs(path); final long size = Utils.apiLowerThan(android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) @@ -305,105 +780,35 @@ public class StoragePathManager return size; } - static private boolean doMoveMaps(StorageItem newStorage, StorageItem oldStorage) + static private boolean IsDirWritable(String path) { - String fullNewPath = getFullPath(newStorage); - File f = new File(fullNewPath); - if (!f.exists()) - f.mkdir(); - - assert(f.canWrite()); - assert(f.isDirectory()); - - if (StoragePathManager.nativeSetStoragePath(fullNewPath)) + final File f = new File(path + "/testDir"); + f.mkdir(); + // we can't only call canWrite, because on KitKat (Samsung S4) this return true + // for sdcard but actually it's read only + if (f.exists()) { - if (oldStorage != null) - deleteFiles(new File(getFullPath(oldStorage))); - + f.delete(); return true; } - + return false; } - //delete all files (except settings.ini) in directory and bookmarks - static private void deleteFiles(File dir) + static private String GetItemFullPath(StorageItem item) { - assert(dir.exists()); - assert(dir.isDirectory()); - - for (final File file : dir.listFiles()) - { - assert(file.isFile()); - - // skip settings.ini - this file should be always in one place - if (file.getName().equalsIgnoreCase("settings.ini")) - continue; - - // skip bookmarks - if (file.getName().endsWith("kml")) - continue; - - if (!file.delete()) - Log.w(TAG, "Can't delete file: " + file.getName()); - } - } - //@} - - private static class MoveFilesTask extends AsyncTask - { - private final ProgressDialog m_dlg; - private final StorageItem m_newStorage; - private final StorageItem m_oldStorage; - private final SetStoragePathListener m_listener; - - public MoveFilesTask(Context context, SetStoragePathListener listener, - StorageItem newStorage, StorageItem oldStorage, int messageID) - { - m_newStorage = newStorage; - m_oldStorage = oldStorage; - m_listener = listener; - - m_dlg = new ProgressDialog(context); - m_dlg.setMessage(context.getString(messageID)); - m_dlg.setProgressStyle(ProgressDialog.STYLE_SPINNER); - m_dlg.setIndeterminate(true); - m_dlg.setCancelable(false); - } - - @Override - protected void onPreExecute() - { - m_dlg.show(); - } - - @Override - protected Boolean doInBackground(String... params) - { - return doMoveMaps(m_newStorage, m_oldStorage); - } - - @Override - protected void onPostExecute(Boolean result) - { - // Using dummy try-catch because of the following: - // http://stackoverflow.com/questions/2745061/java-lang-illegalargumentexception-view-not-attached-to-window-manager - try - { - m_dlg.dismiss(); - } - catch (final Exception e) - { - } - - if (result) - m_listener.MoveFilesFinished(m_newStorage.m_path); - } + return item.m_path + MWM_DIR_POSTFIX; } - static private native boolean nativeMoveBookmarks(String[] additionalFindPathes, long availableSize); - static private native boolean nativeSetStoragePath(String newPath); - static private native String nativeGetBookmarkDir(); - static private native String nativeGetSettingsDir(); - static private native String nativeGetWritableDir(); + static private String GetWritableDirRoot() + { + String writableDir = Framework.GetWritableDir(); + int index = writableDir.lastIndexOf(MWM_DIR_POSTFIX); + if (index != -1) + writableDir = writableDir.substring(0, index); + + return writableDir; + } + + static private native String nativeGenerateUniqueBookmarkName(String baseName); }