// Created by plusminus on 14:51:34 - 02.11.2008 package org.androad.ui.sd; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.androad.R; import org.androad.exc.Exceptor; 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 android.app.ProgressDialog; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.media.MediaPlayer; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.provider.BaseColumns; import android.provider.Contacts; import android.provider.Contacts.ContactMethodsColumns; import android.provider.Contacts.People; import android.provider.Contacts.PeopleColumns; 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.Toast; import android.widget.AdapterView.OnItemClickListener; public class SDContacts extends AndNavBaseActivity{ // =========================================================== // Constants // =========================================================== private static final int REQUESTCODE_DDMAP = 0x1337; private static final String STATE_CONTACTSWITHADDRESSES_ITEMS_ID = "state_contactswithaddresses_items_id"; // =========================================================== // Fields // =========================================================== private Bundle bundleCreatedWith; private ListView mContactsList; private ArrayList<ContactItem> mContactsWithAddress = new ArrayList<ContactItem>(); private boolean mContactsWithAddressInitFinished = 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_contacts); this.bundleCreatedWith = this.getIntent().getExtras(); this.mContactsList = (ListView) this.findViewById(R.id.list_contacts); final TextView empty = new TextView(this); empty.setText(R.string.list_empty); this.mContactsList.setEmptyView(empty); initListView(); this.applyTopMenuButtonListeners(); if(savedInstanceState == null) { updateContactListItems(); } } @Override protected void onPause() { super.onPause(); if (pd != null && pd.isShowing()) pd.dismiss(); } private void updateContactListItems() { android.util.Log.d("Fabien", "SDContacts.updateContactListItems 1"); final ContactListAdapter cla = new ContactListAdapter(this); pd = ProgressDialog.show(this, getString(R.string.sd_contacts_loading_title), getString(R.string.please_wait_a_moment), false); // TODO Make determinate, when SDK supports this. final String progressBaseString = getString(R.string.sd_contacts_loading_progress); new UserTask<Void, Integer, Void>(){ @Override public Void doInBackground(final Void... params) { try{ android.util.Log.d("Fabien", "UserTask.doInBackground 2"); final Map<Integer, List<ContactItemToResolve>> unresolvedPersonsAddresses = new HashMap<Integer, List<ContactItemToResolve>>(); /* Get a cursor on all Contacts.KIND_POSTAL entries. */ final Cursor addrCursor = managedQuery( android.provider.Contacts.ContactMethods.CONTENT_URI, null, ContactMethodsColumns.KIND + "=" + Contacts.KIND_POSTAL, null, null); if(addrCursor == null){ runOnUiThread(new Runnable(){ @Override public void run() { Toast.makeText(SDContacts.this, "Problem loading contacts. Please retry.", Toast.LENGTH_LONG).show(); // TODO i18n } }); }else{ final int colData = addrCursor.getColumnIndexOrThrow(ContactMethodsColumns.DATA); final int colKind = addrCursor.getColumnIndexOrThrow(ContactMethodsColumns.KIND); final int colType = addrCursor.getColumnIndexOrThrow(android.provider.Contacts.ContactMethodsColumns.TYPE); final int colPersonID = addrCursor.getColumnIndexOrThrow(android.provider.Contacts.ContactMethods.PERSON_ID); /* Loop through all results and remember the personID and the textual address. */ while(addrCursor.moveToNext()){ final int kind = addrCursor.getInt(colKind); if(kind == Contacts.KIND_POSTAL){ final String typeAppendix; switch(addrCursor.getInt(colType)){ case android.provider.Contacts.ContactMethodsColumns.TYPE_HOME: typeAppendix = " (" + getString(R.string.sd_contacts_address_type_home) + ")"; break; case android.provider.Contacts.ContactMethodsColumns.TYPE_WORK: typeAppendix = " (" + getString(R.string.sd_contacts_address_type_work) + ")"; break; default: typeAppendix = ""; break; } String address = addrCursor.getString(colData); if(address != null){ address = address.replace('\n', ' '); final int personID = addrCursor.getInt(colPersonID); final ContactItemToResolve newContactItem = new ContactItemToResolve(address, personID, typeAppendix); /* Maybe the personId existed, * so we simply add it to the existing list, * instead of adding a new list. */ final List<ContactItemToResolve> existing = unresolvedPersonsAddresses.get(personID); if(existing == null){ final List<ContactItemToResolve> newList = new ArrayList<ContactItemToResolve>(); newList.add(newContactItem); unresolvedPersonsAddresses.put(personID, newList); }else{ existing.add(newContactItem); } } } } final int contactsToResolveCount = unresolvedPersonsAddresses.size(); final StringBuilder selectionBuilder = new StringBuilder(); /* Build the 'OR'-ed query. */ final String peopleTable = People.CONTENT_URI.getLastPathSegment(); for(final Iterator<Integer> iterator = unresolvedPersonsAddresses.keySet().iterator(); iterator.hasNext(); /* none */){ final int personID = iterator.next(); selectionBuilder .append(peopleTable) .append('.') .append(BaseColumns._ID) .append("=") .append(personID); if(iterator.hasNext()) { selectionBuilder.append(" OR "); } } /* Open a Cursor for each person-ID that has an address to look up the contact-name behind that ID. */ final Cursor cur = managedQuery(People.CONTENT_URI, new String[]{PeopleColumns.NAME, BaseColumns._ID}, selectionBuilder.toString(), null, null ); int i = 0; if(cur != null && cur.moveToFirst()){ /* Resolve columnIndices first. */ final int colName = cur.getColumnIndexOrThrow(PeopleColumns.NAME); final int colID = cur.getColumnIndexOrThrow(BaseColumns._ID); do{ publishProgress(i++, contactsToResolveCount); final int id = cur.getInt(colID); final List<ContactItemToResolve> cts = unresolvedPersonsAddresses.get(id); if(cts != null){ for(final ContactItemToResolve ct : cts){ final String name = cur.getString(colName) + ct.mAddressTypeAppendix; SDContacts.this.mContactsWithAddress.add(new ContactItem(name, ct.mAddressDescription)); } } }while(cur.moveToNext()); } } /* Adapt the list to the Adapter. */ cla.setListItems(SDContacts.this.mContactsWithAddress);/* Orders by name, ascending. */ SDContacts.this.mContactsWithAddressInitFinished = true; }catch(final Exception e){ Exceptor.e("Contacts-Exception", e, SDContacts.this); } 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. */ SDContacts.this.mContactsList.setAdapter(cla); try{ pd.dismiss(); pd = null; }catch(final IllegalArgumentException ia){ // Nothing } } }.execute(); } protected void initListView() { this.mContactsList.setOnItemClickListener(new OnItemClickListener(){ @Override public void onItemClick(final AdapterView<?> parent, final View v, final int position, final long id) { final ContactItem c = (ContactItem)parent.getAdapter().getItem(position); final String addr = c.mAddressDescription; final Intent resolverIntent = new Intent(SDContacts.this, SDResolver.class); SDContacts.this.bundleCreatedWith.putInt(EXTRAS_MODE, EXTRAS_MODE_FREEFORMSEARCH); SDContacts.this.bundleCreatedWith.putString(EXTRAS_FREEFORM_ID, addr); resolverIntent.putExtras(SDContacts.this.bundleCreatedWith); SDContacts.this.startActivityForResult(resolverIntent, REQUESTCODE_DDMAP); } }); } // =========================================================== // 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.mContactsWithAddress, new ContactItem(String.valueOf(c), "")); if(position < 0){ /* Negative result means the insertion-point. * See definition of Collections.binarySearch */ position = -(position + 1); } this.mContactsList.setSelectionFromTop(position, 0); return true; } @Override public void onSaveInstanceState(final Bundle out) { if(this.mContactsWithAddressInitFinished) { out.putParcelableArrayList(STATE_CONTACTSWITHADDRESSES_ITEMS_ID, this.mContactsWithAddress); } } @Override protected void onRestoreInstanceState(final Bundle in) { final ArrayList<ContactItem> restoredItems = in.getParcelableArrayList(STATE_CONTACTSWITHADDRESSES_ITEMS_ID); if(this.mContactsWithAddress == null){ updateContactListItems(); }else{ this.mContactsWithAddress = restoredItems; final ContactListAdapter cla = new ContactListAdapter(this); cla.setListItems(this.mContactsWithAddress); this.mContactsList.setAdapter(cla); this.mContactsWithAddressInitFinished = 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_contacts_back)) { @Override public void onClicked(final View me) { if (SDContacts.super.mMenuVoiceEnabled) { MediaPlayer.create(SDContacts.this, R.raw.close).start(); } /* Back one level. */ SDContacts.this.setResult(SUBACTIVITY_RESULTCODE_UP_ONE_LEVEL); SDContacts.this.finish(); } }; /* Set Listener for Close-Button. */ new OnClickOnFocusChangedListenerAdapter(this.findViewById(R.id.ibtn_sd_contacts_close)) { @Override public void onClicked(final View me) { if (SDContacts.super.mMenuVoiceEnabled) { MediaPlayer.create(SDContacts.this, R.raw.close).start(); } /* * Set ResultCode that the calling activity knows that we want * to go back to the Base-Menu */ SDContacts.this.setResult(SUBACTIVITY_RESULTCODE_CHAINCLOSE_QUITTED); SDContacts.this.finish(); } }; } // =========================================================== // Inner and Anonymous Classes // =========================================================== private static class ContactItemToResolve{ protected final String mAddressDescription; protected final String mAddressTypeAppendix; private ContactItemToResolve(final String addressDescription, final int personID, final String addressTypeAppendix) { this.mAddressDescription = addressDescription; this.mAddressTypeAppendix = addressTypeAppendix; } } static class ContactItem implements Comparable<ContactItem>, Parcelable{ protected final String mName; protected final String mAddressDescription; private ContactItem(final String pName, final String pAddressDescrption) { this.mName = pName; this.mAddressDescription = pAddressDescrption; } @Override public int compareTo(final ContactItem another) { return this.mName.compareToIgnoreCase(another.mName); } // =========================================================== // Parcelable // =========================================================== public static final Parcelable.Creator<ContactItem> CREATOR = new Parcelable.Creator<ContactItem>() { public ContactItem createFromParcel(final Parcel in) { return readFromParcel(in); } public ContactItem[] newArray(final int size) { return new ContactItem[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(final Parcel out, final int flags) { out.writeString(this.mName); out.writeString(this.mAddressDescription); } private static ContactItem readFromParcel(final Parcel in){ final String name = in.readString(); final String addressDescription = in.readString(); return new ContactItem(name, addressDescription); } } private class POIListItemView extends LinearLayout{ private final TextView mTVAddress; private final TextView mTVName; public POIListItemView(final Context context, final ContactItem 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); addView(this.mTVName, new LinearLayout.LayoutParams(android.view.ViewGroup.LayoutParams.FILL_PARENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT)); this.mTVAddress = new TextView(context); this.mTVAddress.setText(aPOIItem.mAddressDescription); this.mTVAddress.setTextSize(TypedValue.COMPLEX_UNIT_PX, 12); addView(this.mTVAddress, new LinearLayout.LayoutParams(android.view.ViewGroup.LayoutParams.FILL_PARENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT)); } } class ContactListAdapter extends BaseAdapter implements FastScrollView.SectionIndexer{ /** Remember our context so we can use it when constructing views. */ private final Context mContext; private List<ContactItem> mItems = new ArrayList<ContactItem>(); private String[] mAlphabet; public ContactListAdapter(final Context context) { this.mContext = context; initAlphabet(context); } public void addItem(final ContactItem it) { this.mItems.add(it); Collections.sort(this.mItems); } public void setListItems(final List<ContactItem> 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.mTVAddress.setText(this.mItems.get(position).mAddressDescription); btv.mTVName.setText( this.mItems.get(position).mName); } 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 Contact-Items */ int position = Collections.binarySearch(this.mItems, new ContactItem(firstChar, null)); 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)); } } } }