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