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