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(Intent.ACTION_VIEW);
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 viewConversationIntent.setType(ConversationActivity.VIEW_CONVERSATION);
485 if (newTask) {
486 viewConversationIntent.setFlags(viewConversationIntent.getFlags()
487 | Intent.FLAG_ACTIVITY_NEW_TASK
488 | Intent.FLAG_ACTIVITY_SINGLE_TOP);
489 } else {
490 viewConversationIntent.setFlags(viewConversationIntent.getFlags()
491 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
492 }
493 startActivity(viewConversationIntent);
494 finish();
495 }
496
497 public void switchToContactDetails(Contact contact) {
498 switchToContactDetails(contact, null);
499 }
500
501 public void switchToContactDetails(Contact contact, String messageFingerprint) {
502 Intent intent = new Intent(this, ContactDetailsActivity.class);
503 intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
504 intent.putExtra(EXTRA_ACCOUNT, contact.getAccount().getJid().toBareJid().toString());
505 intent.putExtra("contact", contact.getJid().toString());
506 intent.putExtra("fingerprint", messageFingerprint);
507 startActivity(intent);
508 }
509
510 public void switchToAccount(Account account) {
511 switchToAccount(account, false);
512 }
513
514 public void switchToAccount(Account account, boolean init) {
515 Intent intent = new Intent(this, EditAccountActivity.class);
516 intent.putExtra("jid", account.getJid().toBareJid().toString());
517 intent.putExtra("init", init);
518 startActivity(intent);
519 }
520
521 protected void inviteToConversation(Conversation conversation) {
522 Intent intent = new Intent(getApplicationContext(),
523 ChooseContactActivity.class);
524 List<String> contacts = new ArrayList<>();
525 if (conversation.getMode() == Conversation.MODE_MULTI) {
526 for (MucOptions.User user : conversation.getMucOptions().getUsers(false)) {
527 Jid jid = user.getRealJid();
528 if (jid != null) {
529 contacts.add(jid.toBareJid().toString());
530 }
531 }
532 } else {
533 contacts.add(conversation.getJid().toBareJid().toString());
534 }
535 intent.putExtra("filter_contacts", contacts.toArray(new String[contacts.size()]));
536 intent.putExtra("conversation", conversation.getUuid());
537 intent.putExtra("multiple", true);
538 intent.putExtra("show_enter_jid", true);
539 intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString());
540 startActivityForResult(intent, REQUEST_INVITE_TO_CONVERSATION);
541 }
542
543 protected void announcePgp(Account account, final Conversation conversation, final Runnable onSuccess) {
544 if (account.getPgpId() == 0) {
545 choosePgpSignId(account);
546 } else {
547 String status = null;
548 if (manuallyChangePresence()) {
549 status = account.getPresenceStatusMessage();
550 }
551 if (status == null) {
552 status = "";
553 }
554 xmppConnectionService.getPgpEngine().generateSignature(account, status, new UiCallback<Account>() {
555
556 @Override
557 public void userInputRequried(PendingIntent pi, Account account) {
558 try {
559 startIntentSenderForResult(pi.getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0);
560 } catch (final SendIntentException ignored) {
561 }
562 }
563
564 @Override
565 public void success(Account account) {
566 xmppConnectionService.databaseBackend.updateAccount(account);
567 xmppConnectionService.sendPresence(account);
568 if (conversation != null) {
569 conversation.setNextEncryption(Message.ENCRYPTION_PGP);
570 xmppConnectionService.databaseBackend.updateConversation(conversation);
571 refreshUi();
572 }
573 if (onSuccess != null) {
574 runOnUiThread(onSuccess);
575 }
576 }
577
578 @Override
579 public void error(int error, Account account) {
580 displayErrorDialog(error);
581 }
582 });
583 }
584 }
585
586 protected boolean noAccountUsesPgp() {
587 if (!hasPgp()) {
588 return true;
589 }
590 for(Account account : xmppConnectionService.getAccounts()) {
591 if (account.getPgpId() != 0) {
592 return false;
593 }
594 }
595 return true;
596 }
597
598 @SuppressWarnings("deprecation")
599 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
600 protected void setListItemBackgroundOnView(View view) {
601 int sdk = android.os.Build.VERSION.SDK_INT;
602 if (sdk < android.os.Build.VERSION_CODES.JELLY_BEAN) {
603 view.setBackgroundDrawable(getResources().getDrawable(R.drawable.greybackground));
604 } else {
605 view.setBackground(getResources().getDrawable(R.drawable.greybackground));
606 }
607 }
608
609 protected void choosePgpSignId(Account account) {
610 xmppConnectionService.getPgpEngine().chooseKey(account, new UiCallback<Account>() {
611 @Override
612 public void success(Account account1) {
613 }
614
615 @Override
616 public void error(int errorCode, Account object) {
617
618 }
619
620 @Override
621 public void userInputRequried(PendingIntent pi, Account object) {
622 try {
623 startIntentSenderForResult(pi.getIntentSender(),
624 REQUEST_CHOOSE_PGP_ID, null, 0, 0, 0);
625 } catch (final SendIntentException ignored) {
626 }
627 }
628 });
629 }
630
631 protected void displayErrorDialog(final int errorCode) {
632 runOnUiThread(new Runnable() {
633
634 @Override
635 public void run() {
636 AlertDialog.Builder builder = new AlertDialog.Builder(
637 XmppActivity.this);
638 builder.setIconAttribute(android.R.attr.alertDialogIcon);
639 builder.setTitle(getString(R.string.error));
640 builder.setMessage(errorCode);
641 builder.setNeutralButton(R.string.accept, null);
642 builder.create().show();
643 }
644 });
645
646 }
647
648 protected void showAddToRosterDialog(final Conversation conversation) {
649 showAddToRosterDialog(conversation.getContact());
650 }
651
652 protected void showAddToRosterDialog(final Contact contact) {
653 AlertDialog.Builder builder = new AlertDialog.Builder(this);
654 builder.setTitle(contact.getJid().toString());
655 builder.setMessage(getString(R.string.not_in_roster));
656 builder.setNegativeButton(getString(R.string.cancel), null);
657 builder.setPositiveButton(getString(R.string.add_contact),
658 new DialogInterface.OnClickListener() {
659
660 @Override
661 public void onClick(DialogInterface dialog, int which) {
662 final Jid jid = contact.getJid();
663 Account account = contact.getAccount();
664 Contact contact = account.getRoster().getContact(jid);
665 xmppConnectionService.createContact(contact);
666 }
667 });
668 builder.create().show();
669 }
670
671 private void showAskForPresenceDialog(final Contact contact) {
672 AlertDialog.Builder builder = new AlertDialog.Builder(this);
673 builder.setTitle(contact.getJid().toString());
674 builder.setMessage(R.string.request_presence_updates);
675 builder.setNegativeButton(R.string.cancel, null);
676 builder.setPositiveButton(R.string.request_now,
677 new DialogInterface.OnClickListener() {
678
679 @Override
680 public void onClick(DialogInterface dialog, int which) {
681 if (xmppConnectionServiceBound) {
682 xmppConnectionService.sendPresencePacket(contact
683 .getAccount(), xmppConnectionService
684 .getPresenceGenerator()
685 .requestPresenceUpdatesFrom(contact));
686 }
687 }
688 });
689 builder.create().show();
690 }
691
692 private void warnMutalPresenceSubscription(final Conversation conversation,
693 final OnPresenceSelected listener) {
694 AlertDialog.Builder builder = new AlertDialog.Builder(this);
695 builder.setTitle(conversation.getContact().getJid().toString());
696 builder.setMessage(R.string.without_mutual_presence_updates);
697 builder.setNegativeButton(R.string.cancel, null);
698 builder.setPositiveButton(R.string.ignore, new OnClickListener() {
699
700 @Override
701 public void onClick(DialogInterface dialog, int which) {
702 conversation.setNextCounterpart(null);
703 if (listener != null) {
704 listener.onPresenceSelected();
705 }
706 }
707 });
708 builder.create().show();
709 }
710
711 protected void quickEdit(String previousValue, int hint, OnValueEdited callback) {
712 quickEdit(previousValue, callback, hint, false);
713 }
714
715 protected void quickPasswordEdit(String previousValue, OnValueEdited callback) {
716 quickEdit(previousValue, callback, R.string.password, true);
717 }
718
719 @SuppressLint("InflateParams")
720 private void quickEdit(final String previousValue,
721 final OnValueEdited callback,
722 final int hint,
723 boolean password) {
724 AlertDialog.Builder builder = new AlertDialog.Builder(this);
725 View view = getLayoutInflater().inflate(R.layout.quickedit, null);
726 final EditText editor = (EditText) view.findViewById(R.id.editor);
727 OnClickListener mClickListener = new OnClickListener() {
728
729 @Override
730 public void onClick(DialogInterface dialog, int which) {
731 String value = editor.getText().toString();
732 if (!value.equals(previousValue) && value.trim().length() > 0) {
733 callback.onValueEdited(value);
734 }
735 }
736 };
737 if (password) {
738 editor.setInputType(InputType.TYPE_CLASS_TEXT
739 | InputType.TYPE_TEXT_VARIATION_PASSWORD);
740 builder.setPositiveButton(R.string.accept, mClickListener);
741 } else {
742 builder.setPositiveButton(R.string.edit, mClickListener);
743 }
744 if (hint != 0) {
745 editor.setHint(hint);
746 }
747 editor.requestFocus();
748 editor.setText("");
749 if (previousValue != null) {
750 editor.getText().append(previousValue);
751 }
752 builder.setView(view);
753 builder.setNegativeButton(R.string.cancel, null);
754 builder.create().show();
755 }
756
757 protected boolean addFingerprintRow(LinearLayout keys, final Account account, final String fingerprint, boolean highlight, View.OnClickListener onKeyClickedListener) {
758 final XmppAxolotlSession.Trust trust = account.getAxolotlService()
759 .getFingerprintTrust(fingerprint);
760 if (trust == null) {
761 return false;
762 }
763 return addFingerprintRowWithListeners(keys, account, fingerprint, highlight, trust, true,
764 new CompoundButton.OnCheckedChangeListener() {
765 @Override
766 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
767 account.getAxolotlService().setFingerprintTrust(fingerprint,
768 (isChecked) ? XmppAxolotlSession.Trust.TRUSTED :
769 XmppAxolotlSession.Trust.UNTRUSTED);
770 }
771 },
772 new View.OnClickListener() {
773 @Override
774 public void onClick(View v) {
775 account.getAxolotlService().setFingerprintTrust(fingerprint,
776 XmppAxolotlSession.Trust.UNTRUSTED);
777 v.setEnabled(true);
778 }
779 },
780 onKeyClickedListener
781
782 );
783 }
784
785 protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account,
786 final String fingerprint,
787 boolean highlight,
788 XmppAxolotlSession.Trust trust,
789 boolean showTag,
790 CompoundButton.OnCheckedChangeListener
791 onCheckedChangeListener,
792 View.OnClickListener onClickListener,
793 View.OnClickListener onKeyClickedListener) {
794 if (trust == XmppAxolotlSession.Trust.COMPROMISED) {
795 return false;
796 }
797 View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false);
798 TextView key = (TextView) view.findViewById(R.id.key);
799 key.setOnClickListener(onKeyClickedListener);
800 TextView keyType = (TextView) view.findViewById(R.id.key_type);
801 keyType.setOnClickListener(onKeyClickedListener);
802 Switch trustToggle = (Switch) view.findViewById(R.id.tgl_trust);
803 trustToggle.setVisibility(View.VISIBLE);
804 trustToggle.setOnCheckedChangeListener(onCheckedChangeListener);
805 trustToggle.setOnClickListener(onClickListener);
806 final View.OnLongClickListener purge = new View.OnLongClickListener() {
807 @Override
808 public boolean onLongClick(View v) {
809 showPurgeKeyDialog(account, fingerprint);
810 return true;
811 }
812 };
813 boolean active = true;
814 view.setOnLongClickListener(purge);
815 key.setOnLongClickListener(purge);
816 keyType.setOnLongClickListener(purge);
817 boolean x509 = Config.X509_VERIFICATION
818 && (trust == XmppAxolotlSession.Trust.TRUSTED_X509 || trust == XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509);
819 switch (trust) {
820 case UNTRUSTED:
821 case TRUSTED:
822 case TRUSTED_X509:
823 trustToggle.setChecked(trust.trusted(), false);
824 trustToggle.setEnabled(!Config.X509_VERIFICATION || trust != XmppAxolotlSession.Trust.TRUSTED_X509);
825 if (Config.X509_VERIFICATION && trust == XmppAxolotlSession.Trust.TRUSTED_X509) {
826 trustToggle.setOnClickListener(null);
827 }
828 key.setTextColor(getPrimaryTextColor());
829 keyType.setTextColor(getSecondaryTextColor());
830 break;
831 case UNDECIDED:
832 trustToggle.setChecked(false, false);
833 trustToggle.setEnabled(false);
834 key.setTextColor(getPrimaryTextColor());
835 keyType.setTextColor(getSecondaryTextColor());
836 break;
837 case INACTIVE_UNTRUSTED:
838 case INACTIVE_UNDECIDED:
839 trustToggle.setOnClickListener(null);
840 trustToggle.setChecked(false, false);
841 trustToggle.setEnabled(false);
842 key.setTextColor(getTertiaryTextColor());
843 keyType.setTextColor(getTertiaryTextColor());
844 active = false;
845 break;
846 case INACTIVE_TRUSTED:
847 case INACTIVE_TRUSTED_X509:
848 trustToggle.setOnClickListener(null);
849 trustToggle.setChecked(true, false);
850 trustToggle.setEnabled(false);
851 key.setTextColor(getTertiaryTextColor());
852 keyType.setTextColor(getTertiaryTextColor());
853 active = false;
854 break;
855 }
856
857 if (showTag) {
858 keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
859 } else {
860 keyType.setVisibility(View.GONE);
861 }
862 if (highlight) {
863 keyType.setTextColor(getResources().getColor(R.color.accent));
864 keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509_selected_message : R.string.omemo_fingerprint_selected_message));
865 } else {
866 keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
867 }
868
869 key.setText(CryptoHelper.prettifyFingerprint(fingerprint.substring(2)));
870
871 final View.OnClickListener toast;
872 if (!active) {
873 toast = new View.OnClickListener() {
874 @Override
875 public void onClick(View v) {
876 replaceToast(getString(R.string.this_device_is_no_longer_in_use), false);
877 }
878 };
879 trustToggle.setOnClickListener(toast);
880 } else {
881 toast = new View.OnClickListener() {
882 @Override
883 public void onClick(View v) {
884 hideToast();
885 }
886 };
887 }
888 view.setOnClickListener(toast);
889 key.setOnClickListener(toast);
890 keyType.setOnClickListener(toast);
891
892 keys.addView(view);
893 return true;
894 }
895
896 public void showPurgeKeyDialog(final Account account, final String fingerprint) {
897 Builder builder = new Builder(this);
898 builder.setTitle(getString(R.string.purge_key));
899 builder.setIconAttribute(android.R.attr.alertDialogIcon);
900 builder.setMessage(getString(R.string.purge_key_desc_part1)
901 + "\n\n" + CryptoHelper.prettifyFingerprint(fingerprint.substring(2))
902 + "\n\n" + getString(R.string.purge_key_desc_part2));
903 builder.setNegativeButton(getString(R.string.cancel), null);
904 builder.setPositiveButton(getString(R.string.purge_key),
905 new DialogInterface.OnClickListener() {
906 @Override
907 public void onClick(DialogInterface dialog, int which) {
908 account.getAxolotlService().purgeKey(fingerprint);
909 refreshUi();
910 }
911 });
912 builder.create().show();
913 }
914
915 public boolean hasStoragePermission(int requestCode) {
916 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
917 if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
918 requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode);
919 return false;
920 } else {
921 return true;
922 }
923 } else {
924 return true;
925 }
926 }
927
928 public void selectPresence(final Conversation conversation,
929 final OnPresenceSelected listener) {
930 final Contact contact = conversation.getContact();
931 if (conversation.hasValidOtrSession()) {
932 SessionID id = conversation.getOtrSession().getSessionID();
933 Jid jid;
934 try {
935 jid = Jid.fromString(id.getAccountID() + "/" + id.getUserID());
936 } catch (InvalidJidException e) {
937 jid = null;
938 }
939 conversation.setNextCounterpart(jid);
940 listener.onPresenceSelected();
941 } else if (!contact.showInRoster()) {
942 showAddToRosterDialog(conversation);
943 } else {
944 Presences presences = contact.getPresences();
945 if (presences.size() == 0) {
946 if (!contact.getOption(Contact.Options.TO)
947 && !contact.getOption(Contact.Options.ASKING)
948 && contact.getAccount().getStatus() == Account.State.ONLINE) {
949 showAskForPresenceDialog(contact);
950 } else if (!contact.getOption(Contact.Options.TO)
951 || !contact.getOption(Contact.Options.FROM)) {
952 warnMutalPresenceSubscription(conversation, listener);
953 } else {
954 conversation.setNextCounterpart(null);
955 listener.onPresenceSelected();
956 }
957 } else if (presences.size() == 1) {
958 String presence = presences.asStringArray()[0];
959 try {
960 conversation.setNextCounterpart(Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),presence));
961 } catch (InvalidJidException e) {
962 conversation.setNextCounterpart(null);
963 }
964 listener.onPresenceSelected();
965 } else {
966 final StringBuilder presence = new StringBuilder();
967 AlertDialog.Builder builder = new AlertDialog.Builder(this);
968 builder.setTitle(getString(R.string.choose_presence));
969 final String[] presencesArray = presences.asStringArray();
970 int preselectedPresence = 0;
971 for (int i = 0; i < presencesArray.length; ++i) {
972 if (presencesArray[i].equals(contact.getLastPresence())) {
973 preselectedPresence = i;
974 break;
975 }
976 }
977 presence.append(presencesArray[preselectedPresence]);
978 builder.setSingleChoiceItems(presencesArray,
979 preselectedPresence,
980 new DialogInterface.OnClickListener() {
981
982 @Override
983 public void onClick(DialogInterface dialog,
984 int which) {
985 presence.delete(0, presence.length());
986 presence.append(presencesArray[which]);
987 }
988 });
989 builder.setNegativeButton(R.string.cancel, null);
990 builder.setPositiveButton(R.string.ok, new OnClickListener() {
991
992 @Override
993 public void onClick(DialogInterface dialog, int which) {
994 try {
995 conversation.setNextCounterpart(Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),presence.toString()));
996 } catch (InvalidJidException e) {
997 conversation.setNextCounterpart(null);
998 }
999 listener.onPresenceSelected();
1000 }
1001 });
1002 builder.create().show();
1003 }
1004 }
1005 }
1006
1007 protected void onActivityResult(int requestCode, int resultCode,
1008 final Intent data) {
1009 super.onActivityResult(requestCode, resultCode, data);
1010 if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) {
1011 mPendingConferenceInvite = ConferenceInvite.parse(data);
1012 if (xmppConnectionServiceBound && mPendingConferenceInvite != null) {
1013 if (mPendingConferenceInvite.execute(this)) {
1014 mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG);
1015 mToast.show();
1016 }
1017 mPendingConferenceInvite = null;
1018 }
1019 }
1020 }
1021
1022
1023 private UiCallback<Conversation> adhocCallback = new UiCallback<Conversation>() {
1024 @Override
1025 public void success(final Conversation conversation) {
1026 runOnUiThread(new Runnable() {
1027 @Override
1028 public void run() {
1029 switchToConversation(conversation);
1030 hideToast();
1031 }
1032 });
1033 }
1034
1035 @Override
1036 public void error(final int errorCode, Conversation object) {
1037 runOnUiThread(new Runnable() {
1038 @Override
1039 public void run() {
1040 replaceToast(getString(errorCode));
1041 }
1042 });
1043 }
1044
1045 @Override
1046 public void userInputRequried(PendingIntent pi, Conversation object) {
1047
1048 }
1049 };
1050
1051 public int getTertiaryTextColor() {
1052 return this.mTertiaryTextColor;
1053 }
1054
1055 public int getSecondaryTextColor() {
1056 return this.mSecondaryTextColor;
1057 }
1058
1059 public int getPrimaryTextColor() {
1060 return this.mPrimaryTextColor;
1061 }
1062
1063 public int getWarningTextColor() {
1064 return this.mColorRed;
1065 }
1066
1067 public int getOnlineColor() {
1068 return this.mColorGreen;
1069 }
1070
1071 public int getPrimaryBackgroundColor() {
1072 return this.mPrimaryBackgroundColor;
1073 }
1074
1075 public int getSecondaryBackgroundColor() {
1076 return this.mSecondaryBackgroundColor;
1077 }
1078
1079 public int getPixel(int dp) {
1080 DisplayMetrics metrics = getResources().getDisplayMetrics();
1081 return ((int) (dp * metrics.density));
1082 }
1083
1084 public boolean copyTextToClipboard(String text, int labelResId) {
1085 ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
1086 String label = getResources().getString(labelResId);
1087 if (mClipBoardManager != null) {
1088 ClipData mClipData = ClipData.newPlainText(label, text);
1089 mClipBoardManager.setPrimaryClip(mClipData);
1090 return true;
1091 }
1092 return false;
1093 }
1094
1095 protected void registerNdefPushMessageCallback() {
1096 NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
1097 if (nfcAdapter != null && nfcAdapter.isEnabled()) {
1098 nfcAdapter.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
1099 @Override
1100 public NdefMessage createNdefMessage(NfcEvent nfcEvent) {
1101 return new NdefMessage(new NdefRecord[]{
1102 NdefRecord.createUri(getShareableUri()),
1103 NdefRecord.createApplicationRecord("eu.siacs.conversations")
1104 });
1105 }
1106 }, this);
1107 }
1108 }
1109
1110 protected boolean neverCompressPictures() {
1111 return getPreferences().getString("picture_compression", "auto").equals("never");
1112 }
1113
1114 protected boolean manuallyChangePresence() {
1115 return getPreferences().getBoolean("manually_change_presence", false);
1116 }
1117
1118 protected void unregisterNdefPushMessageCallback() {
1119 NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
1120 if (nfcAdapter != null && nfcAdapter.isEnabled()) {
1121 nfcAdapter.setNdefPushMessageCallback(null,this);
1122 }
1123 }
1124
1125 protected String getShareableUri() {
1126 return null;
1127 }
1128
1129 protected void shareUri() {
1130 String uri = getShareableUri();
1131 if (uri == null || uri.isEmpty()) {
1132 return;
1133 }
1134 Intent shareIntent = new Intent();
1135 shareIntent.setAction(Intent.ACTION_SEND);
1136 shareIntent.putExtra(Intent.EXTRA_TEXT, getShareableUri());
1137 shareIntent.setType("text/plain");
1138 try {
1139 startActivity(Intent.createChooser(shareIntent, getText(R.string.share_uri_with)));
1140 } catch (ActivityNotFoundException e) {
1141 Toast.makeText(this, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT).show();
1142 }
1143 }
1144
1145 @Override
1146 public void onResume() {
1147 super.onResume();
1148 if (this.getShareableUri()!=null) {
1149 this.registerNdefPushMessageCallback();
1150 }
1151 }
1152
1153 protected int findTheme() {
1154 Boolean dark = getPreferences().getString("theme", "light").equals("dark");
1155 Boolean larger = getPreferences().getBoolean("use_larger_font", false);
1156
1157 if(dark) {
1158 if(larger)
1159 return R.style.ConversationsTheme_Dark_LargerText;
1160 else
1161 return R.style.ConversationsTheme_Dark;
1162 } else {
1163 if (larger)
1164 return R.style.ConversationsTheme_LargerText;
1165 else
1166 return R.style.ConversationsTheme;
1167 }
1168 }
1169
1170 @Override
1171 public void onPause() {
1172 super.onPause();
1173 this.unregisterNdefPushMessageCallback();
1174 }
1175
1176 protected void showQrCode() {
1177 String uri = getShareableUri();
1178 if (uri!=null) {
1179 Point size = new Point();
1180 getWindowManager().getDefaultDisplay().getSize(size);
1181 final int width = (size.x < size.y ? size.x : size.y);
1182 Bitmap bitmap = createQrCodeBitmap(uri, width);
1183 ImageView view = new ImageView(this);
1184 view.setBackgroundColor(Color.WHITE);
1185 view.setImageBitmap(bitmap);
1186 AlertDialog.Builder builder = new AlertDialog.Builder(this);
1187 builder.setView(view);
1188 builder.create().show();
1189 }
1190 }
1191
1192 protected Bitmap createQrCodeBitmap(String input, int size) {
1193 Log.d(Config.LOGTAG,"qr code requested size: "+size);
1194 try {
1195 final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter();
1196 final Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
1197 hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
1198 final BitMatrix result = QR_CODE_WRITER.encode(input, BarcodeFormat.QR_CODE, size, size, hints);
1199 final int width = result.getWidth();
1200 final int height = result.getHeight();
1201 final int[] pixels = new int[width * height];
1202 for (int y = 0; y < height; y++) {
1203 final int offset = y * width;
1204 for (int x = 0; x < width; x++) {
1205 pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT;
1206 }
1207 }
1208 final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
1209 Log.d(Config.LOGTAG,"output size: "+width+"x"+height);
1210 bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
1211 return bitmap;
1212 } catch (final WriterException e) {
1213 return null;
1214 }
1215 }
1216
1217 protected Account extractAccount(Intent intent) {
1218 String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null;
1219 try {
1220 return jid != null ? xmppConnectionService.findAccountByJid(Jid.fromString(jid)) : null;
1221 } catch (InvalidJidException e) {
1222 return null;
1223 }
1224 }
1225
1226 public static class ConferenceInvite {
1227 private String uuid;
1228 private List<Jid> jids = new ArrayList<>();
1229
1230 public static ConferenceInvite parse(Intent data) {
1231 ConferenceInvite invite = new ConferenceInvite();
1232 invite.uuid = data.getStringExtra("conversation");
1233 if (invite.uuid == null) {
1234 return null;
1235 }
1236 try {
1237 if (data.getBooleanExtra("multiple", false)) {
1238 String[] toAdd = data.getStringArrayExtra("contacts");
1239 for (String item : toAdd) {
1240 invite.jids.add(Jid.fromString(item));
1241 }
1242 } else {
1243 invite.jids.add(Jid.fromString(data.getStringExtra("contact")));
1244 }
1245 } catch (final InvalidJidException ignored) {
1246 return null;
1247 }
1248 return invite;
1249 }
1250
1251 public boolean execute(XmppActivity activity) {
1252 XmppConnectionService service = activity.xmppConnectionService;
1253 Conversation conversation = service.findConversationByUuid(this.uuid);
1254 if (conversation == null) {
1255 return false;
1256 }
1257 if (conversation.getMode() == Conversation.MODE_MULTI) {
1258 for (Jid jid : jids) {
1259 service.invite(conversation, jid);
1260 }
1261 return false;
1262 } else {
1263 jids.add(conversation.getJid().toBareJid());
1264 service.createAdhocConference(conversation.getAccount(), null, jids, activity.adhocCallback);
1265 return true;
1266 }
1267 }
1268 }
1269
1270 public AvatarService avatarService() {
1271 return xmppConnectionService.getAvatarService();
1272 }
1273
1274 class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
1275 private final WeakReference<ImageView> imageViewReference;
1276 private Message message = null;
1277
1278 public BitmapWorkerTask(ImageView imageView) {
1279 imageViewReference = new WeakReference<>(imageView);
1280 }
1281
1282 @Override
1283 protected Bitmap doInBackground(Message... params) {
1284 if (isCancelled()) {
1285 return null;
1286 }
1287 message = params[0];
1288 try {
1289 return xmppConnectionService.getFileBackend().getThumbnail(
1290 message, (int) (metrics.density * 288), false);
1291 } catch (FileNotFoundException e) {
1292 return null;
1293 }
1294 }
1295
1296 @Override
1297 protected void onPostExecute(Bitmap bitmap) {
1298 if (bitmap != null && !isCancelled()) {
1299 final ImageView imageView = imageViewReference.get();
1300 if (imageView != null) {
1301 imageView.setImageBitmap(bitmap);
1302 imageView.setBackgroundColor(0x00000000);
1303 }
1304 }
1305 }
1306 }
1307
1308 public void loadBitmap(Message message, ImageView imageView) {
1309 Bitmap bm;
1310 try {
1311 bm = xmppConnectionService.getFileBackend().getThumbnail(message,
1312 (int) (metrics.density * 288), true);
1313 } catch (FileNotFoundException e) {
1314 bm = null;
1315 }
1316 if (bm != null) {
1317 cancelPotentialWork(message, imageView);
1318 imageView.setImageBitmap(bm);
1319 imageView.setBackgroundColor(0x00000000);
1320 } else {
1321 if (cancelPotentialWork(message, imageView)) {
1322 imageView.setBackgroundColor(0xff333333);
1323 imageView.setImageDrawable(null);
1324 final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
1325 final AsyncDrawable asyncDrawable = new AsyncDrawable(
1326 getResources(), null, task);
1327 imageView.setImageDrawable(asyncDrawable);
1328 try {
1329 task.execute(message);
1330 } catch (final RejectedExecutionException ignored) {
1331 ignored.printStackTrace();
1332 }
1333 }
1334 }
1335 }
1336
1337 public static boolean cancelPotentialWork(Message message, ImageView imageView) {
1338 final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
1339
1340 if (bitmapWorkerTask != null) {
1341 final Message oldMessage = bitmapWorkerTask.message;
1342 if (oldMessage == null || message != oldMessage) {
1343 bitmapWorkerTask.cancel(true);
1344 } else {
1345 return false;
1346 }
1347 }
1348 return true;
1349 }
1350
1351 private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
1352 if (imageView != null) {
1353 final Drawable drawable = imageView.getDrawable();
1354 if (drawable instanceof AsyncDrawable) {
1355 final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
1356 return asyncDrawable.getBitmapWorkerTask();
1357 }
1358 }
1359 return null;
1360 }
1361
1362 static class AsyncDrawable extends BitmapDrawable {
1363 private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
1364
1365 public AsyncDrawable(Resources res, Bitmap bitmap,
1366 BitmapWorkerTask bitmapWorkerTask) {
1367 super(res, bitmap);
1368 bitmapWorkerTaskReference = new WeakReference<>(
1369 bitmapWorkerTask);
1370 }
1371
1372 public BitmapWorkerTask getBitmapWorkerTask() {
1373 return bitmapWorkerTaskReference.get();
1374 }
1375 }
1376}