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