From 8057ccf85239acc0892352f9d2c15b323e6af42b Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Fri, 18 Jan 2013 16:10:32 -0800 Subject: [PATCH] Bug 796187 - Send tab: usability tweaks. r=nalexander --- .../base/resources/layout/sync_list_item.xml | 5 +- .../base/resources/layout/sync_send_tab.xml | 20 +++ .../activities/ClientRecordArrayAdapter.java | 90 +++++++++--- .../setup/activities/SendTabActivity.java | 137 +++++++++++++----- 4 files changed, 188 insertions(+), 64 deletions(-) diff --git a/mobile/android/base/resources/layout/sync_list_item.xml b/mobile/android/base/resources/layout/sync_list_item.xml index 97339ccb2648..d70b7cd004a3 100644 --- a/mobile/android/base/resources/layout/sync_list_item.xml +++ b/mobile/android/base/resources/layout/sync_list_item.xml @@ -4,10 +4,11 @@ - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + + + + + { +public class ClientRecordArrayAdapter extends ArrayAdapter { public static final String LOG_TAG = "ClientRecArrayAdapter"; - private ClientRecord[] clientRecordList; + private boolean[] checkedItems; private int numCheckedGUIDs; private SendTabActivity sendTabActivity; - public ClientRecordArrayAdapter(Context context, int textViewResourceId, - ClientRecord[] clientRecordList) { - super(context, textViewResourceId, clientRecordList); + public ClientRecordArrayAdapter(Context context, + int textViewResourceId) { + super(context, textViewResourceId, new ArrayList()); + this.checkedItems = new boolean[0]; this.sendTabActivity = (SendTabActivity) context; - this.clientRecordList = clientRecordList; - this.checkedItems = new boolean[clientRecordList.length]; + } + + public synchronized void setClientRecordList(final Collection clientRecordList) { + this.clear(); + this.checkedItems = new boolean[clientRecordList.size()]; + this.numCheckedGUIDs = 0; + for (ClientRecord clientRecord : clientRecordList) { + this.add(clientRecord); + } + this.notifyDataSetChanged(); + } + + /** + * If we have only a single client record in the list, mark it as checked. + */ + public synchronized void checkItem(final int position, boolean checked) throws ArrayIndexOutOfBoundsException { + if (position < 0 || + position >= checkedItems.length) { + throw new ArrayIndexOutOfBoundsException(position); + } + + if (setRowChecked(position, true)) { + this.notifyDataSetChanged(); + } + } + + /** + * Set the specified row to the specified checked state. + * @param position an index. + * @param checked whether the checkbox should be checked. + * @return true if the state changed, false if the + * box was already in the requested state. + */ + protected synchronized boolean setRowChecked(int position, boolean checked) { + boolean current = checkedItems[position]; + if (current == checked) { + return false; + } + + checkedItems[position] = checked; + numCheckedGUIDs += checked ? 1 : -1; + if (numCheckedGUIDs <= 0) { + sendTabActivity.enableSend(numCheckedGUIDs > 0); + } + return true; } @Override @@ -42,9 +91,10 @@ public class ClientRecordArrayAdapter extends ArrayAdapter { row.setBackgroundResource(android.R.drawable.menuitem_background); } - final ClientRecord clientRecord = clientRecordList[position]; + final ClientRecord clientRecord = this.getItem(position); ImageView clientType = (ImageView) row.findViewById(R.id.img); TextView clientName = (TextView) row.findViewById(R.id.client_name); + // Set up checkbox and restore stored state. CheckBox checkbox = (CheckBox) row.findViewById(R.id.check); checkbox.setChecked(checkedItems[position]); @@ -56,37 +106,29 @@ public class ClientRecordArrayAdapter extends ArrayAdapter { row.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { - CheckBox item = (CheckBox) view.findViewById(R.id.check); + final CheckBox item = (CheckBox) view.findViewById(R.id.check); - // Update the checked item, both in the UI and in the checkedItems array. - boolean newCheckedValue = !item.isChecked(); - item.setChecked(newCheckedValue); - checkedItems[position] = newCheckedValue; - - numCheckedGUIDs += newCheckedValue ? 1 : -1; - if (numCheckedGUIDs <= 0) { - sendTabActivity.enableSend(false); - return; - } - sendTabActivity.enableSend(true); + // Update the checked item, both in the UI and in our internal state. + final boolean checked = !item.isChecked(); // Because it hasn't happened yet. + item.setChecked(checked); + setRowChecked(position, checked); } - }); return row; } - public List getCheckedGUIDs() { + public synchronized List getCheckedGUIDs() { final List guids = new ArrayList(); for (int i = 0; i < checkedItems.length; i++) { if (checkedItems[i]) { - guids.add(clientRecordList[i].guid); + guids.add(this.getItem(i).guid); } } return guids; } - public int getNumCheckedGUIDs() { + public synchronized int getNumCheckedGUIDs() { return numCheckedGUIDs; } diff --git a/mobile/android/base/sync/setup/activities/SendTabActivity.java b/mobile/android/base/sync/setup/activities/SendTabActivity.java index 5fb1ccfac93c..6f290031d1cf 100644 --- a/mobile/android/base/sync/setup/activities/SendTabActivity.java +++ b/mobile/android/base/sync/setup/activities/SendTabActivity.java @@ -4,15 +4,19 @@ package org.mozilla.gecko.sync.setup.activities; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import org.mozilla.gecko.R; import org.mozilla.gecko.sync.CommandProcessor; import org.mozilla.gecko.sync.CommandRunner; -import org.mozilla.gecko.sync.SyncConstants; import org.mozilla.gecko.sync.GlobalSession; import org.mozilla.gecko.sync.Logger; import org.mozilla.gecko.sync.SyncConfiguration; +import org.mozilla.gecko.sync.SyncConstants; import org.mozilla.gecko.sync.repositories.NullCursorException; import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor; import org.mozilla.gecko.sync.repositories.domain.ClientRecord; @@ -31,6 +35,7 @@ import android.os.AsyncTask; import android.os.Bundle; import android.view.View; import android.widget.ListView; +import android.widget.TextView; import android.widget.Toast; public class SendTabActivity extends Activity { @@ -38,10 +43,80 @@ public class SendTabActivity extends Activity { private ClientRecordArrayAdapter arrayAdapter; private AccountManager accountManager; private Account localAccount; + private SendTabData sendTabData; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + if (intent == null) { + Logger.warn(LOG_TAG, "intent was null; aborting without sending tab."); + notifyAndFinish(false); + return; + } + + Bundle extras = intent.getExtras(); + if (extras == null) { + Logger.warn(LOG_TAG, "extras was null; aborting without sending tab."); + notifyAndFinish(false); + return; + } + + sendTabData = SendTabData.fromBundle(extras); + if (sendTabData == null) { + Logger.warn(LOG_TAG, "send tab data was null; aborting without sending tab."); + notifyAndFinish(false); + return; + } + + if (sendTabData.uri == null) { + Logger.warn(LOG_TAG, "uri was null; aborting without sending tab."); + notifyAndFinish(false); + return; + } + + if (sendTabData.title == null) { + Logger.warn(LOG_TAG, "title was null; ignoring and sending tab anyway."); + } + } + + /** + * Ensure that the view's list of clients is backed by a recently populated + * array adapter. But only once, so we don't end up blowing away your selections + * just because you got a text message. + */ + protected synchronized void ensureClientList(final Context context, + final ListView listview) { + if (arrayAdapter != null) { + Logger.debug(LOG_TAG, "Already have an array adapter for client lists."); + listview.setAdapter(arrayAdapter); + return; + } + + arrayAdapter = new ClientRecordArrayAdapter(context, R.layout.sync_list_item); + listview.setAdapter(arrayAdapter); + + // Fetching the client list hits the clients database, so we spin this onto + // a background task. + new AsyncTask>() { + + @Override + protected Collection doInBackground(Void... params) { + return getOtherClients(); + } + + @Override + protected void onPostExecute(final Collection clientArray) { + // We're allowed to update the UI from here. + + Logger.debug(LOG_TAG, "Got " + clientArray.size() + " clients."); + arrayAdapter.setClientRecordList(clientArray); + if (clientArray.size() == 1) { + arrayAdapter.checkItem(0, true); + } + } + }.execute(); } @Override @@ -60,23 +135,13 @@ public class SendTabActivity extends Activity { listview.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); enableSend(false); - // Fetching the client list hits the clients database, so we spin this onto - // a background task. - final Context context = this; - new AsyncTask() { + ensureClientList(this, listview); - @Override - protected ClientRecord[] doInBackground(Void... params) { - return getClientArray(); - } + TextView textView = (TextView) findViewById(R.id.title); + textView.setText(sendTabData.title); - @Override - protected void onPostExecute(final ClientRecord[] clientArray) { - // We're allowed to update the UI from here. - arrayAdapter = new ClientRecordArrayAdapter(context, R.layout.sync_list_item, clientArray); - listview.setAdapter(arrayAdapter); - } - }.execute(); + textView = (TextView) findViewById(R.id.uri); + textView.setText(sendTabData.uri); } private static void registerDisplayURICommand() { @@ -126,25 +191,6 @@ public class SendTabActivity extends Activity { public void sendClickHandler(View view) { Logger.info(LOG_TAG, "Send was clicked."); - Bundle extras = this.getIntent().getExtras(); - if (extras == null) { - Logger.warn(LOG_TAG, "extras was null; aborting without sending tab."); - notifyAndFinish(false); - return; - } - - final SendTabData sendTabData = SendTabData.fromBundle(extras); - - if (sendTabData.title == null) { - Logger.warn(LOG_TAG, "title was null; ignoring and sending tab anyway."); - } - - if (sendTabData.uri == null) { - Logger.warn(LOG_TAG, "uri was null; aborting without sending tab."); - notifyAndFinish(false); - return; - } - final List remoteClientGuids = arrayAdapter.getCheckedGUIDs(); if (remoteClientGuids == null) { @@ -214,11 +260,10 @@ public class SendTabActivity extends Activity { sendButton.setClickable(shouldEnable); } - protected ClientRecord[] getClientArray() { + protected Map getClients() { ClientsDatabaseAccessor db = new ClientsDatabaseAccessor(this.getApplicationContext()); - try { - return db.fetchAllClients().values().toArray(new ClientRecord[0]); + return db.fetchAllClients(); } catch (NullCursorException e) { Logger.warn(LOG_TAG, "NullCursorException while populating device list.", e); return null; @@ -226,4 +271,20 @@ public class SendTabActivity extends Activity { db.close(); } } + + /** + * @return a collection of client records, excluding our own. + */ + protected Collection getOtherClients() { + final Map all = getClients(); + final ArrayList out = new ArrayList(all.size()); + final String ourGUID = getAccountGUID(); + for (Entry entry : all.entrySet()) { + if (ourGUID.equals(entry.getKey())) { + continue; + } + out.add(entry.getValue()); + } + return out; + } }