/* * 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.R; import uk.ac.cam.db538.cryptosms.SimCard; import uk.ac.cam.db538.cryptosms.crypto.EncryptionInterface.EncryptionException; import uk.ac.cam.db538.cryptosms.data.Contact; import uk.ac.cam.db538.cryptosms.data.TextMessage; import uk.ac.cam.db538.cryptosms.data.Message.MessageException; import uk.ac.cam.db538.cryptosms.data.Message.MessageSendingListener; 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.MessageData; import uk.ac.cam.db538.cryptosms.storage.SessionKeys; import uk.ac.cam.db538.cryptosms.storage.SessionKeys.SessionKeysStatus; import uk.ac.cam.db538.cryptosms.storage.Storage; import uk.ac.cam.db538.cryptosms.storage.Storage.StorageChangeListener; import uk.ac.cam.db538.cryptosms.storage.StorageFileException; import uk.ac.cam.db538.cryptosms.ui.DummyOnClickListener; import uk.ac.cam.db538.cryptosms.ui.UtilsContactBadge; 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.AdapterMessages; import uk.ac.cam.db538.cryptosms.ui.list.ListViewMessage; import uk.ac.cam.db538.cryptosms.utils.CompressedText; import android.app.AlertDialog; import android.app.Dialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.DialogInterface.OnClickListener; import android.content.res.Resources; import android.os.AsyncTask; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.view.WindowManager; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; /* * Activity showing conversation thread and allowing messages to be sent */ public class ActivityConversation extends ActivityAppState { public static final String OPTION_PHONE_NUMBER = "PHONE_NUMBER"; public static final String OPTION_OFFER_KEYS_SETUP = "KEYS_SETUP"; private static final String DIALOG_NO_SESSION_KEYS = "DIALOG_NO_SESSION_KEYS"; private static final String DIALOG_SESSION_KEY_EXPIRED = "DIALOG_SESSION_KEY_EXPIRED"; private Contact mContact; private Conversation mConversation; @InjectView(R.id.send) private Button mSendButton; @InjectView(R.id.text_editor) private EditText mTextEditor; @InjectView(R.id.bytes_counter) private TextView mBytesCounterView; @InjectView(R.id.history) private ListViewMessage mListMessageHistory; private AdapterMessages mAdapterMessageHistory; private Context mContext = this; private boolean mErrorNoKeysShow; /* (non-Javadoc) * @see roboguice.activity.RoboActivity#onCreate(android.os.Bundle) */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.screen_conversation); getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); final Resources res = getResources(); Bundle bundle = getIntent().getExtras(); String phoneNumber = bundle.getString(OPTION_PHONE_NUMBER); mErrorNoKeysShow = bundle.getBoolean(OPTION_OFFER_KEYS_SETUP, true); // check that we got arguments if (phoneNumber == null) this.finish(); mContact = Contact.getContact(this, phoneNumber); try { mConversation = Conversation.getConversation(mContact.getPhoneNumber()); } catch (StorageFileException ex) { State.fatalException(ex); return; } UtilsContactBadge.setBadge(mContact, getMainView()); mTextEditor.addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void afterTextChanged(Editable s) { String text = s.toString(); CompressedText msg = CompressedText.fromString(text); mBytesCounterView.setText(TextMessage.getRemainingBytes(msg.getDataLength()) + " (" + TextMessage.getMessagePartCount(msg.getDataLength()) + ")"); mSendButton.setEnabled(true); mBytesCounterView.setVisibility(View.VISIBLE); } }); mSendButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mConversation != null) { try { // start the progress bar // TODO: get rid of this!!! final ProgressDialog pd = new ProgressDialog(ActivityConversation.this); pd.setMessage(res.getString(R.string.sending)); pd.show(); // create message final TextMessage msg = new TextMessage(MessageData.createMessageData(mConversation)); msg.setText(CompressedText.fromString(mTextEditor.getText().toString())); // send it msg.sendSMS(mContact.getPhoneNumber(), ActivityConversation.this, new MessageSendingListener() { @Override public void onMessageSent() { pd.cancel(); mTextEditor.setText(""); mBytesCounterView.setVisibility(View.GONE); updateMessageHistory(); onSimState(); } @Override public void onError(Exception ex) { pd.cancel(); // TODO: get rid of this!!! new AlertDialog.Builder(ActivityConversation.this) .setTitle(res.getString(R.string.error_sms_service)) .setMessage(res.getString(R.string.error_sms_service_details) + "\nError: " + ex.getMessage()) .setNeutralButton(res.getString(R.string.ok), new DummyOnClickListener()) .show(); updateMessageHistory(); onSimState(); } @Override public void onPartSent(int index) { } }); } catch (StorageFileException ex) { State.fatalException(ex); return; } catch (MessageException ex) { State.fatalException(ex); return; } catch (EncryptionException ex) { State.fatalException(ex); return; } } } }); // set appearance of list view mListMessageHistory.setFastScrollEnabled(true); // create the adapter mAdapterMessageHistory = new AdapterMessages(getLayoutInflater(), mListMessageHistory); // prepare for context menus registerForContextMenu(mListMessageHistory); // prepare dialogs getDialogManager().addBuilder(new DialogBuilder() { @Override public Dialog onBuild(Bundle params) { return new AlertDialog.Builder(mContext) .setTitle(res.getString(R.string.conversation_no_keys)) .setMessage(res.getString(R.string.conversation_no_keys_details)) .setPositiveButton(res.getString(R.string.read_only), new DummyOnClickListener()) .setNegativeButton(res.getString(R.string.setup), new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Intent intent = new Intent(ActivityConversation.this, ActivityExchangeMethod.class); intent.putExtra(ActivityExchangeMethod.OPTION_PHONE_NUMBER, mConversation.getPhoneNumber()); startActivity(intent); } }) .create(); } @Override public String getId() { return DIALOG_NO_SESSION_KEYS; } }); getDialogManager().addBuilder(new DialogBuilder() { @Override public Dialog onBuild(Bundle params) { return new AlertDialog.Builder(mContext) .setTitle(res.getString(R.string.conversation_key_expired)) .setMessage(res.getString(R.string.conversation_key_expired_details)) .setPositiveButton(res.getString(R.string.read_only), new DummyOnClickListener()) .setNegativeButton(res.getString(R.string.setup), new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Intent intent = new Intent(ActivityConversation.this, ActivityExchangeMethod.class); intent.putExtra(ActivityExchangeMethod.OPTION_PHONE_NUMBER, mConversation.getPhoneNumber()); startActivity(intent); } }) .create(); } @Override public String getId() { return DIALOG_SESSION_KEY_EXPIRED; } }); } private void modeEnabled(boolean value) { Resources res = getResources(); mSendButton.setEnabled(value); mTextEditor.setEnabled(value); mTextEditor.setHint((value) ? res.getString(R.string.conversation_type_to_compose) : null); mTextEditor.setFocusable(value); mTextEditor.setFocusableInTouchMode(value); } @Override protected void onStart() { super.onStart(); modeEnabled(false); Pki.login(false); } /* (non-Javadoc) * @see uk.ac.cam.db538.cryptosms.ui.activity.ActivityAppState#onPkiLogin() */ @Override public void onPkiLogin() { super.onPkiLogin(); updateMessageHistory(); mListMessageHistory.setAdapter(mAdapterMessageHistory); Storage.addListener(mStorageChangeListener); } /* (non-Javadoc) * @see uk.ac.cam.db538.cryptosms.ui.activity.ActivityAppState#onPkiLogout() */ @Override public void onPkiLogout() { Storage.removeListener(mStorageChangeListener); mListMessageHistory.setAdapter(null); modeEnabled(false); super.onPkiLogout(); } /* (non-Javadoc) * @see uk.ac.cam.db538.cryptosms.ui.activity.ActivityAppState#onSimState() */ @Override public void onSimState() { super.onSimState(); modeEnabled(false); UtilsSimIssues.handleSimIssues(mContext, getDialogManager()); // check for SIM availability try { if (SimCard.getSingleton().isNumberAvailable()) { // check keys availability SessionKeys keys = mConversation.getSessionKeys(SimCard.getSingleton().getNumber()); if (keys != null && keys.getStatus() == SessionKeysStatus.KEYS_EXCHANGED) modeEnabled(true); else if (keys != null && keys.getStatus() == SessionKeysStatus.KEYS_EXPIRED) { if (mErrorNoKeysShow) { // outgoing session key has expired mErrorNoKeysShow = false; getDialogManager().showDialog(DIALOG_SESSION_KEY_EXPIRED, null); } } else { if (mErrorNoKeysShow) { // secure connection has not been successfully established yet mErrorNoKeysShow = false; getDialogManager().showDialog(DIALOG_NO_SESSION_KEYS, null); } } } } catch (StorageFileException ex) { State.fatalException(ex); return; } } /* (non-Javadoc) * @see uk.ac.cam.db538.cryptosms.ui.activity.ActivityAppState#onEventParsingFinished() */ @Override public void onEventParsingFinished() { super.onEventParsingFinished(); onSimState(); } private void updateMessageHistory() { synchronized(mAdapterMessageHistory) { new MessageHistoryUpdateTask().execute(); } } private class MessageHistoryUpdateTask extends AsyncTask<Void, Void, Void> { private ArrayList<TextMessage> mTextMessages = null; private Exception mException = null; @Override protected void onPreExecute() { super.onPreExecute(); } @Override protected Void doInBackground(Void... arg0) { try { ArrayList<MessageData> allMessageData = mConversation.getMessages(); Collections.sort(allMessageData, new Comparator<MessageData>() { @Override public int compare(MessageData arg0, MessageData arg1) { return arg0.getTimeStamp().compareTo(arg1.getTimeStamp()); } }); mTextMessages = new ArrayList<TextMessage>(allMessageData.size()); for (MessageData storage : allMessageData) mTextMessages.add(new TextMessage(storage)); } catch (StorageFileException ex) { mException = ex; } return null; } @Override protected void onPostExecute(Void result) { super.onPostExecute(result); if (mException != null) { State.fatalException(mException); return; } mAdapterMessageHistory.setList(mTextMessages); mAdapterMessageHistory.notifyDataSetChanged(); } } private StorageChangeListener mStorageChangeListener = new StorageChangeListener() { @Override public void onUpdate() { updateMessageHistory(); } }; }