Ahmed I. Khalil
Ahmed I. Khalil

Reputation: 743

How to load a URI with "content://" prefix using Glide Android?

I'm trying to load a Contact photo with URI "content://com.android.contacts/contacts/295" by using Glide.

When I use

Glide.with(context).load(Uri.parse(contactPhoto).into(imageview)

Glide gives me a FileNotFoundException

java.io.FileNotFoundException: File does not exist; URI: content://com.android.contacts/contacts/264, calling user: android.uid.shared:10006, calling package is one of: [com.android.providers.contacts, com.android.contacts, com.android.providers.userdictionary]
        at android.database.DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(DatabaseUtils.java:146)
        at android.content.ContentProviderProxy.openTypedAssetFile(ContentProviderNative.java:689)
        at android.content.ContentResolver.openTypedAssetFileDescriptor(ContentResolver.java:1080)
        at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:921)
        at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:848)
        at com.bumptech.glide.load.data.FileDescriptorLocalUriFetcher.loadResource(FileDescriptorLocalUriFetcher.java:21)
        at com.bumptech.glide.load.data.FileDescriptorLocalUriFetcher.loadResource(FileDescriptorLocalUriFetcher.java:14)
        at com.bumptech.glide.load.data.LocalUriFetcher.loadData(LocalUriFetcher.java:44)
        at com.bumptech.glide.load.model.ImageVideoModelLoader$ImageVideoFetcher.loadData(ImageVideoModelLoader.java:83)
        at com.bumptech.glide.load.model.ImageVideoModelLoader$ImageVideoFetcher.loadData(ImageVideoModelLoader.java:53)
        at com.bumptech.glide.load.engine.DecodeJob.decodeSource(DecodeJob.java:170)
        at com.bumptech.glide.load.engine.DecodeJob.decodeFromSource(DecodeJob.java:128)
        at com.bumptech.glide.load.engine.EngineRunnable.decodeFromSource(EngineRunnable.java:122)
        at com.bumptech.glide.load.engine.EngineRunnable.decode(EngineRunnable.java:101)
        at com.bumptech.glide.load.engine.EngineRunnable.run(EngineRunnable.java:58)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422)
        at java.util.concurrent.FutureTask.run(FutureTask.java:237)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
        at java.lang.Thread.run(Thread.java:818)
        at com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor$DefaultThreadFactory$1.run(FifoPriorityThreadPoolExecutor.java:52)

Obviously Glide tries to get the image from the wrong place.

I would appreciate if someone point me on how to load a photo with "content://" URIs.

Upvotes: 8

Views: 8679

Answers (7)

Martin Zeitler
Martin Zeitler

Reputation: 76799

When data-binding, none of this works.

In order to catch that uncatchable FileNotFoundException stemming from Glide (which may dearly spam the log) or to substitute the missing images with a default image, one has to make sure that the referenced image even exists, before passing the Uri to Glide.

Let's assume column Contacts.PHOTO_THUMBNAIL_URI as item.thumbnail:

content://com.android.contacts/contacts/110/photo

Where the XML looks about like this:

<QuickContactBadge
    android:layout_width="48dp"
    android:layout_height="48dp"
    android:scaleType="center"
    android:contentDescription="@{ item.name }"
    app:imageUrl="@{ item.thumbnail }"/>

The default data-binding component's companion object:

companion object {

    val LOG_TAG: String = BaseFragment::class.java.simpleName

    /** DataBinding the QuickContactBadge. */
    @BindingAdapter("imageUrl")
    fun loadImage(imageView: ImageView, uri: Uri?) {
        if (uri != null && uriWouldResolve(imageView.context, uri)) {
            Glide.with(imageView.context).load(uri).into(imageView)
        } else {
            // TODO: optionally load default image.
        }
    }

    /** Reduce Log Spam. */
    private fun uriWouldResolve(context: Context, uri: Uri): Boolean {
        var exists = false
        try {
            val ins: InputStream? = context.contentResolver.openInputStream(uri)
            if (ins?.read()!! > 0) {exists = true}
            ins.close()
        } catch (e: FileNotFoundException) {
            Log.w(LOG_TAG, "file not found: ${e.message}")
        }
        return exists
    }
}

Upvotes: 0

azwethinkweiz
azwethinkweiz

Reputation: 602

The fastest and easiest way is to first figure out the CONTACT_ID. Then you match this CONTACT_ID against the PHOTO_URI.

