Back to Repositories

Testing Non-Bitmap Drawable Resource Handling in Glide

This test suite validates Glide’s handling of non-bitmap drawable resources in Android applications, focusing on loading and transforming various drawable types including vector drawables, nine-patches, and application icons. The tests ensure proper resource loading, transformation, and bitmap conversion capabilities.

Test Coverage Overview

The test suite provides comprehensive coverage of Glide’s drawable resource handling capabilities.

Key areas tested include:
  • Bitmap resource loading and transformation
  • Shape drawable handling with size constraints
  • Vector drawable processing
  • Nine-patch resource management
  • Application icon resource loading
  • URI-based resource loading across different package contexts

Implementation Analysis

The testing approach employs JUnit4 with AndroidJUnit4 runner for Android-specific testing contexts. The implementation uses a systematic pattern of testing each drawable type under different scenarios: loading as drawable, loading as bitmap, and applying transformations. Each test validates both the successful loading and proper handling of edge cases.

Technical Details

Testing tools and configuration:
  • AndroidJUnit4 test runner
  • Glide transformation utilities
  • Android test core libraries
  • Custom TearDownGlide rule for cleanup
  • Context-based resource loading
  • URI builders for resource access

Best Practices Demonstrated

The test suite exhibits strong testing practices through systematic organization and thorough validation. Notable practices include proper resource cleanup, comprehensive error case handling, and methodical testing of transformations. The code demonstrates proper use of JUnit rules, clear test naming conventions, and efficient test context management.

bumptech/glide

instrumentation/src/androidTest/java/com/bumptech/glide/NonBitmapDrawableResourcesTest.java

            
package com.bumptech.glide;

import static com.bumptech.glide.request.RequestOptions.bitmapTransform;
import static com.bumptech.glide.request.RequestOptions.centerCropTransform;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;

import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.test.ResourceIds;
import com.bumptech.glide.testutil.TearDownGlide;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
public class NonBitmapDrawableResourcesTest {
  @Rule public final TestName testName = new TestName();
  @Rule public final TearDownGlide tearDownGlide = new TearDownGlide();
  private final Context context = ApplicationProvider.getApplicationContext();

  @Test
  public void load_withBitmapResourceId_asDrawable_producesNonNullDrawable()
      throws ExecutionException, InterruptedException {
    Drawable drawable = Glide.with(context).load(android.R.drawable.star_big_off).submit().get();
    assertThat(drawable).isNotNull();
  }

  @Test
  public void load_withBitmapResourceId_asDrawable_withTransformation_producesNonNullBitmap()
      throws ExecutionException, InterruptedException {
    Drawable drawable =
        Glide.with(context)
            .load(android.R.drawable.star_big_off)
            .apply(centerCropTransform())
            .submit()
            .get();
    assertThat(drawable).isNotNull();
  }

  @Test
  public void load_withBitmapResourceId_asBitmap_producesNonNullBitmap()
      throws ExecutionException, InterruptedException {
    Bitmap bitmap =
        Glide.with(context).asBitmap().load(android.R.drawable.star_big_off).submit().get();
    assertThat(bitmap).isNotNull();
  }

  @Test
  public void load_withBitmapAliasResourceId_asDrawable_producesNonNullDrawable()
      throws ExecutionException, InterruptedException {
    Drawable drawable = Glide.with(context).load(ResourceIds.drawable.bitmap_alias).submit().get();
    assertThat(drawable).isNotNull();
  }

  @Test
  public void load_withBitmapAliasResourceId_asDrawable_withTransformation_producesNonNullDrawable()
      throws ExecutionException, InterruptedException {
    Drawable drawable =
        Glide.with(context)
            .load(ResourceIds.drawable.bitmap_alias)
            .apply(centerCropTransform())
            .submit()
            .get();
    assertThat(drawable).isNotNull();
  }

