diff options
author | Daniel Gultsch <daniel.gultsch@rwth-aachen.de> | 2014-02-28 18:46:01 +0100 |
---|---|---|
committer | Daniel Gultsch <daniel.gultsch@rwth-aachen.de> | 2014-02-28 18:46:01 +0100 |
commit | acf80bddd07d5579cdb20a888b3f9e99e53030a5 (patch) | |
tree | 034a8e364488c89c8e1997f56313d919019afa65 /src/eu/siacs/conversations/ui | |
parent | 03d96266f8bbb76e25610e903d74a0afa7dcd03b (diff) |
rebranding
Diffstat (limited to 'src/eu/siacs/conversations/ui')
12 files changed, 2185 insertions, 0 deletions
diff --git a/src/eu/siacs/conversations/ui/ConversationActivity.java b/src/eu/siacs/conversations/ui/ConversationActivity.java new file mode 100644 index 00000000..7660b8fd --- /dev/null +++ b/src/eu/siacs/conversations/ui/ConversationActivity.java @@ -0,0 +1,481 @@ +package eu.siacs.conversations.ui; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.utils.UIHelper; +import android.net.Uri; +import android.os.Bundle; +import android.app.AlertDialog; +import android.app.FragmentTransaction; +import android.app.NotificationManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Typeface; +import android.support.v4.widget.SlidingPaneLayout; +import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener; +import android.util.Log; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.PopupMenu; +import android.widget.PopupMenu.OnMenuItemClickListener; +import android.widget.TextView; +import android.widget.ImageView; + +public class ConversationActivity extends XmppActivity { + + public static final String VIEW_CONVERSATION = "viewConversation"; + public static final String CONVERSATION = "conversationUuid"; + + public static final int REQUEST_SEND_MESSAGE = 0x75441; + public static final int REQUEST_DECRYPT_PGP = 0x76783; + + protected SlidingPaneLayout spl; + + private List<Conversation> conversationList = new ArrayList<Conversation>(); + private Conversation selectedConversation = null; + private ListView listView; + + private boolean paneShouldBeOpen = true; + private ArrayAdapter<Conversation> listAdapter; + + private OnConversationListChangedListener onConvChanged = new OnConversationListChangedListener() { + + @Override + public void onConversationListChanged() { + conversationList.clear(); + conversationList.addAll(xmppConnectionService + .getConversations()); + runOnUiThread(new Runnable() { + + @Override + public void run() { + updateConversationList(); + if(paneShouldBeOpen) { + if (conversationList.size() >= 1) { + swapConversationFragment(); + } else { + startActivity(new Intent(getApplicationContext(), NewConversationActivity.class)); + finish(); + } + } + ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager().findFragmentByTag("conversation"); + if (selectedFragment!=null) { + selectedFragment.updateMessages(); + } + } + }); + } + }; + + private DialogInterface.OnClickListener addToRoster = new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + String jid = getSelectedConversation().getContactJid(); + Account account = getSelectedConversation().getAccount(); + String name = jid.split("@")[0]; + Contact contact = new Contact(account, name, jid, null); + xmppConnectionService.createContact(contact); + } + }; + + public List<Conversation> getConversationList() { + return this.conversationList; + } + + public Conversation getSelectedConversation() { + return this.selectedConversation; + } + + public ListView getConversationListView() { + return this.listView; + } + + public SlidingPaneLayout getSlidingPaneLayout() { + return this.spl; + } + + public boolean shouldPaneBeOpen() { + return paneShouldBeOpen; + } + + public void updateConversationList() { + if (conversationList.size() >= 1) { + Collections.sort(this.conversationList, new Comparator<Conversation>() { + @Override + public int compare(Conversation lhs, Conversation rhs) { + return (int) (rhs.getLatestMessage().getTimeSent() - lhs.getLatestMessage().getTimeSent()); + } + }); + } + this.listView.invalidateViews(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + + super.onCreate(savedInstanceState); + + setContentView(R.layout.fragment_conversations_overview); + + listView = (ListView) findViewById(R.id.list); + + this.listAdapter = new ArrayAdapter<Conversation>(this, + R.layout.conversation_list_row, conversationList) { + @Override + public View getView(int position, View view, ViewGroup parent) { + if (view == null) { + LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + view = (View) inflater.inflate( + R.layout.conversation_list_row, null); + } + Conversation conv = getItem(position); + TextView convName = (TextView) view.findViewById(R.id.conversation_name); + convName.setText(conv.getName()); + TextView convLastMsg = (TextView) view.findViewById(R.id.conversation_lastmsg); + convLastMsg.setText(conv.getLatestMessage().getBody()); + + if(!conv.isRead()) { + convName.setTypeface(null,Typeface.BOLD); + convLastMsg.setTypeface(null,Typeface.BOLD); + } else { + convName.setTypeface(null,Typeface.NORMAL); + convLastMsg.setTypeface(null,Typeface.NORMAL); + } + + ((TextView) view.findViewById(R.id.conversation_lastupdate)) + .setText(UIHelper.readableTimeDifference(getItem(position).getLatestMessage().getTimeSent())); + + Uri profilePhoto = getItem(position).getProfilePhotoUri(); + ImageView imageView = (ImageView) view.findViewById(R.id.conversation_image); + if (profilePhoto!=null) { + imageView.setImageURI(profilePhoto); + } else { + imageView.setImageBitmap(UIHelper.getUnknownContactPicture(getItem(position).getName(),200)); + } + + + ((ImageView) view.findViewById(R.id.conversation_image)) + .setImageURI(getItem(position).getProfilePhotoUri()); + return view; + } + + }; + + listView.setAdapter(this.listAdapter); + + listView.setOnItemClickListener(new OnItemClickListener() { + + @Override + public void onItemClick(AdapterView<?> arg0, View clickedView, + int position, long arg3) { + paneShouldBeOpen = false; + if (selectedConversation != conversationList.get(position)) { + selectedConversation = conversationList.get(position); + swapConversationFragment(); //.onBackendConnected(conversationList.get(position)); + } else { + spl.closePane(); + } + } + }); + spl = (SlidingPaneLayout) findViewById(R.id.slidingpanelayout); + spl.setParallaxDistance(150); + spl.setShadowResource(R.drawable.es_slidingpane_shadow); + spl.setSliderFadeColor(0); + spl.setPanelSlideListener(new PanelSlideListener() { + + @Override + public void onPanelOpened(View arg0) { + paneShouldBeOpen = true; + getActionBar().setDisplayHomeAsUpEnabled(false); + getActionBar().setTitle(R.string.app_name); + invalidateOptionsMenu(); + + InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + + View focus = getCurrentFocus(); + + if (focus != null) { + + inputManager.hideSoftInputFromWindow( + focus.getWindowToken(), + InputMethodManager.HIDE_NOT_ALWAYS); + } + } + + @Override + public void onPanelClosed(View arg0) { + paneShouldBeOpen = false; + if (conversationList.size() > 0) { + getActionBar().setDisplayHomeAsUpEnabled(true); + getActionBar().setTitle(getSelectedConversation().getName()); + invalidateOptionsMenu(); + if (!getSelectedConversation().isRead()) { + getSelectedConversation().markRead(); + updateConversationList(); + } + } + } + + @Override + public void onPanelSlide(View arg0, float arg1) { + // TODO Auto-generated method stub + + } + }); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.conversations, menu); + MenuItem menuSecure = (MenuItem) menu.findItem(R.id.action_security); + MenuItem menuArchive = (MenuItem) menu.findItem(R.id.action_archive); + MenuItem menuMucDetails = (MenuItem) menu.findItem(R.id.action_muc_details); + MenuItem menuContactDetails = (MenuItem) menu.findItem(R.id.action_contact_details); + + if (spl.isOpen()) { + menuArchive.setVisible(false); + menuMucDetails.setVisible(false); + menuContactDetails.setVisible(false); + menuSecure.setVisible(false); + } else { + ((MenuItem) menu.findItem(R.id.action_add)).setVisible(false); + if (this.getSelectedConversation()!=null) { + if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) { + menuMucDetails.setVisible(true); + menuContactDetails.setVisible(false); + menuSecure.setVisible(false); + menuArchive.setTitle("Leave conference"); + } else { + menuContactDetails.setVisible(true); + menuMucDetails.setVisible(false); + if (this.getSelectedConversation().getLatestMessage().getEncryption() != Message.ENCRYPTION_NONE) { + menuSecure.setIcon(R.drawable.ic_action_secure); + } + } + } + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + spl.openPane(); + break; + case R.id.action_settings: + startActivity(new Intent(this, SettingsActivity.class)); + break; + case R.id.action_accounts: + startActivity(new Intent(this, ManageAccountActivity.class)); + break; + case R.id.action_add: + startActivity(new Intent(this, NewConversationActivity.class)); + break; + case R.id.action_archive: + Conversation conv = getSelectedConversation(); + conv.setStatus(Conversation.STATUS_ARCHIVED); + paneShouldBeOpen = true; + spl.openPane(); + xmppConnectionService.archiveConversation(conv); + selectedConversation = conversationList.get(0); + break; + case R.id.action_contact_details: + DialogContactDetails details = new DialogContactDetails(); + Contact contact = this.getSelectedConversation().getContact(); + if (contact != null) { + contact.setAccount(this.selectedConversation.getAccount()); + details.setContact(contact); + details.show(getFragmentManager(), "details"); + } else { + String jid = getSelectedConversation().getContactJid(); + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(jid); + builder.setMessage("The contact is not in your roster. Would you like to add it."); + builder.setNegativeButton("Cancel", null); + builder.setPositiveButton("Add",addToRoster); + builder.create().show(); + } + break; + case R.id.action_security: + final Conversation selConv = getSelectedConversation(); + View menuItemView = findViewById(R.id.action_security); + PopupMenu popup = new PopupMenu(this, menuItemView); + final ConversationFragment fragment = (ConversationFragment) getFragmentManager().findFragmentByTag("conversation"); + if (fragment!=null) { + popup.setOnMenuItemClickListener(new OnMenuItemClickListener() { + + @Override + public boolean onMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + case R.id.encryption_choice_none: + selConv.nextMessageEncryption = Message.ENCRYPTION_NONE; + item.setChecked(true); + break; + case R.id.encryption_choice_otr: + selConv.nextMessageEncryption = Message.ENCRYPTION_OTR; + item.setChecked(true); + break; + case R.id.encryption_choice_pgp: + selConv.nextMessageEncryption = Message.ENCRYPTION_PGP; + item.setChecked(true); + break; + default: + selConv.nextMessageEncryption = Message.ENCRYPTION_NONE; + break; + } + fragment.updateChatMsgHint(); + return true; + } + }); + popup.inflate(R.menu.encryption_choices); + switch (selConv.nextMessageEncryption) { + case Message.ENCRYPTION_NONE: + popup.getMenu().findItem(R.id.encryption_choice_none).setChecked(true); + break; + case Message.ENCRYPTION_OTR: + popup.getMenu().findItem(R.id.encryption_choice_otr).setChecked(true); + break; + case Message.ENCRYPTION_PGP: + popup.getMenu().findItem(R.id.encryption_choice_pgp).setChecked(true); + break; + case Message.ENCRYPTION_DECRYPTED: + popup.getMenu().findItem(R.id.encryption_choice_pgp).setChecked(true); + break; + default: + popup.getMenu().findItem(R.id.encryption_choice_none).setChecked(true); + break; + } + popup.show(); + } + + break; + default: + break; + } + return super.onOptionsItemSelected(item); + } + + protected ConversationFragment swapConversationFragment() { + ConversationFragment selectedFragment = new ConversationFragment(); + + FragmentTransaction transaction = getFragmentManager() + .beginTransaction(); + transaction.replace(R.id.selected_conversation, selectedFragment,"conversation"); + transaction.commit(); + return selectedFragment; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + if (!spl.isOpen()) { + spl.openPane(); + return false; + } + } + return super.onKeyDown(keyCode, event); + } + + public void onStart() { + super.onStart(); + NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + nm.cancelAll(); + if (conversationList.size()>=1) { + onConvChanged.onConversationListChanged(); + } + } + + @Override + protected void onStop() { + Log.d("gultsch","called on stop in conversation activity"); + if (xmppConnectionServiceBound) { + xmppConnectionService.removeOnConversationListChangedListener(); + } + super.onStop(); + } + + @Override + void onBackendConnected() { + + xmppConnectionService.setOnConversationListChangedListener(this.onConvChanged); + + if (conversationList.size()==0) { + conversationList.clear(); + conversationList.addAll(xmppConnectionService + .getConversations()); + + this.updateConversationList(); + } + + if ((getIntent().getAction().equals(Intent.ACTION_VIEW) && (!handledViewIntent))) { + if (getIntent().getType().equals( + ConversationActivity.VIEW_CONVERSATION)) { + handledViewIntent = true; + + String convToView = (String) getIntent().getExtras().get(CONVERSATION); + + for(int i = 0; i < conversationList.size(); ++i) { + if (conversationList.get(i).getUuid().equals(convToView)) { + selectedConversation = conversationList.get(i); + } + } + paneShouldBeOpen = false; + swapConversationFragment(); + } + } else { + if (xmppConnectionService.getAccounts().size() == 0) { + startActivity(new Intent(this, ManageAccountActivity.class)); + finish(); + } else if (conversationList.size() <= 0) { + //add no history + startActivity(new Intent(this, NewConversationActivity.class)); + finish(); + } else { + spl.openPane(); + //find currently loaded fragment + ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager().findFragmentByTag("conversation"); + if (selectedFragment!=null) { + Log.d("gultsch","ConversationActivity. found old fragment."); + selectedFragment.onBackendConnected(); + } else { + Log.d("gultsch","conversationactivity. no old fragment found. creating new one"); + selectedConversation = conversationList.get(0); + Log.d("gultsch","selected conversation is #"+selectedConversation); + swapConversationFragment(); + } + } + } + } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == RESULT_OK) { + if (requestCode == REQUEST_DECRYPT_PGP) { + ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager().findFragmentByTag("conversation"); + if (selectedFragment!=null) { + selectedFragment.hidePgpPassphraseBox(); + } + } + } + } +} diff --git a/src/eu/siacs/conversations/ui/ConversationFragment.java b/src/eu/siacs/conversations/ui/ConversationFragment.java new file mode 100644 index 00000000..ff06fafc --- /dev/null +++ b/src/eu/siacs/conversations/ui/ConversationFragment.java @@ -0,0 +1,602 @@ +package eu.siacs.conversations.ui; + +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import net.java.otr4j.session.SessionStatus; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.PgpEngine.OpenPgpException; +import eu.siacs.conversations.crypto.PgpEngine.UserInputRequiredException; +import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.utils.PhoneHelper; +import eu.siacs.conversations.utils.UIHelper; +import android.app.AlertDialog; +import android.app.Fragment; +import android.content.DialogInterface; +import android.content.IntentSender; +import android.content.SharedPreferences; +import android.content.IntentSender.SendIntentException; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Typeface; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; + +public class ConversationFragment extends Fragment { + + protected Conversation conversation; + protected ListView messagesView; + protected LayoutInflater inflater; + protected List<Message> messageList = new ArrayList<Message>(); + protected ArrayAdapter<Message> messageListAdapter; + protected Contact contact; + protected BitmapCache mBitmapCache = new BitmapCache(); + + protected String queuedPqpMessage = null; + + private EditText chatMsg; + + protected Bitmap selfBitmap; + + private IntentSender askForPassphraseIntent = null; + + private OnClickListener sendMsgListener = new OnClickListener() { + + @Override + public void onClick(View v) { + if (chatMsg.getText().length() < 1) + return; + Message message = new Message(conversation, chatMsg.getText() + .toString(), conversation.nextMessageEncryption); + if (conversation.nextMessageEncryption == Message.ENCRYPTION_OTR) { + sendOtrMessage(message); + } else if (conversation.nextMessageEncryption == Message.ENCRYPTION_PGP) { + sendPgpMessage(message); + } else { + sendPlainTextMessage(message); + } + } + }; + protected OnClickListener clickToDecryptListener = new OnClickListener() { + + @Override + public void onClick(View v) { + Log.d("gultsch","clicked to decrypt"); + if (askForPassphraseIntent!=null) { + try { + getActivity().startIntentSenderForResult(askForPassphraseIntent, ConversationActivity.REQUEST_DECRYPT_PGP, null, 0, 0, 0); + } catch (SendIntentException e) { + Log.d("gultsch","couldnt fire intent"); + } + } + } + }; + private LinearLayout pgpInfo; + + public void hidePgpPassphraseBox() { + pgpInfo.setVisibility(View.GONE); + } + + public void updateChatMsgHint() { + if (conversation.getMode() == Conversation.MODE_MULTI) { + chatMsg.setHint("Send message to conference"); + } else { + switch (conversation.nextMessageEncryption) { + case Message.ENCRYPTION_NONE: + chatMsg.setHint("Send plain text message"); + break; + case Message.ENCRYPTION_OTR: + chatMsg.setHint("Send OTR encrypted message"); + break; + case Message.ENCRYPTION_PGP: + chatMsg.setHint("Send openPGP encryted messeage"); + break; + case Message.ENCRYPTION_DECRYPTED: + chatMsg.setHint("Send openPGP encryted messeage"); + break; + default: + break; + } + } + } + + @Override + public View onCreateView(final LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + + this.inflater = inflater; + + final View view = inflater.inflate(R.layout.fragment_conversation, + container, false); + chatMsg = (EditText) view.findViewById(R.id.textinput); + ImageButton sendButton = (ImageButton) view + .findViewById(R.id.textSendButton); + sendButton.setOnClickListener(this.sendMsgListener); + + pgpInfo = (LinearLayout) view.findViewById(R.id.pgp_keyentry); + pgpInfo.setOnClickListener(clickToDecryptListener); + + messagesView = (ListView) view.findViewById(R.id.messages_view); + + messageListAdapter = new ArrayAdapter<Message>(this.getActivity() + .getApplicationContext(), R.layout.message_sent, + this.messageList) { + + private static final int SENT = 0; + private static final int RECIEVED = 1; + private static final int ERROR = 2; + + @Override + public int getViewTypeCount() { + return 3; + } + + @Override + public int getItemViewType(int position) { + if (getItem(position).getStatus() == Message.STATUS_RECIEVED) { + return RECIEVED; + } else if (getItem(position).getStatus() == Message.STATUS_ERROR) { + return ERROR; + } else { + return SENT; + } + } + + @Override + public View getView(int position, View view, ViewGroup parent) { + Message item = getItem(position); + int type = getItemViewType(position); + ViewHolder viewHolder; + if (view == null) { + viewHolder = new ViewHolder(); + switch (type) { + case SENT: + view = (View) inflater.inflate(R.layout.message_sent, + null); + viewHolder.imageView = (ImageView) view + .findViewById(R.id.message_photo); + viewHolder.imageView.setImageBitmap(selfBitmap); + break; + case RECIEVED: + view = (View) inflater.inflate( + R.layout.message_recieved, null); + viewHolder.imageView = (ImageView) view + .findViewById(R.id.message_photo); + if (item.getConversation().getMode() == Conversation.MODE_SINGLE) { + Uri uri = item.getConversation() + .getProfilePhotoUri(); + if (uri != null) { + viewHolder.imageView + .setImageBitmap(mBitmapCache.get(item + .getConversation().getName(), + uri)); + } else { + viewHolder.imageView + .setImageBitmap(mBitmapCache.get(item + .getConversation().getName(), + null)); + } + } + break; + case ERROR: + view = (View) inflater.inflate(R.layout.message_error, + null); + viewHolder.imageView = (ImageView) view + .findViewById(R.id.message_photo); + viewHolder.imageView.setImageBitmap(mBitmapCache + .getError()); + break; + default: + viewHolder = null; + break; + } + viewHolder.messageBody = (TextView) view + .findViewById(R.id.message_body); + viewHolder.time = (TextView) view + .findViewById(R.id.message_time); + view.setTag(viewHolder); + } else { + viewHolder = (ViewHolder) view.getTag(); + } + if (type == RECIEVED) { + if (item.getConversation().getMode() == Conversation.MODE_MULTI) { + if (item.getCounterpart() != null) { + viewHolder.imageView.setImageBitmap(mBitmapCache + .get(item.getCounterpart(), null)); + } else { + viewHolder.imageView + .setImageBitmap(mBitmapCache.get(item + .getConversation().getName(), null)); + } + } + } + String body = item.getBody(); + if (body != null) { + if (item.getEncryption() == Message.ENCRYPTION_PGP) { + viewHolder.messageBody.setText(getString(R.string.encrypted_message)); + viewHolder.messageBody.setTextColor(0xff33B5E5); + viewHolder.messageBody.setTypeface(null,Typeface.ITALIC); + } else { + viewHolder.messageBody.setText(body.trim()); + viewHolder.messageBody.setTextColor(0xff000000); + viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); + } + } + if (item.getStatus() == Message.STATUS_UNSEND) { + viewHolder.time.setTypeface(null, Typeface.ITALIC); + viewHolder.time.setText("sending\u2026"); + } else { + viewHolder.time.setTypeface(null, Typeface.NORMAL); + if ((item.getConversation().getMode() == Conversation.MODE_SINGLE) + || (type != RECIEVED)) { + viewHolder.time.setText(UIHelper + .readableTimeDifference(item.getTimeSent())); + } else { + viewHolder.time.setText(item.getCounterpart() + + " \u00B7 " + + UIHelper.readableTimeDifference(item + .getTimeSent())); + } + } + return view; + } + }; + messagesView.setAdapter(messageListAdapter); + + return view; + } + + protected Bitmap findSelfPicture() { + SharedPreferences sharedPref = PreferenceManager + .getDefaultSharedPreferences(getActivity() + .getApplicationContext()); + boolean showPhoneSelfContactPicture = sharedPref.getBoolean( + "show_phone_selfcontact_picture", true); + + Bitmap self = null; + + if (showPhoneSelfContactPicture) { + Uri selfiUri = PhoneHelper.getSefliUri(getActivity()); + if (selfiUri != null) { + try { + self = BitmapFactory.decodeStream(getActivity() + .getContentResolver().openInputStream(selfiUri)); + } catch (FileNotFoundException e) { + self = null; + } + } + } + if (self == null) { + self = UIHelper.getUnknownContactPicture(conversation.getAccount() + .getJid(), 200); + } + + final Bitmap selfBitmap = self; + return selfBitmap; + } + + @Override + public void onStart() { + super.onStart(); + ConversationActivity activity = (ConversationActivity) getActivity(); + + if (activity.xmppConnectionServiceBound) { + this.onBackendConnected(); + } + } + + public void onBackendConnected() { + final ConversationActivity activity = (ConversationActivity) getActivity(); + this.conversation = activity.getSelectedConversation(); + this.selfBitmap = findSelfPicture(); + updateMessages(); + // rendering complete. now go tell activity to close pane + if (!activity.shouldPaneBeOpen()) { + activity.getSlidingPaneLayout().closePane(); + activity.getActionBar().setDisplayHomeAsUpEnabled(true); + activity.getActionBar().setTitle(conversation.getName()); + activity.invalidateOptionsMenu(); + if (!conversation.isRead()) { + conversation.markRead(); + activity.updateConversationList(); + } + } + if (queuedPqpMessage != null) { + this.conversation.nextMessageEncryption = Message.ENCRYPTION_PGP; + Message message = new Message(conversation, queuedPqpMessage, + Message.ENCRYPTION_PGP); + sendPgpMessage(message); + } + } + + public void updateMessages() { + ConversationActivity activity = (ConversationActivity) getActivity(); + List<Message> encryptedMessages = new LinkedList<Message>(); + for(Message message : this.conversation.getMessages()) { + if (message.getEncryption() == Message.ENCRYPTION_PGP) { + encryptedMessages.add(message); + } + } + if (encryptedMessages.size() > 0) { + DecryptMessage task = new DecryptMessage(); + Message[] msgs = new Message[encryptedMessages.size()]; + task.execute(encryptedMessages.toArray(msgs)); + } + this.messageList.clear(); + this.messageList.addAll(this.conversation.getMessages()); + this.messageListAdapter.notifyDataSetChanged(); + if (messageList.size() >= 1) { + int latestEncryption = this.conversation.getLatestMessage() + .getEncryption(); + if (latestEncryption== Message.ENCRYPTION_DECRYPTED) { + conversation.nextMessageEncryption = Message.ENCRYPTION_PGP; + } else { + conversation.nextMessageEncryption = latestEncryption; + } + makeFingerprintWarning(latestEncryption); + } + getActivity().invalidateOptionsMenu(); + updateChatMsgHint(); + int size = this.messageList.size(); + if (size >= 1) + messagesView.setSelection(size - 1); + if (!activity.shouldPaneBeOpen()) { + conversation.markRead(); + activity.updateConversationList(); + } + } + + protected void makeFingerprintWarning(int latestEncryption) { + final LinearLayout fingerprintWarning = (LinearLayout) getView() + .findViewById(R.id.new_fingerprint); + if (conversation.getContact() != null) { + Set<String> knownFingerprints = conversation.getContact() + .getOtrFingerprints(); + if ((latestEncryption == Message.ENCRYPTION_OTR) + && (conversation.hasValidOtrSession() + && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints + .contains(conversation.getOtrFingerprint())))) { + fingerprintWarning.setVisibility(View.VISIBLE); + TextView fingerprint = (TextView) getView().findViewById( + R.id.otr_fingerprint); + fingerprint.setText(conversation.getOtrFingerprint()); + fingerprintWarning.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + AlertDialog dialog = UIHelper + .getVerifyFingerprintDialog( + (ConversationActivity) getActivity(), + conversation, fingerprintWarning); + dialog.show(); + } + }); + } else { + fingerprintWarning.setVisibility(View.GONE); + } + } else { + fingerprintWarning.setVisibility(View.GONE); + } + } + + protected void sendPlainTextMessage(Message message) { + ConversationActivity activity = (ConversationActivity) getActivity(); + activity.xmppConnectionService.sendMessage(message, null); + chatMsg.setText(""); + } + + protected void sendPgpMessage(final Message message) { + ConversationActivity activity = (ConversationActivity) getActivity(); + final XmppConnectionService xmppService = activity.xmppConnectionService; + Contact contact = message.getConversation().getContact(); + if (contact.getPgpKeyId() != 0) { + xmppService.sendMessage(message, null); + chatMsg.setText(""); + } else { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle("No openPGP key found"); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setMessage("There is no openPGP key assoziated with this contact"); + builder.setNegativeButton("Cancel", null); + builder.setPositiveButton("Send plain text", + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + conversation.nextMessageEncryption = Message.ENCRYPTION_NONE; + message.setEncryption(Message.ENCRYPTION_NONE); + xmppService.sendMessage(message, null); + chatMsg.setText(""); + } + }); + builder.create().show(); + } + } + + public void resendPgpMessage(String msg) { + this.queuedPqpMessage = msg; + } + + protected void sendOtrMessage(final Message message) { + ConversationActivity activity = (ConversationActivity) getActivity(); + final XmppConnectionService xmppService = activity.xmppConnectionService; + if (conversation.hasValidOtrSession()) { + activity.xmppConnectionService.sendMessage(message, null); + chatMsg.setText(""); + } else { + Hashtable<String, Integer> presences; + if (conversation.getContact() != null) { + presences = conversation.getContact().getPresences(); + } else { + presences = null; + } + if ((presences == null) || (presences.size() == 0)) { + AlertDialog.Builder builder = new AlertDialog.Builder( + getActivity()); + builder.setTitle("Contact is offline"); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setMessage("Sending OTR encrypted messages to an offline contact is impossible."); + builder.setPositiveButton("Send plain text", + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, + int which) { + conversation.nextMessageEncryption = Message.ENCRYPTION_NONE; + message.setEncryption(Message.ENCRYPTION_NONE); + xmppService.sendMessage(message, null); + chatMsg.setText(""); + } + }); + builder.setNegativeButton("Cancel", null); + builder.create().show(); + } else if (presences.size() == 1) { + xmppService.sendMessage(message, (String) presences.keySet() + .toArray()[0]); + chatMsg.setText(""); + } else { + AlertDialog.Builder builder = new AlertDialog.Builder( + getActivity()); + builder.setTitle("Choose Presence"); + final String[] presencesArray = new String[presences.size()]; + presences.keySet().toArray(presencesArray); + builder.setItems(presencesArray, + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, + int which) { + xmppService.sendMessage(message, + presencesArray[which]); + chatMsg.setText(""); + } + }); + builder.create().show(); + } + } + } + + private static class ViewHolder { + + protected TextView time; + protected TextView messageBody; + protected ImageView imageView; + + } + + private class BitmapCache { + private HashMap<String, Bitmap> bitmaps = new HashMap<String, Bitmap>(); + private Bitmap error = null; + + public Bitmap get(String name, Uri uri) { + if (bitmaps.containsKey(name)) { + return bitmaps.get(name); + } else { + Bitmap bm; + if (uri != null) { + try { + bm = BitmapFactory.decodeStream(getActivity() + .getContentResolver().openInputStream(uri)); + } catch (FileNotFoundException e) { + bm = UIHelper.getUnknownContactPicture(name, 200); + } + } else { + bm = UIHelper.getUnknownContactPicture(name, 200); + } + bitmaps.put(name, bm); + return bm; + } + } + + public Bitmap getError() { + if (error == null) { + error = UIHelper.getErrorPicture(200); + } + return error; + } + } + + class DecryptMessage extends AsyncTask<Message, Void, Boolean> { + + @Override + protected Boolean doInBackground(Message... params) { + final ConversationActivity activity = (ConversationActivity) getActivity(); + askForPassphraseIntent = null; + for(int i = 0; i < params.length; ++i) { + if (params[i].getEncryption() == Message.ENCRYPTION_PGP) { + String body = params[i].getBody(); + String decrypted = null; + try { + if (activity==null) { + return false; + } + Log.d("gultsch","calling to decrypt message id #"+params[i].getUuid()); + decrypted = activity.xmppConnectionService.getPgpEngine().decrypt(body); + } catch (UserInputRequiredException e) { + askForPassphraseIntent = e.getPendingIntent().getIntentSender(); + activity.runOnUiThread(new Runnable() { + + @Override + public void run() { + pgpInfo.setVisibility(View.VISIBLE); + } + }); + + return false; + + } catch (OpenPgpException e) { + Log.d("gultsch","error decrypting pgp"); + } + if (decrypted!=null) { + params[i].setBody(decrypted); + params[i].setEncryption(Message.ENCRYPTION_DECRYPTED); + activity.xmppConnectionService.updateMessage(params[i]); + } + if (activity!=null) { + activity.runOnUiThread(new Runnable() { + + @Override + public void run() { + messageListAdapter.notifyDataSetChanged(); + } + }); + } + } + if (activity!=null) { + activity.runOnUiThread(new Runnable() { + + @Override + public void run() { + activity.updateConversationList(); + } + }); + } + } + return true; + } + + } +} diff --git a/src/eu/siacs/conversations/ui/DialogContactDetails.java b/src/eu/siacs/conversations/ui/DialogContactDetails.java new file mode 100644 index 00000000..1210bc3c --- /dev/null +++ b/src/eu/siacs/conversations/ui/DialogContactDetails.java @@ -0,0 +1,218 @@ +package eu.siacs.conversations.ui; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.entities.Presences; +import eu.siacs.conversations.utils.UIHelper; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.provider.ContactsContract.CommonDataKinds; +import android.provider.ContactsContract.Contacts; +import android.provider.ContactsContract.Intents; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.CheckBox; +import android.widget.QuickContactBadge; +import android.widget.TextView; + +public class DialogContactDetails extends DialogFragment { + + private Contact contact = null; + boolean displayingInRoster = false; + + private DialogContactDetails mDetailsDialog = this; + private XmppActivity activity; + + private CheckBox send; + private CheckBox receive; + + private DialogInterface.OnClickListener askRemoveFromRoster = new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle("Delete from roster"); + builder.setMessage("Do you want to delete "+contact.getJid()+" from your roster. The conversation assoziated with this account will not be removed."); + builder.setNegativeButton("Cancel", null); + builder.setPositiveButton("Delete",removeFromRoster); + builder.create().show(); + } + }; + + private DialogInterface.OnClickListener removeFromRoster = new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + activity.xmppConnectionService.deleteContact(contact); + mDetailsDialog.dismiss(); + } + }; + + private DialogInterface.OnClickListener addToPhonebook = new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); + intent.setType(Contacts.CONTENT_ITEM_TYPE); + intent.putExtra(Intents.Insert.IM_HANDLE,contact.getJid()); + intent.putExtra(Intents.Insert.IM_PROTOCOL,CommonDataKinds.Im.PROTOCOL_JABBER); + intent.putExtra("finishActivityOnSaveCompleted", true); + getActivity().startActivityForResult(intent,0); + mDetailsDialog.dismiss(); + } + }; + + private DialogInterface.OnClickListener updateSubscriptions = new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + boolean needsUpdating = false; + if (contact.getSubscriptionOption(Contact.Subscription.FROM)) { + if (!send.isChecked()) { + contact.resetSubscriptionOption(Contact.Subscription.FROM); + contact.resetSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT); + activity.xmppConnectionService.stopPresenceUpdatesTo(contact); + needsUpdating=true; + } + } else { + if (contact.getSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT)) { + if (!send.isChecked()) { + contact.resetSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT); + needsUpdating=true; + } + } else { + if (send.isChecked()) { + contact.setSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT); + needsUpdating=true; + } + } + } + if (contact.getSubscriptionOption(Contact.Subscription.TO)) { + if (!receive.isChecked()) { + contact.resetSubscriptionOption(Contact.Subscription.TO); + activity.xmppConnectionService.stopPresenceUpdatesFrom(contact); + needsUpdating=true; + } + } else { + if (contact.getSubscriptionOption(Contact.Subscription.ASKING)) { + if (!receive.isChecked()) { + contact.resetSubscriptionOption(Contact.Subscription.ASKING); + activity.xmppConnectionService.stopPresenceUpdatesFrom(contact); + needsUpdating=true; + } + } else { + if (receive.isChecked()) { + contact.setSubscriptionOption(Contact.Subscription.ASKING); + activity.xmppConnectionService.requestPresenceUpdatesFrom(contact); + needsUpdating=true; + } + } + } + if (needsUpdating) { + activity.xmppConnectionService.updateContact(contact); + } + } + }; + + public void setContact(Contact contact) { + this.contact = contact; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + this.activity = (XmppActivity) getActivity(); + AlertDialog.Builder builder = new AlertDialog.Builder(this.activity); + LayoutInflater inflater = getActivity().getLayoutInflater(); + View view = inflater.inflate(R.layout.dialog_contact_details, null); + TextView contactJid = (TextView) view.findViewById(R.id.details_contactjid); + TextView accountJid = (TextView) view.findViewById(R.id.details_account); + TextView status = (TextView) view.findViewById(R.id.details_contactstatus); + send = (CheckBox) view.findViewById(R.id.details_send_presence); + receive = (CheckBox) view.findViewById(R.id.details_receive_presence); + //ImageView contactPhoto = (ImageView) view.findViewById(R.id.details_contact_picture); + QuickContactBadge badge = (QuickContactBadge) view.findViewById(R.id.details_contact_badge); + + if (contact.getSubscriptionOption(Contact.Subscription.FROM)) { + send.setChecked(true); + } else { + send.setText("Preemptively grant subscription request"); + if (contact.getSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT)) { + send.setChecked(true); + } else { + send.setChecked(false); + } + } + if (contact.getSubscriptionOption(Contact.Subscription.TO)) { + receive.setChecked(true); + } else { + receive.setText("Request presence updates"); + if (contact.getSubscriptionOption(Contact.Subscription.ASKING)) { + receive.setChecked(true); + } else { + receive.setChecked(false); + } + } + + switch (contact.getMostAvailableStatus()) { + case Presences.CHAT: + status.setText("free to chat"); + status.setTextColor(0xFF83b600); + break; + case Presences.ONLINE: + status.setText("online"); + status.setTextColor(0xFF83b600); + break; + case Presences.AWAY: + status.setText("away"); + status.setTextColor(0xFFffa713); + break; + case Presences.XA: + status.setText("extended away"); + status.setTextColor(0xFFffa713); + break; + case Presences.DND: + status.setText("do not disturb"); + status.setTextColor(0xFFe92727); + break; + case Presences.OFFLINE: + status.setText("offline"); + status.setTextColor(0xFFe92727); + break; + default: + status.setText("offline"); + status.setTextColor(0xFFe92727); + break; + } + contactJid.setText(contact.getJid()); + accountJid.setText(contact.getAccount().getJid()); + + UIHelper.prepareContactBadge(getActivity(), badge, contact); + + if (contact.getSystemAccount()==null) { + badge.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle("Add to phone book"); + builder.setMessage("Do you want to add "+contact.getJid()+" to your phones contact list?"); + builder.setNegativeButton("Cancel", null); + builder.setPositiveButton("Add",addToPhonebook); + builder.create().show(); + } + }); + } + + builder.setView(view); + builder.setTitle(contact.getDisplayName()); + + builder.setNeutralButton("Done", this.updateSubscriptions); + builder.setPositiveButton("Remove from roster", this.askRemoveFromRoster); + return builder.create(); + } +} diff --git a/src/eu/siacs/conversations/ui/EditAccount.java b/src/eu/siacs/conversations/ui/EditAccount.java new file mode 100644 index 00000000..3ec74174 --- /dev/null +++ b/src/eu/siacs/conversations/ui/EditAccount.java @@ -0,0 +1,138 @@ +package eu.siacs.conversations.ui; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.utils.Validator; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.EditText; +import android.widget.TextView; + +public class EditAccount extends DialogFragment { + + protected Account account; + + public void setAccount(Account account) { + this.account = account; + } + + public interface EditAccountListener { + public void onAccountEdited(Account account); + } + + protected EditAccountListener listener = null; + + public void setEditAccountListener(EditAccountListener listener) { + this.listener = listener; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + LayoutInflater inflater = getActivity().getLayoutInflater(); + View view = inflater.inflate(R.layout.edit_account_dialog, null); + final EditText jidText = (EditText) view.findViewById(R.id.account_jid); + final TextView confirmPwDesc = (TextView) view + .findViewById(R.id.account_confirm_password_desc); + CheckBox useTLS = (CheckBox) view.findViewById(R.id.account_usetls); + + final EditText password = (EditText) view + .findViewById(R.id.account_password); + final EditText passwordConfirm = (EditText) view + .findViewById(R.id.account_password_confirm2); + final CheckBox registerAccount = (CheckBox) view + .findViewById(R.id.edit_account_register_new); + + final String okButtonDesc; + + if (account != null) { + builder.setTitle("Edit account"); + registerAccount.setVisibility(View.GONE); + jidText.setText(account.getJid()); + password.setText(account.getPassword()); + okButtonDesc = "Edit"; + if (account.isOptionSet(Account.OPTION_USETLS)) { + useTLS.setChecked(true); + } else { + useTLS.setChecked(false); + } + } else { + builder.setTitle("Add account"); + okButtonDesc = "Add"; + } + + registerAccount + .setOnCheckedChangeListener(new OnCheckedChangeListener() { + + @Override + public void onCheckedChanged(CompoundButton buttonView, + boolean isChecked) { + AlertDialog d = (AlertDialog) getDialog(); + Button positiveButton = (Button) d + .getButton(Dialog.BUTTON_POSITIVE); + if (isChecked) { + positiveButton.setText("Register"); + passwordConfirm.setVisibility(View.VISIBLE); + confirmPwDesc.setVisibility(View.VISIBLE); + } else { + passwordConfirm.setVisibility(View.GONE); + positiveButton.setText("Add"); + confirmPwDesc.setVisibility(View.GONE); + } + } + }); + + builder.setView(view); + builder.setNeutralButton("Cancel", null); + builder.setPositiveButton(okButtonDesc, null); + return builder.create(); + } + + @Override + public void onStart() { + super.onStart(); + final AlertDialog d = (AlertDialog) getDialog(); + Button positiveButton = (Button) d.getButton(Dialog.BUTTON_POSITIVE); + positiveButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + EditText jidEdit = (EditText) d.findViewById(R.id.account_jid); + String jid = jidEdit.getText().toString(); + EditText passwordEdit = (EditText) d + .findViewById(R.id.account_password); + String password = passwordEdit.getText().toString(); + CheckBox useTLS = (CheckBox) d.findViewById(R.id.account_usetls); + String username; + String server; + if (Validator.isValidJid(jid)) { + String[] parts = jid.split("@"); + username = parts[0]; + server = parts[1]; + } else { + jidEdit.setError("Invalid Jabber ID"); + return; + } + if (account != null) { + account.setPassword(password); + account.setUsername(username); + account.setServer(server); + } else { + account = new Account(username, server, password); + } + account.setOption(Account.OPTION_USETLS, useTLS.isChecked()); + if (listener != null) { + listener.onAccountEdited(account); + d.dismiss(); + } + } + }); + } +} diff --git a/src/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/eu/siacs/conversations/ui/ManageAccountActivity.java new file mode 100644 index 00000000..22b82e77 --- /dev/null +++ b/src/eu/siacs/conversations/ui/ManageAccountActivity.java @@ -0,0 +1,312 @@ +package eu.siacs.conversations.ui; + +import java.util.ArrayList; +import java.util.List; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.PgpEngine; +import eu.siacs.conversations.crypto.PgpEngine.UserInputRequiredException; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.ui.EditAccount.EditAccountListener; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.IntentSender.SendIntentException; +import android.os.Bundle; +import android.util.Log; +import android.view.ActionMode; +import android.view.ActionMode.Callback; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; + +public class ManageAccountActivity extends XmppActivity implements ActionMode.Callback { + + public static final int REQUEST_ANNOUNCE_PGP = 0x73731; + + protected boolean isActionMode = false; + protected ActionMode actionMode; + protected Account selectedAccountForActionMode = null; + + protected List<Account> accountList = new ArrayList<Account>(); + protected ListView accountListView; + protected ArrayAdapter<Account> accountListViewAdapter; + protected OnAccountListChangedListener accountChanged = new OnAccountListChangedListener() { + + @Override + public void onAccountListChangedListener() { + Log.d("xmppService", "ui on account list changed listener"); + accountList.clear(); + accountList.addAll(xmppConnectionService.getAccounts()); + runOnUiThread(new Runnable() { + + @Override + public void run() { + if (accountList.size() == 1) { + startActivity(new Intent(getApplicationContext(), + NewConversationActivity.class)); + } + accountListViewAdapter.notifyDataSetChanged(); + } + }); + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + + super.onCreate(savedInstanceState); + + setContentView(R.layout.manage_accounts); + + accountListView = (ListView) findViewById(R.id.account_list); + accountListViewAdapter = new ArrayAdapter<Account>( + getApplicationContext(), R.layout.account_row, this.accountList) { + @Override + public View getView(int position, View view, ViewGroup parent) { + Account account = getItem(position); + if (view == null) { + LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + view = (View) inflater.inflate(R.layout.account_row, null); + } + ((TextView) view.findViewById(R.id.account_jid)) + .setText(account.getJid()); + TextView statusView = (TextView) view + .findViewById(R.id.account_status); + switch (account.getStatus()) { + case Account.STATUS_DISABLED: + statusView.setText("temporarily disabled"); + statusView.setTextColor(0xFF1da9da); + break; + case Account.STATUS_ONLINE: + statusView.setText("online"); + statusView.setTextColor(0xFF83b600); + break; + case Account.STATUS_OFFLINE: + statusView.setText("offline"); + statusView.setTextColor(0xFFe92727); + break; + case Account.STATUS_UNAUTHORIZED: + statusView.setText("unauthorized"); + statusView.setTextColor(0xFFe92727); + break; + case Account.STATUS_SERVER_NOT_FOUND: + statusView.setText("server not found"); + statusView.setTextColor(0xFFe92727); + break; + default: + break; + } + + return view; + } + }; + final Activity activity = this; + accountListView.setAdapter(this.accountListViewAdapter); + accountListView.setOnItemClickListener(new OnItemClickListener() { + + @Override + public void onItemClick(AdapterView<?> arg0, View view, + int position, long arg3) { + if (!isActionMode) { + EditAccount dialog = new EditAccount(); + dialog.setAccount(accountList.get(position)); + dialog.setEditAccountListener(new EditAccountListener() { + + @Override + public void onAccountEdited(Account account) { + xmppConnectionService.updateAccount(account); + } + }); + dialog.show(getFragmentManager(), "edit_account"); + } else { + selectedAccountForActionMode = accountList.get(position); + actionMode.invalidate(); + } + } + }); + accountListView.setOnItemLongClickListener(new OnItemLongClickListener() { + + @Override + public boolean onItemLongClick(AdapterView<?> arg0, View view, + int position, long arg3) { + if (!isActionMode) { + accountListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); + accountListView.setItemChecked(position,true); + selectedAccountForActionMode = accountList.get(position); + actionMode = activity.startActionMode((Callback) activity); + return true; + } else { + return false; + } + } + }); + } + + @Override + protected void onStop() { + if (xmppConnectionServiceBound) { + xmppConnectionService.removeOnAccountListChangedListener(); + } + super.onStop(); + } + + @Override + void onBackendConnected() { + xmppConnectionService.setOnAccountListChangedListener(accountChanged); + this.accountList.clear(); + this.accountList.addAll(xmppConnectionService.getAccounts()); + accountListViewAdapter.notifyDataSetChanged(); + if (this.accountList.size() == 0) { + getActionBar().setDisplayHomeAsUpEnabled(false); + addAccount(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.manageaccounts, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_settings: + startActivity(new Intent(this, SettingsActivity.class)); + break; + case R.id.action_add_account: + addAccount(); + break; + default: + break; + } + return super.onOptionsItemSelected(item); + } + + protected void addAccount() { + final Activity activity = this; + EditAccount dialog = new EditAccount(); + dialog.setEditAccountListener(new EditAccountListener() { + + @Override + public void onAccountEdited(Account account) { + xmppConnectionService.createAccount(account); + activity.getActionBar().setDisplayHomeAsUpEnabled(true); + } + }); + dialog.show(getFragmentManager(), "add_account"); + } + + @Override + public boolean onActionItemClicked(final ActionMode mode, MenuItem item) { + if (item.getItemId()==R.id.account_disable) { + selectedAccountForActionMode.setOption(Account.OPTION_DISABLED, true); + xmppConnectionService.updateAccount(selectedAccountForActionMode); + mode.finish(); + } else if (item.getItemId()==R.id.account_enable) { + selectedAccountForActionMode.setOption(Account.OPTION_DISABLED, false); + xmppConnectionService.updateAccount(selectedAccountForActionMode); + mode.finish(); + } else if (item.getItemId()==R.id.account_delete) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Are you sure?"); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setMessage("If you delete your account your entire conversation history will be lost"); + builder.setPositiveButton("Delete", new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + xmppConnectionService.deleteAccount(selectedAccountForActionMode); + selectedAccountForActionMode = null; + mode.finish(); + } + }); + builder.setNegativeButton("Cancel",null); + builder.create().show(); + } else if (item.getItemId()==R.id.announce_pgp) { + mode.finish(); + try { + xmppConnectionService.generatePgpAnnouncement(selectedAccountForActionMode); + } catch (PgpEngine.UserInputRequiredException e) { + try { + startIntentSenderForResult(e.getPendingIntent().getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0); + } catch (SendIntentException e1) { + Log.d("gultsch","sending intent failed"); + } + } + } + return true; + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + MenuInflater inflater = mode.getMenuInflater(); + inflater.inflate(R.menu.manageaccounts_context, menu); + return true; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + // TODO Auto-generated method stub + + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + if (selectedAccountForActionMode.isOptionSet(Account.OPTION_DISABLED)) { + menu.findItem(R.id.account_enable).setVisible(true); + menu.findItem(R.id.account_disable).setVisible(false); + } else { + menu.findItem(R.id.account_disable).setVisible(true); + menu.findItem(R.id.account_enable).setVisible(false); + } + return true; + } + + @Override + public void onActionModeStarted(ActionMode mode) { + super.onActionModeStarted(mode); + this.isActionMode = true; + } + + @Override + public void onActionModeFinished(ActionMode mode) { + super.onActionModeFinished(mode); + this.isActionMode = false; + accountListView.clearChoices(); + accountListView.requestLayout(); + accountListView.post(new Runnable() { + @Override + public void run() { + accountListView.setChoiceMode(ListView.CHOICE_MODE_NONE); + } + }); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == RESULT_OK) { + if (requestCode == REQUEST_ANNOUNCE_PGP) { + try { + xmppConnectionService.generatePgpAnnouncement(selectedAccountForActionMode); + } catch (UserInputRequiredException e) { + Log.d("gultsch","already came back. ignoring"); + } + } + } + } +} diff --git a/src/eu/siacs/conversations/ui/NewConversationActivity.java b/src/eu/siacs/conversations/ui/NewConversationActivity.java new file mode 100644 index 00000000..628a3047 --- /dev/null +++ b/src/eu/siacs/conversations/ui/NewConversationActivity.java @@ -0,0 +1,332 @@ +package eu.siacs.conversations.ui; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.utils.Validator; +import android.net.Uri; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.ImageView; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; + +public class NewConversationActivity extends XmppActivity { + + protected List<Contact> phoneContacts = new ArrayList<Contact>(); + protected List<Contact> rosterContacts = new ArrayList<Contact>(); + protected List<Contact> aggregatedContacts = new ArrayList<Contact>(); + protected ListView contactsView; + protected ArrayAdapter<Contact> contactsAdapter; + + protected EditText search; + protected String searchString = ""; + private TextView contactsHeader; + private List<Account> accounts; + + protected void updateAggregatedContacts() { + + aggregatedContacts.clear(); + for (Contact contact : rosterContacts) { + if (contact.match(searchString)) + aggregatedContacts.add(contact); + } + + Collections.sort(aggregatedContacts, new Comparator<Contact>() { + + @SuppressLint("DefaultLocale") + @Override + public int compare(Contact lhs, Contact rhs) { + return lhs.getDisplayName().toLowerCase() + .compareTo(rhs.getDisplayName().toLowerCase()); + } + }); + + if (aggregatedContacts.size() == 0) { + + if (Validator.isValidJid(searchString)) { + String name = searchString.split("@")[0]; + Contact newContact = new Contact(null, name, searchString, null); + newContact.flagAsNotInRoster(); + aggregatedContacts.add(newContact); + contactsHeader.setText("Create new contact"); + } else { + contactsHeader.setText("Contacts"); + } + } else { + contactsHeader.setText("Contacts"); + } + + contactsAdapter.notifyDataSetChanged(); + contactsView.setScrollX(0); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_new_conversation); + + contactsHeader = (TextView) findViewById(R.id.contacts_header); + + search = (EditText) findViewById(R.id.new_conversation_search); + search.addTextChangedListener(new TextWatcher() { + + @Override + public void onTextChanged(CharSequence s, int start, int before, + int count) { + searchString = search.getText().toString(); + updateAggregatedContacts(); + } + + @Override + public void afterTextChanged(Editable s) { + // TODO Auto-generated method stub + + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + // TODO Auto-generated method stub + + } + }); + + contactsView = (ListView) findViewById(R.id.contactList); + contactsAdapter = new ArrayAdapter<Contact>(getApplicationContext(), + R.layout.contact, aggregatedContacts) { + @Override + public View getView(int position, View view, ViewGroup parent) { + LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + Contact contact = getItem(position); + if (view == null) { + view = (View) inflater.inflate(R.layout.contact, null); + } + + ((TextView) view.findViewById(R.id.contact_display_name)) + .setText(getItem(position).getDisplayName()); + TextView contactJid = (TextView) view + .findViewById(R.id.contact_jid); + contactJid.setText(contact.getJid()); + String profilePhoto = getItem(position).getProfilePhoto(); + ImageView imageView = (ImageView) view + .findViewById(R.id.contact_photo); + if (profilePhoto != null) { + imageView.setImageURI(Uri.parse(profilePhoto)); + } else { + imageView.setImageBitmap(UIHelper.getUnknownContactPicture( + getItem(position).getDisplayName(), 90)); + } + return view; + } + }; + contactsView.setAdapter(contactsAdapter); + final Activity activity = this; + contactsView.setOnItemClickListener(new OnItemClickListener() { + + @Override + public void onItemClick(AdapterView<?> arg0, final View view, + int pos, long arg3) { + final Contact clickedContact = aggregatedContacts.get(pos); + + if ((clickedContact.getAccount()==null)&&(accounts.size()>1)) { + String[] accountList = new String[accounts.size()]; + for (int i = 0; i < accounts.size(); ++i) { + accountList[i] = accounts.get(i).getJid(); + } + + AlertDialog.Builder accountChooser = new AlertDialog.Builder( + activity); + accountChooser.setTitle("Choose account"); + accountChooser.setItems(accountList, new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + clickedContact.setAccount(accounts.get(which)); + showIsMucDialogIfNeeded(clickedContact); + } + }); + accountChooser.create().show(); + } else { + if (clickedContact.getAccount()==null) { + clickedContact.setAccount(accounts.get(0)); + } + showIsMucDialogIfNeeded(clickedContact); + } + } + }); + contactsView.setOnItemLongClickListener(new OnItemLongClickListener() { + + @Override + public boolean onItemLongClick(AdapterView<?> arg0, View arg1, + int pos, long arg3) { + Contact clickedContact = aggregatedContacts.get(pos); + DialogContactDetails dialog = new DialogContactDetails(); + dialog.setContact(clickedContact); + dialog.show(getFragmentManager(), "details"); + return true; + } + }); + } + + public void showIsMucDialogIfNeeded(final Contact clickedContact) { + if (clickedContact.couldBeMuc()) { + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + dialog.setTitle("Multi User Conference"); + dialog.setMessage("Are you trying to join a conference?"); + dialog.setPositiveButton("Yes", new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + startConversation(clickedContact, clickedContact.getAccount(),true); + } + }); + dialog.setNegativeButton("No", new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + startConversation(clickedContact, clickedContact.getAccount(),false); + } + }); + dialog.create().show(); + } else { + startConversation(clickedContact, clickedContact.getAccount(),false); + } + } + + public void startConversation(Contact contact, Account account, boolean muc) { + if (!contact.isInRoster()) { + xmppConnectionService.createContact(contact); + } + Conversation conversation = xmppConnectionService + .findOrCreateConversation(account, contact.getJid(), muc); + + Intent viewConversationIntent = new Intent(this, + ConversationActivity.class); + viewConversationIntent.setAction(Intent.ACTION_VIEW); + viewConversationIntent.putExtra(ConversationActivity.CONVERSATION, + conversation.getUuid()); + viewConversationIntent.setType(ConversationActivity.VIEW_CONVERSATION); + viewConversationIntent.setFlags(viewConversationIntent.getFlags() + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(viewConversationIntent); + } + + @Override + void onBackendConnected() { + if (xmppConnectionService.getConversationCount() == 0) { + getActionBar().setDisplayHomeAsUpEnabled(false); + getActionBar().setHomeButtonEnabled(false); + } + this.accounts = xmppConnectionService.getAccounts(); + this.rosterContacts.clear(); + for (int i = 0; i < accounts.size(); ++i) { + xmppConnectionService.getRoster(accounts.get(i), + new OnRosterFetchedListener() { + + @Override + public void onRosterFetched(List<Contact> roster) { + rosterContacts.addAll(roster); + runOnUiThread(new Runnable() { + + @Override + public void run() { + updateAggregatedContacts(); + } + }); + + } + }); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.newconversation, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_settings: + startActivity(new Intent(this, SettingsActivity.class)); + break; + case R.id.action_accounts: + startActivity(new Intent(this, ManageAccountActivity.class)); + break; + case R.id.action_refresh_contacts: + refreshContacts(); + break; + default: + break; + } + return super.onOptionsItemSelected(item); + } + + private void refreshContacts() { + final ProgressBar progress = (ProgressBar) findViewById(R.id.progressBar1); + final EditText searchBar = (EditText) findViewById(R.id.new_conversation_search); + final TextView contactsHeader = (TextView) findViewById(R.id.contacts_header); + final ListView contactList = (ListView) findViewById(R.id.contactList); + searchBar.setVisibility(View.GONE); + contactsHeader.setVisibility(View.GONE); + contactList.setVisibility(View.GONE); + progress.setVisibility(View.VISIBLE); + this.accounts = xmppConnectionService.getAccounts(); + this.rosterContacts.clear(); + for (int i = 0; i < accounts.size(); ++i) { + if (accounts.get(i).getStatus() == Account.STATUS_ONLINE) { + xmppConnectionService.updateRoster(accounts.get(i), + new OnRosterFetchedListener() { + + @Override + public void onRosterFetched( + final List<Contact> roster) { + runOnUiThread(new Runnable() { + + @Override + public void run() { + rosterContacts.addAll(roster); + progress.setVisibility(View.GONE); + searchBar.setVisibility(View.VISIBLE); + contactList.setVisibility(View.VISIBLE); + contactList.setVisibility(View.VISIBLE); + updateAggregatedContacts(); + } + }); + } + }); + } + } + } +} diff --git a/src/eu/siacs/conversations/ui/OnAccountListChangedListener.java b/src/eu/siacs/conversations/ui/OnAccountListChangedListener.java new file mode 100644 index 00000000..98ef445e --- /dev/null +++ b/src/eu/siacs/conversations/ui/OnAccountListChangedListener.java @@ -0,0 +1,5 @@ +package eu.siacs.conversations.ui; + +public interface OnAccountListChangedListener { + public void onAccountListChangedListener(); +} diff --git a/src/eu/siacs/conversations/ui/OnConversationListChangedListener.java b/src/eu/siacs/conversations/ui/OnConversationListChangedListener.java new file mode 100644 index 00000000..2a922e21 --- /dev/null +++ b/src/eu/siacs/conversations/ui/OnConversationListChangedListener.java @@ -0,0 +1,5 @@ +package eu.siacs.conversations.ui; + +public interface OnConversationListChangedListener { + public void onConversationListChanged(); +} diff --git a/src/eu/siacs/conversations/ui/OnRosterFetchedListener.java b/src/eu/siacs/conversations/ui/OnRosterFetchedListener.java new file mode 100644 index 00000000..d69ce35b --- /dev/null +++ b/src/eu/siacs/conversations/ui/OnRosterFetchedListener.java @@ -0,0 +1,9 @@ +package eu.siacs.conversations.ui; + +import java.util.List; + +import eu.siacs.conversations.entities.Contact; + +public interface OnRosterFetchedListener { + public void onRosterFetched(List<Contact> roster); +} diff --git a/src/eu/siacs/conversations/ui/SettingsActivity.java b/src/eu/siacs/conversations/ui/SettingsActivity.java new file mode 100644 index 00000000..abaf8c68 --- /dev/null +++ b/src/eu/siacs/conversations/ui/SettingsActivity.java @@ -0,0 +1,16 @@ +package eu.siacs.conversations.ui; + +import android.app.Activity; +import android.os.Bundle; + +public class SettingsActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Display the fragment as the main content. + getFragmentManager().beginTransaction() + .replace(android.R.id.content, new SettingsFragment()).commit(); + } + +} diff --git a/src/eu/siacs/conversations/ui/SettingsFragment.java b/src/eu/siacs/conversations/ui/SettingsFragment.java new file mode 100644 index 00000000..7e1c3698 --- /dev/null +++ b/src/eu/siacs/conversations/ui/SettingsFragment.java @@ -0,0 +1,15 @@ +package eu.siacs.conversations.ui; + +import eu.siacs.conversations.R; +import android.os.Bundle; +import android.preference.PreferenceFragment; + +public class SettingsFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Load the preferences from an XML resource + addPreferencesFromResource(R.xml.preferences); + } +} diff --git a/src/eu/siacs/conversations/ui/XmppActivity.java b/src/eu/siacs/conversations/ui/XmppActivity.java new file mode 100644 index 00000000..5114e640 --- /dev/null +++ b/src/eu/siacs/conversations/ui/XmppActivity.java @@ -0,0 +1,52 @@ +package eu.siacs.conversations.ui; + +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder; +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; + +public abstract class XmppActivity extends Activity { + public XmppConnectionService xmppConnectionService; + public boolean xmppConnectionServiceBound = false; + protected boolean handledViewIntent = false; + protected ServiceConnection mConnection = new ServiceConnection() { + + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + XmppConnectionBinder binder = (XmppConnectionBinder) service; + xmppConnectionService = binder.getService(); + xmppConnectionServiceBound = true; + onBackendConnected(); + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + xmppConnectionServiceBound = false; + } + }; + + @Override + protected void onStart() { + startService(new Intent(this, XmppConnectionService.class)); + super.onStart(); + if (!xmppConnectionServiceBound) { + Intent intent = new Intent(this, XmppConnectionService.class); + bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + } + } + + @Override + protected void onStop() { + super.onStop(); + if (xmppConnectionServiceBound) { + unbindService(mConnection); + xmppConnectionServiceBound = false; + } + } + + abstract void onBackendConnected(); +} |