//first create a cursor
val photoCursor = contentResolver.query(
                                ContactsContract.Contacts.CONTENT_URI,
                                photoProjection,
                                ContactsContract.Contacts._ID + "=?",
                                arrayOf(contactId),
                                null
                            )
//where photoProjection is like so
        val photoProjection: Array<String> = arrayOf(
            ContactsContract.Contacts.Photo.PHOTO_URI,
            ContactsContract.Contacts._ID
        )

//now grab the PHOTO_URI, this will only exist if there is a photo
                            val photo = if (photoCursor?.moveToFirst() == true) {
                       photoCursor.getString(photoCursor.getColumnIndex(ContactsContract.Contacts.PHOTO_URI))
                            } else {
                                null
                            }

//In Glide now you can load the URI directly
        Glide.with(this)
            .load(uri)
            .apply(imageTransform)
            .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
            .into(image)

Upvotes: 0

Nidhi Savaliya
Nidhi Savaliya

Reputation: 179

I am use this code then working successfully

 Glide.with(context)
                    .load(uri)
                    .addListener(new RequestListener<Drawable>() {
                        @Override
                        public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
                            Log.i(TAG, "onLoadFailed: ");
                            return false;
                        }

                        @Override
                        public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
                            Log.i(TAG, "onResourceReady: ");
                            return false;
                        }
                    })
                    .into(holder.imgProfile);

Upvotes: 0

Bunny Bandewar
Bunny Bandewar

Reputation: 21

  Uri uri =   
   ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, new
   Long(contactsPojo.getId()));
          final Uri displayPhotoUri = Uri.withAppendedPath(uri,
                  ContactsContract.Contacts.Photo.DISPLAY_PHOTO);
          Glide.with(context)
                  .load(displayPhotoUri)
                  .placeholder(R.mipmap.ic_launcher)
                  .error(R.mipmap.ic_launcher)
                  .fallback(R.mipmap.ic_launcher)
                  .diskCacheStrategy(DiskCacheStrategy.ALL)
                  .into(holder.image);

Upvotes: 0

lujop
lujop

Reputation: 13883

You need to create a custom loader that uses a ContentResolver. In Picasso for example this works because there is already a custom request handler that uses a ContentResolver.

I created one custom Glide loader for contacts for my internal use that you can take as reference.

Upvotes: 3

Ahmed I. Khalil
Ahmed I. Khalil

Reputation: 743

Seems that Glide doesn't handle content photos Uri automatically.

So I've solved my issue using an RxJava approach.

Here is a method that emits a bitmap (Please notice the scheduler as it is important to not lag the scrolling performance)

private Observable<Bitmap> _getConvertInputStreamToBitmapObservable(ContentResolver cr,
                                                                    Uri contactUri) {
    return Observable.create(new Observable.OnSubscribe<Bitmap>() {
        @Override
        public void call(Subscriber<? super Bitmap> subscriber) {
            InputStream inputStream =
                    ContactsContract.Contacts.openContactPhotoInputStream(cr, contactUri);
            if (inputStream != null) {
                Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                subscriber.onNext(bitmap);
            }
            subscriber.onCompleted();
        }
    }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}

And here is the client code that uses the method (Please notice the unsubscribing as it is important in recycling).

       if (holder.bitmapSubscription != null) {
            holder.bitmapSubscription.unsubscribe();
        }

        holder.bitmapSubscription = _getConvertInputStreamToBitmapObservable(context.getContentResolver(),
                contactUri)
                .subscribe(holder.userImg::setImageBitmap);

Upvotes: 1

Yash Sampat
Yash Sampat

Reputation: 30611

You need to use a ContentResolver for this.

ContentResolver contextResolver = context.getContentResolver();
Uri uri = Uri.parse("content://com.android.contacts/contacts/295");
Bitmap thumbnail = null;
Cursor cursor = contentResolver.query(uri, new String[] {ContactsContract.CommonDataKinds.Photo.PHOTO}, null, null, null);

try {
    if (cursor.moveToFirst()) {
        final byte[] thumbnailBytes = cursor.getBlob(0);
        if (thumbnailBytes != null) {
            thumbnail = BitmapFactory.decodeByteArray(thumbnailBytes, 0, thumbnailBytes.length);
        }
    }
}
finally {
    cursor.close();
}

if (thumbnail != null) {
    imageView.setImageBitmap(thumbnail);
}

Try this. This should work.

Upvotes: 0

Related Questions