XmppActivity.java

  1package eu.siacs.conversations.ui;
  2
  3import android.annotation.SuppressLint;
  4import android.annotation.TargetApi;
  5import android.app.ActionBar;
  6import android.app.Activity;
  7import android.app.AlertDialog;
  8import android.app.AlertDialog.Builder;
  9import android.app.PendingIntent;
 10import android.content.ClipData;
 11import android.content.ClipboardManager;
 12import android.content.ComponentName;
 13import android.content.Context;
 14import android.content.DialogInterface;
 15import android.content.DialogInterface.OnClickListener;
 16import android.content.Intent;
 17import android.content.IntentSender.SendIntentException;
 18import android.content.ServiceConnection;
 19import android.content.SharedPreferences;
 20import android.content.pm.PackageManager;
 21import android.content.pm.ResolveInfo;
 22import android.content.res.Resources;
 23import android.graphics.Bitmap;
 24import android.graphics.Color;
 25import android.graphics.Point;
 26import android.graphics.drawable.BitmapDrawable;
 27import android.graphics.drawable.Drawable;
 28import android.net.Uri;
 29import android.nfc.NdefMessage;
 30import android.nfc.NdefRecord;
 31import android.nfc.NfcAdapter;
 32import android.nfc.NfcEvent;
 33import android.os.AsyncTask;
 34import android.os.Build;
 35import android.os.Bundle;
 36import android.os.Handler;
 37import android.os.IBinder;
 38import android.os.SystemClock;
 39import android.preference.PreferenceManager;
 40import android.text.InputType;
 41import android.util.DisplayMetrics;
 42import android.util.Log;
 43import android.view.MenuItem;
 44import android.view.View;
 45import android.view.inputmethod.InputMethodManager;
 46import android.widget.EditText;
 47import android.widget.ImageView;
 48import android.widget.Toast;
 49
 50import com.google.zxing.BarcodeFormat;
 51import com.google.zxing.EncodeHintType;
 52import com.google.zxing.WriterException;
 53import com.google.zxing.common.BitMatrix;
 54import com.google.zxing.qrcode.QRCodeWriter;
 55import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
 56
 57import net.java.otr4j.session.SessionID;
 58
 59import java.io.FileNotFoundException;
 60import java.lang.ref.WeakReference;
 61import java.util.ArrayList;
 62import java.util.Hashtable;
 63import java.util.List;
 64import java.util.concurrent.RejectedExecutionException;
 65
 66import eu.siacs.conversations.Config;
 67import eu.siacs.conversations.R;
 68import eu.siacs.conversations.entities.Account;
 69import eu.siacs.conversations.entities.Contact;
 70import eu.siacs.conversations.entities.Conversation;
 71import eu.siacs.conversations.entities.Message;
 72import eu.siacs.conversations.entities.MucOptions;
 73import eu.siacs.conversations.entities.Presences;
 74import eu.siacs.conversations.services.AvatarService;
 75import eu.siacs.conversations.services.XmppConnectionService;
 76import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
 77import eu.siacs.conversations.utils.ExceptionHelper;
 78import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
 79import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 80import eu.siacs.conversations.xmpp.jid.Jid;
 81
 82public abstract class XmppActivity extends Activity {
 83
 84	protected static final int REQUEST_ANNOUNCE_PGP = 0x0101;
 85	protected static final int REQUEST_INVITE_TO_CONVERSATION = 0x0102;
 86
 87	public XmppConnectionService xmppConnectionService;
 88	public boolean xmppConnectionServiceBound = false;
 89	protected boolean registeredListeners = false;
 90
 91	protected int mPrimaryTextColor;
 92	protected int mSecondaryTextColor;
 93	protected int mSecondaryBackgroundColor;
 94	protected int mColorRed;
 95	protected int mColorOrange;
 96	protected int mColorGreen;
 97	protected int mPrimaryColor;
 98
 99	protected boolean mUseSubject = true;
100
101	private DisplayMetrics metrics;
102	protected int mTheme;
103	protected boolean mUsingEnterKey = false;
104
105	private long mLastUiRefresh = 0;
106	private Handler mRefreshUiHandler = new Handler();
107	private Runnable mRefreshUiRunnable = new Runnable() {
108		@Override
109		public void run() {
110			mLastUiRefresh = SystemClock.elapsedRealtime();
111			refreshUiReal();
112		}
113	};
114
115
116	protected void refreshUi() {
117		final long diff = SystemClock.elapsedRealtime() - mLastUiRefresh;
118		if (diff > Config.REFRESH_UI_INTERVAL) {
119			mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable);
120			runOnUiThread(mRefreshUiRunnable);
121		} else {
122			final long next = Config.REFRESH_UI_INTERVAL - diff;
123			mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable);
124			mRefreshUiHandler.postDelayed(mRefreshUiRunnable,next);
125		}
126	}
127
128	protected void refreshUiReal() {
129
130	};
131
132	protected interface OnValueEdited {
133		public void onValueEdited(String value);
134	}
135
136	public interface OnPresenceSelected {
137		public void onPresenceSelected();
138	}
139
140	protected ServiceConnection mConnection = new ServiceConnection() {
141
142		@Override
143		public void onServiceConnected(ComponentName className, IBinder service) {
144			XmppConnectionBinder binder = (XmppConnectionBinder) service;
145			xmppConnectionService = binder.getService();
146			xmppConnectionServiceBound = true;
147			if (!registeredListeners && shouldRegisterListeners()) {
148				registerListeners();
149				registeredListeners = true;
150			}
151			onBackendConnected();
152		}
153
154		@Override
155		public void onServiceDisconnected(ComponentName arg0) {
156			xmppConnectionServiceBound = false;
157		}
158	};
159
160	@Override
161	protected void onStart() {
162		super.onStart();
163		if (!xmppConnectionServiceBound) {
164			connectToBackend();
165		} else {
166			if (!registeredListeners) {
167				this.registerListeners();
168				this.registeredListeners = true;
169			}
170			this.onBackendConnected();
171		}
172	}
173
174	@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
175	protected boolean shouldRegisterListeners() {
176		if  (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
177			return !isDestroyed() && !isFinishing();
178		} else {
179			return !isFinishing();
180		}
181	}
182
183	public void connectToBackend() {
184		Intent intent = new Intent(this, XmppConnectionService.class);
185		intent.setAction("ui");
186		startService(intent);
187		bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
188	}
189
190	@Override
191	protected void onStop() {
192		super.onStop();
193		if (xmppConnectionServiceBound) {
194			if (registeredListeners) {
195				this.unregisterListeners();
196				this.registeredListeners = false;
197			}
198			unbindService(mConnection);
199			xmppConnectionServiceBound = false;
200		}
201	}
202
203	protected void hideKeyboard() {
204		InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
205
206		View focus = getCurrentFocus();
207
208		if (focus != null) {
209
210			inputManager.hideSoftInputFromWindow(focus.getWindowToken(),
211					InputMethodManager.HIDE_NOT_ALWAYS);
212		}
213	}
214
215	public boolean hasPgp() {
216		return xmppConnectionService.getPgpEngine() != null;
217	}
218
219	public void showInstallPgpDialog() {
220		Builder builder = new AlertDialog.Builder(this);
221		builder.setTitle(getString(R.string.openkeychain_required));
222		builder.setIconAttribute(android.R.attr.alertDialogIcon);
223		builder.setMessage(getText(R.string.openkeychain_required_long));
224		builder.setNegativeButton(getString(R.string.cancel), null);
225		builder.setNeutralButton(getString(R.string.restart),
226				new OnClickListener() {
227
228					@Override
229					public void onClick(DialogInterface dialog, int which) {
230						if (xmppConnectionServiceBound) {
231							unbindService(mConnection);
232							xmppConnectionServiceBound = false;
233						}
234						stopService(new Intent(XmppActivity.this,
235									XmppConnectionService.class));
236						finish();
237					}
238				});
239		builder.setPositiveButton(getString(R.string.install),
240				new OnClickListener() {
241
242					@Override
243					public void onClick(DialogInterface dialog, int which) {
244						Uri uri = Uri
245							.parse("market://details?id=org.sufficientlysecure.keychain");
246						Intent marketIntent = new Intent(Intent.ACTION_VIEW,
247								uri);
248						PackageManager manager = getApplicationContext()
249							.getPackageManager();
250						List<ResolveInfo> infos = manager
251							.queryIntentActivities(marketIntent, 0);
252						if (infos.size() > 0) {
253							startActivity(marketIntent);
254						} else {
255							uri = Uri.parse("http://www.openkeychain.org/");
256							Intent browserIntent = new Intent(
257									Intent.ACTION_VIEW, uri);
258							startActivity(browserIntent);
259						}
260						finish();
261					}
262				});
263		builder.create().show();
264	}
265
266	abstract void onBackendConnected();
267
268	protected void registerListeners() {
269		if (this instanceof XmppConnectionService.OnConversationUpdate) {
270			this.xmppConnectionService.setOnConversationListChangedListener((XmppConnectionService.OnConversationUpdate) this);
271		}
272		if (this instanceof XmppConnectionService.OnAccountUpdate) {
273			this.xmppConnectionService.setOnAccountListChangedListener((XmppConnectionService.OnAccountUpdate) this);
274		}
275		if (this instanceof XmppConnectionService.OnRosterUpdate) {
276			this.xmppConnectionService.setOnRosterUpdateListener((XmppConnectionService.OnRosterUpdate) this);
277		}
278		if (this instanceof XmppConnectionService.OnMucRosterUpdate) {
279			this.xmppConnectionService.setOnMucRosterUpdateListener((XmppConnectionService.OnMucRosterUpdate) this);
280		}
281		if (this instanceof OnUpdateBlocklist) {
282			this.xmppConnectionService.setOnUpdateBlocklistListener((OnUpdateBlocklist) this);
283		}
284	}
285
286	protected void unregisterListeners() {
287		if (this instanceof XmppConnectionService.OnConversationUpdate) {
288			this.xmppConnectionService.removeOnConversationListChangedListener();
289		}
290		if (this instanceof XmppConnectionService.OnAccountUpdate) {
291			this.xmppConnectionService.removeOnAccountListChangedListener();
292		}
293		if (this instanceof XmppConnectionService.OnRosterUpdate) {
294			this.xmppConnectionService.removeOnRosterUpdateListener();
295		}
296		if (this instanceof XmppConnectionService.OnMucRosterUpdate) {
297			this.xmppConnectionService.removeOnMucRosterUpdateListener();
298		}
299		if (this instanceof OnUpdateBlocklist) {
300			this.xmppConnectionService.removeOnUpdateBlocklistListener();
301		}
302	}
303
304	@Override
305	public boolean onOptionsItemSelected(final MenuItem item) {
306		switch (item.getItemId()) {
307			case R.id.action_settings:
308				startActivity(new Intent(this, SettingsActivity.class));
309				break;
310			case R.id.action_accounts:
311				startActivity(new Intent(this, ManageAccountActivity.class));
312				break;
313			case android.R.id.home:
314				finish();
315				break;
316			case R.id.action_show_qr_code:
317				showQrCode();
318				break;
319		}
320		return super.onOptionsItemSelected(item);
321	}
322
323	@Override
324	protected void onCreate(Bundle savedInstanceState) {
325		super.onCreate(savedInstanceState);
326		metrics = getResources().getDisplayMetrics();
327		ExceptionHelper.init(getApplicationContext());
328		mPrimaryTextColor = getResources().getColor(R.color.primarytext);
329		mSecondaryTextColor = getResources().getColor(R.color.secondarytext);
330		mColorRed = getResources().getColor(R.color.red);
331		mColorOrange = getResources().getColor(R.color.orange);
332		mColorGreen = getResources().getColor(R.color.green);
333		mPrimaryColor = getResources().getColor(R.color.primary);
334		mSecondaryBackgroundColor = getResources().getColor(R.color.secondarybackground);
335		this.mTheme = findTheme();
336		setTheme(this.mTheme);
337		this.mUsingEnterKey = usingEnterKey();
338		mUseSubject = getPreferences().getBoolean("use_subject", true);
339		final ActionBar ab = getActionBar();
340		if (ab!=null) {
341			ab.setDisplayHomeAsUpEnabled(true);
342		}
343	}
344
345	protected boolean usingEnterKey() {
346		return getPreferences().getBoolean("display_enter_key", false);
347	}
348
349	protected SharedPreferences getPreferences() {
350		return PreferenceManager
351			.getDefaultSharedPreferences(getApplicationContext());
352	}
353
354	public boolean useSubjectToIdentifyConference() {
355		return mUseSubject;
356	}
357
358	public void switchToConversation(Conversation conversation) {
359		switchToConversation(conversation, null, false);
360	}
361
362	public void switchToConversation(Conversation conversation, String text,
363			boolean newTask) {
364		switchToConversation(conversation,text,null,newTask);
365	}
366
367	public void highlightInMuc(Conversation conversation, String nick) {
368		switchToConversation(conversation,null,nick,false);
369	}
370
371	private void switchToConversation(Conversation conversation, String text, String nick, boolean newTask) {
372		Intent viewConversationIntent = new Intent(this,
373				ConversationActivity.class);
374		viewConversationIntent.setAction(Intent.ACTION_VIEW);
375		viewConversationIntent.putExtra(ConversationActivity.CONVERSATION,
376				conversation.getUuid());
377		if (text != null) {
378			viewConversationIntent.putExtra(ConversationActivity.TEXT, text);
379		}
380		if (nick != null) {
381			viewConversationIntent.putExtra(ConversationActivity.NICK, nick);
382		}
383		viewConversationIntent.setType(ConversationActivity.VIEW_CONVERSATION);
384		if (newTask) {
385			viewConversationIntent.setFlags(viewConversationIntent.getFlags()
386					| Intent.FLAG_ACTIVITY_NEW_TASK
387					| Intent.FLAG_ACTIVITY_SINGLE_TOP);
388		} else {
389			viewConversationIntent.setFlags(viewConversationIntent.getFlags()
390					| Intent.FLAG_ACTIVITY_CLEAR_TOP);
391		}
392		startActivity(viewConversationIntent);
393		finish();
394	}
395
396	public void switchToContactDetails(Contact contact) {
397		Intent intent = new Intent(this, ContactDetailsActivity.class);
398		intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
399		intent.putExtra("account", contact.getAccount().getJid().toBareJid().toString());
400		intent.putExtra("contact", contact.getJid().toString());
401		startActivity(intent);
402	}
403
404	public void switchToAccount(Account account) {
405		Intent intent = new Intent(this, EditAccountActivity.class);
406		intent.putExtra("jid", account.getJid().toBareJid().toString());
407		startActivity(intent);
408	}
409
410	protected void inviteToConversation(Conversation conversation) {
411		Intent intent = new Intent(getApplicationContext(),
412				ChooseContactActivity.class);
413		List<String> contacts = new ArrayList<>();
414		if (conversation.getMode() == Conversation.MODE_MULTI) {
415			for (MucOptions.User user : conversation.getMucOptions().getUsers()) {
416				Jid jid = user.getJid();
417				if (jid != null) {
418					contacts.add(jid.toBareJid().toString());
419				}
420			}
421		} else {
422			contacts.add(conversation.getJid().toBareJid().toString());
423		}
424		intent.putExtra("filter_contacts", contacts.toArray(new String[contacts.size()]));
425		intent.putExtra("conversation", conversation.getUuid());
426		intent.putExtra("multiple", true);
427		startActivityForResult(intent, REQUEST_INVITE_TO_CONVERSATION);
428	}
429
430	protected void announcePgp(Account account, final Conversation conversation) {
431		xmppConnectionService.getPgpEngine().generateSignature(account,
432				"online", new UiCallback<Account>() {
433
434					@Override
435					public void userInputRequried(PendingIntent pi,
436							Account account) {
437						try {
438							startIntentSenderForResult(pi.getIntentSender(),
439									REQUEST_ANNOUNCE_PGP, null, 0, 0, 0);
440						} catch (final SendIntentException ignored) {
441						}
442					}
443
444					@Override
445					public void success(Account account) {
446						xmppConnectionService.databaseBackend
447							.updateAccount(account);
448						xmppConnectionService.sendPresence(account);
449						if (conversation != null) {
450							conversation
451								.setNextEncryption(Message.ENCRYPTION_PGP);
452							xmppConnectionService.databaseBackend
453								.updateConversation(conversation);
454						}
455					}
456
457					@Override
458					public void error(int error, Account account) {
459						displayErrorDialog(error);
460					}
461				});
462	}
463
464	protected void displayErrorDialog(final int errorCode) {
465		runOnUiThread(new Runnable() {
466
467			@Override
468			public void run() {
469				AlertDialog.Builder builder = new AlertDialog.Builder(
470						XmppActivity.this);
471				builder.setIconAttribute(android.R.attr.alertDialogIcon);
472				builder.setTitle(getString(R.string.error));
473				builder.setMessage(errorCode);
474				builder.setNeutralButton(R.string.accept, null);
475				builder.create().show();
476			}
477		});
478
479	}
480
481	protected void showAddToRosterDialog(final Conversation conversation) {
482		showAddToRosterDialog(conversation.getContact());
483	}
484
485	protected void showAddToRosterDialog(final Contact contact) {
486		AlertDialog.Builder builder = new AlertDialog.Builder(this);
487		builder.setTitle(contact.getJid().toString());
488		builder.setMessage(getString(R.string.not_in_roster));
489		builder.setNegativeButton(getString(R.string.cancel), null);
490		builder.setPositiveButton(getString(R.string.add_contact),
491				new DialogInterface.OnClickListener() {
492
493					@Override
494					public void onClick(DialogInterface dialog, int which) {
495						final Jid jid = contact.getJid();
496						Account account = contact.getAccount();
497						Contact contact = account.getRoster().getContact(jid);
498						xmppConnectionService.createContact(contact);
499					}
500				});
501		builder.create().show();
502	}
503
504	private void showAskForPresenceDialog(final Contact contact) {
505		AlertDialog.Builder builder = new AlertDialog.Builder(this);
506		builder.setTitle(contact.getJid().toString());
507		builder.setMessage(R.string.request_presence_updates);
508		builder.setNegativeButton(R.string.cancel, null);
509		builder.setPositiveButton(R.string.request_now,
510				new DialogInterface.OnClickListener() {
511
512					@Override
513					public void onClick(DialogInterface dialog, int which) {
514						if (xmppConnectionServiceBound) {
515							xmppConnectionService.sendPresencePacket(contact
516									.getAccount(), xmppConnectionService
517									.getPresenceGenerator()
518									.requestPresenceUpdatesFrom(contact));
519						}
520					}
521				});
522		builder.create().show();
523	}
524
525	private void warnMutalPresenceSubscription(final Conversation conversation,
526			final OnPresenceSelected listener) {
527		AlertDialog.Builder builder = new AlertDialog.Builder(this);
528		builder.setTitle(conversation.getContact().getJid().toString());
529		builder.setMessage(R.string.without_mutual_presence_updates);
530		builder.setNegativeButton(R.string.cancel, null);
531		builder.setPositiveButton(R.string.ignore, new OnClickListener() {
532
533			@Override
534			public void onClick(DialogInterface dialog, int which) {
535				conversation.setNextCounterpart(null);
536				if (listener != null) {
537					listener.onPresenceSelected();
538				}
539			}
540		});
541		builder.create().show();
542	}
543
544	protected void quickEdit(String previousValue, OnValueEdited callback) {
545		quickEdit(previousValue, callback, false);
546	}
547
548	protected void quickPasswordEdit(String previousValue,
549			OnValueEdited callback) {
550		quickEdit(previousValue, callback, true);
551	}
552
553	@SuppressLint("InflateParams")
554	private void quickEdit(final String previousValue,
555			final OnValueEdited callback, boolean password) {
556		AlertDialog.Builder builder = new AlertDialog.Builder(this);
557		View view = getLayoutInflater().inflate(R.layout.quickedit, null);
558		final EditText editor = (EditText) view.findViewById(R.id.editor);
559		OnClickListener mClickListener = new OnClickListener() {
560
561			@Override
562			public void onClick(DialogInterface dialog, int which) {
563				String value = editor.getText().toString();
564				if (!previousValue.equals(value) && value.trim().length() > 0) {
565					callback.onValueEdited(value);
566				}
567			}
568		};
569		if (password) {
570			editor.setInputType(InputType.TYPE_CLASS_TEXT
571					| InputType.TYPE_TEXT_VARIATION_PASSWORD);
572			editor.setHint(R.string.password);
573			builder.setPositiveButton(R.string.accept, mClickListener);
574		} else {
575			builder.setPositiveButton(R.string.edit, mClickListener);
576		}
577		editor.requestFocus();
578		editor.setText(previousValue);
579		builder.setView(view);
580		builder.setNegativeButton(R.string.cancel, null);
581		builder.create().show();
582	}
583
584	public void selectPresence(final Conversation conversation,
585			final OnPresenceSelected listener) {
586		final Contact contact = conversation.getContact();
587		if (conversation.hasValidOtrSession()) {
588			SessionID id = conversation.getOtrSession().getSessionID();
589			Jid jid;
590			try {
591				jid = Jid.fromString(id.getAccountID() + "/" + id.getUserID());
592			} catch (InvalidJidException e) {
593				jid = null;
594			}
595			conversation.setNextCounterpart(jid);
596			listener.onPresenceSelected();
597		} else 	if (!contact.showInRoster()) {
598			showAddToRosterDialog(conversation);
599		} else {
600			Presences presences = contact.getPresences();
601			if (presences.size() == 0) {
602				if (!contact.getOption(Contact.Options.TO)
603						&& !contact.getOption(Contact.Options.ASKING)
604						&& contact.getAccount().getStatus() == Account.State.ONLINE) {
605					showAskForPresenceDialog(contact);
606				} else if (!contact.getOption(Contact.Options.TO)
607						|| !contact.getOption(Contact.Options.FROM)) {
608					warnMutalPresenceSubscription(conversation, listener);
609				} else {
610					conversation.setNextCounterpart(null);
611					listener.onPresenceSelected();
612				}
613			} else if (presences.size() == 1) {
614				String presence = presences.asStringArray()[0];
615				try {
616					conversation.setNextCounterpart(Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),presence));
617				} catch (InvalidJidException e) {
618					conversation.setNextCounterpart(null);
619				}
620				listener.onPresenceSelected();
621			} else {
622				final StringBuilder presence = new StringBuilder();
623				AlertDialog.Builder builder = new AlertDialog.Builder(this);
624				builder.setTitle(getString(R.string.choose_presence));
625				final String[] presencesArray = presences.asStringArray();
626				int preselectedPresence = 0;
627				for (int i = 0; i < presencesArray.length; ++i) {
628					if (presencesArray[i].equals(contact.lastseen.presence)) {
629						preselectedPresence = i;
630						break;
631					}
632				}
633				presence.append(presencesArray[preselectedPresence]);
634				builder.setSingleChoiceItems(presencesArray,
635						preselectedPresence,
636						new DialogInterface.OnClickListener() {
637
638							@Override
639							public void onClick(DialogInterface dialog,
640									int which) {
641								presence.delete(0, presence.length());
642								presence.append(presencesArray[which]);
643							}
644						});
645				builder.setNegativeButton(R.string.cancel, null);
646				builder.setPositiveButton(R.string.ok, new OnClickListener() {
647
648					@Override
649					public void onClick(DialogInterface dialog, int which) {
650						try {
651							conversation.setNextCounterpart(Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),presence.toString()));
652						} catch (InvalidJidException e) {
653							conversation.setNextCounterpart(null);
654						}
655						listener.onPresenceSelected();
656					}
657				});
658				builder.create().show();
659			}
660		}
661	}
662
663	protected void onActivityResult(int requestCode, int resultCode,
664			final Intent data) {
665		super.onActivityResult(requestCode, resultCode, data);
666		if (requestCode == REQUEST_INVITE_TO_CONVERSATION
667				&& resultCode == RESULT_OK) {
668			try {
669				String conversationUuid = data.getStringExtra("conversation");
670				Conversation conversation = xmppConnectionService
671					.findConversationByUuid(conversationUuid);
672				List<Jid> jids = new ArrayList<Jid>();
673				if (data.getBooleanExtra("multiple", false)) {
674					String[] toAdd = data.getStringArrayExtra("contacts");
675					for (String item : toAdd) {
676						jids.add(Jid.fromString(item));
677					}
678				} else {
679					jids.add(Jid.fromString(data.getStringExtra("contact")));
680				}
681
682				if (conversation.getMode() == Conversation.MODE_MULTI) {
683					for (Jid jid : jids) {
684						xmppConnectionService.invite(conversation, jid);
685					}
686				} else {
687					jids.add(conversation.getJid().toBareJid());
688					xmppConnectionService.createAdhocConference(conversation.getAccount(), jids, adhocCallback);
689				}
690			} catch (final InvalidJidException ignored) {
691
692			}
693		}
694	}
695
696	private UiCallback<Conversation> adhocCallback = new UiCallback<Conversation>() {
697		@Override
698		public void success(final Conversation conversation) {
699			switchToConversation(conversation);
700			runOnUiThread(new Runnable() {
701				@Override
702				public void run() {
703					Toast.makeText(XmppActivity.this,R.string.conference_created,Toast.LENGTH_LONG).show();
704				}
705			});
706		}
707
708		@Override
709		public void error(final int errorCode, Conversation object) {
710			runOnUiThread(new Runnable() {
711				@Override
712				public void run() {
713					Toast.makeText(XmppActivity.this,errorCode,Toast.LENGTH_LONG).show();
714				}
715			});
716		}
717
718		@Override
719		public void userInputRequried(PendingIntent pi, Conversation object) {
720
721		}
722	};
723
724	public int getSecondaryTextColor() {
725		return this.mSecondaryTextColor;
726	}
727
728	public int getPrimaryTextColor() {
729		return this.mPrimaryTextColor;
730	}
731
732	public int getWarningTextColor() {
733		return this.mColorRed;
734	}
735
736	public int getPrimaryColor() {
737		return this.mPrimaryColor;
738	}
739
740	public int getOnlineColor() {
741		return this.mColorGreen;
742	}
743	
744	public int getSecondaryBackgroundColor() {
745		return this.mSecondaryBackgroundColor;
746	}
747
748	public int getPixel(int dp) {
749		DisplayMetrics metrics = getResources().getDisplayMetrics();
750		return ((int) (dp * metrics.density));
751	}
752
753	public boolean copyTextToClipboard(String text, int labelResId) {
754		ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
755		String label = getResources().getString(labelResId);
756		if (mClipBoardManager != null) {
757			ClipData mClipData = ClipData.newPlainText(label, text);
758			mClipBoardManager.setPrimaryClip(mClipData);
759			return true;
760		}
761		return false;
762	}
763
764	protected void registerNdefPushMessageCallback() {
765		NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
766		if (nfcAdapter != null && nfcAdapter.isEnabled()) {
767			nfcAdapter.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
768				@Override
769				public NdefMessage createNdefMessage(NfcEvent nfcEvent) {
770					return new NdefMessage(new NdefRecord[]{
771						NdefRecord.createUri(getShareableUri()),
772							NdefRecord.createApplicationRecord("eu.siacs.conversations")
773					});
774				}
775			}, this);
776		}
777	}
778
779	protected void unregisterNdefPushMessageCallback() {
780		NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
781		if (nfcAdapter != null && nfcAdapter.isEnabled()) {
782			nfcAdapter.setNdefPushMessageCallback(null,this);
783		}
784	}
785
786	protected String getShareableUri() {
787		return null;
788	}
789
790	@Override
791	public void onResume() {
792		super.onResume();
793		if (this.getShareableUri()!=null) {
794			this.registerNdefPushMessageCallback();
795		}
796	}
797
798	protected int findTheme() {
799		if (getPreferences().getBoolean("use_larger_font", false)) {
800			return R.style.ConversationsTheme_LargerText;
801		} else {
802			return R.style.ConversationsTheme;
803		}
804	}
805
806	@Override
807	public void onPause() {
808		super.onPause();
809		this.unregisterNdefPushMessageCallback();
810	}
811
812	protected void showQrCode() {
813		String uri = getShareableUri();
814		if (uri!=null) {
815			Point size = new Point();
816			getWindowManager().getDefaultDisplay().getSize(size);
817			final int width = (size.x < size.y ? size.x : size.y);
818			Bitmap bitmap = createQrCodeBitmap(uri, width);
819			ImageView view = new ImageView(this);
820			view.setImageBitmap(bitmap);
821			AlertDialog.Builder builder = new AlertDialog.Builder(this);
822			builder.setView(view);
823			builder.create().show();
824		}
825	}
826
827	protected Bitmap createQrCodeBitmap(String input, int size) {
828		Log.d(Config.LOGTAG,"qr code requested size: "+size);
829		try {
830			final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter();
831			final Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
832			hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
833			final BitMatrix result = QR_CODE_WRITER.encode(input, BarcodeFormat.QR_CODE, size, size, hints);
834			final int width = result.getWidth();
835			final int height = result.getHeight();
836			final int[] pixels = new int[width * height];
837			for (int y = 0; y < height; y++) {
838				final int offset = y * width;
839				for (int x = 0; x < width; x++) {
840					pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT;
841				}
842			}
843			final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
844			Log.d(Config.LOGTAG,"output size: "+width+"x"+height);
845			bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
846			return bitmap;
847		} catch (final WriterException e) {
848			return null;
849		}
850	}
851
852	public AvatarService avatarService() {
853		return xmppConnectionService.getAvatarService();
854	}
855
856	class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
857		private final WeakReference<ImageView> imageViewReference;
858		private Message message = null;
859
860		public BitmapWorkerTask(ImageView imageView) {
861			imageViewReference = new WeakReference<>(imageView);
862		}
863
864		@Override
865		protected Bitmap doInBackground(Message... params) {
866			message = params[0];
867			try {
868				return xmppConnectionService.getFileBackend().getThumbnail(
869						message, (int) (metrics.density * 288), false);
870			} catch (FileNotFoundException e) {
871				return null;
872			}
873		}
874
875		@Override
876		protected void onPostExecute(Bitmap bitmap) {
877			if (bitmap != null) {
878				final ImageView imageView = imageViewReference.get();
879				if (imageView != null) {
880					imageView.setImageBitmap(bitmap);
881					imageView.setBackgroundColor(0x00000000);
882				}
883			}
884		}
885	}
886
887	public void loadBitmap(Message message, ImageView imageView) {
888		Bitmap bm;
889		try {
890			bm = xmppConnectionService.getFileBackend().getThumbnail(message,
891					(int) (metrics.density * 288), true);
892		} catch (FileNotFoundException e) {
893			bm = null;
894		}
895		if (bm != null) {
896			imageView.setImageBitmap(bm);
897			imageView.setBackgroundColor(0x00000000);
898		} else {
899			if (cancelPotentialWork(message, imageView)) {
900				imageView.setBackgroundColor(0xff333333);
901				final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
902				final AsyncDrawable asyncDrawable = new AsyncDrawable(
903						getResources(), null, task);
904				imageView.setImageDrawable(asyncDrawable);
905				try {
906					task.execute(message);
907				} catch (final RejectedExecutionException ignored) {
908				}
909			}
910		}
911	}
912
913	public static boolean cancelPotentialWork(Message message,
914			ImageView imageView) {
915		final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
916
917		if (bitmapWorkerTask != null) {
918			final Message oldMessage = bitmapWorkerTask.message;
919			if (oldMessage == null || message != oldMessage) {
920				bitmapWorkerTask.cancel(true);
921			} else {
922				return false;
923			}
924		}
925		return true;
926	}
927
928	private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
929		if (imageView != null) {
930			final Drawable drawable = imageView.getDrawable();
931			if (drawable instanceof AsyncDrawable) {
932				final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
933				return asyncDrawable.getBitmapWorkerTask();
934			}
935		}
936		return null;
937	}
938
939	static class AsyncDrawable extends BitmapDrawable {
940		private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
941
942		public AsyncDrawable(Resources res, Bitmap bitmap,
943				BitmapWorkerTask bitmapWorkerTask) {
944			super(res, bitmap);
945			bitmapWorkerTaskReference = new WeakReference<>(
946					bitmapWorkerTask);
947		}
948
949		public BitmapWorkerTask getBitmapWorkerTask() {
950			return bitmapWorkerTaskReference.get();
951		}
952	}
953}