XmppActivity.java

  1package eu.siacs.conversations.ui;
  2
  3import java.io.FileNotFoundException;
  4import java.lang.ref.WeakReference;
  5import java.util.concurrent.RejectedExecutionException;
  6
  7import eu.siacs.conversations.Config;
  8import eu.siacs.conversations.R;
  9import eu.siacs.conversations.entities.Account;
 10import eu.siacs.conversations.entities.Contact;
 11import eu.siacs.conversations.entities.Conversation;
 12import eu.siacs.conversations.entities.Message;
 13import eu.siacs.conversations.entities.Presences;
 14import eu.siacs.conversations.services.XmppConnectionService;
 15import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
 16import eu.siacs.conversations.utils.ExceptionHelper;
 17import android.app.Activity;
 18import android.app.AlertDialog;
 19import android.app.PendingIntent;
 20import android.app.AlertDialog.Builder;
 21import android.content.ComponentName;
 22import android.content.Context;
 23import android.content.DialogInterface;
 24import android.content.DialogInterface.OnClickListener;
 25import android.content.IntentSender.SendIntentException;
 26import android.content.res.Resources;
 27import android.content.Intent;
 28import android.content.ServiceConnection;
 29import android.graphics.Bitmap;
 30import android.graphics.drawable.BitmapDrawable;
 31import android.graphics.drawable.Drawable;
 32import android.net.Uri;
 33import android.os.AsyncTask;
 34import android.os.Bundle;
 35import android.os.IBinder;
 36import android.text.InputType;
 37import android.util.DisplayMetrics;
 38import android.util.Log;
 39import android.view.MenuItem;
 40import android.view.View;
 41import android.view.inputmethod.InputMethodManager;
 42import android.widget.EditText;
 43import android.widget.ImageView;
 44import android.widget.Toast;
 45
 46public abstract class XmppActivity extends Activity {
 47
 48	protected static final int REQUEST_ANNOUNCE_PGP = 0x0101;
 49	protected static final int REQUEST_INVITE_TO_CONVERSATION = 0x0102;
 50
 51	public XmppConnectionService xmppConnectionService;
 52	public boolean xmppConnectionServiceBound = false;
 53	protected boolean handledViewIntent = false;
 54
 55	protected int mPrimaryTextColor;
 56	protected int mSecondaryTextColor;
 57	protected int mWarningTextColor;
 58	protected int mPrimaryColor;
 59
 60	private DisplayMetrics metrics;
 61
 62	protected interface OnValueEdited {
 63		public void onValueEdited(String value);
 64	}
 65
 66	public interface OnPresenceSelected {
 67		public void onPresenceSelected();
 68	}
 69
 70	protected ServiceConnection mConnection = new ServiceConnection() {
 71
 72		@Override
 73		public void onServiceConnected(ComponentName className, IBinder service) {
 74			XmppConnectionBinder binder = (XmppConnectionBinder) service;
 75			xmppConnectionService = binder.getService();
 76			xmppConnectionServiceBound = true;
 77			onBackendConnected();
 78		}
 79
 80		@Override
 81		public void onServiceDisconnected(ComponentName arg0) {
 82			xmppConnectionServiceBound = false;
 83		}
 84	};
 85
 86	@Override
 87	protected void onStart() {
 88		super.onStart();
 89		if (!xmppConnectionServiceBound) {
 90			connectToBackend();
 91		}
 92	}
 93
 94	public void connectToBackend() {
 95		Intent intent = new Intent(this, XmppConnectionService.class);
 96		intent.setAction("ui");
 97		startService(intent);
 98		bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
 99	}
100
101	@Override
102	protected void onStop() {
103		super.onStop();
104		if (xmppConnectionServiceBound) {
105			unbindService(mConnection);
106			xmppConnectionServiceBound = false;
107		}
108	}
109
110	protected void hideKeyboard() {
111		InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
112
113		View focus = getCurrentFocus();
114
115		if (focus != null) {
116
117			inputManager.hideSoftInputFromWindow(focus.getWindowToken(),
118					InputMethodManager.HIDE_NOT_ALWAYS);
119		}
120	}
121
122	public boolean hasPgp() {
123		return xmppConnectionService.getPgpEngine() != null;
124	}
125
126	public void showInstallPgpDialog() {
127		Builder builder = new AlertDialog.Builder(this);
128		builder.setTitle(getString(R.string.openkeychain_required));
129		builder.setIconAttribute(android.R.attr.alertDialogIcon);
130		builder.setMessage(getText(R.string.openkeychain_required_long));
131		builder.setNegativeButton(getString(R.string.cancel), null);
132		builder.setNeutralButton(getString(R.string.restart),
133				new OnClickListener() {
134
135					@Override
136					public void onClick(DialogInterface dialog, int which) {
137						if (xmppConnectionServiceBound) {
138							unbindService(mConnection);
139							xmppConnectionServiceBound = false;
140						}
141						stopService(new Intent(XmppActivity.this,
142								XmppConnectionService.class));
143						finish();
144					}
145				});
146		builder.setPositiveButton(getString(R.string.install),
147				new OnClickListener() {
148
149					@Override
150					public void onClick(DialogInterface dialog, int which) {
151						Uri uri = Uri
152								.parse("market://details?id=org.sufficientlysecure.keychain");
153						Intent intent = new Intent(Intent.ACTION_VIEW, uri);
154						startActivity(intent);
155						finish();
156					}
157				});
158		builder.create().show();
159	}
160
161	abstract void onBackendConnected();
162
163	public boolean onOptionsItemSelected(MenuItem item) {
164		switch (item.getItemId()) {
165		case R.id.action_settings:
166			startActivity(new Intent(this, SettingsActivity.class));
167			break;
168		case R.id.action_accounts:
169			startActivity(new Intent(this, ManageAccountActivity.class));
170			break;
171		case android.R.id.home:
172			finish();
173			break;
174		}
175		return super.onOptionsItemSelected(item);
176	}
177
178	@Override
179	protected void onCreate(Bundle savedInstanceState) {
180		super.onCreate(savedInstanceState);
181		metrics = getResources().getDisplayMetrics();
182		ExceptionHelper.init(getApplicationContext());
183		mPrimaryTextColor = getResources().getColor(R.color.primarytext);
184		mSecondaryTextColor = getResources().getColor(R.color.secondarytext);
185		mWarningTextColor = getResources().getColor(R.color.warningtext);
186		mPrimaryColor = getResources().getColor(R.color.primary);
187	}
188
189	public void switchToConversation(Conversation conversation) {
190		switchToConversation(conversation, null, false);
191	}
192
193	public void switchToConversation(Conversation conversation, String text,
194			boolean newTask) {
195		Intent viewConversationIntent = new Intent(this,
196				ConversationActivity.class);
197		viewConversationIntent.setAction(Intent.ACTION_VIEW);
198		viewConversationIntent.putExtra(ConversationActivity.CONVERSATION,
199				conversation.getUuid());
200		if (text != null) {
201			viewConversationIntent.putExtra(ConversationActivity.TEXT, text);
202		}
203		viewConversationIntent.setType(ConversationActivity.VIEW_CONVERSATION);
204		if (newTask) {
205			viewConversationIntent.setFlags(viewConversationIntent.getFlags()
206					| Intent.FLAG_ACTIVITY_NEW_TASK
207					| Intent.FLAG_ACTIVITY_SINGLE_TOP);
208		} else {
209			viewConversationIntent.setFlags(viewConversationIntent.getFlags()
210					| Intent.FLAG_ACTIVITY_CLEAR_TOP);
211		}
212		startActivity(viewConversationIntent);
213	}
214
215	public void switchToContactDetails(Contact contact) {
216		Intent intent = new Intent(this, ContactDetailsActivity.class);
217		intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
218		intent.putExtra("account", contact.getAccount().getJid());
219		intent.putExtra("contact", contact.getJid());
220		startActivity(intent);
221	}
222
223	protected void inviteToConversation(Conversation conversation) {
224		Intent intent = new Intent(getApplicationContext(),
225				ChooseContactActivity.class);
226		intent.putExtra("conversation", conversation.getUuid());
227		startActivityForResult(intent, REQUEST_INVITE_TO_CONVERSATION);
228	}
229
230	protected void announcePgp(Account account, final Conversation conversation) {
231		xmppConnectionService.getPgpEngine().generateSignature(account,
232				"online", new UiCallback<Account>() {
233
234					@Override
235					public void userInputRequried(PendingIntent pi,
236							Account account) {
237						try {
238							startIntentSenderForResult(pi.getIntentSender(),
239									REQUEST_ANNOUNCE_PGP, null, 0, 0, 0);
240						} catch (SendIntentException e) {
241						}
242					}
243
244					@Override
245					public void success(Account account) {
246						xmppConnectionService.databaseBackend
247								.updateAccount(account);
248						xmppConnectionService.sendPresencePacket(account,
249								xmppConnectionService.getPresenceGenerator()
250										.sendPresence(account));
251						if (conversation != null) {
252							conversation
253									.setNextEncryption(Message.ENCRYPTION_PGP);
254						}
255					}
256
257					@Override
258					public void error(int error, Account account) {
259						displayErrorDialog(error);
260					}
261				});
262	}
263
264	protected void displayErrorDialog(final int errorCode) {
265		runOnUiThread(new Runnable() {
266
267			@Override
268			public void run() {
269				AlertDialog.Builder builder = new AlertDialog.Builder(
270						XmppActivity.this);
271				builder.setIconAttribute(android.R.attr.alertDialogIcon);
272				builder.setTitle(getString(R.string.error));
273				builder.setMessage(errorCode);
274				builder.setNeutralButton(R.string.accept, null);
275				builder.create().show();
276			}
277		});
278
279	}
280
281	protected void showAddToRosterDialog(final Conversation conversation) {
282		String jid = conversation.getContactJid();
283		AlertDialog.Builder builder = new AlertDialog.Builder(this);
284		builder.setTitle(jid);
285		builder.setMessage(getString(R.string.not_in_roster));
286		builder.setNegativeButton(getString(R.string.cancel), null);
287		builder.setPositiveButton(getString(R.string.add_contact),
288				new DialogInterface.OnClickListener() {
289
290					@Override
291					public void onClick(DialogInterface dialog, int which) {
292						String jid = conversation.getContactJid();
293						Account account = conversation.getAccount();
294						Contact contact = account.getRoster().getContact(jid);
295						xmppConnectionService.createContact(contact);
296						switchToContactDetails(contact);
297					}
298				});
299		builder.create().show();
300	}
301
302	private void showAskForPresenceDialog(final Contact contact) {
303		AlertDialog.Builder builder = new AlertDialog.Builder(this);
304		builder.setTitle(contact.getJid());
305		builder.setMessage(R.string.request_presence_updates);
306		builder.setNegativeButton(getString(R.string.cancel), null);
307		builder.setPositiveButton(getString(R.string.request_now),
308				new DialogInterface.OnClickListener() {
309
310					@Override
311					public void onClick(DialogInterface dialog, int which) {
312						if (xmppConnectionServiceBound) {
313							xmppConnectionService.sendPresencePacket(contact.getAccount(),
314									xmppConnectionService.getPresenceGenerator()
315											.requestPresenceUpdatesFrom(contact));
316						}
317					}
318				});
319		builder.create().show();
320	}
321
322	protected void quickEdit(String previousValue, OnValueEdited callback) {
323		quickEdit(previousValue, callback, false);
324	}
325
326	protected void quickPasswordEdit(String previousValue,
327			OnValueEdited callback) {
328		quickEdit(previousValue, callback, true);
329	}
330
331	private void quickEdit(final String previousValue,
332			final OnValueEdited callback, boolean password) {
333		AlertDialog.Builder builder = new AlertDialog.Builder(this);
334		View view = (View) getLayoutInflater()
335				.inflate(R.layout.quickedit, null);
336		final EditText editor = (EditText) view.findViewById(R.id.editor);
337		OnClickListener mClickListener = new OnClickListener() {
338
339			@Override
340			public void onClick(DialogInterface dialog, int which) {
341				String value = editor.getText().toString();
342				if (!previousValue.equals(value) && value.trim().length() > 0) {
343					callback.onValueEdited(value);
344				}
345			}
346		};
347		if (password) {
348			editor.setInputType(InputType.TYPE_CLASS_TEXT
349					| InputType.TYPE_TEXT_VARIATION_PASSWORD);
350			editor.setHint(R.string.password);
351			builder.setPositiveButton(R.string.accept, mClickListener);
352		} else {
353			builder.setPositiveButton(R.string.edit, mClickListener);
354		}
355		editor.requestFocus();
356		editor.setText(previousValue);
357		builder.setView(view);
358		builder.setNegativeButton(R.string.cancel, null);
359		builder.create().show();
360	}
361
362	public void selectPresence(final Conversation conversation,
363			final OnPresenceSelected listener) {
364		Contact contact = conversation.getContact();
365		if (!contact.showInRoster()) {
366			showAddToRosterDialog(conversation);
367		} else {
368			Presences presences = contact.getPresences();
369			if (presences.size() == 0) {
370				if (!contact.getOption(Contact.Options.TO)
371						&& !contact.getOption(Contact.Options.ASKING)
372						&& contact.getAccount().getStatus() == Account.STATUS_ONLINE) {
373					showAskForPresenceDialog(contact);
374					return;
375				} else if (!contact.getOption(Contact.Options.TO)) {
376					Toast toast = Toast.makeText(this,
377							R.string.missing_presence_updates,
378							Toast.LENGTH_LONG);
379					toast.show();
380				}
381				conversation.setNextPresence(null);
382				listener.onPresenceSelected();
383			} else if (presences.size() == 1) {
384				String presence = (String) presences.asStringArray()[0];
385				conversation.setNextPresence(presence);
386				listener.onPresenceSelected();
387			} else {
388				final StringBuilder presence = new StringBuilder();
389				AlertDialog.Builder builder = new AlertDialog.Builder(this);
390				builder.setTitle(getString(R.string.choose_presence));
391				final String[] presencesArray = presences.asStringArray();
392				int preselectedPresence = 0;
393				for (int i = 0; i < presencesArray.length; ++i) {
394					if (presencesArray[i].equals(contact.lastseen.presence)) {
395						preselectedPresence = i;
396						break;
397					}
398				}
399				presence.append(presencesArray[preselectedPresence]);
400				builder.setSingleChoiceItems(presencesArray,
401						preselectedPresence,
402						new DialogInterface.OnClickListener() {
403
404							@Override
405							public void onClick(DialogInterface dialog,
406									int which) {
407								presence.delete(0, presence.length());
408								presence.append(presencesArray[which]);
409							}
410						});
411				builder.setNegativeButton(R.string.cancel, null);
412				builder.setPositiveButton(R.string.ok, new OnClickListener() {
413
414					@Override
415					public void onClick(DialogInterface dialog, int which) {
416						conversation.setNextPresence(presence.toString());
417						listener.onPresenceSelected();
418					}
419				});
420				builder.create().show();
421			}
422		}
423	}
424
425	protected void onActivityResult(int requestCode, int resultCode,
426			final Intent data) {
427		super.onActivityResult(requestCode, resultCode, data);
428		if (requestCode == REQUEST_INVITE_TO_CONVERSATION
429				&& resultCode == RESULT_OK) {
430			String contactJid = data.getStringExtra("contact");
431			String conversationUuid = data.getStringExtra("conversation");
432			Conversation conversation = xmppConnectionService
433					.findConversationByUuid(conversationUuid);
434			if (conversation.getMode() == Conversation.MODE_MULTI) {
435				xmppConnectionService.invite(conversation, contactJid);
436			}
437			Log.d(Config.LOGTAG, "inviting " + contactJid + " to "
438					+ conversation.getName());
439		}
440	}
441
442	public int getSecondaryTextColor() {
443		return this.mSecondaryTextColor;
444	}
445
446	public int getPrimaryTextColor() {
447		return this.mPrimaryTextColor;
448	}
449
450	public int getWarningTextColor() {
451		return this.mWarningTextColor;
452	}
453
454	public int getPrimaryColor() {
455		return this.mPrimaryColor;
456	}
457
458	class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
459		private final WeakReference<ImageView> imageViewReference;
460		private Message message = null;
461
462		public BitmapWorkerTask(ImageView imageView) {
463			imageViewReference = new WeakReference<ImageView>(imageView);
464		}
465
466		@Override
467		protected Bitmap doInBackground(Message... params) {
468			message = params[0];
469			try {
470				return xmppConnectionService.getFileBackend().getThumbnail(
471						message, (int) (metrics.density * 288), false);
472			} catch (FileNotFoundException e) {
473				return null;
474			}
475		}
476
477		@Override
478		protected void onPostExecute(Bitmap bitmap) {
479			if (imageViewReference != null && bitmap != null) {
480				final ImageView imageView = imageViewReference.get();
481				if (imageView != null) {
482					imageView.setImageBitmap(bitmap);
483					imageView.setBackgroundColor(0x00000000);
484				}
485			}
486		}
487	}
488
489	public void loadBitmap(Message message, ImageView imageView) {
490		Bitmap bm;
491		try {
492			bm = xmppConnectionService.getFileBackend().getThumbnail(message,
493					(int) (metrics.density * 288), true);
494		} catch (FileNotFoundException e) {
495			bm = null;
496		}
497		if (bm != null) {
498			imageView.setImageBitmap(bm);
499			imageView.setBackgroundColor(0x00000000);
500		} else {
501			if (cancelPotentialWork(message, imageView)) {
502				imageView.setBackgroundColor(0xff333333);
503				final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
504				final AsyncDrawable asyncDrawable = new AsyncDrawable(
505						getResources(), null, task);
506				imageView.setImageDrawable(asyncDrawable);
507				try {
508					task.execute(message);
509				} catch (RejectedExecutionException e) {
510					return;
511				}
512			}
513		}
514	}
515
516	public static boolean cancelPotentialWork(Message message,
517			ImageView imageView) {
518		final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
519
520		if (bitmapWorkerTask != null) {
521			final Message oldMessage = bitmapWorkerTask.message;
522			if (oldMessage == null || message != oldMessage) {
523				bitmapWorkerTask.cancel(true);
524			} else {
525				return false;
526			}
527		}
528		return true;
529	}
530
531	private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
532		if (imageView != null) {
533			final Drawable drawable = imageView.getDrawable();
534			if (drawable instanceof AsyncDrawable) {
535				final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
536				return asyncDrawable.getBitmapWorkerTask();
537			}
538		}
539		return null;
540	}
541
542	static class AsyncDrawable extends BitmapDrawable {
543		private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
544
545		public AsyncDrawable(Resources res, Bitmap bitmap,
546				BitmapWorkerTask bitmapWorkerTask) {
547			super(res, bitmap);
548			bitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>(
549					bitmapWorkerTask);
550		}
551
552		public BitmapWorkerTask getBitmapWorkerTask() {
553			return bitmapWorkerTaskReference.get();
554		}
555	}
556}