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