Added custom adapter for AccountSelectionSpinner and async details loading
diff --git a/src/com/savoirfairelinux/sflphone/adapters/AccountSelectionAdapter.java b/src/com/savoirfairelinux/sflphone/adapters/AccountSelectionAdapter.java
new file mode 100644
index 0000000..7f4b099
--- /dev/null
+++ b/src/com/savoirfairelinux/sflphone/adapters/AccountSelectionAdapter.java
@@ -0,0 +1,232 @@
+package com.savoirfairelinux.sflphone.adapters;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Stack;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.RadioButton;
+import android.widget.TextView;
+
+import com.savoirfairelinux.sflphone.R;
+import com.savoirfairelinux.sflphone.account.AccountDetailBasic;
+import com.savoirfairelinux.sflphone.service.ISipService;
+
+public class AccountSelectionAdapter extends BaseAdapter {
+
+ private static final String TAG = AccountSelectionAdapter.class.getSimpleName();
+
+ ArrayList<String> accountIDs;
+ Context mContext;
+ AccountManager accManager;
+ ISipService service;
+ int selectedAccount = 0;
+
+ public AccountSelectionAdapter(Context cont, ISipService s, ArrayList<String> newList) {
+ super();
+ accountIDs = newList;
+ mContext = cont;
+ service = s;
+ accManager = new AccountManager(mContext);
+ }
+
+ @Override
+ public int getCount() {
+ return accountIDs.size();
+ }
+
+ @Override
+ public String getItem(int pos) {
+ return accountIDs.get(pos);
+ }
+
+ @Override
+ public long getItemId(int pos) {
+ return 0;
+ }
+
+ @Override
+ public View getView(int pos, View convertView, ViewGroup parent) {
+ View rowView = convertView;
+ AccountView entryView = null;
+
+ if (rowView == null) {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ rowView = inflater.inflate(R.layout.item_account, null);
+
+ entryView = new AccountView();
+ entryView.alias = (TextView) rowView.findViewById(R.id.account_alias);
+ entryView.host = (TextView) rowView.findViewById(R.id.account_host);
+ entryView.select = (RadioButton) rowView.findViewById(R.id.account_checked);
+ rowView.setTag(entryView);
+ } else {
+ entryView = (AccountView) rowView.getTag();
+ }
+
+ accManager.displayAccountDetails(accountIDs.get(pos), entryView);
+ if(pos == selectedAccount){
+ entryView.select.setChecked(true);
+ }
+
+ return rowView;
+ }
+
+ /*********************
+ * ViewHolder Pattern
+ *********************/
+ public class AccountView {
+ public TextView alias;
+ public TextView host;
+ public RadioButton select;
+ }
+
+ /**
+ * Asynchronous account details retriever
+ */
+ public class AccountManager {
+
+ // private static final String TAG = ImageManager.class.getSimpleName();
+
+ private HashMap<String, HashMap<String, String>> accountMap = new HashMap<String, HashMap<String, String>>();
+ private AccountQueue accountQueue = new AccountQueue();
+ private Thread accountLoaderThread = new Thread(new AccountQueueManager());
+
+
+ public AccountManager(Context context) {
+ accountLoaderThread.setPriority(Thread.NORM_PRIORITY - 1);
+
+ }
+
+ public void displayAccountDetails(String id, AccountView account) {
+
+ if (accountMap.containsKey(id)) {
+
+ HashMap<String, String> details = accountMap.get(id);
+ account.alias.setText(details.get(AccountDetailBasic.CONFIG_ACCOUNT_ALIAS));
+ account.host.setText(details.get(AccountDetailBasic.CONFIG_ACCOUNT_HOSTNAME));
+
+ } else {
+ queueAccount(id, account);
+ }
+ }
+
+ private void queueAccount(String id, AccountView row) {
+ // This ImageView might have been used for other images, so we clear
+ // the queue of old tasks before starting.
+ accountQueue.Clean(row);
+ AccountRef p = new AccountRef(id, row);
+
+ synchronized (accountQueue.accountRefsStack) {
+ accountQueue.accountRefsStack.push(p);
+ accountQueue.accountRefsStack.notifyAll();
+ }
+
+ // Start thread if it's not started yet
+ if (accountLoaderThread.getState() == Thread.State.NEW) {
+ accountLoaderThread.start();
+ }
+ }
+
+ /** Classes **/
+
+ private class AccountRef {
+ public String accountID;
+ public AccountView row;
+
+ public AccountRef(String u, AccountView i) {
+ accountID = u;
+ row = i;
+ }
+ }
+
+ private class AccountQueue {
+ private Stack<AccountRef> accountRefsStack = new Stack<AccountRef>();
+
+ // removes all instances of this account
+ public void Clean(AccountView view) {
+
+ for (int i = 0; i < accountRefsStack.size();) {
+ if (accountRefsStack.get(i).row == view)
+ accountRefsStack.remove(i);
+ else
+ ++i;
+ }
+ }
+ }
+
+ private class AccountQueueManager implements Runnable {
+ @Override
+ public void run() {
+ try {
+ while (true) {
+ // Thread waits until there are accountsID in the queue to be retrieved
+ if (accountQueue.accountRefsStack.size() == 0) {
+ synchronized (accountQueue.accountRefsStack) {
+ accountQueue.accountRefsStack.wait();
+ }
+ }
+
+ // When we have accounts to load
+ if (accountQueue.accountRefsStack.size() != 0) {
+ AccountRef accountToLoad;
+
+ synchronized (accountQueue.accountRefsStack) {
+ accountToLoad = accountQueue.accountRefsStack.pop();
+ }
+
+ HashMap<String, String> details = (HashMap<String, String>) service.getAccountDetails(accountToLoad.accountID);
+ accountMap.put(accountToLoad.accountID, details);
+ AccountDisplayer accDisplayer = new AccountDisplayer(details, accountToLoad.row);
+ Activity a = (Activity) mContext;
+
+ a.runOnUiThread(accDisplayer);
+
+ }
+
+ if (Thread.interrupted())
+ break;
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, e.toString());
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
+ }
+ }
+
+ // Used to display details in the UI thread
+ private class AccountDisplayer implements Runnable {
+ HashMap<String, String> displayDetails;
+ AccountView displayRow;
+
+ public AccountDisplayer(HashMap<String, String> details, AccountView row) {
+ displayRow = row;
+ displayDetails = details;
+ }
+
+ public void run() {
+ displayRow.alias.setText(displayDetails.get(AccountDetailBasic.CONFIG_ACCOUNT_ALIAS));
+ displayRow.host.setText(displayDetails.get(AccountDetailBasic.CONFIG_ACCOUNT_HOSTNAME));
+ }
+ }
+
+ public void removeFromCache(Uri uri) {
+ if (accountMap.containsKey(uri)) {
+ accountMap.remove(uri);
+ }
+ }
+ }
+
+ public void setSelectedAccount(int pos) {
+ selectedAccount = pos;
+ }
+
+}
diff --git a/src/com/savoirfairelinux/sflphone/adapters/CallElementAdapter.java b/src/com/savoirfairelinux/sflphone/adapters/CallElementAdapter.java
new file mode 100644
index 0000000..5196cf8
--- /dev/null
+++ b/src/com/savoirfairelinux/sflphone/adapters/CallElementAdapter.java
@@ -0,0 +1,87 @@
+package com.savoirfairelinux.sflphone.adapters;
+
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.savoirfairelinux.sflphone.R;
+import com.savoirfairelinux.sflphone.model.SipCall;
+
+/**
+ * A CursorAdapter that creates and update call elements using corresponding contact infos. TODO: handle contact list separatly to allow showing
+ * synchronized contacts on Call cards with multiple contacts etc.
+ */
+public class CallElementAdapter extends ArrayAdapter {
+ private ExecutorService infos_fetcher = Executors.newCachedThreadPool();
+ private Context mContext;
+ private final List mCallList;
+ private static final String CURRENT_STATE_LABEL = " CURRENT STATE: ";
+
+ public CallElementAdapter(Context context, List callList) {
+ super(context, R.layout.item_contact, callList);
+ mContext = context;
+ mCallList = callList;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View rowView = convertView;
+ CallElementView entryView = null;
+
+ if (rowView == null) {
+ // Get a new instance of the row layout view
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ rowView = inflater.inflate(R.layout.item_contact, null);
+
+ // Hold the view objects in an object
+ // so they don't need to be re-fetched
+ entryView = new CallElementView();
+ entryView.toggleButton = (ImageButton) rowView.findViewById(R.id.toggleButton1);
+ entryView.button = (Button) rowView.findViewById(R.id.button2);
+ entryView.photo = (ImageView) rowView.findViewById(R.id.photo);
+ entryView.displayName = (TextView) rowView.findViewById(R.id.display_name);
+ entryView.phones = (TextView) rowView.findViewById(R.id.phones);
+ entryView.state = (TextView) rowView.findViewById(R.id.callstate);
+
+ // Cache the view obects in the tag
+ // so they can be re-accessed later
+ rowView.setTag(entryView);
+ } else {
+ entryView = (CallElementView) rowView.getTag();
+ }
+
+ // Transfer the stock data from the data object
+ // to the view objects
+ SipCall call = (SipCall) mCallList.get(position);
+ call.setAssociatedRowView(rowView);
+ entryView.displayName.setText(call.getDisplayName());
+ entryView.phones.setText(call.getPhone());
+ entryView.state.setText(CURRENT_STATE_LABEL + call.getCallStateString());
+
+ return rowView;
+ }
+
+ /*********************
+ * ViewHolder Pattern
+ *********************/
+ public class CallElementView {
+ protected ImageButton toggleButton;
+ protected Button button;
+ protected ImageView photo;
+ protected TextView displayName;
+ protected TextView phones;
+ public TextView state;
+ }
+
+
+}