  @Test
  public void load_withBitmapAliasResourceId_asBitmap_producesNonNullBitmap()
      throws ExecutionException, InterruptedException {
    Bitmap bitmap =
        Glide.with(context).asBitmap().load(ResourceIds.drawable.bitmap_alias).submit().get();
    assertThat(bitmap).isNotNull();
  }

  @Test
  public void load_withShapeDrawableResourceId_asDrawable_producesNonNullDrawable()
      throws ExecutionException, InterruptedException {
    Drawable drawable =
        Glide.with(context).load(ResourceIds.drawable.shape_drawable).submit().get();
    assertThat(drawable).isNotNull();
  }

  @Test
  public void load_withShapeDrawableResourceId_asDrawable_withTransformation_sizeOriginal_fails()
      throws ExecutionException, InterruptedException {
    assertThrows(
        ExecutionException.class,
        new ThrowingRunnable() {
          @Override
          public void run() throws Throwable {
            Glide.with(context)
                .load(ResourceIds.drawable.shape_drawable)
                .apply(centerCropTransform())
                .submit()
                .get();
          }
        });
  }

  @Test
  public void load_withShapeDrawableResourceId_asDrawable_withTransformation_validSize_succeeds()
      throws ExecutionException, InterruptedException {
    Drawable drawable =
        Glide.with(context)
            .load(ResourceIds.drawable.shape_drawable)
            .apply(bitmapTransform(new RoundedCorners(10)))
            .submit(100, 200)
            .get();
    assertThat(drawable).isNotNull();
    assertThat(drawable.getIntrinsicWidth()).isEqualTo(100);
    assertThat(drawable.getIntrinsicHeight()).isEqualTo(200);
  }

  @Test
  public void load_withShapeDrawableResourceId_asBitmap_withSizeOriginal_fails()
      throws ExecutionException, InterruptedException {
    assertThrows(
        ExecutionException.class,
        new ThrowingRunnable() {
          @Override
          public void run() throws Throwable {
            Glide.with(context).asBitmap().load(ResourceIds.drawable.shape_drawable).submit().get();
          }
        });
  }

  @Test
  public void load_withShapeDrawableResourceId_asBitmap_withValidSize_returnsNonNullBitmap()
      throws ExecutionException, InterruptedException {
    Bitmap bitmap =
        Glide.with(context)
            .asBitmap()
            .load(ResourceIds.drawable.shape_drawable)
            .submit(100, 200)
            .get();
    assertThat(bitmap).isNotNull();
    assertThat(bitmap.getWidth()).isEqualTo(100);
    assertThat(bitmap.getHeight()).isEqualTo(200);
  }

  @Test
  public void load_withShapeDrawableResourceId_asBitmap_withValidSizeAndTransform_nonNullBitmap()
      throws ExecutionException, InterruptedException {
    Bitmap bitmap =
        Glide.with(context)
            .asBitmap()
            .load(ResourceIds.drawable.shape_drawable)
            .apply(centerCropTransform())
            .submit(100, 200)
            .get();
    assertThat(bitmap).isNotNull();
    assertThat(bitmap.getWidth()).isEqualTo(100);
    assertThat(bitmap.getHeight()).isEqualTo(200);
  }

  @Test
  public void load_withStateListDrawableResourceId_asDrawable_producesNonNullDrawable()
      throws ExecutionException, InterruptedException {
    Drawable drawable =
        Glide.with(context).load(ResourceIds.drawable.state_list_drawable).submit().get();
    assertThat(drawable).isNotNull();
  }

  @Test
  public void load_withStateListDrawableResourceId_asDrawable_withTransformation_nonNullDrawable()
      throws ExecutionException, InterruptedException {
    Drawable drawable =
        Glide.with(context)
            .load(ResourceIds.drawable.state_list_drawable)
            .apply(centerCropTransform())
            .submit()
            .get();
    assertThat(drawable).isNotNull();
  }

  @Test
  public void load_withStateListDrawableResourceId_asBitmap_producesNonNullBitmap()
      throws ExecutionException, InterruptedException {
    Bitmap bitmap =
        Glide.with(context)
            .asBitmap()
            .load(ResourceIds.drawable.state_list_drawable)
            .submit()
            .get();
    assertThat(bitmap).isNotNull();
  }

