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}