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