  @Test
  public void load_withStateListDrawableResourceId_asBitmap_withTransformation_nonNullBitmap()
      throws ExecutionException, InterruptedException {
    Bitmap bitmap =
        Glide.with(context)
            .asBitmap()
            .load(ResourceIds.drawable.state_list_drawable)
            .apply(centerCropTransform())
            .submit()
            .get();
    assertThat(bitmap).isNotNull();
  }

  @Test
  public void load_withVectorDrawableResourceId_asDrawable_producesNonNullDrawable()
      throws ExecutionException, InterruptedException {
    Drawable drawable =
        Glide.with(context).load(ResourceIds.drawable.vector_drawable).submit().get();
    assertThat(drawable).isNotNull();
  }

  @Test
  public void load_withVectorDrawableResourceId_asDrawable_withTransformation_nonNullDrawable()
      throws ExecutionException, InterruptedException {
    Drawable drawable =
        Glide.with(context)
            .load(ResourceIds.drawable.vector_drawable)
            .apply(centerCropTransform())
            .submit()
            .get();
    assertThat(drawable).isNotNull();
  }

  @Test
  public void load_withVectorDrawableResourceId_asBitmap_producesNonNullBitmap()
      throws ExecutionException, InterruptedException {
    Bitmap bitmap =
        Glide.with(context).asBitmap().load(ResourceIds.drawable.vector_drawable).submit().get();
    assertThat(bitmap).isNotNull();
  }

  @Test
  public void load_withVectorDrawableResourceId_asBitmap_withTransformation_producesNonNullBitmap()
      throws ExecutionException, InterruptedException {
    Bitmap bitmap =
        Glide.with(context)
            .asBitmap()
            .load(ResourceIds.drawable.vector_drawable)
            .apply(centerCropTransform())
            .submit()
            .get();
    assertThat(bitmap).isNotNull();
  }

  @Test
  public void load_withNinePatchResourceId_asDrawable_producesNonNullDrawable()
      throws ExecutionException, InterruptedException {
    Drawable drawable =
        Glide.with(context).load(ResourceIds.drawable.googlelogo_color_120x44dp).submit().get();

    assertThat(drawable).isNotNull();
  }

  @Test
  public void load_withNinePatchResourceId_asDrawable_withTransformation_producesNonNullDrawable()
      throws ExecutionException, InterruptedException {
    Drawable drawable =
        Glide.with(context)
            .load(ResourceIds.drawable.googlelogo_color_120x44dp)
            .apply(centerCropTransform())
            .submit()
            .get();

    assertThat(drawable).isNotNull();
  }

  @Test
  public void load_withNinePatchResourceId_asBitmap_producesNonNullBitmap()
      throws ExecutionException, InterruptedException {
    Bitmap bitmap =
        Glide.with(context)
            .asBitmap()
            .load(ResourceIds.drawable.googlelogo_color_120x44dp)
            .submit()
            .get();

    assertThat(bitmap).isNotNull();
  }

  @Test
  public void load_withNinePatchResourceId_asBitmap_withTransformation_producesNonNullBitmap()
      throws ExecutionException, InterruptedException {
    Bitmap bitmap =
        Glide.with(context)
            .asBitmap()
            .load(ResourceIds.drawable.googlelogo_color_120x44dp)
            .apply(centerCropTransform())
            .submit()
            .get();

    assertThat(bitmap).isNotNull();
  }

  @Test
  public void load_withApplicationIconResourceIdUri_asDrawable_producesNonNullDrawable()
      throws NameNotFoundException, ExecutionException, InterruptedException {
    for (String packageName : getInstalledPackages()) {
      int iconResourceId = getResourceId(packageName);

      Uri uri =
          new Uri.Builder()
              .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
              .authority(packageName)
              .path(String.valueOf(iconResourceId))
              .build();

      Drawable drawable = Glide.with(context).load(uri).submit().get();
      assertThat(drawable).isNotNull();
    }
  }

