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