1package eu.siacs.conversations.ui;
2
3import android.Manifest;
4import android.annotation.SuppressLint;
5import android.annotation.TargetApi;
6import android.app.ActionBar;
7import android.app.Activity;
8import android.app.AlertDialog;
9import android.app.AlertDialog.Builder;
10import android.app.PendingIntent;
11import android.content.ActivityNotFoundException;
12import android.content.ClipData;
13import android.content.ClipboardManager;
14import android.content.ComponentName;
15import android.content.Context;
16import android.content.DialogInterface;
17import android.content.DialogInterface.OnClickListener;
18import android.content.Intent;
19import android.content.IntentSender.SendIntentException;
20import android.content.ServiceConnection;
21import android.content.SharedPreferences;
22import android.content.pm.PackageManager;
23import android.content.pm.ResolveInfo;
24import android.content.res.Resources;
25import android.content.res.TypedArray;
26import android.graphics.Bitmap;
27import android.graphics.Color;
28import android.graphics.Point;
29import android.graphics.drawable.BitmapDrawable;
30import android.graphics.drawable.Drawable;
31import android.net.ConnectivityManager;
32import android.net.Uri;
33import android.nfc.NdefMessage;
34import android.nfc.NdefRecord;
35import android.nfc.NfcAdapter;
36import android.nfc.NfcEvent;
37import android.os.AsyncTask;
38import android.os.Build;
39import android.os.Bundle;
40import android.os.Handler;
41import android.os.IBinder;
42import android.os.PowerManager;
43import android.os.SystemClock;
44import android.preference.PreferenceManager;
45import android.text.InputType;
46import android.util.DisplayMetrics;
47import android.util.Log;
48import android.util.Pair;
49import android.view.MenuItem;
50import android.view.View;
51import android.view.inputmethod.InputMethodManager;
52import android.widget.CompoundButton;
53import android.widget.EditText;
54import android.widget.ImageView;
55import android.widget.LinearLayout;
56import android.widget.TextView;
57import android.widget.Toast;
58
59import com.google.zxing.BarcodeFormat;
60import com.google.zxing.EncodeHintType;
61import com.google.zxing.WriterException;
62import com.google.zxing.common.BitMatrix;
63import com.google.zxing.qrcode.QRCodeWriter;
64import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
65
66import net.java.otr4j.session.SessionID;
67
68import java.io.FileNotFoundException;
69import java.lang.ref.WeakReference;
70import java.util.ArrayList;
71import java.util.Collections;
72import java.util.HashMap;
73import java.util.Hashtable;
74import java.util.List;
75import java.util.Map;
76import java.util.concurrent.RejectedExecutionException;
77import java.util.concurrent.atomic.AtomicInteger;
78
79import eu.siacs.conversations.Config;
80import eu.siacs.conversations.R;
81import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
82import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
83import eu.siacs.conversations.entities.Account;
84import eu.siacs.conversations.entities.Contact;
85import eu.siacs.conversations.entities.Conversation;
86import eu.siacs.conversations.entities.Message;
87import eu.siacs.conversations.entities.MucOptions;
88import eu.siacs.conversations.entities.Presence;
89import eu.siacs.conversations.entities.Presences;
90import eu.siacs.conversations.entities.ServiceDiscoveryResult;
91import eu.siacs.conversations.services.AvatarService;
92import eu.siacs.conversations.services.XmppConnectionService;
93import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
94import eu.siacs.conversations.ui.widget.Switch;
95import eu.siacs.conversations.utils.CryptoHelper;
96import eu.siacs.conversations.utils.ExceptionHelper;
97import eu.siacs.conversations.utils.UIHelper;
98import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
99import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
100import eu.siacs.conversations.xmpp.jid.InvalidJidException;
101import eu.siacs.conversations.xmpp.jid.Jid;
102
103public abstract class XmppActivity extends Activity {
104
105 protected static final int REQUEST_ANNOUNCE_PGP = 0x0101;
106 protected static final int REQUEST_INVITE_TO_CONVERSATION = 0x0102;
107 protected static final int REQUEST_CHOOSE_PGP_ID = 0x0103;
108 protected static final int REQUEST_BATTERY_OP = 0x13849ff;
109
110 public static final String EXTRA_ACCOUNT = "account";
111
112 public XmppConnectionService xmppConnectionService;
113 public boolean xmppConnectionServiceBound = false;
114 protected boolean registeredListeners = false;
115
116 protected int mPrimaryTextColor;
117 protected int mSecondaryTextColor;
118 protected int mTertiaryTextColor;
119 protected int mPrimaryBackgroundColor;
120 protected int mSecondaryBackgroundColor;
121 protected int mColorRed;
122 protected int mColorOrange;
123 protected int mColorGreen;
124 protected int mPrimaryColor;
125
126 protected boolean mUseSubject = true;
127
128 private DisplayMetrics metrics;
129 protected int mTheme;
130 protected boolean mUsingEnterKey = false;
131
132 protected Toast mToast;
133
134 protected void hideToast() {
135 if (mToast != null) {
136 mToast.cancel();
137 }
138 }
139
140 protected void replaceToast(String msg) {
141 replaceToast(msg, true);
142 }
143
144 protected void replaceToast(String msg, boolean showlong) {
145 hideToast();
146 mToast = Toast.makeText(this, msg ,showlong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT);
147 mToast.show();
148 }
149
150 protected Runnable onOpenPGPKeyPublished = new Runnable() {
151 @Override
152 public void run() {
153 Toast.makeText(XmppActivity.this,R.string.openpgp_has_been_published, Toast.LENGTH_SHORT).show();
154 }
155 };
156
157 private long mLastUiRefresh = 0;
158 private Handler mRefreshUiHandler = new Handler();
159 private Runnable mRefreshUiRunnable = new Runnable() {
160 @Override
161 public void run() {
162 mLastUiRefresh = SystemClock.elapsedRealtime();
163 refreshUiReal();
164 }
165 };
166
167 protected ConferenceInvite mPendingConferenceInvite = null;
168
169
170 protected final void refreshUi() {
171 final long diff = SystemClock.elapsedRealtime() - mLastUiRefresh;
172 if (diff > Config.REFRESH_UI_INTERVAL) {
173 mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable);
174 runOnUiThread(mRefreshUiRunnable);
175 } else {
176 final long next = Config.REFRESH_UI_INTERVAL - diff;
177 mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable);
178 mRefreshUiHandler.postDelayed(mRefreshUiRunnable,next);
179 }
180 }
181
182 abstract protected void refreshUiReal();
183
184 protected interface OnValueEdited {
185 public void onValueEdited(String value);
186 }
187
188 public interface OnPresenceSelected {
189 public void onPresenceSelected();
190 }
191
192 protected ServiceConnection mConnection = new ServiceConnection() {
193
194 @Override
195 public void onServiceConnected(ComponentName className, IBinder service) {
196 XmppConnectionBinder binder = (XmppConnectionBinder) service;
197 xmppConnectionService = binder.getService();
198 xmppConnectionServiceBound = true;
199 if (!registeredListeners && shouldRegisterListeners()) {
200 registerListeners();
201 registeredListeners = true;
202 }
203 onBackendConnected();
204 }
205
206 @Override
207 public void onServiceDisconnected(ComponentName arg0) {
208 xmppConnectionServiceBound = false;
209 }
210 };
211
212 @Override
213 protected void onStart() {
214 super.onStart();
215 if (!xmppConnectionServiceBound) {
216 connectToBackend();
217 } else {
218 if (!registeredListeners) {
219 this.registerListeners();
220 this.registeredListeners = true;
221 }
222 this.onBackendConnected();
223 }
224 }
225
226 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
227 protected boolean shouldRegisterListeners() {
228 if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
229 return !isDestroyed() && !isFinishing();
230 } else {
231 return !isFinishing();
232 }
233 }
234
235 public void connectToBackend() {
236 Intent intent = new Intent(this, XmppConnectionService.class);
237 intent.setAction("ui");
238 startService(intent);
239 bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
240 }
241
242 @Override
243 protected void onStop() {
244 super.onStop();
245 if (xmppConnectionServiceBound) {
246 if (registeredListeners) {
247 this.unregisterListeners();
248 this.registeredListeners = false;
249 }
250 unbindService(mConnection);
251 xmppConnectionServiceBound = false;
252 }
253 }
254
255 protected void hideKeyboard() {
256 InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
257
258 View focus = getCurrentFocus();
259
260 if (focus != null) {
261
262 inputManager.hideSoftInputFromWindow(focus.getWindowToken(),
263 InputMethodManager.HIDE_NOT_ALWAYS);
264 }
265 }
266
267 public boolean hasPgp() {
268 return xmppConnectionService.getPgpEngine() != null;
269 }
270
271 public void showInstallPgpDialog() {
272 Builder builder = new AlertDialog.Builder(this);
273 builder.setTitle(getString(R.string.openkeychain_required));
274 builder.setIconAttribute(android.R.attr.alertDialogIcon);
275 builder.setMessage(getText(R.string.openkeychain_required_long));
276 builder.setNegativeButton(getString(R.string.cancel), null);
277 builder.setNeutralButton(getString(R.string.restart),
278 new OnClickListener() {
279
280 @Override
281 public void onClick(DialogInterface dialog, int which) {
282 if (xmppConnectionServiceBound) {
283 unbindService(mConnection);
284 xmppConnectionServiceBound = false;
285 }
286 stopService(new Intent(XmppActivity.this,
287 XmppConnectionService.class));
288 finish();
289 }
290 });
291 builder.setPositiveButton(getString(R.string.install),
292 new OnClickListener() {
293
294 @Override
295 public void onClick(DialogInterface dialog, int which) {
296 Uri uri = Uri
297 .parse("market://details?id=org.sufficientlysecure.keychain");
298 Intent marketIntent = new Intent(Intent.ACTION_VIEW,
299 uri);
300 PackageManager manager = getApplicationContext()
301 .getPackageManager();
302 List<ResolveInfo> infos = manager
303 .queryIntentActivities(marketIntent, 0);
304 if (infos.size() > 0) {
305 startActivity(marketIntent);
306 } else {
307 uri = Uri.parse("http://www.openkeychain.org/");
308 Intent browserIntent = new Intent(
309 Intent.ACTION_VIEW, uri);
310 startActivity(browserIntent);
311 }
312 finish();
313 }
314 });
315 builder.create().show();
316 }
317
318 abstract void onBackendConnected();
319
320 protected void registerListeners() {
321 if (this instanceof XmppConnectionService.OnConversationUpdate) {
322 this.xmppConnectionService.setOnConversationListChangedListener((XmppConnectionService.OnConversationUpdate) this);
323 }
324 if (this instanceof XmppConnectionService.OnAccountUpdate) {
325 this.xmppConnectionService.setOnAccountListChangedListener((XmppConnectionService.OnAccountUpdate) this);
326 }
327 if (this instanceof XmppConnectionService.OnCaptchaRequested) {
328 this.xmppConnectionService.setOnCaptchaRequestedListener((XmppConnectionService.OnCaptchaRequested) this);
329 }
330 if (this instanceof XmppConnectionService.OnRosterUpdate) {
331 this.xmppConnectionService.setOnRosterUpdateListener((XmppConnectionService.OnRosterUpdate) this);
332 }
333 if (this instanceof XmppConnectionService.OnMucRosterUpdate) {
334 this.xmppConnectionService.setOnMucRosterUpdateListener((XmppConnectionService.OnMucRosterUpdate) this);
335 }
336 if (this instanceof OnUpdateBlocklist) {
337 this.xmppConnectionService.setOnUpdateBlocklistListener((OnUpdateBlocklist) this);
338 }
339 if (this instanceof XmppConnectionService.OnShowErrorToast) {
340 this.xmppConnectionService.setOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this);
341 }
342 if (this instanceof OnKeyStatusUpdated) {
343 this.xmppConnectionService.setOnKeyStatusUpdatedListener((OnKeyStatusUpdated) this);
344 }
345 }
346
347 protected void unregisterListeners() {
348 if (this instanceof XmppConnectionService.OnConversationUpdate) {
349 this.xmppConnectionService.removeOnConversationListChangedListener();
350 }
351 if (this instanceof XmppConnectionService.OnAccountUpdate) {
352 this.xmppConnectionService.removeOnAccountListChangedListener();
353 }
354 if (this instanceof XmppConnectionService.OnCaptchaRequested) {
355 this.xmppConnectionService.removeOnCaptchaRequestedListener();
356 }
357 if (this instanceof XmppConnectionService.OnRosterUpdate) {
358 this.xmppConnectionService.removeOnRosterUpdateListener();
359 }
360 if (this instanceof XmppConnectionService.OnMucRosterUpdate) {
361 this.xmppConnectionService.removeOnMucRosterUpdateListener();
362 }
363 if (this instanceof OnUpdateBlocklist) {
364 this.xmppConnectionService.removeOnUpdateBlocklistListener();
365 }
366 if (this instanceof XmppConnectionService.OnShowErrorToast) {
367 this.xmppConnectionService.removeOnShowErrorToastListener();
368 }
369 if (this instanceof OnKeyStatusUpdated) {
370 this.xmppConnectionService.removeOnNewKeysAvailableListener();
371 }
372 }
373
374 @Override
375 public boolean onOptionsItemSelected(final MenuItem item) {
376 switch (item.getItemId()) {
377 case R.id.action_settings:
378 startActivity(new Intent(this, SettingsActivity.class));
379 break;
380 case R.id.action_accounts:
381 startActivity(new Intent(this, ManageAccountActivity.class));
382 break;
383 case android.R.id.home:
384 finish();
385 break;
386 case R.id.action_show_qr_code:
387 showQrCode();
388 break;
389 }
390 return super.onOptionsItemSelected(item);
391 }
392
393 @Override
394 protected void onCreate(Bundle savedInstanceState) {
395 super.onCreate(savedInstanceState);
396 metrics = getResources().getDisplayMetrics();
397 ExceptionHelper.init(getApplicationContext());
398
399 mPrimaryTextColor = getResources().getColor(R.color.black87);
400 mSecondaryTextColor = getResources().getColor(R.color.black54);
401 mTertiaryTextColor = getResources().getColor(R.color.black12);
402 mColorRed = getResources().getColor(R.color.red800);
403 mColorOrange = getResources().getColor(R.color.orange500);
404 mColorGreen = getResources().getColor(R.color.green500);
405 mPrimaryColor = getResources().getColor(R.color.primary500);
406 mPrimaryBackgroundColor = getResources().getColor(R.color.grey50);
407 mSecondaryBackgroundColor = getResources().getColor(R.color.grey200);
408
409 if(isDarkTheme()) {
410 mPrimaryTextColor = getResources().getColor(R.color.white);
411 mSecondaryTextColor = getResources().getColor(R.color.white70);
412 mTertiaryTextColor = getResources().getColor(R.color.white12);
413 mPrimaryBackgroundColor = getResources().getColor(R.color.grey800);
414 mSecondaryBackgroundColor = getResources().getColor(R.color.grey900);
415 }
416
417 this.mTheme = findTheme();
418 setTheme(this.mTheme);
419
420 this.mUsingEnterKey = usingEnterKey();
421 mUseSubject = getPreferences().getBoolean("use_subject", true);
422 final ActionBar ab = getActionBar();
423 if (ab!=null) {
424 ab.setDisplayHomeAsUpEnabled(true);
425 }
426 }
427
428 public boolean isDarkTheme() {
429 return getPreferences().getString("theme", "light").equals("dark");
430 }
431
432 public int getThemeResource(int r_attr_name, int r_drawable_def) {
433 int[] attrs = { r_attr_name };
434 TypedArray ta = this.getTheme().obtainStyledAttributes(attrs);
435
436 int res = ta.getResourceId(0, r_drawable_def);
437 ta.recycle();
438
439 return res;
440 }
441
442 protected boolean isOptimizingBattery() {
443 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
444 PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
445 return !pm.isIgnoringBatteryOptimizations(getPackageName());
446 } else {
447 return false;
448 }
449 }
450
451 protected boolean isAffectedByDataSaver() {
452 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
453 ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
454 return cm.isActiveNetworkMetered()
455 && cm.getRestrictBackgroundStatus() == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
456 } else {
457 return false;
458 }
459 }
460
461 protected boolean usingEnterKey() {
462 return getPreferences().getBoolean("display_enter_key", false);
463 }
464
465 protected SharedPreferences getPreferences() {
466 return PreferenceManager
467 .getDefaultSharedPreferences(getApplicationContext());
468 }
469
470 public boolean useSubjectToIdentifyConference() {
471 return mUseSubject;
472 }
473
474 public void switchToConversation(Conversation conversation) {
475 switchToConversation(conversation, null, false);
476 }
477
478 public void switchToConversation(Conversation conversation, String text,
479 boolean newTask) {
480 switchToConversation(conversation,text,null,false,newTask);
481 }
482
483 public void highlightInMuc(Conversation conversation, String nick) {
484 switchToConversation(conversation, null, nick, false, false);
485 }
486
487 public void privateMsgInMuc(Conversation conversation, String nick) {
488 switchToConversation(conversation, null, nick, true, false);
489 }
490
491 private void switchToConversation(Conversation conversation, String text, String nick, boolean pm, boolean newTask) {
492 Intent viewConversationIntent = new Intent(this,
493 ConversationActivity.class);
494 viewConversationIntent.setAction(ConversationActivity.ACTION_VIEW_CONVERSATION);
495 viewConversationIntent.putExtra(ConversationActivity.CONVERSATION,
496 conversation.getUuid());
497 if (text != null) {
498 viewConversationIntent.putExtra(ConversationActivity.TEXT, text);
499 }
500 if (nick != null) {
501 viewConversationIntent.putExtra(ConversationActivity.NICK, nick);
502 viewConversationIntent.putExtra(ConversationActivity.PRIVATE_MESSAGE,pm);
503 }
504 if (newTask) {
505 viewConversationIntent.setFlags(viewConversationIntent.getFlags()
506 | Intent.FLAG_ACTIVITY_NEW_TASK
507 | Intent.FLAG_ACTIVITY_SINGLE_TOP);
508 } else {
509 viewConversationIntent.setFlags(viewConversationIntent.getFlags()
510 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
511 }
512 startActivity(viewConversationIntent);
513 finish();
514 }
515
516 public void switchToContactDetails(Contact contact) {
517 switchToContactDetails(contact, null);
518 }
519
520 public void switchToContactDetails(Contact contact, String messageFingerprint) {
521 Intent intent = new Intent(this, ContactDetailsActivity.class);
522 intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
523 intent.putExtra(EXTRA_ACCOUNT, contact.getAccount().getJid().toBareJid().toString());
524 intent.putExtra("contact", contact.getJid().toString());
525 intent.putExtra("fingerprint", messageFingerprint);
526 startActivity(intent);
527 }
528
529 public void switchToAccount(Account account) {
530 switchToAccount(account, false);
531 }
532
533 public void switchToAccount(Account account, boolean init) {
534 Intent intent = new Intent(this, EditAccountActivity.class);
535 intent.putExtra("jid", account.getJid().toBareJid().toString());
536 intent.putExtra("init", init);
537 startActivity(intent);
538 }
539
540 protected void inviteToConversation(Conversation conversation) {
541 Intent intent = new Intent(getApplicationContext(),
542 ChooseContactActivity.class);
543 List<String> contacts = new ArrayList<>();
544 if (conversation.getMode() == Conversation.MODE_MULTI) {
545 for (MucOptions.User user : conversation.getMucOptions().getUsers(false)) {
546 Jid jid = user.getRealJid();
547 if (jid != null) {
548 contacts.add(jid.toBareJid().toString());
549 }
550 }
551 } else {
552 contacts.add(conversation.getJid().toBareJid().toString());
553 }
554 intent.putExtra("filter_contacts", contacts.toArray(new String[contacts.size()]));
555 intent.putExtra("conversation", conversation.getUuid());
556 intent.putExtra("multiple", true);
557 intent.putExtra("show_enter_jid", true);
558 intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString());
559 startActivityForResult(intent, REQUEST_INVITE_TO_CONVERSATION);
560 }
561
562 protected void announcePgp(Account account, final Conversation conversation, final Runnable onSuccess) {
563 if (account.getPgpId() == 0) {
564 choosePgpSignId(account);
565 } else {
566 String status = null;
567 if (manuallyChangePresence()) {
568 status = account.getPresenceStatusMessage();
569 }
570 if (status == null) {
571 status = "";
572 }
573 xmppConnectionService.getPgpEngine().generateSignature(account, status, new UiCallback<Account>() {
574
575 @Override
576 public void userInputRequried(PendingIntent pi, Account account) {
577 try {
578 startIntentSenderForResult(pi.getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0);
579 } catch (final SendIntentException ignored) {
580 }
581 }
582
583 @Override
584 public void success(Account account) {
585 xmppConnectionService.databaseBackend.updateAccount(account);
586 xmppConnectionService.sendPresence(account);
587 if (conversation != null) {
588 conversation.setNextEncryption(Message.ENCRYPTION_PGP);
589 xmppConnectionService.updateConversation(conversation);
590 refreshUi();
591 }
592 if (onSuccess != null) {
593 runOnUiThread(onSuccess);
594 }
595 }
596
597 @Override
598 public void error(int error, Account account) {
599 if (error == 0 && account != null) {
600 account.setPgpSignId(0);
601 account.unsetPgpSignature();
602 xmppConnectionService.databaseBackend.updateAccount(account);
603 choosePgpSignId(account);
604 } else {
605 displayErrorDialog(error);
606 }
607 }
608 });
609 }
610 }
611
612 protected boolean noAccountUsesPgp() {
613 if (!hasPgp()) {
614 return true;
615 }
616 for(Account account : xmppConnectionService.getAccounts()) {
617 if (account.getPgpId() != 0) {
618 return false;
619 }
620 }
621 return true;
622 }
623
624 @SuppressWarnings("deprecation")
625 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
626 protected void setListItemBackgroundOnView(View view) {
627 int sdk = android.os.Build.VERSION.SDK_INT;
628 if (sdk < android.os.Build.VERSION_CODES.JELLY_BEAN) {
629 view.setBackgroundDrawable(getResources().getDrawable(R.drawable.greybackground));
630 } else {
631 view.setBackground(getResources().getDrawable(R.drawable.greybackground));
632 }
633 }
634
635 protected void choosePgpSignId(Account account) {
636 xmppConnectionService.getPgpEngine().chooseKey(account, new UiCallback<Account>() {
637 @Override
638 public void success(Account account1) {
639 }
640
641 @Override
642 public void error(int errorCode, Account object) {
643
644 }
645
646 @Override
647 public void userInputRequried(PendingIntent pi, Account object) {
648 try {
649 startIntentSenderForResult(pi.getIntentSender(),
650 REQUEST_CHOOSE_PGP_ID, null, 0, 0, 0);
651 } catch (final SendIntentException ignored) {
652 }
653 }
654 });
655 }
656
657 protected void displayErrorDialog(final int errorCode) {
658 runOnUiThread(new Runnable() {
659
660 @Override
661 public void run() {
662 AlertDialog.Builder builder = new AlertDialog.Builder(
663 XmppActivity.this);
664 builder.setIconAttribute(android.R.attr.alertDialogIcon);
665 builder.setTitle(getString(R.string.error));
666 builder.setMessage(errorCode);
667 builder.setNeutralButton(R.string.accept, null);
668 builder.create().show();
669 }
670 });
671
672 }
673
674 protected void showAddToRosterDialog(final Conversation conversation) {
675 showAddToRosterDialog(conversation.getContact());
676 }
677
678 protected void showAddToRosterDialog(final Contact contact) {
679 AlertDialog.Builder builder = new AlertDialog.Builder(this);
680 builder.setTitle(contact.getJid().toString());
681 builder.setMessage(getString(R.string.not_in_roster));
682 builder.setNegativeButton(getString(R.string.cancel), null);
683 builder.setPositiveButton(getString(R.string.add_contact),
684 new DialogInterface.OnClickListener() {
685
686 @Override
687 public void onClick(DialogInterface dialog, int which) {
688 final Jid jid = contact.getJid();
689 Account account = contact.getAccount();
690 Contact contact = account.getRoster().getContact(jid);
691 xmppConnectionService.createContact(contact);
692 }
693 });
694 builder.create().show();
695 }
696
697 private void showAskForPresenceDialog(final Contact contact) {
698 AlertDialog.Builder builder = new AlertDialog.Builder(this);
699 builder.setTitle(contact.getJid().toString());
700 builder.setMessage(R.string.request_presence_updates);
701 builder.setNegativeButton(R.string.cancel, null);
702 builder.setPositiveButton(R.string.request_now,
703 new DialogInterface.OnClickListener() {
704
705 @Override
706 public void onClick(DialogInterface dialog, int which) {
707 if (xmppConnectionServiceBound) {
708 xmppConnectionService.sendPresencePacket(contact
709 .getAccount(), xmppConnectionService
710 .getPresenceGenerator()
711 .requestPresenceUpdatesFrom(contact));
712 }
713 }
714 });
715 builder.create().show();
716 }
717
718 private void warnMutalPresenceSubscription(final Conversation conversation,
719 final OnPresenceSelected listener) {
720 AlertDialog.Builder builder = new AlertDialog.Builder(this);
721 builder.setTitle(conversation.getContact().getJid().toString());
722 builder.setMessage(R.string.without_mutual_presence_updates);
723 builder.setNegativeButton(R.string.cancel, null);
724 builder.setPositiveButton(R.string.ignore, new OnClickListener() {
725
726 @Override
727 public void onClick(DialogInterface dialog, int which) {
728 conversation.setNextCounterpart(null);
729 if (listener != null) {
730 listener.onPresenceSelected();
731 }
732 }
733 });
734 builder.create().show();
735 }
736
737 protected void quickEdit(String previousValue, int hint, OnValueEdited callback) {
738 quickEdit(previousValue, callback, hint, false);
739 }
740
741 protected void quickPasswordEdit(String previousValue, OnValueEdited callback) {
742 quickEdit(previousValue, callback, R.string.password, true);
743 }
744
745 @SuppressLint("InflateParams")
746 private void quickEdit(final String previousValue,
747 final OnValueEdited callback,
748 final int hint,
749 boolean password) {
750 AlertDialog.Builder builder = new AlertDialog.Builder(this);
751 View view = getLayoutInflater().inflate(R.layout.quickedit, null);
752 final EditText editor = (EditText) view.findViewById(R.id.editor);
753 OnClickListener mClickListener = new OnClickListener() {
754
755 @Override
756 public void onClick(DialogInterface dialog, int which) {
757 String value = editor.getText().toString();
758 if (!value.equals(previousValue) && value.trim().length() > 0) {
759 callback.onValueEdited(value);
760 }
761 }
762 };
763 if (password) {
764 editor.setInputType(InputType.TYPE_CLASS_TEXT
765 | InputType.TYPE_TEXT_VARIATION_PASSWORD);
766 builder.setPositiveButton(R.string.accept, mClickListener);
767 } else {
768 builder.setPositiveButton(R.string.edit, mClickListener);
769 }
770 if (hint != 0) {
771 editor.setHint(hint);
772 }
773 editor.requestFocus();
774 editor.setText("");
775 if (previousValue != null) {
776 editor.getText().append(previousValue);
777 }
778 builder.setView(view);
779 builder.setNegativeButton(R.string.cancel, null);
780 builder.create().show();
781 }
782
783 protected boolean addFingerprintRow(LinearLayout keys, final Account account, final String fingerprint, boolean highlight, View.OnClickListener onKeyClickedListener) {
784 final FingerprintStatus status = account.getAxolotlService().getFingerprintTrust(fingerprint);
785 if (status == null) {
786 return false;
787 }
788 return addFingerprintRowWithListeners(keys, account, fingerprint, highlight, status, true,
789 new CompoundButton.OnCheckedChangeListener() {
790 @Override
791 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
792 account.getAxolotlService().setFingerprintTrust(fingerprint,FingerprintStatus.createActive(isChecked));
793 }
794 },
795 new View.OnClickListener() {
796 @Override
797 public void onClick(View v) {
798 account.getAxolotlService().setFingerprintTrust(fingerprint,FingerprintStatus.createActive(true));
799 v.setEnabled(true);
800 }
801 },
802 onKeyClickedListener
803
804 );
805 }
806
807 protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account,
808 final String fingerprint,
809 boolean highlight,
810 FingerprintStatus status,
811 boolean showTag,
812 CompoundButton.OnCheckedChangeListener
813 onCheckedChangeListener,
814 View.OnClickListener onClickListener,
815 View.OnClickListener onKeyClickedListener) {
816 if (status.isCompromised()) {
817 return false;
818 }
819 View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false);
820 TextView key = (TextView) view.findViewById(R.id.key);
821 key.setOnClickListener(onKeyClickedListener);
822 TextView keyType = (TextView) view.findViewById(R.id.key_type);
823 keyType.setOnClickListener(onKeyClickedListener);
824 Switch trustToggle = (Switch) view.findViewById(R.id.tgl_trust);
825 trustToggle.setVisibility(View.VISIBLE);
826 final View.OnLongClickListener purge = new View.OnLongClickListener() {
827 @Override
828 public boolean onLongClick(View v) {
829 showPurgeKeyDialog(account, fingerprint);
830 return true;
831 }
832 };
833 view.setOnLongClickListener(purge);
834 key.setOnLongClickListener(purge);
835 keyType.setOnLongClickListener(purge);
836 boolean x509 = Config.X509_VERIFICATION && status.getTrust() == FingerprintStatus.Trust.VERIFIED_X509;
837 final View.OnClickListener toast;
838 if (status.isActive()) {
839 key.setTextColor(getPrimaryTextColor());
840 keyType.setTextColor(getSecondaryTextColor());
841 trustToggle.setOnCheckedChangeListener(onCheckedChangeListener);
842 if (status.getTrust() == FingerprintStatus.Trust.UNDECIDED) {
843 trustToggle.setOnClickListener(onClickListener);
844 trustToggle.setEnabled(false);
845 } else {
846 trustToggle.setOnClickListener(null);
847 trustToggle.setChecked(status.isTrusted(), false);
848 trustToggle.setEnabled(true);
849 }
850 toast = new View.OnClickListener() {
851 @Override
852 public void onClick(View v) {
853 hideToast();
854 }
855 };
856 } else {
857 key.setTextColor(getTertiaryTextColor());
858 keyType.setTextColor(getTertiaryTextColor());
859 trustToggle.setOnClickListener(null);
860 trustToggle.setEnabled(false);
861 trustToggle.setChecked(status.isTrusted(), false);
862 toast = new View.OnClickListener() {
863 @Override
864 public void onClick(View v) {
865 replaceToast(getString(R.string.this_device_is_no_longer_in_use), false);
866 }
867 };
868 trustToggle.setOnClickListener(toast);
869 }
870 view.setOnClickListener(toast);
871 key.setOnClickListener(toast);
872 keyType.setOnClickListener(toast);
873 if (showTag) {
874 keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
875 } else {
876 keyType.setVisibility(View.GONE);
877 }
878 if (highlight) {
879 keyType.setTextColor(getResources().getColor(R.color.accent));
880 keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509_selected_message : R.string.omemo_fingerprint_selected_message));
881 } else {
882 keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
883 }
884
885 key.setText(CryptoHelper.prettifyFingerprint(fingerprint.substring(2)));
886
887 keys.addView(view);
888 return true;
889 }
890
891 public void showPurgeKeyDialog(final Account account, final String fingerprint) {
892 Builder builder = new Builder(this);
893 builder.setTitle(getString(R.string.purge_key));
894 builder.setIconAttribute(android.R.attr.alertDialogIcon);
895 builder.setMessage(getString(R.string.purge_key_desc_part1)
896 + "\n\n" + CryptoHelper.prettifyFingerprint(fingerprint.substring(2))
897 + "\n\n" + getString(R.string.purge_key_desc_part2));
898 builder.setNegativeButton(getString(R.string.cancel), null);
899 builder.setPositiveButton(getString(R.string.purge_key),
900 new DialogInterface.OnClickListener() {
901 @Override
902 public void onClick(DialogInterface dialog, int which) {
903 account.getAxolotlService().purgeKey(fingerprint);
904 refreshUi();
905 }
906 });
907 builder.create().show();
908 }
909
910 public boolean hasStoragePermission(int requestCode) {
911 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
912 if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
913 requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode);
914 return false;
915 } else {
916 return true;
917 }
918 } else {
919 return true;
920 }
921 }
922
923 public void selectPresence(final Conversation conversation,
924 final OnPresenceSelected listener) {
925 final Contact contact = conversation.getContact();
926 if (conversation.hasValidOtrSession()) {
927 SessionID id = conversation.getOtrSession().getSessionID();
928 Jid jid;
929 try {
930 jid = Jid.fromString(id.getAccountID() + "/" + id.getUserID());
931 } catch (InvalidJidException e) {
932 jid = null;
933 }
934 conversation.setNextCounterpart(jid);
935 listener.onPresenceSelected();
936 } else if (!contact.showInRoster()) {
937 showAddToRosterDialog(conversation);
938 } else {
939 final Presences presences = contact.getPresences();
940 if (presences.size() == 0) {
941 if (!contact.getOption(Contact.Options.TO)
942 && !contact.getOption(Contact.Options.ASKING)
943 && contact.getAccount().getStatus() == Account.State.ONLINE) {
944 showAskForPresenceDialog(contact);
945 } else if (!contact.getOption(Contact.Options.TO)
946 || !contact.getOption(Contact.Options.FROM)) {
947 warnMutalPresenceSubscription(conversation, listener);
948 } else {
949 conversation.setNextCounterpart(null);
950 listener.onPresenceSelected();
951 }
952 } else if (presences.size() == 1) {
953 String presence = presences.toResourceArray()[0];
954 try {
955 conversation.setNextCounterpart(Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),presence));
956 } catch (InvalidJidException e) {
957 conversation.setNextCounterpart(null);
958 }
959 listener.onPresenceSelected();
960 } else {
961 showPresenceSelectionDialog(presences,conversation,listener);
962 }
963 }
964 }
965
966 private void showPresenceSelectionDialog(Presences presences, final Conversation conversation, final OnPresenceSelected listener) {
967 final Contact contact = conversation.getContact();
968 AlertDialog.Builder builder = new AlertDialog.Builder(this);
969 builder.setTitle(getString(R.string.choose_presence));
970 final String[] resourceArray = presences.toResourceArray();
971 Pair<Map<String, String>, Map<String, String>> typeAndName = presences.toTypeAndNameMap();
972 final Map<String,String> resourceTypeMap = typeAndName.first;
973 final Map<String,String> resourceNameMap = typeAndName.second;
974 final String[] readableIdentities = new String[resourceArray.length];
975 final AtomicInteger selectedResource = new AtomicInteger(0);
976 for (int i = 0; i < resourceArray.length; ++i) {
977 String resource = resourceArray[i];
978 if (resource.equals(contact.getLastResource())) {
979 selectedResource.set(i);
980 }
981 String type = resourceTypeMap.get(resource);
982 String name = resourceNameMap.get(resource);
983 if (type != null) {
984 if (Collections.frequency(resourceTypeMap.values(),type) == 1) {
985 readableIdentities[i] = UIHelper.tranlasteType(this,type);
986 } else if (name != null) {
987 if (Collections.frequency(resourceNameMap.values(), name) == 1
988 || CryptoHelper.UUID_PATTERN.matcher(resource).matches()) {
989 readableIdentities[i] = UIHelper.tranlasteType(this,type) + " (" + name+")";
990 } else {
991 readableIdentities[i] = UIHelper.tranlasteType(this,type) + " (" + name +" / " + resource+")";
992 }
993 } else {
994 readableIdentities[i] = UIHelper.tranlasteType(this,type) + " (" + resource+")";
995 }
996 } else {
997 readableIdentities[i] = resource;
998 }
999 }
1000 builder.setSingleChoiceItems(readableIdentities,
1001 selectedResource.get(),
1002 new DialogInterface.OnClickListener() {
1003
1004 @Override
1005 public void onClick(DialogInterface dialog, int which) {
1006 selectedResource.set(which);
1007 }
1008 });
1009 builder.setNegativeButton(R.string.cancel, null);
1010 builder.setPositiveButton(R.string.ok, new OnClickListener() {
1011
1012 @Override
1013 public void onClick(DialogInterface dialog, int which) {
1014 try {
1015 Jid next = Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),resourceArray[selectedResource.get()]);
1016 conversation.setNextCounterpart(next);
1017 } catch (InvalidJidException e) {
1018 conversation.setNextCounterpart(null);
1019 }
1020 listener.onPresenceSelected();
1021 }
1022 });
1023 builder.create().show();
1024 }
1025
1026 protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
1027 super.onActivityResult(requestCode, resultCode, data);
1028 if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) {
1029 mPendingConferenceInvite = ConferenceInvite.parse(data);
1030 if (xmppConnectionServiceBound && mPendingConferenceInvite != null) {
1031 if (mPendingConferenceInvite.execute(this)) {
1032 mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG);
1033 mToast.show();
1034 }
1035 mPendingConferenceInvite = null;
1036 }
1037 }
1038 }
1039
1040
1041 private UiCallback<Conversation> adhocCallback = new UiCallback<Conversation>() {
1042 @Override
1043 public void success(final Conversation conversation) {
1044 runOnUiThread(new Runnable() {
1045 @Override
1046 public void run() {
1047 switchToConversation(conversation);
1048 hideToast();
1049 }
1050 });
1051 }
1052
1053 @Override
1054 public void error(final int errorCode, Conversation object) {
1055 runOnUiThread(new Runnable() {
1056 @Override
1057 public void run() {
1058 replaceToast(getString(errorCode));
1059 }
1060 });
1061 }
1062
1063 @Override
1064 public void userInputRequried(PendingIntent pi, Conversation object) {
1065
1066 }
1067 };
1068
1069 public int getTertiaryTextColor() {
1070 return this.mTertiaryTextColor;
1071 }
1072
1073 public int getSecondaryTextColor() {
1074 return this.mSecondaryTextColor;
1075 }
1076
1077 public int getPrimaryTextColor() {
1078 return this.mPrimaryTextColor;
1079 }
1080
1081 public int getWarningTextColor() {
1082 return this.mColorRed;
1083 }
1084
1085 public int getOnlineColor() {
1086 return this.mColorGreen;
1087 }
1088
1089 public int getPrimaryBackgroundColor() {
1090 return this.mPrimaryBackgroundColor;
1091 }
1092
1093 public int getSecondaryBackgroundColor() {
1094 return this.mSecondaryBackgroundColor;
1095 }
1096
1097 public int getPixel(int dp) {
1098 DisplayMetrics metrics = getResources().getDisplayMetrics();
1099 return ((int) (dp * metrics.density));
1100 }
1101
1102 public boolean copyTextToClipboard(String text, int labelResId) {
1103 ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
1104 String label = getResources().getString(labelResId);
1105 if (mClipBoardManager != null) {
1106 ClipData mClipData = ClipData.newPlainText(label, text);
1107 mClipBoardManager.setPrimaryClip(mClipData);
1108 return true;
1109 }
1110 return false;
1111 }
1112
1113 protected void registerNdefPushMessageCallback() {
1114 NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
1115 if (nfcAdapter != null && nfcAdapter.isEnabled()) {
1116 nfcAdapter.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
1117 @Override
1118 public NdefMessage createNdefMessage(NfcEvent nfcEvent) {
1119 return new NdefMessage(new NdefRecord[]{
1120 NdefRecord.createUri(getShareableUri()),
1121 NdefRecord.createApplicationRecord("eu.siacs.conversations")
1122 });
1123 }
1124 }, this);
1125 }
1126 }
1127
1128 protected boolean neverCompressPictures() {
1129 return getPreferences().getString("picture_compression", "auto").equals("never");
1130 }
1131
1132 protected boolean manuallyChangePresence() {
1133 return getPreferences().getBoolean("manually_change_presence", false);
1134 }
1135
1136 protected void unregisterNdefPushMessageCallback() {
1137 NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
1138 if (nfcAdapter != null && nfcAdapter.isEnabled()) {
1139 nfcAdapter.setNdefPushMessageCallback(null,this);
1140 }
1141 }
1142
1143 protected String getShareableUri() {
1144 return null;
1145 }
1146
1147 protected void shareUri() {
1148 String uri = getShareableUri();
1149 if (uri == null || uri.isEmpty()) {
1150 return;
1151 }
1152 Intent shareIntent = new Intent();
1153 shareIntent.setAction(Intent.ACTION_SEND);
1154 shareIntent.putExtra(Intent.EXTRA_TEXT, getShareableUri());
1155 shareIntent.setType("text/plain");
1156 try {
1157 startActivity(Intent.createChooser(shareIntent, getText(R.string.share_uri_with)));
1158 } catch (ActivityNotFoundException e) {
1159 Toast.makeText(this, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT).show();
1160 }
1161 }
1162
1163 @Override
1164 public void onResume() {
1165 super.onResume();
1166 if (this.getShareableUri()!=null) {
1167 this.registerNdefPushMessageCallback();
1168 }
1169 }
1170
1171 protected int findTheme() {
1172 Boolean dark = getPreferences().getString("theme", "light").equals("dark");
1173 Boolean larger = getPreferences().getBoolean("use_larger_font", false);
1174
1175 if(dark) {
1176 if(larger)
1177 return R.style.ConversationsTheme_Dark_LargerText;
1178 else
1179 return R.style.ConversationsTheme_Dark;
1180 } else {
1181 if (larger)
1182 return R.style.ConversationsTheme_LargerText;
1183 else
1184 return R.style.ConversationsTheme;
1185 }
1186 }
1187
1188 @Override
1189 public void onPause() {
1190 super.onPause();
1191 this.unregisterNdefPushMessageCallback();
1192 }
1193
1194 protected void showQrCode() {
1195 String uri = getShareableUri();
1196 if (uri!=null) {
1197 Point size = new Point();
1198 getWindowManager().getDefaultDisplay().getSize(size);
1199 final int width = (size.x < size.y ? size.x : size.y);
1200 Bitmap bitmap = createQrCodeBitmap(uri, width);
1201 ImageView view = new ImageView(this);
1202 view.setBackgroundColor(Color.WHITE);
1203 view.setImageBitmap(bitmap);
1204 AlertDialog.Builder builder = new AlertDialog.Builder(this);
1205 builder.setView(view);
1206 builder.create().show();
1207 }
1208 }
1209
1210 protected Bitmap createQrCodeBitmap(String input, int size) {
1211 Log.d(Config.LOGTAG,"qr code requested size: "+size);
1212 try {
1213 final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter();
1214 final Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
1215 hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
1216 final BitMatrix result = QR_CODE_WRITER.encode(input, BarcodeFormat.QR_CODE, size, size, hints);
1217 final int width = result.getWidth();
1218 final int height = result.getHeight();
1219 final int[] pixels = new int[width * height];
1220 for (int y = 0; y < height; y++) {
1221 final int offset = y * width;
1222 for (int x = 0; x < width; x++) {
1223 pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT;
1224 }
1225 }
1226 final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
1227 Log.d(Config.LOGTAG,"output size: "+width+"x"+height);
1228 bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
1229 return bitmap;
1230 } catch (final WriterException e) {
1231 return null;
1232 }
1233 }
1234
1235 protected Account extractAccount(Intent intent) {
1236 String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null;
1237 try {
1238 return jid != null ? xmppConnectionService.findAccountByJid(Jid.fromString(jid)) : null;
1239 } catch (InvalidJidException e) {
1240 return null;
1241 }
1242 }
1243
1244 public static class ConferenceInvite {
1245 private String uuid;
1246 private List<Jid> jids = new ArrayList<>();
1247
1248 public static ConferenceInvite parse(Intent data) {
1249 ConferenceInvite invite = new ConferenceInvite();
1250 invite.uuid = data.getStringExtra("conversation");
1251 if (invite.uuid == null) {
1252 return null;
1253 }
1254 try {
1255 if (data.getBooleanExtra("multiple", false)) {
1256 String[] toAdd = data.getStringArrayExtra("contacts");
1257 for (String item : toAdd) {
1258 invite.jids.add(Jid.fromString(item));
1259 }
1260 } else {
1261 invite.jids.add(Jid.fromString(data.getStringExtra("contact")));
1262 }
1263 } catch (final InvalidJidException ignored) {
1264 return null;
1265 }
1266 return invite;
1267 }
1268
1269 public boolean execute(XmppActivity activity) {
1270 XmppConnectionService service = activity.xmppConnectionService;
1271 Conversation conversation = service.findConversationByUuid(this.uuid);
1272 if (conversation == null) {
1273 return false;
1274 }
1275 if (conversation.getMode() == Conversation.MODE_MULTI) {
1276 for (Jid jid : jids) {
1277 service.invite(conversation, jid);
1278 }
1279 return false;
1280 } else {
1281 jids.add(conversation.getJid().toBareJid());
1282 service.createAdhocConference(conversation.getAccount(), null, jids, activity.adhocCallback);
1283 return true;
1284 }
1285 }
1286 }
1287
1288 public AvatarService avatarService() {
1289 return xmppConnectionService.getAvatarService();
1290 }
1291
1292 class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
1293 private final WeakReference<ImageView> imageViewReference;
1294 private Message message = null;
1295
1296 public BitmapWorkerTask(ImageView imageView) {
1297 imageViewReference = new WeakReference<>(imageView);
1298 }
1299
1300 @Override
1301 protected Bitmap doInBackground(Message... params) {
1302 if (isCancelled()) {
1303 return null;
1304 }
1305 message = params[0];
1306 try {
1307 return xmppConnectionService.getFileBackend().getThumbnail(
1308 message, (int) (metrics.density * 288), false);
1309 } catch (FileNotFoundException e) {
1310 return null;
1311 }
1312 }
1313
1314 @Override
1315 protected void onPostExecute(Bitmap bitmap) {
1316 if (bitmap != null && !isCancelled()) {
1317 final ImageView imageView = imageViewReference.get();
1318 if (imageView != null) {
1319 imageView.setImageBitmap(bitmap);
1320 imageView.setBackgroundColor(0x00000000);
1321 }
1322 }
1323 }
1324 }
1325
1326 public void loadBitmap(Message message, ImageView imageView) {
1327 Bitmap bm;
1328 try {
1329 bm = xmppConnectionService.getFileBackend().getThumbnail(message,
1330 (int) (metrics.density * 288), true);
1331 } catch (FileNotFoundException e) {
1332 bm = null;
1333 }
1334 if (bm != null) {
1335 cancelPotentialWork(message, imageView);
1336 imageView.setImageBitmap(bm);
1337 imageView.setBackgroundColor(0x00000000);
1338 } else {
1339 if (cancelPotentialWork(message, imageView)) {
1340 imageView.setBackgroundColor(0xff333333);
1341 imageView.setImageDrawable(null);
1342 final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
1343 final AsyncDrawable asyncDrawable = new AsyncDrawable(
1344 getResources(), null, task);
1345 imageView.setImageDrawable(asyncDrawable);
1346 try {
1347 task.execute(message);
1348 } catch (final RejectedExecutionException ignored) {
1349 ignored.printStackTrace();
1350 }
1351 }
1352 }
1353 }
1354
1355 public static boolean cancelPotentialWork(Message message, ImageView imageView) {
1356 final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
1357
1358 if (bitmapWorkerTask != null) {
1359 final Message oldMessage = bitmapWorkerTask.message;
1360 if (oldMessage == null || message != oldMessage) {
1361 bitmapWorkerTask.cancel(true);
1362 } else {
1363 return false;
1364 }
1365 }
1366 return true;
1367 }
1368
1369 private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
1370 if (imageView != null) {
1371 final Drawable drawable = imageView.getDrawable();
1372 if (drawable instanceof AsyncDrawable) {
1373 final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
1374 return asyncDrawable.getBitmapWorkerTask();
1375 }
1376 }
1377 return null;
1378 }
1379
1380 static class AsyncDrawable extends BitmapDrawable {
1381 private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
1382
1383 public AsyncDrawable(Resources res, Bitmap bitmap,
1384 BitmapWorkerTask bitmapWorkerTask) {
1385 super(res, bitmap);
1386 bitmapWorkerTaskReference = new WeakReference<>(
1387 bitmapWorkerTask);
1388 }
1389
1390 public BitmapWorkerTask getBitmapWorkerTask() {
1391 return bitmapWorkerTaskReference.get();
1392 }
1393 }
1394}