ConversationActivity.java

  1package eu.siacs.conversations.ui;
  2
  3import java.util.ArrayList;
  4import java.util.Hashtable;
  5import java.util.List;
  6
  7import eu.siacs.conversations.R;
  8import eu.siacs.conversations.entities.Account;
  9import eu.siacs.conversations.entities.Contact;
 10import eu.siacs.conversations.entities.Conversation;
 11import eu.siacs.conversations.entities.Message;
 12import eu.siacs.conversations.utils.ExceptionHelper;
 13import eu.siacs.conversations.utils.UIHelper;
 14import android.os.Bundle;
 15import android.preference.PreferenceManager;
 16import android.app.AlertDialog;
 17import android.app.AlertDialog.Builder;
 18import android.app.FragmentTransaction;
 19import android.content.Context;
 20import android.content.DialogInterface;
 21import android.content.DialogInterface.OnClickListener;
 22import android.content.Intent;
 23import android.content.SharedPreferences;
 24import android.graphics.Color;
 25import android.graphics.Typeface;
 26import android.support.v4.widget.SlidingPaneLayout;
 27import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener;
 28import android.view.KeyEvent;
 29import android.view.LayoutInflater;
 30import android.view.Menu;
 31import android.view.MenuItem;
 32import android.view.View;
 33import android.view.ViewGroup;
 34import android.widget.AdapterView;
 35import android.widget.AdapterView.OnItemClickListener;
 36import android.widget.ArrayAdapter;
 37import android.widget.CheckBox;
 38import android.widget.ListView;
 39import android.widget.PopupMenu;
 40import android.widget.PopupMenu.OnMenuItemClickListener;
 41import android.widget.TextView;
 42import android.widget.ImageView;
 43
 44public class ConversationActivity extends XmppActivity {
 45
 46	public static final String VIEW_CONVERSATION = "viewConversation";
 47	public static final String CONVERSATION = "conversationUuid";
 48	public static final String TEXT = "text";
 49	public static final String PRESENCE = "eu.siacs.conversations.presence";
 50
 51	public static final int REQUEST_SEND_MESSAGE = 0x75441;
 52	public static final int REQUEST_DECRYPT_PGP = 0x76783;
 53	private static final int ATTACH_FILE = 0x48502;
 54
 55	protected SlidingPaneLayout spl;
 56
 57	private List<Conversation> conversationList = new ArrayList<Conversation>();
 58	private Conversation selectedConversation = null;
 59	private ListView listView;
 60
 61	private boolean paneShouldBeOpen = true;
 62	private boolean useSubject = true;
 63	private ArrayAdapter<Conversation> listAdapter;
 64
 65	private OnConversationListChangedListener onConvChanged = new OnConversationListChangedListener() {
 66
 67		@Override
 68		public void onConversationListChanged() {
 69			runOnUiThread(new Runnable() {
 70
 71				@Override
 72				public void run() {
 73					updateConversationList();
 74					if (paneShouldBeOpen) {
 75						if (conversationList.size() >= 1) {
 76							swapConversationFragment();
 77						} else {
 78							startActivity(new Intent(getApplicationContext(),
 79									ContactsActivity.class));
 80							finish();
 81						}
 82					}
 83					ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
 84							.findFragmentByTag("conversation");
 85					if (selectedFragment != null) {
 86						selectedFragment.updateMessages();
 87					}
 88				}
 89			});
 90		}
 91	};
 92	
 93	protected ConversationActivity activity = this;
 94
 95	public List<Conversation> getConversationList() {
 96		return this.conversationList;
 97	}
 98
 99	public Conversation getSelectedConversation() {
100		return this.selectedConversation;
101	}
102
103	public ListView getConversationListView() {
104		return this.listView;
105	}
106
107	public SlidingPaneLayout getSlidingPaneLayout() {
108		return this.spl;
109	}
110
111	public boolean shouldPaneBeOpen() {
112		return paneShouldBeOpen;
113	}
114
115	@Override
116	protected void onCreate(Bundle savedInstanceState) {
117
118		super.onCreate(savedInstanceState);
119
120		setContentView(R.layout.fragment_conversations_overview);
121
122		listView = (ListView) findViewById(R.id.list);
123
124		this.listAdapter = new ArrayAdapter<Conversation>(this,
125				R.layout.conversation_list_row, conversationList) {
126			@Override
127			public View getView(int position, View view, ViewGroup parent) {
128				if (view == null) {
129					LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
130					view = (View) inflater.inflate(
131							R.layout.conversation_list_row, null);
132				}
133				Conversation conv;
134				if (conversationList.size() > position) {
135					conv = getItem(position);
136				} else {
137					return view;
138				}
139				if (!spl.isSlideable()) {
140					if (conv == getSelectedConversation()) {
141						view.setBackgroundColor(0xffdddddd);
142					} else {
143						view.setBackgroundColor(Color.TRANSPARENT);
144					}
145				} else {
146					view.setBackgroundColor(Color.TRANSPARENT);
147				}
148				TextView convName = (TextView) view
149						.findViewById(R.id.conversation_name);
150				convName.setText(conv.getName(useSubject));
151				TextView convLastMsg = (TextView) view
152						.findViewById(R.id.conversation_lastmsg);
153				convLastMsg.setText(conv.getLatestMessage().getBody());
154
155				if (!conv.isRead()) {
156					convName.setTypeface(null, Typeface.BOLD);
157					convLastMsg.setTypeface(null, Typeface.BOLD);
158				} else {
159					convName.setTypeface(null, Typeface.NORMAL);
160					convLastMsg.setTypeface(null, Typeface.NORMAL);
161				}
162
163				((TextView) view.findViewById(R.id.conversation_lastupdate))
164						.setText(UIHelper.readableTimeDifference(conv
165								.getLatestMessage().getTimeSent()));
166
167				ImageView imageView = (ImageView) view
168						.findViewById(R.id.conversation_image);
169				imageView.setImageBitmap(UIHelper.getContactPicture(
170						conv, 56, activity.getApplicationContext(), false));
171				return view;
172			}
173
174		};
175
176		listView.setAdapter(this.listAdapter);
177
178		listView.setOnItemClickListener(new OnItemClickListener() {
179
180			@Override
181			public void onItemClick(AdapterView<?> arg0, View clickedView,
182					int position, long arg3) {
183				paneShouldBeOpen = false;
184				if (selectedConversation != conversationList.get(position)) {
185					selectedConversation = conversationList.get(position);
186					swapConversationFragment(); // .onBackendConnected(conversationList.get(position));
187				} else {
188					spl.closePane();
189				}
190			}
191		});
192		spl = (SlidingPaneLayout) findViewById(R.id.slidingpanelayout);
193		spl.setParallaxDistance(150);
194		spl.setShadowResource(R.drawable.es_slidingpane_shadow);
195		spl.setSliderFadeColor(0);
196		spl.setPanelSlideListener(new PanelSlideListener() {
197
198			@Override
199			public void onPanelOpened(View arg0) {
200				paneShouldBeOpen = true;
201				getActionBar().setDisplayHomeAsUpEnabled(false);
202				getActionBar().setTitle(R.string.app_name);
203				invalidateOptionsMenu();
204				hideKeyboard();
205			}
206
207			@Override
208			public void onPanelClosed(View arg0) {
209				paneShouldBeOpen = false;
210				if ((conversationList.size() > 0)
211						&& (getSelectedConversation() != null)) {
212					getActionBar().setDisplayHomeAsUpEnabled(true);
213					getActionBar().setTitle(
214							getSelectedConversation().getName(useSubject));
215					invalidateOptionsMenu();
216					if (!getSelectedConversation().isRead()) {
217						getSelectedConversation().markRead();
218						UIHelper.updateNotification(getApplicationContext(),
219								getConversationList(), null, false);
220						listView.invalidateViews();
221					}
222				}
223			}
224
225			@Override
226			public void onPanelSlide(View arg0, float arg1) {
227				// TODO Auto-generated method stub
228
229			}
230		});
231	}
232
233	@Override
234	public boolean onCreateOptionsMenu(Menu menu) {
235		getMenuInflater().inflate(R.menu.conversations, menu);
236		MenuItem menuSecure = (MenuItem) menu.findItem(R.id.action_security);
237		MenuItem menuArchive = (MenuItem) menu.findItem(R.id.action_archive);
238		MenuItem menuMucDetails = (MenuItem) menu
239				.findItem(R.id.action_muc_details);
240		MenuItem menuContactDetails = (MenuItem) menu
241				.findItem(R.id.action_contact_details);
242		MenuItem menuInviteContacts = (MenuItem) menu
243				.findItem(R.id.action_invite);
244		MenuItem menuAttach = (MenuItem) menu.findItem(R.id.action_attach_file);
245		MenuItem menuClearHistory = (MenuItem) menu.findItem(R.id.action_clear_history);
246
247		if ((spl.isOpen() && (spl.isSlideable()))) {
248			menuArchive.setVisible(false);
249			menuMucDetails.setVisible(false);
250			menuContactDetails.setVisible(false);
251			menuSecure.setVisible(false);
252			menuInviteContacts.setVisible(false);
253			menuAttach.setVisible(false);
254			menuClearHistory.setVisible(false);
255		} else {
256			((MenuItem) menu.findItem(R.id.action_add)).setVisible(!spl
257					.isSlideable());
258			if (this.getSelectedConversation() != null) {
259				if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) {
260					menuContactDetails.setVisible(false);
261					menuSecure.setVisible(false);
262					menuAttach.setVisible(false);
263				} else {
264					menuMucDetails.setVisible(false);
265					menuInviteContacts.setVisible(false);
266					if (this.getSelectedConversation().getLatestMessage()
267							.getEncryption() != Message.ENCRYPTION_NONE) {
268						menuSecure.setIcon(R.drawable.ic_action_secure);
269					}
270				}
271			}
272		}
273		return true;
274	}
275
276	@Override
277	public boolean onOptionsItemSelected(MenuItem item) {
278		switch (item.getItemId()) {
279		case android.R.id.home:
280			spl.openPane();
281			break;
282		case R.id.action_attach_file:
283			selectPresence(getSelectedConversation(), new OnPresenceSelected() {
284				
285				@Override
286				public void onPresenceSelected(boolean success, String presence) {
287					if (success) {
288						Intent attachFileIntent = new Intent();
289						attachFileIntent.setType("image/*");
290						attachFileIntent.setAction(Intent.ACTION_GET_CONTENT);
291						Intent chooser = Intent.createChooser(attachFileIntent, getString(R.string.attach_file));
292						startActivityForResult(chooser,	ATTACH_FILE);
293					}
294				}
295			});
296			break;
297		case R.id.action_add:
298			startActivity(new Intent(this, ContactsActivity.class));
299			break;
300		case R.id.action_archive:
301			this.endConversation(getSelectedConversation());
302			break;
303		case R.id.action_contact_details:
304			Contact contact = this.getSelectedConversation().getContact();
305			if (contact != null) {
306				Intent intent = new Intent(this, ContactDetailsActivity.class);
307				intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
308				intent.putExtra("uuid", contact.getUuid());
309				startActivity(intent);
310			} else {
311				showAddToRosterDialog(getSelectedConversation());
312			}
313			break;
314		case R.id.action_muc_details:
315			Intent intent = new Intent(this, MucDetailsActivity.class);
316			intent.setAction(MucDetailsActivity.ACTION_VIEW_MUC);
317			intent.putExtra("uuid", getSelectedConversation().getUuid());
318			startActivity(intent);
319			break;
320		case R.id.action_invite:
321			Intent inviteIntent = new Intent(getApplicationContext(),
322					ContactsActivity.class);
323			inviteIntent.setAction("invite");
324			inviteIntent.putExtra("uuid", selectedConversation.getUuid());
325			startActivity(inviteIntent);
326			break;
327		case R.id.action_security:
328			final Conversation selConv = getSelectedConversation();
329			View menuItemView = findViewById(R.id.action_security);
330			PopupMenu popup = new PopupMenu(this, menuItemView);
331			final ConversationFragment fragment = (ConversationFragment) getFragmentManager()
332					.findFragmentByTag("conversation");
333			if (fragment != null) {
334				popup.setOnMenuItemClickListener(new OnMenuItemClickListener() {
335
336					@Override
337					public boolean onMenuItemClick(MenuItem item) {
338						switch (item.getItemId()) {
339						case R.id.encryption_choice_none:
340							selConv.nextMessageEncryption = Message.ENCRYPTION_NONE;
341							item.setChecked(true);
342							break;
343						case R.id.encryption_choice_otr:
344							selConv.nextMessageEncryption = Message.ENCRYPTION_OTR;
345							item.setChecked(true);
346							break;
347						case R.id.encryption_choice_pgp:
348							selConv.nextMessageEncryption = Message.ENCRYPTION_PGP;
349							item.setChecked(true);
350							break;
351						default:
352							selConv.nextMessageEncryption = Message.ENCRYPTION_NONE;
353							break;
354						}
355						fragment.updateChatMsgHint();
356						return true;
357					}
358				});
359				popup.inflate(R.menu.encryption_choices);
360				switch (selConv.nextMessageEncryption) {
361				case Message.ENCRYPTION_NONE:
362					popup.getMenu().findItem(R.id.encryption_choice_none)
363							.setChecked(true);
364					break;
365				case Message.ENCRYPTION_OTR:
366					popup.getMenu().findItem(R.id.encryption_choice_otr)
367							.setChecked(true);
368					break;
369				case Message.ENCRYPTION_PGP:
370					popup.getMenu().findItem(R.id.encryption_choice_pgp)
371							.setChecked(true);
372					break;
373				case Message.ENCRYPTION_DECRYPTED:
374					popup.getMenu().findItem(R.id.encryption_choice_pgp)
375							.setChecked(true);
376					break;
377				default:
378					popup.getMenu().findItem(R.id.encryption_choice_none)
379							.setChecked(true);
380					break;
381				}
382				popup.show();
383			}
384
385			break;
386		case R.id.action_clear_history:
387			clearHistoryDialog(getSelectedConversation());
388			break;
389		default:
390			break;
391		}
392		return super.onOptionsItemSelected(item);
393	}
394	
395	private void endConversation(Conversation conversation) {
396		conversation.setStatus(Conversation.STATUS_ARCHIVED);
397		paneShouldBeOpen = true;
398		spl.openPane();
399		xmppConnectionService.archiveConversation(conversation);
400		if (conversationList.size() > 0) {
401			selectedConversation = conversationList.get(0);
402		} else {
403			selectedConversation = null;
404		}
405	}
406
407	protected void clearHistoryDialog(final Conversation conversation) {
408		AlertDialog.Builder builder = new AlertDialog.Builder(this);
409		builder.setTitle(getString(R.string.clear_conversation_history));
410		View dialogView = getLayoutInflater().inflate(R.layout.dialog_clear_history, null);
411		final CheckBox endConversationCheckBox = (CheckBox) dialogView.findViewById(R.id.end_conversation_checkbox);
412		builder.setView(dialogView);
413		builder.setNegativeButton(getString(R.string.cancel), null);
414		builder.setPositiveButton(getString(R.string.delete_messages), new OnClickListener() {
415			
416			@Override
417			public void onClick(DialogInterface dialog, int which) {
418				activity.xmppConnectionService.clearConversationHistory(conversation);
419				if (endConversationCheckBox.isChecked()) {
420					endConversation(conversation);
421				}
422			}
423		});
424		builder.create().show();
425	}
426
427	protected ConversationFragment swapConversationFragment() {
428		ConversationFragment selectedFragment = new ConversationFragment();
429
430		FragmentTransaction transaction = getFragmentManager()
431				.beginTransaction();
432		transaction.replace(R.id.selected_conversation, selectedFragment,
433				"conversation");
434		transaction.commit();
435		return selectedFragment;
436	}
437
438	@Override
439	public boolean onKeyDown(int keyCode, KeyEvent event) {
440		if (keyCode == KeyEvent.KEYCODE_BACK) {
441			if (!spl.isOpen()) {
442				spl.openPane();
443				return false;
444			}
445		}
446		return super.onKeyDown(keyCode, event);
447	}
448
449	@Override
450	public void onStart() {
451		super.onStart();
452		SharedPreferences preferences = PreferenceManager
453				.getDefaultSharedPreferences(this);
454		this.useSubject = preferences.getBoolean("use_subject_in_muc", true);
455		if (this.xmppConnectionServiceBound) {
456			this.onBackendConnected();
457		}
458		if (conversationList.size() >= 1) {
459			onConvChanged.onConversationListChanged();
460		}
461	}
462
463	@Override
464	protected void onStop() {
465		if (xmppConnectionServiceBound) {
466			xmppConnectionService.removeOnConversationListChangedListener();
467		}
468		super.onStop();
469	}
470
471	@Override
472	void onBackendConnected() {
473		this.registerListener();
474		if (conversationList.size() == 0) {
475			updateConversationList();
476		}
477
478		if ((getIntent().getAction() != null)
479				&& (getIntent().getAction().equals(Intent.ACTION_VIEW) && (!handledViewIntent))) {
480			if (getIntent().getType().equals(
481					ConversationActivity.VIEW_CONVERSATION)) {
482				handledViewIntent = true;
483
484				String convToView = (String) getIntent().getExtras().get(
485						CONVERSATION);
486
487				for (int i = 0; i < conversationList.size(); ++i) {
488					if (conversationList.get(i).getUuid().equals(convToView)) {
489						selectedConversation = conversationList.get(i);
490					}
491				}
492				paneShouldBeOpen = false;
493				String text = getIntent().getExtras().getString(TEXT, null);
494				swapConversationFragment().setText(text);
495			}
496		} else {
497			if (xmppConnectionService.getAccounts().size() == 0) {
498				startActivity(new Intent(this, ManageAccountActivity.class));
499				finish();
500			} else if (conversationList.size() <= 0) {
501				// add no history
502				startActivity(new Intent(this, ContactsActivity.class));
503				finish();
504			} else {
505				spl.openPane();
506				// find currently loaded fragment
507				ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
508						.findFragmentByTag("conversation");
509				if (selectedFragment != null) {
510					selectedFragment.onBackendConnected();
511				} else {
512					selectedConversation = conversationList.get(0);
513					swapConversationFragment();
514				}
515				ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
516			}
517		}
518	}
519
520	public void registerListener() {
521		if (xmppConnectionServiceBound) {
522			xmppConnectionService
523					.setOnConversationListChangedListener(this.onConvChanged);
524		}
525	}
526
527	@Override
528	protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
529		super.onActivityResult(requestCode, resultCode, data);
530		if (resultCode == RESULT_OK) {
531			if (requestCode == REQUEST_DECRYPT_PGP) {
532				ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
533						.findFragmentByTag("conversation");
534				if (selectedFragment != null) {
535					selectedFragment.hidePgpPassphraseBox();
536				}
537			} else if (requestCode == ATTACH_FILE) {
538				Conversation conversation = getSelectedConversation();
539				String presence = conversation.getNextPresence();
540				xmppConnectionService.attachImageToConversation(conversation, presence, data.getData());
541				
542			}
543		}
544	}
545
546	public void updateConversationList() {
547		conversationList.clear();
548		conversationList.addAll(xmppConnectionService.getConversations());
549		listView.invalidateViews();
550	}
551	
552	public void selectPresence(final Conversation conversation, final OnPresenceSelected listener) {
553		Contact contact = conversation.getContact();
554		if (contact==null) {
555			showAddToRosterDialog(conversation);
556			listener.onPresenceSelected(false,null);
557		} else {
558			Hashtable<String, Integer> presences = contact.getPresences();
559			if (presences.size() == 0) {
560				listener.onPresenceSelected(false, null);
561			} else if (presences.size() == 1) {
562				String presence = (String) presences.keySet().toArray()[0];
563				conversation.setNextPresence(presence);
564				listener.onPresenceSelected(true, presence);
565			} else {
566				AlertDialog.Builder builder = new AlertDialog.Builder(this);
567				builder.setTitle(getString(R.string.choose_presence));
568				final String[] presencesArray = new String[presences.size()];
569				presences.keySet().toArray(presencesArray);
570				builder.setItems(presencesArray,
571						new DialogInterface.OnClickListener() {
572
573							@Override
574							public void onClick(DialogInterface dialog,
575									int which) {
576								String presence = presencesArray[which];
577								conversation.setNextPresence(presence);
578								listener.onPresenceSelected(true,presence);
579							}
580						});
581				builder.create().show();
582			}
583		}
584	}
585	
586	private void showAddToRosterDialog(final Conversation conversation) {
587		String jid = conversation.getContactJid();
588		AlertDialog.Builder builder = new AlertDialog.Builder(this);
589		builder.setTitle(jid);
590		builder.setMessage(getString(R.string.not_in_roster));
591		builder.setNegativeButton(getString(R.string.cancel), null);
592		builder.setPositiveButton(getString(R.string.add_contact), new DialogInterface.OnClickListener() {
593
594			@Override
595			public void onClick(DialogInterface dialog, int which) {
596				String jid = conversation.getContactJid();
597				Account account = getSelectedConversation().getAccount();
598				String name = jid.split("@")[0];
599				Contact contact = new Contact(account, name, jid, null);
600				xmppConnectionService.createContact(contact);
601			}
602		});
603		builder.create().show();
604	}
605}