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(false));
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 trustToggle.setChecked(status.isTrusted(), false);
839 if (status.isActive()) {
840 key.setTextColor(getPrimaryTextColor());
841 keyType.setTextColor(getSecondaryTextColor());
842 trustToggle.setOnCheckedChangeListener(onCheckedChangeListener);
843 if (status.getTrust() == FingerprintStatus.Trust.UNDECIDED) {
844 trustToggle.setOnClickListener(onClickListener);
845 trustToggle.setEnabled(false);
846 } else {
847 trustToggle.setOnClickListener(null);
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 toast = new View.OnClickListener() {
862 @Override
863 public void onClick(View v) {
864 replaceToast(getString(R.string.this_device_is_no_longer_in_use), false);
865 }
866 };
867 trustToggle.setOnClickListener(toast);
868 }
869 view.setOnClickListener(toast);
870 key.setOnClickListener(toast);
871 keyType.setOnClickListener(toast);
872 if (showTag) {
873 keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
874 } else {
875 keyType.setVisibility(View.GONE);
876 }
877 if (highlight) {
878 keyType.setTextColor(getResources().getColor(R.color.accent));
879 keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509_selected_message : R.string.omemo_fingerprint_selected_message));
880 } else {
881 keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
882 }
883
884 key.setText(CryptoHelper.prettifyFingerprint(fingerprint.substring(2)));
885
886 keys.addView(view);
887 return true;
888 }
889
890 public void showPurgeKeyDialog(final Account account, final String fingerprint) {
891 Builder builder = new Builder(this);
892 builder.setTitle(getString(R.string.purge_key));
893 builder.setIconAttribute(android.R.attr.alertDialogIcon);
894 builder.setMessage(getString(R.string.purge_key_desc_part1)
895 + "\n\n" + CryptoHelper.prettifyFingerprint(fingerprint.substring(2))
896 + "\n\n" + getString(R.string.purge_key_desc_part2));
897 builder.setNegativeButton(getString(R.string.cancel), null);
898 builder.setPositiveButton(getString(R.string.purge_key),
899 new DialogInterface.OnClickListener() {
900 @Override
901 public void onClick(DialogInterface dialog, int which) {
902 account.getAxolotlService().purgeKey(fingerprint);
903 refreshUi();
904 }
905 });
906 builder.create().show();
907 }
908
909 public boolean hasStoragePermission(int requestCode) {
910 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
911 if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
912 requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode);
913 return false;
914 } else {
915 return true;
916 }
917 } else {
918 return true;
919 }
920 }
921
922 public void selectPresence(final Conversation conversation,
923 final OnPresenceSelected listener) {
924 final Contact contact = conversation.getContact();
925 if (conversation.hasValidOtrSession()) {
926 SessionID id = conversation.getOtrSession().getSessionID();
927 Jid jid;
928 try {
929 jid = Jid.fromString(id.getAccountID() + "/" + id.getUserID());
930 } catch (InvalidJidException e) {
931 jid = null;
932 }
933 conversation.setNextCounterpart(jid);
934 listener.onPresenceSelected();
935 } else if (!contact.showInRoster()) {
936 showAddToRosterDialog(conversation);
937 } else {
938 final Presences presences = contact.getPresences();
939 if (presences.size() == 0) {
940 if (!contact.getOption(Contact.Options.TO)
941 && !contact.getOption(Contact.Options.ASKING)
942 && contact.getAccount().getStatus() == Account.State.ONLINE) {
943 showAskForPresenceDialog(contact);
944 } else if (!contact.getOption(Contact.Options.TO)
945 || !contact.getOption(Contact.Options.FROM)) {
946 warnMutalPresenceSubscription(conversation, listener);
947 } else {
948 conversation.setNextCounterpart(null);
949 listener.onPresenceSelected();
950 }
951 } else if (presences.size() == 1) {
952 String presence = presences.toResourceArray()[0];
953 try {
954 conversation.setNextCounterpart(Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),presence));
955 } catch (InvalidJidException e) {
956 conversation.setNextCounterpart(null);
957 }
958 listener.onPresenceSelected();
959 } else {
960 showPresenceSelectionDialog(presences,conversation,listener);
961 }
962 }
963 }
964
965 private void showPresenceSelectionDialog(Presences presences, final Conversation conversation, final OnPresenceSelected listener) {
966 final Contact contact = conversation.getContact();
967 AlertDialog.Builder builder = new AlertDialog.Builder(this);
968 builder.setTitle(getString(R.string.choose_presence));
969 final String[] resourceArray = presences.toResourceArray();
970 Pair<Map<String, String>, Map<String, String>> typeAndName = presences.toTypeAndNameMap();
971 final Map<String,String> resourceTypeMap = typeAndName.first;
972 final Map<String,String> resourceNameMap = typeAndName.second;
973 final String[] readableIdentities = new String[resourceArray.length];
974 final AtomicInteger selectedResource = new AtomicInteger(0);
975 for (int i = 0; i < resourceArray.length; ++i) {
976 String resource = resourceArray[i];
977 if (resource.equals(contact.getLastResource())) {
978 selectedResource.set(i);
979 }
980 String type = resourceTypeMap.get(resource);
981 String name = resourceNameMap.get(resource);
982 if (type != null) {
983 if (Collections.frequency(resourceTypeMap.values(),type) == 1) {
984 readableIdentities[i] = UIHelper.tranlasteType(this,type);
985 } else if (name != null) {
986 if (Collections.frequency(resourceNameMap.values(), name) == 1
987 || CryptoHelper.UUID_PATTERN.matcher(resource).matches()) {
988 readableIdentities[i] = UIHelper.tranlasteType(this,type) + " (" + name+")";
989 } else {
990 readableIdentities[i] = UIHelper.tranlasteType(this,type) + " (" + name +" / " + resource+")";
991 }
992 } else {
993 readableIdentities[i] = UIHelper.tranlasteType(this,type) + " (" + resource+")";
994 }
995 } else {
996 readableIdentities[i] = resource;
997 }
998 }
999 builder.setSingleChoiceItems(readableIdentities,
1000 selectedResource.get(),
1001 new DialogInterface.OnClickListener() {
1002
1003 @Override
1004 public void onClick(DialogInterface dialog, int which) {
1005 selectedResource.set(which);
1006 }
1007 });
1008 builder.setNegativeButton(R.string.cancel, null);
1009 builder.setPositiveButton(R.string.ok, new OnClickListener() {
1010
1011 @Override
1012 public void onClick(DialogInterface dialog, int which) {
1013 try {
1014 Jid next = Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),resourceArray[selectedResource.get()]);
1015 conversation.setNextCounterpart(next);
1016 } catch (InvalidJidException e) {
1017 conversation.setNextCounterpart(null);
1018 }
1019 listener.onPresenceSelected();
1020 }
1021 });
1022 builder.create().show();
1023 }
1024
1025 protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
1026 super.onActivityResult(requestCode, resultCode, data);
1027 if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) {
1028 mPendingConferenceInvite = ConferenceInvite.parse(data);
1029 if (xmppConnectionServiceBound && mPendingConferenceInvite != null) {
1030 if (mPendingConferenceInvite.execute(this)) {
1031 mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG);
1032 mToast.show();
1033 }
1034 mPendingConferenceInvite = null;
1035 }
1036 }
1037 }
1038
1039
1040 private UiCallback<Conversation> adhocCallback = new UiCallback<Conversation>() {
1041 @Override
1042 public void success(final Conversation conversation) {
1043 runOnUiThread(new Runnable() {
1044 @Override
1045 public void run() {
1046 switchToConversation(conversation);
1047 hideToast();
1048 }
1049 });
1050 }
1051
1052 @Override
1053 public void error(final int errorCode, Conversation object) {
1054 runOnUiThread(new Runnable() {
1055 @Override
1056 public void run() {
1057 replaceToast(getString(errorCode));
1058 }
1059 });
1060 }
1061
1062 @Override
1063 public void userInputRequried(PendingIntent pi, Conversation object) {
1064
1065 }
1066 };
1067
1068 public int getTertiaryTextColor() {
1069 return this.mTertiaryTextColor;
1070 }
1071
1072 public int getSecondaryTextColor() {
1073 return this.mSecondaryTextColor;
1074 }
1075
1076 public int getPrimaryTextColor() {
1077 return this.mPrimaryTextColor;
1078 }
1079
1080 public int getWarningTextColor() {
1081 return this.mColorRed;
1082 }
1083
1084 public int getOnlineColor() {
1085 return this.mColorGreen;
1086 }
1087
1088 public int getPrimaryBackgroundColor() {
1089 return this.mPrimaryBackgroundColor;
1090 }
1091
1092 public int getSecondaryBackgroundColor() {
1093 return this.mSecondaryBackgroundColor;
1094 }
1095
1096 public int getPixel(int dp) {
1097 DisplayMetrics metrics = getResources().getDisplayMetrics();
1098 return ((int) (dp * metrics.density));
1099 }
1100
1101 public boolean copyTextToClipboard(String text, int labelResId) {
1102 ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
1103 String label = getResources().getString(labelResId);
1104 if (mClipBoardManager != null) {
1105 ClipData mClipData = ClipData.newPlainText(label, text);
1106 mClipBoardManager.setPrimaryClip(mClipData);
1107 return true;
1108 }
1109 return false;
1110 }
1111
1112 protected void registerNdefPushMessageCallback() {
1113 NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
1114 if (nfcAdapter != null && nfcAdapter.isEnabled()) {
1115 nfcAdapter.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
1116 @Override
1117 public NdefMessage createNdefMessage(NfcEvent nfcEvent) {
1118 return new NdefMessage(new NdefRecord[]{
1119 NdefRecord.createUri(getShareableUri()),
1120 NdefRecord.createApplicationRecord("eu.siacs.conversations")
1121 });
1122 }
1123 }, this);
1124 }
1125 }
1126
1127 protected boolean neverCompressPictures() {
1128 return getPreferences().getString("picture_compression", "auto").equals("never");
1129 }
1130
1131 protected boolean manuallyChangePresence() {
1132 return getPreferences().getBoolean("manually_change_presence", false);
1133 }
1134
1135 protected void unregisterNdefPushMessageCallback() {
1136 NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
1137 if (nfcAdapter != null && nfcAdapter.isEnabled()) {
1138 nfcAdapter.setNdefPushMessageCallback(null,this);
1139 }
1140 }
1141
1142 protected String getShareableUri() {
1143 return null;
1144 }
1145
1146 protected void shareUri() {
1147 String uri = getShareableUri();
1148 if (uri == null || uri.isEmpty()) {
1149 return;
1150 }
1151 Intent shareIntent = new Intent();
1152 shareIntent.setAction(Intent.ACTION_SEND);
1153 shareIntent.putExtra(Intent.EXTRA_TEXT, getShareableUri());
1154 shareIntent.setType("text/plain");
1155 try {
1156 startActivity(Intent.createChooser(shareIntent, getText(R.string.share_uri_with)));
1157 } catch (ActivityNotFoundException e) {
1158 Toast.makeText(this, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT).show();
1159 }
1160 }
1161
1162 @Override
1163 public void onResume() {
1164 super.onResume();
1165 if (this.getShareableUri()!=null) {
1166 this.registerNdefPushMessageCallback();
1167 }
1168 }
1169
1170 protected int findTheme() {
1171 Boolean dark = getPreferences().getString("theme", "light").equals("dark");
1172 Boolean larger = getPreferences().getBoolean("use_larger_font", false);
1173
1174 if(dark) {
1175 if(larger)
1176 return R.style.ConversationsTheme_Dark_LargerText;
1177 else
1178 return R.style.ConversationsTheme_Dark;
1179 } else {
1180 if (larger)
1181 return R.style.ConversationsTheme_LargerText;
1182 else
1183 return R.style.ConversationsTheme;
1184 }
1185 }
1186
1187 @Override
1188 public void onPause() {
1189 super.onPause();
1190 this.unregisterNdefPushMessageCallback();
1191 }
1192
1193 protected void showQrCode() {
1194 String uri = getShareableUri();
1195 if (uri!=null) {
1196 Point size = new Point();
1197 getWindowManager().getDefaultDisplay().getSize(size);
1198 final int width = (size.x < size.y ? size.x : size.y);
1199 Bitmap bitmap = createQrCodeBitmap(uri, width);
1200 ImageView view = new ImageView(this);
1201 view.setBackgroundColor(Color.WHITE);
1202 view.setImageBitmap(bitmap);
1203 AlertDialog.Builder builder = new AlertDialog.Builder(this);
1204 builder.setView(view);
1205 builder.create().show();
1206 }
1207 }
1208
1209 protected Bitmap createQrCodeBitmap(String input, int size) {
1210 Log.d(Config.LOGTAG,"qr code requested size: "+size);
1211 try {
1212 final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter();
1213 final Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
1214 hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
1215 final BitMatrix result = QR_CODE_WRITER.encode(input, BarcodeFormat.QR_CODE, size, size, hints);
1216 final int width = result.getWidth();
1217 final int height = result.getHeight();
1218 final int[] pixels = new int[width * height];
1219 for (int y = 0; y < height; y++) {
1220 final int offset = y * width;
1221 for (int x = 0; x < width; x++) {
1222 pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT;
1223 }
1224 }
1225 final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
1226 Log.d(Config.LOGTAG,"output size: "+width+"x"+height);
1227 bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
1228 return bitmap;
1229 } catch (final WriterException e) {
1230 return null;
1231 }
1232 }
1233
1234 protected Account extractAccount(Intent intent) {
1235 String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null;
1236 try {
1237 return jid != null ? xmppConnectionService.findAccountByJid(Jid.fromString(jid)) : null;
1238 } catch (InvalidJidException e) {
1239 return null;
1240 }
1241 }
1242
1243 public static class ConferenceInvite {
1244 private String uuid;
1245 private List<Jid> jids = new ArrayList<>();
1246
1247 public static ConferenceInvite parse(Intent data) {
1248 ConferenceInvite invite = new ConferenceInvite();
1249 invite.uuid = data.getStringExtra("conversation");
1250 if (invite.uuid == null) {
1251 return null;
1252 }
1253 try {
1254 if (data.getBooleanExtra("multiple", false)) {
1255 String[] toAdd = data.getStringArrayExtra("contacts");
1256 for (String item : toAdd) {
1257 invite.jids.add(Jid.fromString(item));
1258 }
1259 } else {
1260 invite.jids.add(Jid.fromString(data.getStringExtra("contact")));
1261 }
1262 } catch (final InvalidJidException ignored) {
1263 return null;
1264 }
1265 return invite;
1266 }
1267
1268 public boolean execute(XmppActivity activity) {
1269 XmppConnectionService service = activity.xmppConnectionService;
1270 Conversation conversation = service.findConversationByUuid(this.uuid);
1271 if (conversation == null) {
1272 return false;
1273 }
1274 if (conversation.getMode() == Conversation.MODE_MULTI) {
1275 for (Jid jid : jids) {
1276 service.invite(conversation, jid);
1277 }
1278 return false;
1279 } else {
1280 jids.add(conversation.getJid().toBareJid());
1281 service.createAdhocConference(conversation.getAccount(), null, jids, activity.adhocCallback);
1282 return true;
1283 }
1284 }
1285 }
1286
1287 public AvatarService avatarService() {
1288 return xmppConnectionService.getAvatarService();
1289 }
1290
1291 class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
1292 private final WeakReference<ImageView> imageViewReference;
1293 private Message message = null;
1294
1295 public BitmapWorkerTask(ImageView imageView) {
1296 imageViewReference = new WeakReference<>(imageView);
1297 }
1298
1299 @Override
1300 protected Bitmap doInBackground(Message... params) {
1301 if (isCancelled()) {
1302 return null;
1303 }
1304 message = params[0];
1305 try {
1306 return xmppConnectionService.getFileBackend().getThumbnail(
1307 message, (int) (metrics.density * 288), false);
1308 } catch (FileNotFoundException e) {
1309 return null;
1310 }
1311 }
1312
1313 @Override
1314 protected void onPostExecute(Bitmap bitmap) {
1315 if (bitmap != null && !isCancelled()) {
1316 final ImageView imageView = imageViewReference.get();
1317 if (imageView != null) {
1318 imageView.setImageBitmap(bitmap);
1319 imageView.setBackgroundColor(0x00000000);
1320 }
1321 }
1322 }
1323 }
1324
1325 public void loadBitmap(Message message, ImageView imageView) {
1326 Bitmap bm;
1327 try {
1328 bm = xmppConnectionService.getFileBackend().getThumbnail(message,
1329 (int) (metrics.density * 288), true);
1330 } catch (FileNotFoundException e) {
1331 bm = null;
1332 }
1333 if (bm != null) {
1334 cancelPotentialWork(message, imageView);
1335 imageView.setImageBitmap(bm);
1336 imageView.setBackgroundColor(0x00000000);
1337 } else {
1338 if (cancelPotentialWork(message, imageView)) {
1339 imageView.setBackgroundColor(0xff333333);
1340 imageView.setImageDrawable(null);
1341 final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
1342 final AsyncDrawable asyncDrawable = new AsyncDrawable(
1343 getResources(), null, task);
1344 imageView.setImageDrawable(asyncDrawable);
1345 try {
1346 task.execute(message);
1347 } catch (final RejectedExecutionException ignored) {
1348 ignored.printStackTrace();
1349 }
1350 }
1351 }
1352 }
1353
1354 public static boolean cancelPotentialWork(Message message, ImageView imageView) {
1355 final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
1356
1357 if (bitmapWorkerTask != null) {
1358 final Message oldMessage = bitmapWorkerTask.message;
1359 if (oldMessage == null || message != oldMessage) {
1360 bitmapWorkerTask.cancel(true);
1361 } else {
1362 return false;
1363 }
1364 }
1365 return true;
1366 }
1367
1368 private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
1369 if (imageView != null) {
1370 final Drawable drawable = imageView.getDrawable();
1371 if (drawable instanceof AsyncDrawable) {
1372 final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
1373 return asyncDrawable.getBitmapWorkerTask();
1374 }
1375 }
1376 return null;
1377 }
1378
1379 static class AsyncDrawable extends BitmapDrawable {
1380 private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
1381
1382 public AsyncDrawable(Resources res, Bitmap bitmap,
1383 BitmapWorkerTask bitmapWorkerTask) {
1384 super(res, bitmap);
1385 bitmapWorkerTaskReference = new WeakReference<>(
1386 bitmapWorkerTask);
1387 }
1388
1389 public BitmapWorkerTask getBitmapWorkerTask() {
1390 return bitmapWorkerTaskReference.get();
1391 }
1392 }
1393}