XmppActivity.java

  1package eu.siacs.conversations.ui;
  2
  3import android.annotation.SuppressLint;
  4import android.app.Activity;
  5import android.app.AlertDialog;
  6import android.app.AlertDialog.Builder;
  7import android.app.PendingIntent;
  8import android.content.ClipData;
  9import android.content.ClipboardManager;
 10import android.content.ComponentName;
 11import android.content.Context;
 12import android.content.DialogInterface;
 13import android.content.DialogInterface.OnClickListener;
 14import android.content.Intent;
 15import android.content.IntentSender.SendIntentException;
 16import android.content.ServiceConnection;
 17import android.content.SharedPreferences;
 18import android.content.pm.PackageManager;
 19import android.content.pm.ResolveInfo;
 20import android.content.res.Resources;
 21import android.graphics.Bitmap;
 22import android.graphics.Color;
 23import android.graphics.Point;
 24import android.graphics.drawable.BitmapDrawable;
 25import android.graphics.drawable.Drawable;
 26import android.net.Uri;
 27import android.nfc.NdefMessage;
 28import android.nfc.NdefRecord;
 29import android.nfc.NfcAdapter;
 30import android.nfc.NfcEvent;
 31import android.os.AsyncTask;
 32import android.os.Bundle;
 33import android.os.IBinder;
 34import android.preference.PreferenceManager;
 35import android.text.InputType;
 36import android.util.DisplayMetrics;
 37import android.util.Log;
 38import android.view.MenuItem;
 39import android.view.View;
 40import android.view.inputmethod.InputMethodManager;
 41import android.widget.EditText;
 42import android.widget.ImageView;
 43
 44import com.google.zxing.BarcodeFormat;
 45import com.google.zxing.EncodeHintType;
 46import com.google.zxing.WriterException;
 47import com.google.zxing.common.BitMatrix;
 48import com.google.zxing.qrcode.QRCodeWriter;
 49import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
 50
 51import java.io.FileNotFoundException;
 52import java.lang.ref.WeakReference;
 53import java.util.Hashtable;
 54import java.util.List;
 55import java.util.concurrent.RejectedExecutionException;
 56
 57import eu.siacs.conversations.Config;
 58import eu.siacs.conversations.R;
 59import eu.siacs.conversations.entities.Account;
 60import eu.siacs.conversations.entities.Contact;
 61import eu.siacs.conversations.entities.Conversation;
 62import eu.siacs.conversations.entities.Message;
 63import eu.siacs.conversations.entities.Presences;
 64import eu.siacs.conversations.services.AvatarService;
 65import eu.siacs.conversations.services.XmppConnectionService;
 66import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
 67import eu.siacs.conversations.utils.ExceptionHelper;
 68
 69public abstract class XmppActivity extends Activity {
 70
 71	protected static final int REQUEST_ANNOUNCE_PGP = 0x0101;
 72	protected static final int REQUEST_INVITE_TO_CONVERSATION = 0x0102;
 73
 74	public XmppConnectionService xmppConnectionService;
 75	public boolean xmppConnectionServiceBound = false;
 76
 77	protected int mPrimaryTextColor;
 78	protected int mSecondaryTextColor;
 79	protected int mSecondaryBackgroundColor;
 80	protected int mColorRed;
 81	protected int mColorOrange;
 82	protected int mColorGreen;
 83	protected int mPrimaryColor;
 84
 85	protected boolean mUseSubject = true;
 86
 87	private DisplayMetrics metrics;
 88
 89	protected interface OnValueEdited {
 90		public void onValueEdited(String value);
 91	}
 92
 93	public interface OnPresenceSelected {
 94		public void onPresenceSelected();
 95	}
 96
 97	protected ServiceConnection mConnection = new ServiceConnection() {
 98
 99		@Override
100		public void onServiceConnected(ComponentName className, IBinder service) {
101			XmppConnectionBinder binder = (XmppConnectionBinder) service;
102			xmppConnectionService = binder.getService();
103			xmppConnectionServiceBound = true;
104			onBackendConnected();
105		}
106
107		@Override
108		public void onServiceDisconnected(ComponentName arg0) {
109			xmppConnectionServiceBound = false;
110		}
111	};
112
113	@Override
114	protected void onStart() {
115		super.onStart();
116		if (!xmppConnectionServiceBound) {
117			connectToBackend();
118		}
119	}
120
121	public void connectToBackend() {
122		Intent intent = new Intent(this, XmppConnectionService.class);
123		intent.setAction("ui");
124		startService(intent);
125		bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
126	}
127
128	@Override
129	protected void onStop() {
130		super.onStop();
131		if (xmppConnectionServiceBound) {
132			unbindService(mConnection);
133			xmppConnectionServiceBound = false;
134		}
135	}
136
137	protected void hideKeyboard() {
138		InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
139
140		View focus = getCurrentFocus();
141
142		if (focus != null) {
143
144			inputManager.hideSoftInputFromWindow(focus.getWindowToken(),
145					InputMethodManager.HIDE_NOT_ALWAYS);
146		}
147	}
148
149	public boolean hasPgp() {
150		return xmppConnectionService.getPgpEngine() != null;
151	}
152
153	public void showInstallPgpDialog() {
154		Builder builder = new AlertDialog.Builder(this);
155		builder.setTitle(getString(R.string.openkeychain_required));
156		builder.setIconAttribute(android.R.attr.alertDialogIcon);
157		builder.setMessage(getText(R.string.openkeychain_required_long));
158		builder.setNegativeButton(getString(R.string.cancel), null);
159		builder.setNeutralButton(getString(R.string.restart),
160				new OnClickListener() {
161
162					@Override
163					public void onClick(DialogInterface dialog, int which) {
164						if (xmppConnectionServiceBound) {
165							unbindService(mConnection);
166							xmppConnectionServiceBound = false;
167						}
168						stopService(new Intent(XmppActivity.this,
169								XmppConnectionService.class));
170						finish();
171					}
172				});
173		builder.setPositiveButton(getString(R.string.install),
174				new OnClickListener() {
175
176					@Override
177					public void onClick(DialogInterface dialog, int which) {
178						Uri uri = Uri
179								.parse("market://details?id=org.sufficientlysecure.keychain");
180						Intent marketIntent = new Intent(Intent.ACTION_VIEW,
181								uri);
182						PackageManager manager = getApplicationContext()
183								.getPackageManager();
184						List<ResolveInfo> infos = manager
185								.queryIntentActivities(marketIntent, 0);
186						if (infos.size() > 0) {
187							startActivity(marketIntent);
188						} else {
189							uri = Uri.parse("http://www.openkeychain.org/");
190							Intent browserIntent = new Intent(
191									Intent.ACTION_VIEW, uri);
192							startActivity(browserIntent);
193						}
194						finish();
195					}
196				});
197		builder.create().show();
198	}
199
200	abstract void onBackendConnected();
201
202	public boolean onOptionsItemSelected(MenuItem item) {
203		switch (item.getItemId()) {
204			case R.id.action_settings:
205				startActivity(new Intent(this, SettingsActivity.class));
206				break;
207			case R.id.action_accounts:
208				startActivity(new Intent(this, ManageAccountActivity.class));
209				break;
210			case android.R.id.home:
211				finish();
212				break;
213			case R.id.action_show_qr_code:
214				showQrCode();
215				break;
216		}
217		return super.onOptionsItemSelected(item);
218	}
219
220	@Override
221	protected void onCreate(Bundle savedInstanceState) {
222		super.onCreate(savedInstanceState);
223		metrics = getResources().getDisplayMetrics();
224		ExceptionHelper.init(getApplicationContext());
225		mPrimaryTextColor = getResources().getColor(R.color.primarytext);
226		mSecondaryTextColor = getResources().getColor(R.color.secondarytext);
227		mColorRed = getResources().getColor(R.color.red);
228		mColorOrange = getResources().getColor(R.color.orange);
229		mColorGreen = getResources().getColor(R.color.green);
230		mPrimaryColor = getResources().getColor(R.color.primary);
231		mSecondaryBackgroundColor = getResources().getColor(
232				R.color.secondarybackground);
233		if (getPreferences().getBoolean("use_larger_font", false)) {
234			setTheme(R.style.ConversationsTheme_LargerText);
235		}
236		mUseSubject = getPreferences().getBoolean("use_subject", true);
237	}
238
239	protected SharedPreferences getPreferences() {
240		return PreferenceManager
241				.getDefaultSharedPreferences(getApplicationContext());
242	}
243
244	public boolean useSubjectToIdentifyConference() {
245		return mUseSubject;
246	}
247
248	public void switchToConversation(Conversation conversation) {
249		switchToConversation(conversation, null, false);
250	}
251
252	public void switchToConversation(Conversation conversation, String text,
253									 boolean newTask) {
254		Intent viewConversationIntent = new Intent(this,
255				ConversationActivity.class);
256		viewConversationIntent.setAction(Intent.ACTION_VIEW);
257		viewConversationIntent.putExtra(ConversationActivity.CONVERSATION,
258				conversation.getUuid());
259		if (text != null) {
260			viewConversationIntent.putExtra(ConversationActivity.TEXT, text);
261		}
262		viewConversationIntent.setType(ConversationActivity.VIEW_CONVERSATION);
263		if (newTask) {
264			viewConversationIntent.setFlags(viewConversationIntent.getFlags()
265					| Intent.FLAG_ACTIVITY_NEW_TASK
266					| Intent.FLAG_ACTIVITY_SINGLE_TOP);
267		} else {
268			viewConversationIntent.setFlags(viewConversationIntent.getFlags()
269					| Intent.FLAG_ACTIVITY_CLEAR_TOP);
270		}
271		startActivity(viewConversationIntent);
272		finish();
273	}
274
275	public void switchToContactDetails(Contact contact) {
276		Intent intent = new Intent(this, ContactDetailsActivity.class);
277		intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
278		intent.putExtra("account", contact.getAccount().getJid());
279		intent.putExtra("contact", contact.getJid());
280		startActivity(intent);
281	}
282
283	public void switchToAccount(Account account) {
284		Intent intent = new Intent(this, EditAccountActivity.class);
285		intent.putExtra("jid", account.getJid());
286		startActivity(intent);
287	}
288
289	protected void inviteToConversation(Conversation conversation) {
290		Intent intent = new Intent(getApplicationContext(),
291				ChooseContactActivity.class);
292		intent.putExtra("conversation", conversation.getUuid());
293		startActivityForResult(intent, REQUEST_INVITE_TO_CONVERSATION);
294	}
295
296	protected void announcePgp(Account account, final Conversation conversation) {
297		xmppConnectionService.getPgpEngine().generateSignature(account,
298				"online", new UiCallback<Account>() {
299
300					@Override
301					public void userInputRequried(PendingIntent pi,
302												  Account account) {
303						try {
304							startIntentSenderForResult(pi.getIntentSender(),
305									REQUEST_ANNOUNCE_PGP, null, 0, 0, 0);
306						} catch (SendIntentException e) {
307						}
308					}
309
310					@Override
311					public void success(Account account) {
312						xmppConnectionService.databaseBackend
313								.updateAccount(account);
314						xmppConnectionService.sendPresencePacket(account,
315								xmppConnectionService.getPresenceGenerator()
316										.sendPresence(account));
317						if (conversation != null) {
318							conversation
319									.setNextEncryption(Message.ENCRYPTION_PGP);
320							xmppConnectionService.databaseBackend
321									.updateConversation(conversation);
322						}
323					}
324
325					@Override
326					public void error(int error, Account account) {
327						displayErrorDialog(error);
328					}
329				});
330	}
331
332	protected void displayErrorDialog(final int errorCode) {
333		runOnUiThread(new Runnable() {
334
335			@Override
336			public void run() {
337				AlertDialog.Builder builder = new AlertDialog.Builder(
338						XmppActivity.this);
339				builder.setIconAttribute(android.R.attr.alertDialogIcon);
340				builder.setTitle(getString(R.string.error));
341				builder.setMessage(errorCode);
342				builder.setNeutralButton(R.string.accept, null);
343				builder.create().show();
344			}
345		});
346
347	}
348
349	protected void showAddToRosterDialog(final Conversation conversation) {
350		String jid = conversation.getContactJid();
351		AlertDialog.Builder builder = new AlertDialog.Builder(this);
352		builder.setTitle(jid);
353		builder.setMessage(getString(R.string.not_in_roster));
354		builder.setNegativeButton(getString(R.string.cancel), null);
355		builder.setPositiveButton(getString(R.string.add_contact),
356				new DialogInterface.OnClickListener() {
357
358					@Override
359					public void onClick(DialogInterface dialog, int which) {
360						String jid = conversation.getContactJid();
361						Account account = conversation.getAccount();
362						Contact contact = account.getRoster().getContact(jid);
363						xmppConnectionService.createContact(contact);
364						switchToContactDetails(contact);
365					}
366				});
367		builder.create().show();
368	}
369
370	private void showAskForPresenceDialog(final Contact contact) {
371		AlertDialog.Builder builder = new AlertDialog.Builder(this);
372		builder.setTitle(contact.getJid());
373		builder.setMessage(R.string.request_presence_updates);
374		builder.setNegativeButton(R.string.cancel, null);
375		builder.setPositiveButton(R.string.request_now,
376				new DialogInterface.OnClickListener() {
377
378					@Override
379					public void onClick(DialogInterface dialog, int which) {
380						if (xmppConnectionServiceBound) {
381							xmppConnectionService.sendPresencePacket(contact
382									.getAccount(), xmppConnectionService
383									.getPresenceGenerator()
384									.requestPresenceUpdatesFrom(contact));
385						}
386					}
387				});
388		builder.create().show();
389	}
390
391	private void warnMutalPresenceSubscription(final Conversation conversation,
392											   final OnPresenceSelected listener) {
393		AlertDialog.Builder builder = new AlertDialog.Builder(this);
394		builder.setTitle(conversation.getContact().getJid());
395		builder.setMessage(R.string.without_mutual_presence_updates);
396		builder.setNegativeButton(R.string.cancel, null);
397		builder.setPositiveButton(R.string.ignore, new OnClickListener() {
398
399			@Override
400			public void onClick(DialogInterface dialog, int which) {
401				conversation.setNextPresence(null);
402				if (listener != null) {
403					listener.onPresenceSelected();
404				}
405			}
406		});
407		builder.create().show();
408	}
409
410	protected void quickEdit(String previousValue, OnValueEdited callback) {
411		quickEdit(previousValue, callback, false);
412	}
413
414	protected void quickPasswordEdit(String previousValue,
415									 OnValueEdited callback) {
416		quickEdit(previousValue, callback, true);
417	}
418
419	@SuppressLint("InflateParams")
420	private void quickEdit(final String previousValue,
421						   final OnValueEdited callback, boolean password) {
422		AlertDialog.Builder builder = new AlertDialog.Builder(this);
423		View view = getLayoutInflater().inflate(R.layout.quickedit, null);
424		final EditText editor = (EditText) view.findViewById(R.id.editor);
425		OnClickListener mClickListener = new OnClickListener() {
426
427			@Override
428			public void onClick(DialogInterface dialog, int which) {
429				String value = editor.getText().toString();
430				if (!previousValue.equals(value) && value.trim().length() > 0) {
431					callback.onValueEdited(value);
432				}
433			}
434		};
435		if (password) {
436			editor.setInputType(InputType.TYPE_CLASS_TEXT
437					| InputType.TYPE_TEXT_VARIATION_PASSWORD);
438			editor.setHint(R.string.password);
439			builder.setPositiveButton(R.string.accept, mClickListener);
440		} else {
441			builder.setPositiveButton(R.string.edit, mClickListener);
442		}
443		editor.requestFocus();
444		editor.setText(previousValue);
445		builder.setView(view);
446		builder.setNegativeButton(R.string.cancel, null);
447		builder.create().show();
448	}
449
450	public void selectPresence(final Conversation conversation,
451							   final OnPresenceSelected listener) {
452		Contact contact = conversation.getContact();
453		if (!contact.showInRoster()) {
454			showAddToRosterDialog(conversation);
455		} else {
456			Presences presences = contact.getPresences();
457			if (presences.size() == 0) {
458				if (!contact.getOption(Contact.Options.TO)
459						&& !contact.getOption(Contact.Options.ASKING)
460						&& contact.getAccount().getStatus() == Account.STATUS_ONLINE) {
461					showAskForPresenceDialog(contact);
462				} else if (!contact.getOption(Contact.Options.TO)
463						|| !contact.getOption(Contact.Options.FROM)) {
464					warnMutalPresenceSubscription(conversation, listener);
465				} else {
466					conversation.setNextPresence(null);
467					listener.onPresenceSelected();
468				}
469			} else if (presences.size() == 1) {
470				String presence = presences.asStringArray()[0];
471				conversation.setNextPresence(presence);
472				listener.onPresenceSelected();
473			} else {
474				final StringBuilder presence = new StringBuilder();
475				AlertDialog.Builder builder = new AlertDialog.Builder(this);
476				builder.setTitle(getString(R.string.choose_presence));
477				final String[] presencesArray = presences.asStringArray();
478				int preselectedPresence = 0;
479				for (int i = 0; i < presencesArray.length; ++i) {
480					if (presencesArray[i].equals(contact.lastseen.presence)) {
481						preselectedPresence = i;
482						break;
483					}
484				}
485				presence.append(presencesArray[preselectedPresence]);
486				builder.setSingleChoiceItems(presencesArray,
487						preselectedPresence,
488						new DialogInterface.OnClickListener() {
489
490							@Override
491							public void onClick(DialogInterface dialog,
492												int which) {
493								presence.delete(0, presence.length());
494								presence.append(presencesArray[which]);
495							}
496						});
497				builder.setNegativeButton(R.string.cancel, null);
498				builder.setPositiveButton(R.string.ok, new OnClickListener() {
499
500					@Override
501					public void onClick(DialogInterface dialog, int which) {
502						conversation.setNextPresence(presence.toString());
503						listener.onPresenceSelected();
504					}
505				});
506				builder.create().show();
507			}
508		}
509	}
510
511	protected void onActivityResult(int requestCode, int resultCode,
512									final Intent data) {
513		super.onActivityResult(requestCode, resultCode, data);
514		if (requestCode == REQUEST_INVITE_TO_CONVERSATION
515				&& resultCode == RESULT_OK) {
516			String contactJid = data.getStringExtra("contact");
517			String conversationUuid = data.getStringExtra("conversation");
518			Conversation conversation = xmppConnectionService
519					.findConversationByUuid(conversationUuid);
520			if (conversation.getMode() == Conversation.MODE_MULTI) {
521				xmppConnectionService.invite(conversation, contactJid);
522			}
523			Log.d(Config.LOGTAG, "inviting " + contactJid + " to "
524					+ conversation.getName());
525		}
526	}
527
528	public int getSecondaryTextColor() {
529		return this.mSecondaryTextColor;
530	}
531
532	public int getPrimaryTextColor() {
533		return this.mPrimaryTextColor;
534	}
535
536	public int getWarningTextColor() {
537		return this.mColorRed;
538	}
539
540	public int getPrimaryColor() {
541		return this.mPrimaryColor;
542	}
543
544	public int getSecondaryBackgroundColor() {
545		return this.mSecondaryBackgroundColor;
546	}
547
548	public int getPixel(int dp) {
549		DisplayMetrics metrics = getResources().getDisplayMetrics();
550		return ((int) (dp * metrics.density));
551	}
552
553	public boolean copyTextToClipboard(String text, int labelResId) {
554		ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
555		String label = getResources().getString(labelResId);
556		if (mClipBoardManager != null) {
557			ClipData mClipData = ClipData.newPlainText(label, text);
558			mClipBoardManager.setPrimaryClip(mClipData);
559			return true;
560		}
561		return false;
562	}
563
564	protected void registerNdefPushMessageCallback() {
565			NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
566			if (nfcAdapter != null && nfcAdapter.isEnabled()) {
567				nfcAdapter.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
568					@Override
569					public NdefMessage createNdefMessage(NfcEvent nfcEvent) {
570						NdefMessage msg = new NdefMessage(new NdefRecord[]{
571								NdefRecord.createUri(getShareableUri()),
572								NdefRecord.createApplicationRecord("eu.siacs.conversations")
573						});
574						return msg;
575					}
576				}, this);
577			}
578	}
579
580	protected void unregisterNdefPushMessageCallback() {
581		NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
582		if (nfcAdapter != null && nfcAdapter.isEnabled()) {
583			nfcAdapter.setNdefPushMessageCallback(null,this);
584		}
585	}
586
587	protected String getShareableUri() {
588		return null;
589	}
590
591	@Override
592	public void onResume() {
593		super.onResume();
594		if (this.getShareableUri()!=null) {
595			this.registerNdefPushMessageCallback();
596		}
597	}
598
599	@Override
600	public void onPause() {
601		super.onPause();
602		this.unregisterNdefPushMessageCallback();
603	}
604
605	protected void showQrCode() {
606		String uri = getShareableUri();
607		if (uri!=null) {
608			Point size = new Point();
609			getWindowManager().getDefaultDisplay().getSize(size);
610			final int width = (size.x < size.y ? size.x : size.y);
611			Bitmap bitmap = createQrCodeBitmap(uri, width);
612			ImageView view = new ImageView(this);
613			view.setImageBitmap(bitmap);
614			AlertDialog.Builder builder = new AlertDialog.Builder(this);
615			builder.setView(view);
616			builder.create().show();
617		}
618	}
619
620	protected Bitmap createQrCodeBitmap(String input, int size) {
621		try {
622			final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter();
623			final Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
624			hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
625			final BitMatrix result = QR_CODE_WRITER.encode(input, BarcodeFormat.QR_CODE, size, size, hints);
626			final int width = result.getWidth();
627			final int height = result.getHeight();
628			final int[] pixels = new int[width * height];
629			for (int y = 0; y < height; y++) {
630				final int offset = y * width;
631				for (int x = 0; x < width; x++) {
632					pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT;
633				}
634			}
635			final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
636			bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
637			return bitmap;
638		} catch (final WriterException e) {
639			return null;
640		}
641	}
642
643	public AvatarService avatarService() {
644		return xmppConnectionService.getAvatarService();
645	}
646
647	class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
648		private final WeakReference<ImageView> imageViewReference;
649		private Message message = null;
650
651		public BitmapWorkerTask(ImageView imageView) {
652			imageViewReference = new WeakReference<ImageView>(imageView);
653		}
654
655		@Override
656		protected Bitmap doInBackground(Message... params) {
657			message = params[0];
658			try {
659				return xmppConnectionService.getFileBackend().getThumbnail(
660						message, (int) (metrics.density * 288), false);
661			} catch (FileNotFoundException e) {
662				return null;
663			}
664		}
665
666		@Override
667		protected void onPostExecute(Bitmap bitmap) {
668			if (imageViewReference != null && bitmap != null) {
669				final ImageView imageView = imageViewReference.get();
670				if (imageView != null) {
671					imageView.setImageBitmap(bitmap);
672					imageView.setBackgroundColor(0x00000000);
673				}
674			}
675		}
676	}
677
678	public void loadBitmap(Message message, ImageView imageView) {
679		Bitmap bm;
680		try {
681			bm = xmppConnectionService.getFileBackend().getThumbnail(message,
682					(int) (metrics.density * 288), true);
683		} catch (FileNotFoundException e) {
684			bm = null;
685		}
686		if (bm != null) {
687			imageView.setImageBitmap(bm);
688			imageView.setBackgroundColor(0x00000000);
689		} else {
690			if (cancelPotentialWork(message, imageView)) {
691				imageView.setBackgroundColor(0xff333333);
692				final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
693				final AsyncDrawable asyncDrawable = new AsyncDrawable(
694						getResources(), null, task);
695				imageView.setImageDrawable(asyncDrawable);
696				try {
697					task.execute(message);
698				} catch (RejectedExecutionException e) {
699					return;
700				}
701			}
702		}
703	}
704
705	public static boolean cancelPotentialWork(Message message,
706											  ImageView imageView) {
707		final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
708
709		if (bitmapWorkerTask != null) {
710			final Message oldMessage = bitmapWorkerTask.message;
711			if (oldMessage == null || message != oldMessage) {
712				bitmapWorkerTask.cancel(true);
713			} else {
714				return false;
715			}
716		}
717		return true;
718	}
719
720	private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
721		if (imageView != null) {
722			final Drawable drawable = imageView.getDrawable();
723			if (drawable instanceof AsyncDrawable) {
724				final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
725				return asyncDrawable.getBitmapWorkerTask();
726			}
727		}
728		return null;
729	}
730
731	static class AsyncDrawable extends BitmapDrawable {
732		private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
733
734		public AsyncDrawable(Resources res, Bitmap bitmap,
735							 BitmapWorkerTask bitmapWorkerTask) {
736			super(res, bitmap);
737			bitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>(
738					bitmapWorkerTask);
739		}
740
741		public BitmapWorkerTask getBitmapWorkerTask() {
742			return bitmapWorkerTaskReference.get();
743		}
744	}
745}