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