1 /* 2 * Copyright 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.apps.common.imaging; 18 19 20 import static androidx.core.util.Preconditions.checkNotNull; 21 22 import android.content.Context; 23 import android.graphics.drawable.ColorDrawable; 24 import android.graphics.drawable.Drawable; 25 import android.net.Uri; 26 import android.util.Size; 27 28 import androidx.annotation.NonNull; 29 import androidx.annotation.Nullable; 30 31 import com.android.car.apps.common.R; 32 import com.android.car.apps.common.UriUtils; 33 34 import java.util.Objects; 35 import java.util.function.BiConsumer; 36 import java.util.function.Consumer; 37 38 /** 39 * A helper class to bind an image to a UI element, updating the image when needed. 40 * @param <T> see {@link ImageRef}. 41 */ 42 public class ImageBinder<T extends ImageBinder.ImageRef> { 43 44 public enum PlaceholderType { 45 /** For elements that don't want to display a placeholder (like tabs). */ 46 NONE, 47 /** A placeholder displayed in the foreground, typically has more details. */ 48 FOREGROUND, 49 /** A placeholder displayed in the background, typically has less details. */ 50 BACKGROUND 51 } 52 53 /** 54 * Interface to define keys for identifying images. 55 */ 56 public interface ImageRef { 57 58 /** Returns whether the given {@link ImageRef} and this one reference the same image. */ equals(Context context, Object other)59 boolean equals(Context context, Object other); 60 61 /** Returns the uri to use to retrieve the image. */ getImageURI()62 @Nullable Uri getImageURI(); 63 64 /** For when the image ref doesn't always use a uri. */ getImage(Context context)65 default @Nullable Drawable getImage(Context context) { 66 return null; 67 } 68 69 /** Returns a placeholder for images that can't be found. */ getPlaceholder(Context context, @NonNull PlaceholderType type)70 Drawable getPlaceholder(Context context, @NonNull PlaceholderType type); 71 } 72 73 private final PlaceholderType mPlaceholderType; 74 private final Size mMaxImageSize; 75 @Nullable 76 private final Consumer<Drawable> mClient; 77 78 private T mCurrentRef; 79 private ImageKey mCurrentKey; 80 private BiConsumer<ImageKey, Drawable> mFetchReceiver; 81 private Drawable mLoadingDrawable; 82 83 ImageBinder(@onNull PlaceholderType type, @NonNull Size maxImageSize, @NonNull Consumer<Drawable> consumer)84 public ImageBinder(@NonNull PlaceholderType type, @NonNull Size maxImageSize, 85 @NonNull Consumer<Drawable> consumer) { 86 mPlaceholderType = checkNotNull(type, "Need a type"); 87 mMaxImageSize = checkNotNull(maxImageSize, "Need a size"); 88 mClient = checkNotNull(consumer, "Cannot bind a null consumer"); 89 } 90 ImageBinder(@onNull PlaceholderType type, @NonNull Size maxImageSize)91 protected ImageBinder(@NonNull PlaceholderType type, @NonNull Size maxImageSize) { 92 mPlaceholderType = checkNotNull(type, "Need a type"); 93 mMaxImageSize = checkNotNull(maxImageSize, "Need a size"); 94 mClient = null; 95 } 96 setDrawable(@ullable Drawable drawable)97 protected void setDrawable(@Nullable Drawable drawable) { 98 if (mClient != null) { 99 mClient.accept(drawable); 100 } 101 } 102 103 /** Fetches a new image if needed. */ setImage(Context context, @Nullable T newRef)104 public void setImage(Context context, @Nullable T newRef) { 105 if (isSameImage(context, newRef)) { 106 return; 107 } 108 109 prepareForNewBinding(context); 110 111 mCurrentRef = newRef; 112 113 if (mCurrentRef == null) { 114 setDrawable(null); 115 } else { 116 Drawable image = mCurrentRef.getImage(context); 117 if (image != null) { 118 setDrawable(image); 119 return; 120 } 121 122 mFetchReceiver = (key, drawable) -> { 123 if (Objects.equals(mCurrentKey, key)) { 124 Drawable displayed = 125 (drawable == null && mPlaceholderType != PlaceholderType.NONE) 126 ? mCurrentRef.getPlaceholder(context, mPlaceholderType) 127 : drawable; 128 setDrawable(displayed); 129 onRequestFinished(); 130 } 131 }; 132 133 if (UriUtils.isEmpty(mCurrentRef.getImageURI())) { 134 mCurrentKey = null; 135 mFetchReceiver.accept(null, null); 136 } else { 137 mCurrentKey = new ImageKey(mCurrentRef.getImageURI(), mMaxImageSize); 138 getImageFetcher(context).getImage(context, mCurrentKey, mFetchReceiver); 139 } 140 } 141 } 142 isSameImage(Context context, @Nullable T newRef)143 private boolean isSameImage(Context context, @Nullable T newRef) { 144 if (mCurrentRef == null && newRef == null) return true; 145 146 if (mCurrentRef != null && newRef != null) { 147 return mCurrentRef.equals(context, newRef); 148 } 149 150 return false; 151 } 152 getImageFetcher(Context context)153 private LocalImageFetcher getImageFetcher(Context context) { 154 return LocalImageFetcher.getInstance(context); 155 } 156 prepareForNewBinding(Context context)157 protected void prepareForNewBinding(Context context) { 158 if (mCurrentKey != null) { 159 getImageFetcher(context).cancelRequest(mCurrentKey, mFetchReceiver); 160 onRequestFinished(); 161 } 162 setDrawable(mPlaceholderType != PlaceholderType.NONE ? getLoadingDrawable(context) : null); 163 } 164 onRequestFinished()165 private void onRequestFinished() { 166 mCurrentKey = null; 167 mFetchReceiver = null; 168 } 169 getLoadingDrawable(Context context)170 private Drawable getLoadingDrawable(Context context) { 171 if (mLoadingDrawable == null) { 172 int color = context.getColor(R.color.loading_image_placeholder_color); 173 mLoadingDrawable = new ColorDrawable(color); 174 } 175 return mLoadingDrawable; 176 } 177 } 178