  @Test
  public void load_withApplicationIconResourceIdUri_asDrawable_withTransformation_nonNullDrawable()
      throws NameNotFoundException, ExecutionException, InterruptedException {
    for (String packageName : getInstalledPackages()) {
      int iconResourceId = getResourceId(packageName);

      Uri uri =
          new Uri.Builder()
              .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
              .authority(packageName)
              .path(String.valueOf(iconResourceId))
              .build();

      Drawable drawable = Glide.with(context).load(uri).apply(centerCropTransform()).submit().get();
      assertThat(drawable).isNotNull();
    }
  }

  @Test
  public void load_withApplicationIconResourceIdUri_asBitmap_producesNonNullBitmap()
      throws NameNotFoundException, ExecutionException, InterruptedException {
    for (String packageName : getInstalledPackages()) {
      int iconResourceId = getResourceId(packageName);

      Uri uri =
          new Uri.Builder()
              .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
              .authority(packageName)
              .path(String.valueOf(iconResourceId))
              .build();

      Bitmap bitmap = Glide.with(context).asBitmap().load(uri).submit().get();
      assertThat(bitmap).isNotNull();
    }
  }

  @Test
  public void load_withApplicationIconResourceIdUri_asBitmap_withTransformation_nonNullBitmap()
      throws ExecutionException, InterruptedException {
    for (String packageName : getInstalledPackages()) {
      int iconResourceId = getResourceId(packageName);

      Uri uri =
          new Uri.Builder()
              .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
              .authority(packageName)
              .path(String.valueOf(iconResourceId))
              .build();

      Bitmap bitmap =
          Glide.with(context).asBitmap().apply(centerCropTransform()).load(uri).submit().get();
      assertThat(bitmap).isNotNull();
    }
  }

  @Test
  public void load_withApplicationIconResourceNameUri_asDrawable_producesNonNullDrawable()
      throws ExecutionException, InterruptedException, NameNotFoundException {
    for (String packageName : getInstalledPackages()) {
      int iconResourceId = getResourceId(packageName);

      Context toUse = context.createPackageContext(packageName, /* flags= */ 0);
      Resources resources = toUse.getResources();
      Uri uri =
          new Uri.Builder()
              .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
              .authority(packageName)
              .appendPath(resources.getResourceTypeName(iconResourceId))
              .appendPath(resources.getResourceEntryName(iconResourceId))
              .build();

      Drawable drawable = Glide.with(context).load(uri).submit().get();
      assertThat(drawable).isNotNull();
    }
  }

  @Test
  public void load_withApplicationIconResourceNameUri_asDrawable_withTransform_nonNullDrawable()
      throws ExecutionException, InterruptedException, NameNotFoundException {
    for (String packageName : getInstalledPackages()) {
      int iconResourceId = getResourceId(packageName);

      Context toUse = context.createPackageContext(packageName, /* flags= */ 0);
      Resources resources = toUse.getResources();
      Uri uri =
          new Uri.Builder()
              .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
              .authority(packageName)
              .appendPath(resources.getResourceTypeName(iconResourceId))
              .appendPath(resources.getResourceEntryName(iconResourceId))
              .build();

      Drawable drawable = Glide.with(context).load(uri).apply(centerCropTransform()).submit().get();
      assertThat(drawable).isNotNull();
    }
  }

  @Test
  public void load_withApplicationIconResourceNameUri_asBitmap_producesNonNullBitmap()
      throws ExecutionException, InterruptedException, NameNotFoundException {
    for (String packageName : getInstalledPackages()) {
      int iconResourceId = getResourceId(packageName);

      Context toUse = context.createPackageContext(packageName, /* flags= */ 0);
      Resources resources = toUse.getResources();
      Uri uri =
          new Uri.Builder()
              .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
              .authority(packageName)
              .appendPath(resources.getResourceTypeName(iconResourceId))
              .appendPath(resources.getResourceEntryName(iconResourceId))
              .build();

      Bitmap bitmap = Glide.with(context).asBitmap().load(uri).submit().get();
      assertThat(bitmap).isNotNull();
    }
  }

