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