/*
* Copyright 2011 David Brazdil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package uk.ac.cam.db538.cryptosms.ui.activity;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import roboguice.inject.InjectView;
import uk.ac.cam.db538.cryptosms.MyApplication;
import uk.ac.cam.db538.cryptosms.R;
import uk.ac.cam.db538.cryptosms.SimCard;
import uk.ac.cam.db538.cryptosms.crypto.EncryptionInterface.EncryptionException;
import uk.ac.cam.db538.cryptosms.crypto.EncryptionInterface.WrongKeyDecryptionException;
import uk.ac.cam.db538.cryptosms.data.Contact;
import uk.ac.cam.db538.cryptosms.data.DbPendingAdapter;
import uk.ac.cam.db538.cryptosms.data.KeysMessage;
import uk.ac.cam.db538.cryptosms.data.Message.MessageException;
import uk.ac.cam.db538.cryptosms.data.Message.MessageSendingListener;
import uk.ac.cam.db538.cryptosms.data.PendingParser;
import uk.ac.cam.db538.cryptosms.data.PendingParser.ParseResult;
import uk.ac.cam.db538.cryptosms.state.Pki;
import uk.ac.cam.db538.cryptosms.state.State;
import uk.ac.cam.db538.cryptosms.storage.Conversation;
import uk.ac.cam.db538.cryptosms.storage.Header;
import uk.ac.cam.db538.cryptosms.storage.Storage;
import uk.ac.cam.db538.cryptosms.storage.StorageFileException;
import uk.ac.cam.db538.cryptosms.storage.StorageUtils;
import uk.ac.cam.db538.cryptosms.storage.Storage.StorageChangeListener;
import uk.ac.cam.db538.cryptosms.ui.DummyOnClickListener;
import uk.ac.cam.db538.cryptosms.ui.UtilsSendMessage;
import uk.ac.cam.db538.cryptosms.ui.UtilsSimIssues;
import uk.ac.cam.db538.cryptosms.ui.DialogManager.DialogBuilder;
import uk.ac.cam.db538.cryptosms.ui.adapter.AdapterContacts;
import uk.ac.cam.db538.cryptosms.ui.adapter.AdapterConversations;
import uk.ac.cam.db538.cryptosms.ui.adapter.AdapterNotifications;
import uk.ac.cam.db538.cryptosms.ui.list.ListItemContact;
import uk.ac.cam.db538.cryptosms.ui.list.ListItemConversation;
import uk.ac.cam.db538.cryptosms.ui.list.ListItemNotification;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.res.Resources;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.MenuItem.OnMenuItemClickListener;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TabHost;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.TabHost.TabContentFactory;
import android.widget.TabHost.TabSpec;
/*
* Main activity, showing tabs with conversation threads, contacts and notifications
*/
public class ActivityLists extends ActivityAppState {
private static final int ACTIVITY_NEW_CONTACT = 1;
private static final int ACTIVITY_CHOOSE_KEY = 2;
private static final String TAB_CONVERSATIONS = "CONVERSATIONS";
private static final String TAB_CONTACTS = "CONTACTS";
private static final String TAB_NOTIFICATIONS = "NOTIFICATIONS";
private static final int MENU_MOVE_SESSIONS = Menu.FIRST;
private static final int MENU_PROCESS_PENDING = MENU_MOVE_SESSIONS + 1;
private static final String DIALOG_PHONE_NUMBER_PICKER = "DIALOG_PHONE_NUMBER_PICKER";
private static final String PARAMS_PHONE_NUMBER_PICKER_ID = "PARAMS_PHONE_NUMBER_PICKER_ID";
private static final String PARAMS_PHONE_NUMBER_PICKER_KEY_NAME = "PARAMS_PHONE_NUMBER_PICKER_KEY_NAME";
private static final String DIALOG_NO_PHONE_NUMBERS = "DIALOG_NO_PHONE_NUMBERS";
private static final String DIALOG_CONFIRM_INVALIDATION = "DIALOG_CONFIRM_INVALIDATION";
private static final String PARAMS_CONFIRM_INVALIDATION_PHONE_NUMBER = "PARAMS_CONFIRM_INVALIDATION_PHONE_NUMBER";
private static final String DIALOG_ACCEPT_KEYS_AND_CONFIRM = "DIALOG_ACCEPT_KEYS_AND_CONFIRM";
private static final String DIALOG_CLEAR_PENDING = "DIALOG_CLEAR_PENDING";
private static final String DIALOG_CLEAR_ALL_PENDING = "DIALOG_CLEAR_ALL_PENDING";
private LayoutInflater mInflater;
@InjectView(R.id.tab_host)
private TabHost mTabHost;
private TabSpec mSpecConversations;
private ListView mListConversations;
private View mListConversationsLoading;
private AdapterConversations mAdapterConversations;
private TabSpec mSpecContacts;
private ListView mListContacts;
private View mListContactsLoading;
private ListItemContact mNewContactView;
private AdapterContacts mAdapterContacts;
private TabSpec mSpecNotifications;
private ListView mListNotifications;
private View mListNotificationsLoading;
private ListItemNotification mClearPendingView;
private AdapterNotifications mAdapterNotifications;
private ParseResult mNotificationsContextMenuItem;
// TODO: listen to contact name changes
/* (non-Javadoc)
* @see roboguice.activity.RoboActivity#onCreate(android.os.Bundle)
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.screen_lists);
mInflater = getLayoutInflater();
Resources res = getResources();
mTabHost.setup();
// TAB OF RECENT CONVERSATIONS
mSpecConversations = mTabHost.newTabSpec(TAB_CONVERSATIONS)
.setIndicator(res.getString(R.string.tab_conversations), res.getDrawable(R.drawable.tab_conversations))
.setContent(new TabContentFactory() {
@Override
public View createTabContent(String tag) {
View layout = mInflater.inflate(R.layout.view_listtab, mTabHost.getTabContentView(), false);
mListConversations = (ListView) layout.findViewById(android.R.id.list);
mListConversationsLoading = layout.findViewById(R.id.loadingState);
// set appearance of list view
mListConversations.setFastScrollEnabled(true);
// create the adapter
mAdapterConversations = new AdapterConversations(mInflater, mListConversations);
//specify what to do when clicked on items
mListConversations.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int arg2, long arg3) {
ListItemConversation item = (ListItemConversation) view;
Conversation conv;
if ((conv = item.getConversationHeader()) != null) {
// clicked on a conversation
startConversation(conv);
}
}
});
// prepare for context menus
ActivityLists.this.registerForContextMenu(mListConversations);
return layout;
}
});
mTabHost.addTab(mSpecConversations);
// force it to inflate the UI
mTabHost.setCurrentTabByTag(TAB_CONVERSATIONS);
// TAB OF CONTACTS
mSpecContacts = mTabHost.newTabSpec(TAB_CONTACTS)
.setIndicator(res.getString(R.string.tab_contacts), res.getDrawable(R.drawable.tab_contacts))
.setContent(new TabContentFactory() {
@Override
public View createTabContent(String tag) {
View layout = mInflater.inflate(R.layout.view_listtab, mTabHost.getTabContentView(), false);
mListContacts = (ListView) layout.findViewById(android.R.id.list);
mListContactsLoading = layout.findViewById(R.id.loadingState);
// set appearance of list view
mListContacts.setFastScrollEnabled(true);
// the New contact header
mNewContactView = (ListItemContact) mInflater.inflate(R.layout.item_main_contact, mListContacts, false);
mListContacts.addHeaderView(mNewContactView, null, true);
// specify what to do when clicked on items
mListContacts.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
if (!SimCard.getSingleton().isNumberAvailable())
return;
if (id < 0) {
// clicked on the header
Context context = ActivityLists.this;
Resources res = context.getResources();
// pick a contact from PKI
Intent intent = new Intent(MyApplication.PKI_CONTACT_PICKER);
intent.putExtra("pick", true);
intent.putExtra("Contact Criteria", "in_visible_group=1");
intent.putExtra("Key Criteria", "contact_id IN (SELECT contact_id FROM keys GROUP BY contact_id HAVING COUNT(key_name)>0)");
intent.putExtra("sort", "display_name COLLATE LOCALIZED ASC");
intent.putExtra("empty", res.getString(R.string.pki_contact_picker_empty) );
try {
startActivityForResult(intent, ACTIVITY_NEW_CONTACT);
} catch(ActivityNotFoundException e) {
State.notifyPkiMissing();
}
} else {
// clicked on a conversation
ListItemContact item = (ListItemContact) view;
Conversation conv = item.getConversationHeader();
try {
if (StorageUtils.hasKeysExchangedForSim(conv))
startConversation(conv);
} catch (StorageFileException ex) {
State.fatalException(ex);
return;
}
}
}
});
// create the adapter
mAdapterContacts = new AdapterContacts(mInflater, mListContacts);
// prepare for context menus
ActivityLists.this.registerForContextMenu(mListContacts);
return layout;
}
});
mTabHost.addTab(mSpecContacts);
// force it to inflate the UI
mTabHost.setCurrentTabByTag(TAB_CONTACTS);
// TAB OF EVENTS
mSpecNotifications = mTabHost.newTabSpec(TAB_NOTIFICATIONS)
.setIndicator(res.getString(R.string.tab_notifications), res.getDrawable(R.drawable.tab_events))
.setContent(new TabContentFactory() {
@Override
public View createTabContent(String tag) {
View layout = mInflater.inflate(R.layout.view_listtab, mTabHost.getTabContentView(), false);
mListNotifications = (ListView) layout.findViewById(android.R.id.list);
mListNotificationsLoading = layout.findViewById(R.id.loadingState);
// set appearance of list view
mListNotifications.setFastScrollEnabled(true);
// the Clear pending header
mClearPendingView = (ListItemNotification) mInflater.inflate(R.layout.item_main_notification, mListNotifications, false);
Log.d(MyApplication.APP_TAG, "Adding header");
mListNotifications.addHeaderView(mClearPendingView, null, true);
// create the adapter
mAdapterNotifications = new AdapterNotifications(mInflater, mListNotifications);
// specify what to do when clicked on items
mListNotifications.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
if (id < 0) {
// clicked on the Clear Pending header
getDialogManager().showDialog(DIALOG_CLEAR_ALL_PENDING, null);
} else {
// clicked on a notification
ListItemNotification notification = (ListItemNotification) view;
notification.performLongClick();
}
}
});
// prepare for context menus
ActivityLists.this.registerForContextMenu(mListNotifications);
return layout;
}
});
mTabHost.addTab(mSpecNotifications);
// force it to inflate the UI
mTabHost.setCurrentTabByTag(TAB_NOTIFICATIONS);
// select the Recent tab
mTabHost.setCurrentTabByTag(TAB_CONVERSATIONS);
// PREPARE DIALOGS
getDialogManager().addBuilder(new DialogBuilder() {
@Override
public Dialog onBuild(Bundle params) {
Resources res = ActivityLists.this.getResources();
final long contactId = params.getLong(PARAMS_PHONE_NUMBER_PICKER_ID);
// get phone numbers associated with the contact
final ArrayList<Contact.PhoneNumber> phoneNumbers =
Contact.getPhoneNumbers(ActivityLists.this, contactId);
final CharSequence[] items = new CharSequence[phoneNumbers.size()];
for (int i = 0; i < phoneNumbers.size(); ++i)
items[i] = phoneNumbers.get(i).toString();
// display them in a dialog
return new AlertDialog.Builder(ActivityLists.this)
.setTitle(res.getString(R.string.contacts_pick_phone_number))
.setItems(items, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int item) {
Contact.PhoneNumber phoneNumber = phoneNumbers.get(item);
startKeyExchange(phoneNumber.getPhoneNumber());
}
})
.create();
}
@Override
public String getId() {
return DIALOG_PHONE_NUMBER_PICKER;
}
});
getDialogManager().addBuilder(new DialogBuilder() {
@Override
public Dialog onBuild(Bundle params) {
Resources res = ActivityLists.this.getResources();
return new AlertDialog.Builder(ActivityLists.this)
.setTitle(res.getString(R.string.contacts_no_phone_numbers))
.setMessage(res.getString(R.string.contacts_no_phone_numbers_details))
.setNeutralButton(res.getString(R.string.ok), new DummyOnClickListener())
.create();
}
@Override
public String getId() {
return DIALOG_NO_PHONE_NUMBERS;
}
});
getDialogManager().addBuilder(new DialogBuilder() {
@Override
public Dialog onBuild(Bundle params) {
final String phoneNumber = params.getString(PARAMS_CONFIRM_INVALIDATION_PHONE_NUMBER);
return new AlertDialog.Builder(ActivityLists.this)
.setTitle(R.string.invalidate_encryption)
.setMessage(R.string.invalidate_confirm)
.setPositiveButton(R.string.yes, new OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
try {
Conversation conv = Conversation.getConversation(phoneNumber);
conv.deleteSessionKeys(SimCard.getSingleton().getNumber());
} catch (StorageFileException e) {
State.fatalException(e);
return;
}
}
})
.setNegativeButton(R.string.no, new DummyOnClickListener())
.create();
}
@Override
public String getId() {
return DIALOG_CONFIRM_INVALIDATION;
}
});
getDialogManager().addBuilder(new DialogBuilder() {
@Override
public Dialog onBuild(Bundle params) {
return new AlertDialog.Builder(ActivityLists.this)
.setCancelable(false)
.setTitle(R.string.accept_and_confirm)
.setMessage(R.string.accept_and_confirm_details)
.setPositiveButton(R.string.ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// check the input
if (mNotificationsContextMenuItem == null ||
mNotificationsContextMenuItem.getResult() != PendingParser.PendingParseResult.OK_HANDSHAKE_MESSAGE ||
! (mNotificationsContextMenuItem.getMessage() instanceof KeysMessage))
return;
final KeysMessage keysMsg = (KeysMessage) mNotificationsContextMenuItem.getMessage();
final String phoneNumber = mNotificationsContextMenuItem.getSender();
ActivityLists.this.getDialogManager().dismissDialog(getId());
ActivityLists.this.getDialogManager().showDialog(UtilsSendMessage.DIALOG_SENDING, null);
// send confirmation
// Log.d(MyApplication.APP_TAG, "Encrypting with " + LowLevel.toHex(keysMsg.getKeyOut()));
// Log.d(MyApplication.APP_TAG, "NOT encrypting with " + LowLevel.toHex(keysMsg.getKeyIn()));
try {
keysMsg.sendSMS(phoneNumber, ActivityLists.this, new MessageSendingListener() {
@Override
public void onMessageSent() {
// remove id group from database
removeParseResult(mNotificationsContextMenuItem);
ActivityLists.this.getDialogManager().dismissDialog(UtilsSendMessage.DIALOG_SENDING);
}
@Override
public void onPartSent(int index) {
}
@Override
public void onError(Exception ex) {
ActivityLists.this.getDialogManager().dismissDialog(UtilsSendMessage.DIALOG_SENDING);
Bundle params = new Bundle();
params.putString(UtilsSendMessage.PARAM_SENDING_ERROR, ex.getMessage());
getDialogManager().showDialog(UtilsSendMessage.DIALOG_SENDING_ERROR, params);
}
});
} catch (StorageFileException e) {
State.fatalException(e);
return;
} catch (MessageException e) {
State.fatalException(e);
return;
} catch (EncryptionException e) {
State.fatalException(e);
return;
}
}
})
.setNegativeButton(R.string.cancel, new DummyOnClickListener())
.create();
}
@Override
public String getId() {
return DIALOG_ACCEPT_KEYS_AND_CONFIRM;
}
});
getDialogManager().addBuilder(new DialogBuilder() {
@Override
public Dialog onBuild(Bundle params) {
Resources res = ActivityLists.this.getResources();
return new AlertDialog.Builder(ActivityLists.this)
.setTitle(res.getString(R.string.notifications_clear_pending))
.setMessage(res.getString(R.string.notifications_clear_pending_details))
.setNegativeButton(res.getString(R.string.no), new DummyOnClickListener())
.setPositiveButton(res.getString(R.string.yes), new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// check the input
if (mNotificationsContextMenuItem == null)
return;
removeParseResult(mNotificationsContextMenuItem);
}
})
.create();
}
@Override
public String getId() {
return DIALOG_CLEAR_PENDING;
}
});
getDialogManager().addBuilder(new DialogBuilder() {
@Override
public Dialog onBuild(Bundle params) {
Resources res = ActivityLists.this.getResources();
return new AlertDialog.Builder(ActivityLists.this)
.setTitle(res.getString(R.string.notifications_clear_all_pending))
.setMessage(res.getString(R.string.notifications_clear_all_pending_details))
.setNegativeButton(res.getString(R.string.no), new DummyOnClickListener())
.setPositiveButton(res.getString(R.string.yes), new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
DbPendingAdapter database = new DbPendingAdapter(ActivityLists.this);
database.open();
try {
database.clear();
} finally {
database.close();
}
mAdapterNotifications.setList(new ArrayList<PendingParser.ParseResult>());
State.notifyNewEvent();
}
})
.create();
}
@Override
public String getId() {
return DIALOG_CLEAR_ALL_PENDING;
}
});
UtilsSendMessage.prepareDialogs(getDialogManager(), this);
UtilsSimIssues.prepareDialogs(getDialogManager(), this);
}
private String mTempPhoneNumber;
/* (non-Javadoc)
* @see roboguice.activity.RoboActivity#onActivityResult(int, int, android.content.Intent)
*/
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (!Pki.isLoggedIn())
return;
switch (requestCode) {
case ACTIVITY_NEW_CONTACT:
if (resultCode == Activity.RESULT_OK) {
long contactId = data.getLongExtra("contact", 0);
String contactKey = data.getStringExtra("key name");
if (contactId > 0 && contactKey != null) {
// get phone numbers associated with the contact
final ArrayList<Contact.PhoneNumber> phoneNumbers = Contact.getPhoneNumbers(this, contactId);
if (phoneNumbers.size() > 1) {
Bundle params = new Bundle();
params.putLong(PARAMS_PHONE_NUMBER_PICKER_ID, contactId);
params.putString(PARAMS_PHONE_NUMBER_PICKER_KEY_NAME, contactKey);
getDialogManager().showDialog(DIALOG_PHONE_NUMBER_PICKER, params);
} else if (phoneNumbers.size() == 1) {
startKeyExchange(phoneNumbers.get(0).getPhoneNumber());
} else {
// no phone numbers assigned to the contact
getDialogManager().showDialog(DIALOG_NO_PHONE_NUMBERS, null);
}
}
}
break;
case ACTIVITY_CHOOSE_KEY:
if (resultCode == Activity.RESULT_OK) {
startKeyExchange(mTempPhoneNumber);
}
break;
}
}
/* (non-Javadoc)
* @see android.app.Activity#onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo)
*/
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
Log.d(MyApplication.APP_TAG, "Item: " + info.id);
if (v == mListContacts && info.id >= 0) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.lists_contacts_context, menu);
} else if (v == mListConversations) {
} else if (v == mListNotifications && info.id >= 0) {
MenuInflater inflater = getMenuInflater();
mNotificationsContextMenuItem = (ParseResult) mAdapterNotifications.getItem((int)info.id);
switch (mNotificationsContextMenuItem.getResult()) {
case OK_HANDSHAKE_MESSAGE:
inflater.inflate(R.menu.lists_notifications_context_keys, menu);
break;
default:
inflater.inflate(R.menu.lists_notifications_context, menu);
break;
}
}
}
/* (non-Javadoc)
* @see android.app.Activity#onContextItemSelected(android.view.MenuItem)
*/
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
if (info.targetView instanceof ListItemContact && info.id != -1) {
Conversation conv = ((ListItemContact) info.targetView).getConversationHeader();
switch (item.getItemId()) {
case R.id.resend_keys:
Contact contact = Contact.getContact(this, conv.getPhoneNumber());
mTempPhoneNumber = contact.getPhoneNumber();
// pick a key from PKI
Intent intent = new Intent(MyApplication.PKI_KEY_PICKER);
intent.putExtra("contact", contact.getId());
intent.putExtra("empty", this.getResources().getString(R.string.pki_key_picker_empty) );
try {
startActivityForResult(intent, ACTIVITY_CHOOSE_KEY);
} catch(ActivityNotFoundException e) {
State.notifyPkiMissing();
}
return true;
case R.id.invalidate:
Bundle params = new Bundle();
params.putString(PARAMS_CONFIRM_INVALIDATION_PHONE_NUMBER, conv.getPhoneNumber());
getDialogManager().showDialog(DIALOG_CONFIRM_INVALIDATION, params);
return true;
}
} else if (info.targetView instanceof ListItemConversation) {
switch (item.getItemId()) {
}
} else if (info.targetView instanceof ListItemNotification) {
switch (item.getItemId()) {
case R.id.accept:
getDialogManager().showDialog(DIALOG_ACCEPT_KEYS_AND_CONFIRM, null);
break;
case R.id.discard:
getDialogManager().showDialog(DIALOG_CLEAR_PENDING, null);
break;
}
}
return super.onContextItemSelected(item);
}
/* (non-Javadoc)
* @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
Resources res = this.getResources();
int idGroup = 0;
MenuItem menuMoveSessions = menu.add(idGroup, MENU_MOVE_SESSIONS, Menu.NONE, res.getString(R.string.menu_move_sessions));
menuMoveSessions.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
UtilsSimIssues.moveSessionKeys(getDialogManager());
return true;
}
});
MenuItem menuProcessPending = menu.add(idGroup, MENU_PROCESS_PENDING, Menu.NONE, res.getString(R.string.menu_process_pending));
menuProcessPending.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
PendingParser.getSingleton().parseEvents();
return true;
}
});
return true;
}
/* (non-Javadoc)
* @see uk.ac.cam.db538.cryptosms.ui.activity.ActivityAppState#onSimState()
*/
@Override
public void onSimState() {
super.onSimState();
UtilsSimIssues.handleSimIssues(this, getDialogManager());
// check SIM availability
if (SimCard.getSingleton().isNumberAvailable()) {
mListNotifications.setAdapter(mAdapterNotifications);
mListContacts.setAdapter(mAdapterContacts);
mNewContactView.bind(getString(R.string.tab_contacts_new_contact), getString(R.string.tab_contacts_new_contact_details));
mClearPendingView.bind(getString(R.string.clear_pending), getString(R.string.clear_pending_details));
updateConversations();
updateEvents();
} else {
mListNotifications.setAdapter(null);
mListContacts.setAdapter(null);
mNewContactView.bind(getString(R.string.tab_contacts_not_available), getString(R.string.tab_contacts_not_available_details));
mClearPendingView.bind(getString(R.string.tab_contacts_not_available), getString(R.string.tab_contacts_not_available_details));
}
}
/* (non-Javadoc)
* @see uk.ac.cam.db538.cryptosms.ui.activity.ActivityAppState#onPkiLogin()
*/
@Override
public void onPkiLogin() {
super.onPkiLogin();
Storage.addListener(mConversationChangeListener);
mListConversations.setAdapter(mAdapterConversations);
mListContacts.setAdapter(mAdapterContacts);
mListNotifications.setAdapter(mAdapterNotifications);
}
/* (non-Javadoc)
* @see uk.ac.cam.db538.cryptosms.ui.activity.ActivityAppState#onPkiLogout()
*/
@Override
public void onPkiLogout() {
Storage.removeListener(mConversationChangeListener);
mListConversations.setAdapter(null);
mListContacts.setAdapter(null);
mListNotifications.setAdapter(null);
super.onPkiLogout();
}
private boolean mFirstEventParsing = true;
/* (non-Javadoc)
* @see uk.ac.cam.db538.cryptosms.ui.activity.ActivityAppState#onEventParsingStarted()
*/
@Override
public void onEventParsingStarted() {
super.onEventParsingStarted();
if (mFirstEventParsing && (mAdapterNotifications.getList() == null || mAdapterNotifications.getList().size() == 0)) {
mListNotificationsLoading.setVisibility(View.VISIBLE);
mListNotifications.setVisibility(View.GONE);
}
mFirstEventParsing = false;
Log.d(MyApplication.APP_TAG, "Parsing started (apparently)");
}
/* (non-Javadoc)
* @see uk.ac.cam.db538.cryptosms.ui.activity.ActivityAppState#onEventParsingFinished()
*/
@Override
public void onEventParsingFinished() {
super.onEventParsingFinished();
updateEvents();
mListNotificationsLoading.setVisibility(View.GONE);
mListNotifications.setVisibility(View.VISIBLE);
}
private void startConversation(Conversation conv) {
Intent intent = new Intent(ActivityLists.this, ActivityConversation.class);
intent.putExtra(ActivityConversation.OPTION_PHONE_NUMBER, conv.getPhoneNumber());
startActivity(intent);
}
private void startKeyExchange(String phoneNumber) {
Intent intent = new Intent(ActivityLists.this, ActivityExchangeMethod.class);
intent.putExtra(ActivityExchangeMethod.OPTION_PHONE_NUMBER, phoneNumber);
startActivity(intent);
}
private void removeParseResult(ParseResult parseResult) {
DbPendingAdapter database = new DbPendingAdapter(ActivityLists.this);
database.open();
try {
parseResult.removeFromDb(database);
} finally {
database.close();
}
PendingParser.getSingleton().getParseResults().remove(mNotificationsContextMenuItem);
updateEvents();
PendingParser.forceParsing();
}
private StorageChangeListener mConversationChangeListener = new StorageChangeListener() {
@Override
public void onUpdate() {
updateConversations();
}
};
private void updateConversations() {
synchronized(mAdapterConversations) {
new ConversationsUpdateTask().execute();
}
}
private class ConversationsUpdateTask extends AsyncTask<Void, Void, Void> {
private Exception mException = null;
@Override
protected void onPreExecute() {
super.onPreExecute();
Log.d(MyApplication.APP_TAG, "Conversation update");
if (mAdapterConversations.getList() == null || mAdapterConversations.getList().size() == 0) {
mListConversationsLoading.setVisibility(View.VISIBLE);
mListConversations.setVisibility(View.GONE);
}
if (mAdapterContacts.getList() == null || mAdapterContacts.getList().size() == 0) {
mListContactsLoading.setVisibility(View.VISIBLE);
mListContacts.setVisibility(View.GONE);
}
}
@Override
protected Void doInBackground(Void... arg0) {
// update lists
ArrayList<Conversation> listConversations = new ArrayList<Conversation>();
ArrayList<Conversation> listContacts = new ArrayList<Conversation>();
try {
Conversation conv = Header.getHeader().getFirstConversation();
while (conv != null) {
if (conv.hasMessageData())
listConversations.add(conv);
if (StorageUtils.hasKeysForSim(conv))
listContacts.add(conv);
conv = conv.getNextConversation();
}
} catch (StorageFileException ex) {
if (ex.getCause() instanceof EncryptionException) {
// don't really care
// probably Pki not ready
} else {
mException = ex;
return null;
}
} catch (WrongKeyDecryptionException ex) {
mException = ex;
return null;
}
Collections.sort(listConversations, Collections.reverseOrder());
Collections.sort(listContacts, new Comparator<Conversation>() {
@Override
public int compare(Conversation conv1, Conversation conv2) {
Contact contact1 = Contact.getContact(ActivityLists.this, conv1.getPhoneNumber());
Contact contact2 = Contact.getContact(ActivityLists.this, conv2.getPhoneNumber());
return contact1.compareTo(contact2);
}
});
mAdapterConversations.setList(listConversations);
mAdapterContacts.setList(listContacts);
return null;
}
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
if (mException == null) {
mAdapterConversations.notifyDataSetChanged();
mAdapterContacts.notifyDataSetChanged();
mListConversationsLoading.setVisibility(View.GONE);
mListConversations.setVisibility(View.VISIBLE);
mListContactsLoading.setVisibility(View.GONE);
mListContacts.setVisibility(View.VISIBLE);
} else {
State.fatalException(mException);
return;
}
}
}
/**
* Update list of notifications.
*/
public void updateEvents() {
mAdapterNotifications.setList(PendingParser.getSingleton().getParseResults());
mAdapterNotifications.notifyDataSetChanged();
}
}