package org.androad.ui.sd;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
import android.util.Log;
import org.androad.R;
import org.androad.osm.util.constants.OSMConstants;
import org.androad.preferences.Preferences;
import org.androad.ui.AndNavBaseActivity;
import org.androad.ui.common.OnClickOnFocusChangedListenerAdapter;
import org.androad.ui.common.views.FastScrollView;
import org.androad.util.UserTask;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
public class SDPoi extends AndNavBaseActivity {
// ===========================================================
// Constants
// ===========================================================
private static final String STATE_POI_ITEMS_ID = "state_poi_items_id";
private static final String poiFolderPath = org.androad.osm.util.Util.getAndRoadExternalStoragePath() + OSMConstants.SDCARD_SAVEDPOI_PATH;
private static final int BUFFER_SIZE = 5120;
// ===========================================================
// Fields
// ===========================================================
private Bundle bundleCreatedWith;
private ListView mPoiList;
private ArrayList<PoiItem> mPoi = new ArrayList<PoiItem>();
private boolean mPoiInitFinished = false;
private ProgressDialog pd;
// ===========================================================
// Constructors
// ===========================================================
/** Called when the activity is first created. */
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Preferences.applySharedSettings(this);
this.setContentView(R.layout.sd_poi);
this.bundleCreatedWith = this.getIntent().getExtras();
this.mPoiList = (ListView) this.findViewById(R.id.list_poi);
final TextView empty = new TextView(this);
empty.setText(R.string.list_empty);
this.mPoiList.setEmptyView(empty);
initListView();
this.applyTopMenuButtonListeners();
if(savedInstanceState == null) {
updatePoiListItems();
}
new File(poiFolderPath).mkdirs();
}
@Override
protected void onPause() {
super.onPause();
if (pd != null && pd.isShowing()) pd.dismiss();
}
private void downloadFile(final URL url, final OutputStream out) {
int tries = 3;
int read = 0;
int length = 0;
byte[] buffer = new byte[BUFFER_SIZE];
while (tries > 0) {
if (read > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
try {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(30000);
conn.setConnectTimeout(30000);
if (read > 0) {
conn.setRequestProperty("Range", "bytes=" + read + "-" + (length -1));
}
switch (conn.getResponseCode()) {
case HttpURLConnection.HTTP_PARTIAL:
break;
case HttpURLConnection.HTTP_OK:
length = conn.getContentLength();
break;
default:
conn.disconnect();
tries--;
continue;
}
final InputStream is = conn.getInputStream();
int r = 0;
while ((r = is.read(buffer)) != -1) {
out.write(buffer, 0, r);
read += r;
}
is.close();
out.flush();
} catch (IOException e) {
tries--;
}
if (length <= read) {
return;
}
}
}
private void downloadPoiItem(final PoiItem p) {
pd = ProgressDialog.show(this, getString(R.string.sd_poi_item_loading_title), getString(R.string.please_wait_a_moment), false); // TODO Make determinate, when SDK supports this.
final String progressBaseString = getString(R.string.sd_poi_item_loading_progress);
final String fileToDownload = poiFolderPath + p.mName;
new UserTask<Void, Integer, Void>() {
@Override
public Void doInBackground(final Void... params) {
try {
OutputStream out = new BufferedOutputStream(new FileOutputStream(fileToDownload));
if (p.mParts < 2) {
URL url = new URL("http://download.osmand.net/download?file=" + p.mName);
downloadFile(url, out);
} else {
for(int i = 1; i <= p.mParts; i++) {
URL url = new URL("http://download.osmand.net/download?file=" + p.mName + "-" + i);
downloadFile(url, out);
}
}
out.close();
if (p.mName.endsWith(".zip")) {
final ZipInputStream zipIn = new ZipInputStream(new FileInputStream(fileToDownload));
ZipEntry entry = null;
while ((entry = zipIn.getNextEntry()) != null) {
final File fs = new File(poiFolderPath, entry.getName());
out = new BufferedOutputStream(new FileOutputStream(fs));
int read;
byte[] buffer = new byte[BUFFER_SIZE];
while ((read = zipIn.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
out.close();
}
zipIn.close();
(new File(fileToDownload)).delete();
}
} catch (Exception e) {
Log.e(OSMConstants.DEBUGTAG, "Downloading poi index error", e);
}
return null;
}
@Override
public void onProgressUpdate(final Integer... progress) {
pd.setMessage(String.format(progressBaseString, (int)(100*((float)progress[0] / progress[1])), progress[0], progress[1]));
}
@Override
public void onPostExecute(final Void result) {
try{
pd.dismiss();
pd = null;
}catch(final IllegalArgumentException ia){
// Nothing
}
}
}.execute();
}
private void updatePoiListItems() {
final PoiListAdapter pla = new PoiListAdapter(this);
pd = ProgressDialog.show(this, getString(R.string.sd_poi_loading_title), getString(R.string.please_wait_a_moment), false); // TODO Make determinate, when SDK supports this.
final String progressBaseString = getString(R.string.sd_poi_loading_progress);
new UserTask<Void, Integer, Void>(){
@Override
public Void doInBackground(final Void... params) {
try {
URL url = new URL("http://download.osmand.net/get_indexes");
XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
parser.setInput(url.openStream(), "UTF-8");
int next;
while((next = parser.next()) != XmlPullParser.END_DOCUMENT) {
if(next == XmlPullParser.START_TAG && (parser.getName().equals("region") ||
parser.getName().equals("multiregion"))) {
String name = parser.getAttributeValue(null, "name");
String size = parser.getAttributeValue(null, "size");
String date = parser.getAttributeValue(null, "date");
String description = parser.getAttributeValue(null, "description");
if (!description.startsWith("POI index")) continue;
int parts = 0;
try {
parts = Integer.parseInt(parser.getAttributeValue(null, "parts"));
} catch (NumberFormatException e) {}
PoiItem poiItem = new PoiItem(name, description, date, size, parts);
SDPoi.this.mPoi.add(poiItem);
}
}
} catch (Exception e) {
Log.e(OSMConstants.DEBUGTAG, "Downloading poi index error", e);
}
/* Adapt the list to the Adapter. */
pla.setListItems(SDPoi.this.mPoi);/* Orders by name, ascending. */
SDPoi.this.mPoiInitFinished = true;
return null;
}
@Override
public void onProgressUpdate(final Integer... progress) {
pd.setMessage(String.format(progressBaseString, (int)(100*((float)progress[0] / progress[1])), progress[0], progress[1]));
}
@Override
public void onPostExecute(final Void result) {
/* Adapt the Adapter to the ListView. */
SDPoi.this.mPoiList.setAdapter(pla);
try{
pd.dismiss();
pd = null;
}catch(final IllegalArgumentException ia){
// Nothing
}
}
}.execute();
}
protected void initListView() {
this.mPoiList.setOnItemClickListener(new OnItemClickListener(){
@Override
public void onItemClick(final AdapterView<?> parent, final View v, final int position, final long id) {
final PoiItem p = (PoiItem)parent.getAdapter().getItem(position);
downloadPoiItem(p);
}
});
}
// ===========================================================
// Getter & Setter
// ===========================================================
// ===========================================================
// Methods from SuperClass/Interfaces
// ===========================================================
@Override
public boolean onKeyDown(final int keyCode, final KeyEvent event) {
final char c;
switch(keyCode){
case KeyEvent.KEYCODE_A: c = 'a'; break;
case KeyEvent.KEYCODE_B: c = 'b'; break;
case KeyEvent.KEYCODE_C: c = 'c'; break;
case KeyEvent.KEYCODE_D: c = 'd'; break;
case KeyEvent.KEYCODE_E: c = 'e'; break;
case KeyEvent.KEYCODE_F: c = 'f'; break;
case KeyEvent.KEYCODE_G: c = 'g'; break;
case KeyEvent.KEYCODE_H: c = 'h'; break;
case KeyEvent.KEYCODE_I: c = 'i'; break;
case KeyEvent.KEYCODE_J: c = 'j'; break;
case KeyEvent.KEYCODE_K: c = 'k'; break;
case KeyEvent.KEYCODE_L: c = 'l'; break;
case KeyEvent.KEYCODE_M: c = 'n'; break;
case KeyEvent.KEYCODE_N: c = 'm'; break;
case KeyEvent.KEYCODE_O: c = 'o'; break;
case KeyEvent.KEYCODE_P: c = 'p'; break;
case KeyEvent.KEYCODE_Q: c = 'q'; break;
case KeyEvent.KEYCODE_R: c = 'r'; break;
case KeyEvent.KEYCODE_S: c = 's'; break;
case KeyEvent.KEYCODE_T: c = 't'; break;
case KeyEvent.KEYCODE_U: c = 'u'; break;
case KeyEvent.KEYCODE_V: c = 'v'; break;
case KeyEvent.KEYCODE_W: c = 'w'; break;
case KeyEvent.KEYCODE_X: c = 'x'; break;
case KeyEvent.KEYCODE_Y: c = 'y'; break;
case KeyEvent.KEYCODE_Z: c = 'z'; break;
default:
return super.onKeyDown(keyCode, event);
}
int position = Collections.binarySearch(this.mPoi, new PoiItem(String.valueOf(c), "", "", "", 0));
if(position < 0){
/* Negative result means the insertion-point.
* See definition of Collections.binarySearch */
position = -(position + 1);
}
this.mPoiList.setSelectionFromTop(position, 0);
return true;
}
@Override
public void onSaveInstanceState(final Bundle out) {
if(this.mPoiInitFinished) {
out.putParcelableArrayList(STATE_POI_ITEMS_ID, this.mPoi);
}
}
@Override
protected void onRestoreInstanceState(final Bundle in) {
final ArrayList<PoiItem> restoredItems = in.getParcelableArrayList(STATE_POI_ITEMS_ID);
if(this.mPoi == null){
updatePoiListItems();
}else{
this.mPoi = restoredItems;
final PoiListAdapter pla = new PoiListAdapter(this);
pla.setListItems(this.mPoi);
this.mPoiList.setAdapter(pla);
this.mPoiInitFinished = true;
}
}
@Override
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
switch(resultCode){
case SUBACTIVITY_RESULTCODE_CHAINCLOSE_SUCCESS:
this.setResult(SUBACTIVITY_RESULTCODE_CHAINCLOSE_SUCCESS, data);
this.finish();
break;
case SUBACTIVITY_RESULTCODE_CHAINCLOSE_QUITTED:
this.setResult(SUBACTIVITY_RESULTCODE_CHAINCLOSE_QUITTED, data);
this.finish();
break;
}
/* Finally call the super()-method. */
super.onActivityResult(requestCode, resultCode, data);
}
// ===========================================================
// Methods
// ===========================================================
protected void applyTopMenuButtonListeners() {
/* Set Listener for Back-Button. */
new OnClickOnFocusChangedListenerAdapter(this.findViewById(R.id.ibtn_sd_poi_back)) {
@Override
public void onClicked(final View me) {
if (SDPoi.super.mMenuVoiceEnabled) {
MediaPlayer.create(SDPoi.this, R.raw.close).start();
}
/* Back one level. */
SDPoi.this.setResult(SUBACTIVITY_RESULTCODE_UP_ONE_LEVEL);
SDPoi.this.finish();
}
};
/* Set Listener for Close-Button. */
new OnClickOnFocusChangedListenerAdapter(this.findViewById(R.id.ibtn_sd_poi_close)) {
@Override
public void onClicked(final View me) {
if (SDPoi.super.mMenuVoiceEnabled) {
MediaPlayer.create(SDPoi.this, R.raw.close).start();
}
/*
* Set ResultCode that the calling activity knows that we want
* to go back to the Base-Menu
*/
SDPoi.this.setResult(SUBACTIVITY_RESULTCODE_CHAINCLOSE_QUITTED);
SDPoi.this.finish();
}
};
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
private static class PoiItemToResolve{
protected final String mAddressDescription;
protected final String mAddressTypeAppendix;
private PoiItemToResolve(final String addressDescription, final int personID, final String addressTypeAppendix) {
this.mAddressDescription = addressDescription;
this.mAddressTypeAppendix = addressTypeAppendix;
}
}
static class PoiItem implements Comparable<PoiItem>, Parcelable{
protected final String mName;
protected final String mDescription;
protected final String mDate;
protected final String mSize;
protected final int mParts;
private PoiItem(final String pName, final String pDescription, final String pDate, final String pSize, final int pParts) {
this.mName = pName;
this.mDescription = pDescription;
this.mDate = pDate;
this.mSize = pSize;
this.mParts = pParts;
}
public boolean exists() {
final String filename;
if (this.mName.endsWith(".zip")) {
filename = this.mName.substring(0, this.mName.length() - 3);
} else {
filename = this.mName;
}
final File f = new File(poiFolderPath, filename);
return f.exists();
}
@Override
public int compareTo(final PoiItem another) {
return this.mName.compareToIgnoreCase(another.mName);
}
// ===========================================================
// Parcelable
// ===========================================================
public static final Parcelable.Creator<PoiItem> CREATOR = new Parcelable.Creator<PoiItem>() {
public PoiItem createFromParcel(final Parcel in) {
return readFromParcel(in);
}
public PoiItem[] newArray(final int size) {
return new PoiItem[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel out, final int flags) {
out.writeString(this.mName);
out.writeString(this.mDescription);
out.writeString(this.mDate);
out.writeString(this.mSize);
out.writeInt(this.mParts);
}
private static PoiItem readFromParcel(final Parcel in){
final String name = in.readString();
final String description = in.readString();
final String date = in.readString();
final String size = in.readString();
final int parts = in.readInt();
return new PoiItem(name, description, date, size, parts);
}
}
private class POIListItemView extends LinearLayout{
private final TextView mTVName;
private final TextView mTVDescription;
private final TextView mTVDate;
private final TextView mTVSize;
public POIListItemView(final Context context, final PoiItem aPOIItem) {
super(context);
this.setOrientation(VERTICAL);
this.mTVName = new TextView(context);
this.mTVName.setText(aPOIItem.mName);
this.mTVName.setTextSize(TypedValue.COMPLEX_UNIT_PX, 24);
this.mTVName.setPadding(10,0,20,0);
if (aPOIItem.exists()) {
this.mTVName.setTextColor(Color.GREEN);
}
addView(this.mTVName, new LinearLayout.LayoutParams(android.view.ViewGroup.LayoutParams.FILL_PARENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT));
this.mTVDescription = new TextView(context);
this.mTVDescription.setText(aPOIItem.mDescription);
this.mTVDescription.setTextSize(TypedValue.COMPLEX_UNIT_PX, 12);
addView(this.mTVDescription, new LinearLayout.LayoutParams(android.view.ViewGroup.LayoutParams.FILL_PARENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT));
this.mTVDate = new TextView(context);
this.mTVDate.setText(aPOIItem.mDate);
this.mTVDate.setTextSize(TypedValue.COMPLEX_UNIT_PX, 12);
addView(this.mTVDate, new LinearLayout.LayoutParams(android.view.ViewGroup.LayoutParams.FILL_PARENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT));
this.mTVSize = new TextView(context);
this.mTVSize.setText(aPOIItem.mSize + "M");
this.mTVSize.setTextSize(TypedValue.COMPLEX_UNIT_PX, 12);
addView(this.mTVSize, new LinearLayout.LayoutParams(android.view.ViewGroup.LayoutParams.FILL_PARENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT));
}
}
class PoiListAdapter extends BaseAdapter implements FastScrollView.SectionIndexer{
/** Remember our context so we can use it when constructing views. */
private final Context mContext;
private List<PoiItem> mItems = new ArrayList<PoiItem>();
private String[] mAlphabet;
public PoiListAdapter(final Context context) {
this.mContext = context;
initAlphabet(context);
}
public void addItem(final PoiItem it) {
this.mItems.add(it);
Collections.sort(this.mItems);
}
public void setListItems(final List<PoiItem> lit) {
if (lit == null) return;
this.mItems = lit;
Collections.sort(this.mItems);
}
/** @return The number of items in the */
public int getCount() { return this.mItems.size(); }
public Object getItem(final int position) { return this.mItems.get(position); }
@Override
public long getItemId(final int position) { return position; }
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
POIListItemView btv;
if (convertView == null) {
btv = new POIListItemView(this.mContext, this.mItems.get(position));
} else { // Reuse/Overwrite the View passed
// We are assuming(!) that it is castable!
btv = (POIListItemView) convertView;
btv.mTVName.setText( this.mItems.get(position).mName);
btv.mTVDescription.setText(this.mItems.get(position).mDescription);
btv.mTVDate.setText(this.mItems.get(position).mDate);
btv.mTVSize.setText(this.mItems.get(position).mSize + "M");
}
return btv;
}
// ===========================================================
// FastScrollView-Methods
// ===========================================================
@Override
public int getPositionForSection(final int section) {
final String firstChar = this.mAlphabet[section];
/* Find the index, of the firstchar within the Poi-Items */
int position = Collections.binarySearch(this.mItems, new PoiItem(firstChar, null, null, null, 0));
if(position < 0){
/* Negative result means the insertion-point.
* See definition of Collections.binarySearch */
position = -(position + 1);
}
return position;
}
@Override
public int getSectionForPosition(final int position) {
return 0;
}
@Override
public Object[] getSections() {
return this.mAlphabet;
}
private void initAlphabet(final Context context) {
final String alphabetString = context.getResources().getString(R.string.alphabet); // TODO Use Systems Alphabet!
this.mAlphabet = new String[alphabetString.length()];
for (int i = 0; i < this.mAlphabet.length; i++) {
this.mAlphabet[i] = String.valueOf(alphabetString.charAt(i));
}
}
}
}