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