  @Test
  public void load_withApplicationIconResourceNameUri_asBitmap_withTransform_nonNullBitmap()
      throws ExecutionException, InterruptedException, NameNotFoundException {
    for (String packageName : getInstalledPackages()) {
      int iconResourceId = getResourceId(packageName);

      Context toUse = context.createPackageContext(packageName, /* flags= */ 0);
      Resources resources = toUse.getResources();
      Uri uri =
          new Uri.Builder()
              .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
              .authority(packageName)
              .appendPath(resources.getResourceTypeName(iconResourceId))
              .appendPath(resources.getResourceEntryName(iconResourceId))
              .build();

      Bitmap bitmap =
          Glide.with(context).asBitmap().apply(centerCropTransform()).load(uri).submit().get();
      assertThat(bitmap).isNotNull();
    }
  }

  private Set<String> getInstalledPackages() {
    Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
    mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
    PackageManager packageManager = context.getPackageManager();
    List<ResolveInfo> pkgAppsList =
        packageManager.queryIntentActivities(mainIntent, /* flags= */ 0);
    Set<String> result = new HashSet<>();
    for (ResolveInfo info : pkgAppsList) {
      String packageName = info.activityInfo.packageName;
      int iconResourceId = getResourceId(packageName);
      if (iconResourceId != 0
          && doesApplicationPackageNameMatchResourcePackageName(packageName, iconResourceId)) {
        result.add(info.activityInfo.packageName);
      }
    }
    return result;
  }

  private int getResourceId(String packageName) {
    PackageInfo packageInfo;
    try {
      packageInfo = context.getPackageManager().getPackageInfo(packageName, /* flags= */ 0);
    } catch (NameNotFoundException e) {
      return 0;
    }
    return packageInfo.applicationInfo.icon;
  }

  /**
   * Returns {@code true} iff the resource package name is exactly the same as the containing
   * application package name for a given resource id.
   *
   * <p>The resource package name is the value returned by {@link
   * Resources#getResourcePackageName(int)}. The application package name is package name of the
   * enclosing application. If these two things are equal, then we can both construct a Context for
   * that package and retrieve a resource id for that package from a "standard" resource Uri
   * containing a name instead of an id. If they aren't equal, then we can do only one of the two
   * required tasks, so our Uri load will always fail. To handle this properly, we'd need callers to
   * include both package names in the Uri. I'm not aware of any standardized Uri format for doing
   * so, so these requests will just be treated as unsupported for the time being.
   *
   * <p>Take Calendar (emulators API 24 and below) as an example:
   *
   * <ul>
   *   <li>package name: com.google.android.calendar
   *   <li>resource package name: com.android.calendar
   * </ul>
   *
   * We can construct one of two possible Uris:
   *
   * <ul>
   *   <li>android.resource://com.google.android.calendar/mipmap/ic_icon_calendar.
   *   <li>android.resource://com.android.calendar/mipmap/ic_icon_calendar.<
   * </ul>
   *
   * From the first Uri, we can obtain the correct Context/Resources for the calendar package, but
   * our attempts to resolve the correct resource id will fail because we do not have the resource
   * package name. From the second Uri we cannot obtain the Context/Resources for the calendar
   * package because the resource package name doesn't match the application package name.
   */
  private boolean doesApplicationPackageNameMatchResourcePackageName(
      String applicationPackageName, int iconResourceId) {
    try {
      Context current = context.createPackageContext(applicationPackageName, /* flags= */ 0);
      String resourcePackageName = current.getResources().getResourcePackageName(iconResourceId);
      return applicationPackageName.equals(resourcePackageName);
    } catch (NameNotFoundException e) {
      // This should never happen
      throw new RuntimeException(e);
    }
  }
}