From b28457b29b5889f42dad0d4b69f1eef95b20af10 Mon Sep 17 00:00:00 2001 From: vng Date: Tue, 2 Oct 2012 18:53:47 +0300 Subject: [PATCH] [android] Rewrite SettingsActivity to use custom ListAdapter and AsyncTask. --- .../com/mapswithme/maps/SettingsActivity.java | 660 +++++++++++------- android/src/com/mapswithme/util/Utils.java | 11 + 2 files changed, 427 insertions(+), 244 deletions(-) diff --git a/android/src/com/mapswithme/maps/SettingsActivity.java b/android/src/com/mapswithme/maps/SettingsActivity.java index a7474b5e7d..262811a14e 100644 --- a/android/src/com/mapswithme/maps/SettingsActivity.java +++ b/android/src/com/mapswithme/maps/SettingsActivity.java @@ -7,219 +7,468 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import android.app.Activity; import android.app.AlertDialog; import android.app.ListActivity; +import android.app.ProgressDialog; import android.content.DialogInterface; +import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; import android.os.StatFs; import android.util.Log; +import android.view.LayoutInflater; import android.view.View; -import android.widget.ArrayAdapter; +import android.view.ViewGroup; +import android.widget.BaseAdapter; import android.widget.CheckedTextView; import android.widget.ListView; import android.widget.TextView; import com.mapswithme.util.Utils; + public class SettingsActivity extends ListActivity { private static String TAG = "SettingsActivity"; - private static String MWM_DIR_POSTFIX = "/MapsWithMe/"; - private List m_choices = new ArrayList(); - private List m_pathes = new ArrayList(); - private int m_checked = -1; - - private String getSizeString(long size) + /// ListView adapter + private static class SettingsAdapter extends BaseAdapter { - String arrS[] = { "Kb", "Mb", "Gb" }; + private static String TAG = "SettingsAdapter"; + private static String MWM_DIR_POSTFIX = "/MapsWithMe/"; - long current = 1024; - int i = 0; - for (; i < arrS.length; ++i) + /// @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 LayoutInflater m_inflater; + private Activity m_context; + + private String m_currPath; + private String m_defPath; + private long m_sizeNeeded; + + private int m_listItemHeight; + + public SettingsAdapter(Activity context, String currPath, String defPath) { - final long bound = 1024 * current; - if (size < bound) - break; - else - current = bound; + m_context = context; + m_inflater = m_context.getLayoutInflater(); + + m_currPath = currPath; + m_defPath = defPath; + + m_listItemHeight = (int)Utils.getAttributeDimension(context, android.R.attr.listPreferredItemHeight); + + updateList(); } - return String.format("%.1f %s", (double)size / (double)current, arrS[i]); - } - - private boolean addStorage(String path, long sizeNeeded) - { - try + private String getSizeString(long size) { - final File f = new File(path); - if (f.exists() && f.isDirectory() && f.canWrite()) - { - StatFs stat = new StatFs(path); - final long size = (long)stat.getAvailableBlocks() * (long)stat.getBlockSize(); - Log.i(TAG, "Available size = " + size); + String arrS[] = { "Kb", "Mb", "Gb" }; - if (size >= sizeNeeded) + 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 static class StorageItem + { + String m_path; + long m_size; + } + + 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 void updateList() + { + m_sizeNeeded = getDirSize(m_currPath + MWM_DIR_POSTFIX); + Log.i(TAG, "Needed size for maps: " + m_sizeNeeded); + + m_items.clear(); + + //parseMountFile("/etc/vold.conf", VOLD_MODE, currPath, defPath, sizeNeeded); + //parseMountFile("/etc/vold.fstab", VOLD_MODE, currPath, defPath, sizeNeeded); + parseMountFile("/proc/mounts", MOUNTS_MODE); + + m_current = -1; + for (int i = 0; i < m_items.size(); ++i) + if (m_items.get(i).m_path.equals(m_currPath)) { - m_choices.add(path + ": " + getSizeString(size)); - m_pathes.add(path); + m_current = i; + break; + } + + assert(m_current != -1); + } + + private boolean addStorage(String path) + { + try + { + final File f = new File(path); + if (f.exists() && f.isDirectory() && f.canWrite()) + { + StatFs stat = new StatFs(path); + final long size = (long)stat.getAvailableBlocks() * (long)stat.getBlockSize(); + Log.i(TAG, "Available size = " + size); + + StorageItem item = new StorageItem(); + item.m_path = path; + item.m_size = size; + + m_items.add(item); return true; } + else + Log.i(TAG, "File error for storage: " + path); } - else - Log.i(TAG, "File error for storage: " + path); - } - catch (IllegalArgumentException ex) - { - // Suppress exceptions for unavailable storages. - Log.i(TAG, "StatFs error for storage: " + path); - } - - return false; - } - - private static int CURRENT_PATH = 1; - private static int DEFAULT_PATH = 2; - - /// @return Flag that current path or default path is added successfully. - private int callAddStorage(String path, String currPath, String defPath, - long sizeNeeded, int flag) - { - assert(sizeNeeded > 0); - if (path.equals(currPath)) - sizeNeeded = 0; - - if (addStorage(path, sizeNeeded)) - { - if (path.equals(currPath)) - flag |= CURRENT_PATH; - if (path.equals(defPath)) - flag |= DEFAULT_PATH; - } - - return flag; - } - - private static int VOLD_MODE = 1; - private static int MOUNTS_MODE = 2; - - private void parseMountFile(String file, int mode, - String currPath, String defPath, long sizeNeeded) - { - BufferedReader reader = null; - int addFlag = 0; - - try - { - reader = new BufferedReader(new FileReader(file)); - - while (true) + catch (IllegalArgumentException ex) { - String line = reader.readLine(); - if (line == null) break; + // Suppress exceptions for unavailable storages. + Log.i(TAG, "StatFs error for storage: " + path); + } - String[] arr = line.split(" "); - if (arr.length >= 3) + return false; + } + + private static int CURRENT_PATH = 1; + private static int DEFAULT_PATH = 2; + + /// @return Flag that current path or default path is added successfully. + private int callAddStorage(String path, int flag) + { + if (addStorage(path)) + { + if (path.equals(m_currPath)) + flag |= CURRENT_PATH; + if (path.equals(m_defPath)) + flag |= DEFAULT_PATH; + } + + return flag; + } + + private static int VOLD_MODE = 1; + private static int MOUNTS_MODE = 2; + + private void parseMountFile(String file, int mode) + { + BufferedReader reader = null; + int addFlag = 0; + + try + { + reader = new BufferedReader(new FileReader(file)); + + while (true) { - if (arr[0].charAt(0) == '#') - continue; + String line = reader.readLine(); + if (line == null) break; - if (mode == VOLD_MODE) + String[] arr = line.split(" "); + if (arr.length >= 3) { - Log.i(TAG, "Label = " + arr[1] + "; Path = " + arr[2]); + if (arr[0].charAt(0) == '#') + continue; - if (arr[0].startsWith("dev_mount")) - addFlag = callAddStorage(arr[2], currPath, defPath, sizeNeeded, addFlag); - } - else - { - assert(mode == MOUNTS_MODE); - Log.i(TAG, "Label = " + arr[0] + "; Path = " + arr[1]); + if (mode == VOLD_MODE) + { + Log.i(TAG, "Label = " + arr[1] + "; Path = " + arr[2]); - String prefixes[] = { "tmpfs", "/dev/block/vold", "/dev/fuse" }; - for (String s : prefixes) - if (arr[0].startsWith(s)) - addFlag = callAddStorage(arr[1], currPath, defPath, sizeNeeded, addFlag); + if (arr[0].startsWith("dev_mount")) + addFlag = callAddStorage(arr[2], addFlag); + } + else + { + assert(mode == MOUNTS_MODE); + Log.i(TAG, "Label = " + arr[0] + "; Path = " + arr[1]); + + String prefixes[] = { "tmpfs", "/dev/block/vold", "/dev/fuse" }; + for (String s : prefixes) + if (arr[0].startsWith(s)) + addFlag = callAddStorage(arr[1], addFlag); + } } } } - } - catch (IOException e) - { - Log.w(TAG, "Can't read file: " + file); - } - finally - { - Utils.closeStream(reader); + catch (IOException e) + { + Log.w(TAG, "Can't read file: " + file); + } + finally + { + Utils.closeStream(reader); + } + + // Check that current and default paths are added to the list. + if ((addFlag & CURRENT_PATH) == 0) + addFlag = callAddStorage(m_currPath, addFlag); + if ((addFlag & DEFAULT_PATH) == 0) + addFlag = callAddStorage(m_defPath, addFlag); + + assert(addFlag == (CURRENT_PATH | DEFAULT_PATH)); } - // Check that current and default paths are added to the list. - if ((addFlag & CURRENT_PATH) == 0) - addFlag = callAddStorage(currPath, currPath, defPath, sizeNeeded, addFlag); - if ((addFlag & DEFAULT_PATH) == 0) - addFlag = callAddStorage(defPath, currPath, defPath, sizeNeeded, addFlag); - - assert(addFlag == (CURRENT_PATH | DEFAULT_PATH)); - } - - private static int LV_HEADERS_COUNT = 1; - - private CheckedTextView getViewByPos(ListView lv, int position) - { - assert(lv.getHeaderViewsCount() == LV_HEADERS_COUNT); - final int child = position - (lv.getFirstVisiblePosition() - LV_HEADERS_COUNT); - - if (child < 0 || child >= lv.getChildCount()) + /// @name Assume that MapsWithMe folder doesn't have inner folders and symbolic links. + //@{ + private long getDirSize(String name) { - Log.e(TAG, "Unable to get view for desired position: " + position); - return null; - } - return (CheckedTextView) lv.getChildAt(child); - } + File dir = new File(name); + assert(dir.exists()); + assert(dir.isDirectory()); - private String getFullPath(int position) - { - return m_pathes.get(position - LV_HEADERS_COUNT) + MWM_DIR_POSTFIX; - } + long size = 0; + for (File f : dir.listFiles()) + { + assert(f.isFile()); + size += f.length(); + } - /// @name Assume that MapsWithMe folder doesn't have inner folders and symbolic links. - //@{ - private long getDirSize(String name) - { - File dir = new File(name); - assert(dir.exists()); - assert(dir.isDirectory()); - - long size = 0; - for (File f : dir.listFiles()) - { - assert(f.isFile()); - size += f.length(); + return (size + 1024*1024); } - return (size + 1024*1024); - } - - // delete all files (except settings.ini) in directory - private void deleteFiles(File dir) - { - assert(dir.exists()); - assert(dir.isDirectory()); - - for (File file : dir.listFiles()) + // delete all files (except settings.ini) in directory + private void deleteFiles(File dir) { - assert(file.isFile()); + assert(dir.exists()); + assert(dir.isDirectory()); - // skip settings.ini - this file should be always in one place - if (file.getName().equalsIgnoreCase("settings.ini")) - continue; + for (File file : dir.listFiles()) + { + assert(file.isFile()); - if (!file.delete()) - Log.w(TAG, "Can't delete file: " + file.getName()); + // skip settings.ini - this file should be always in one place + if (file.getName().equalsIgnoreCase("settings.ini")) + continue; + + if (!file.delete()) + Log.w(TAG, "Can't delete file: " + file.getName()); + } + } + //@} + + 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 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); + } + + 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); + } + + 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 (m_items.get(index).m_path + MWM_DIR_POSTFIX); + } + + private boolean doMoveMaps(String path) + { + if (SettingsActivity.nativeSetStoragePath(path)) + { + if (m_current != -1) + deleteFiles(new File(getFullPath(m_current))); + + return true; + } + + return false; + } + + private void doUpdateAfterMove(String path) + { + m_currPath = path; + + updateList(); + notifyDataSetChanged(); + } + + private class MoveFilesTask extends AsyncTask + { + private ProgressDialog m_dlg; + private String m_resPath; + + public MoveFilesTask(Activity context, String path) + { + m_resPath = path; + + m_dlg = new ProgressDialog(context); + m_dlg.setMessage(context.getString(R.string.wait_several_minutes)); + 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(params[0]); + } + + @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 (Exception e) + { + } + + if (result) + doUpdateAfterMove(m_resPath); + } + } + + public void onListItemClick(final int position) + { + final int index = getIndexFromPos(position); + if (isAvailable(index)) + { + final String path = getFullPath(index); + + 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); + + MoveFilesTask task = new MoveFilesTask(m_context, m_items.get(index).m_path); + task.execute(path); + + dlg.dismiss(); + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dlg, int which) + { + dlg.dismiss(); + } + }) + .create() + .show(); + } } } - //@} @Override public void onCreate(Bundle savedInstanceState) @@ -230,94 +479,17 @@ public class SettingsActivity extends ListActivity final String defPath = Environment.getExternalStorageDirectory().getAbsolutePath(); Log.i(TAG, "Current and Default maps pathes: " + currPath + "; " + defPath); - final long sizeNeeded = getDirSize(currPath + MWM_DIR_POSTFIX); - Log.i(TAG, "Needed size for maps: " + sizeNeeded); - - //parseMountFile("/etc/vold.conf", VOLD_MODE, currPath, defPath, sizeNeeded); - //parseMountFile("/etc/vold.fstab", VOLD_MODE, currPath, defPath, sizeNeeded); - parseMountFile("/proc/mounts", MOUNTS_MODE, currPath, defPath, sizeNeeded); - - final ListView lv = getListView(); - lv.setItemsCanFocus(false); - lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE); - - // add header item with maps size - TextView tv = (TextView) getLayoutInflater().inflate(android.R.layout.simple_list_item_1, lv, false); - tv.setText(getString(R.string.maps) + ": " + getSizeString(sizeNeeded)); - lv.addHeaderView(tv, null, false); - - setListAdapter(new ArrayAdapter(this, - android.R.layout.simple_list_item_single_choice, m_choices)); - - // set current selection by current storage path - for (int i = 0; i < m_pathes.size(); ++i) - if (m_pathes.get(i).equals(currPath)) - { - m_checked = i + LV_HEADERS_COUNT; - lv.setItemChecked(m_checked, true); - break; - } + setListAdapter(new SettingsAdapter(this, currPath, defPath)); } @Override protected void onListItemClick(final ListView l, View v, final int position, long id) { - if (position != m_checked) - { - final String path = getFullPath(position); - - File f = new File(path); - if (!f.exists() && !f.mkdirs()) - { - Log.e(TAG, "Can't create directory: " + path); - return; - } - - new AlertDialog.Builder(this) - .setCancelable(false) - .setTitle(R.string.move_maps) - .setMessage(R.string.wait_several_minutes) - .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dlg, int which) - { - Log.i(TAG, "Transfer data to storage: " + path); - if (nativeSetStoragePath(path)) - { - if (m_checked != -1) - deleteFiles(new File(getFullPath(m_checked))); - - l.setItemChecked(position, true); - - m_checked = position; - } - else if (m_checked != -1) - { - // set check state back to m_checked item - l.setItemChecked(m_checked, true); - } - - dlg.dismiss(); - } - }) - .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dlg, int which) - { - // set check state back to m_checked item - if (m_checked != -1) - l.setItemChecked(m_checked, true); - - dlg.dismiss(); - } - }) - .create() - .show(); - } + // Do not process clicks on header items. + if (position != 0) + ((SettingsAdapter) getListView().getAdapter()).onListItemClick(position); } private native String nativeGetStoragePath(); - private native boolean nativeSetStoragePath(String newPath); + private static native boolean nativeSetStoragePath(String newPath); } diff --git a/android/src/com/mapswithme/util/Utils.java b/android/src/com/mapswithme/util/Utils.java index 901a130a2d..8c396bc170 100644 --- a/android/src/com/mapswithme/util/Utils.java +++ b/android/src/com/mapswithme/util/Utils.java @@ -3,6 +3,7 @@ package com.mapswithme.util; import java.io.Closeable; import java.io.IOException; +import android.app.Activity; import android.util.Log; import android.view.Window; @@ -39,4 +40,14 @@ public class Utils else w.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } + + public static float getAttributeDimension(Activity activity, int attr) + { + android.util.TypedValue value = new android.util.TypedValue(); + boolean b = activity.getTheme().resolveAttribute(attr, value, true); + assert(b); + android.util.DisplayMetrics metrics = new android.util.DisplayMetrics(); + activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); + return value.getDimension(metrics); + } }