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 com.android.internal.util.Preconditions.checkNotNull; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.content.Context; 25 import android.graphics.drawable.ColorDrawable; 26 import android.graphics.drawable.Drawable; 27 import android.net.Uri; 28 import android.util.Size; 29 30 import com.android.car.apps.common.R; 31 import com.android.car.apps.common.UriUtils; 32 33 import java.util.Objects; 34 import java.util.function.BiConsumer; 35 import java.util.function.Consumer; 36 37 /** 38 * A helper class to bind an image to a UI element, updating the image when needed. 39 * @param <T> see {@link ImageRef}. 40 */ 41 public class ImageBinder<T extends ImageBinder.ImageRef> { 42 43 public enum PlaceholderType { 44 /** For elements that don't want to display a placeholder (like tabs). */ 45 NONE, 46 /** A placeholder displayed in the foreground, typically has more details. */ 47 FOREGROUND, 48 /** A placeholder displayed in the background, typically has less details. */ 49 BACKGROUND 50 } 51 52 /** 53 * Interface to define keys for identifying images. 54 */ 55 public interface ImageRef { 56 57 /** Returns whether the given {@link ImageRef} and this one reference the same image. */ equals(Context context, Object other)58 boolean equals(Context context, Object other); 59 60 /** Returns the uri to use to retrieve the image. */ getImageURI()61 @Nullable Uri getImageURI(); 62 63 /** For when the image ref doesn't always use a uri. */ getImage(Context context)64 default @Nullable Drawable getImage(Context context) { 65 return null; 66 } 67 68 /** Returns a placeholder for images that can't be found. */ getPlaceholder(Context context, @NonNull PlaceholderType type)69 Drawable getPlaceholder(Context context, @NonNull PlaceholderType type); 70 } 71 72 private final PlaceholderType mPlaceholderType; 73 private final Size mMaxImageSize; 74 @Nullable 75 private final Consumer<Drawable> mClient; 76 77 private T mCurrentRef; 78 private ImageKey mCurrentKey; 79 private BiConsumer<ImageKey, Drawable> mFetchReceiver; 80 private Drawable mLoadingDrawable; 81 82 ImageBinder(@onNull PlaceholderType type, @NonNull Size maxImageSize, @NonNull Consumer<Drawable> consumer)83 public ImageBinder(@NonNull PlaceholderType type, @NonNull Size maxImageSize, 84 @NonNull Consumer<Drawable> consumer) { 85 mPlaceholderType = checkNotNull(type, "Need a type"); 86 mMaxImageSize = checkNotNull(maxImageSize, "Need a size"); 87 mClient = checkNotNull(consumer, "Cannot bind a null consumer"); 88 } 89 ImageBinder(@onNull PlaceholderType type, @NonNull Size maxImageSize)90 protected ImageBinder(@NonNull PlaceholderType type, @NonNull Size maxImageSize) { 91 mPlaceholderType = checkNotNull(type, "Need a type"); 92 mMaxImageSize = checkNotNull(maxImageSize, "Need a size"); 93 mClient = null; 94 } 95 setDrawable(@ullable Drawable drawable)96 protected void setDrawable(@Nullable Drawable drawable) { 97 if (mClient != null) { 98 mClient.accept(drawable); 99 } 100 } 101 102 /** Fetches a new image if needed. */ setImage(Context context, @Nullable T newRef)103 public void setImage(Context context, @Nullable T newRef) { 104 if (isSameImage(context, newRef)) { 105 return; 106 } 107 108 prepareForNewBinding(context); 109 110 mCurrentRef = newRef; 111 112 if (mCurrentRef == null) { 113 setDrawable(null); 114 } else { 115 Drawable image = mCurrentRef.getImage(context); 116 if (image != null) { 117 setDrawable(image); 118 return; 119 } 120 121 mFetchReceiver = (key, drawable) -> { 122 if (Objects.equals(mCurrentKey, key)) { 123 Drawable displayed = 124 (drawable == null && mPlaceholderType != PlaceholderType.NONE) 125 ? mCurrentRef.getPlaceholder(context, mPlaceholderType) 126 : drawable; 127 setDrawable(displayed); 128 onRequestFinished(); 129 } 130 }; 131 132 if (UriUtils.isEmpty(mCurrentRef.getImageURI())) { 133 mCurrentKey = null; 134 mFetchReceiver.accept(null, null); 135 } else { 136 mCurrentKey = new ImageKey(mCurrentRef.getImageURI(), mMaxImageSize); 137 getImageFetcher(context).getImage(context, mCurrentKey, mFetchReceiver); 138 } 139 } 140 } 141 isSameImage(Context context, @Nullable T newRef)142 private boolean isSameImage(Context context, @Nullable T newRef) { 143 if (mCurrentRef == null && newRef == null) return true; 144 145 if (mCurrentRef != null && newRef != null) { 146 return mCurrentRef.equals(context, newRef); 147 } 148 149 return false; 150 } 151 getImageFetcher(Context context)152 private LocalImageFetcher getImageFetcher(Context context) { 153 return LocalImageFetcher.getInstance(context); 154 } 155 prepareForNewBinding(Context context)156 protected void prepareForNewBinding(Context context) { 157 if (mCurrentKey != null) { 158 getImageFetcher(context).cancelRequest(mCurrentKey, mFetchReceiver); 159 onRequestFinished(); 160 } 161 setDrawable(mPlaceholderType != PlaceholderType.NONE ? getLoadingDrawable(context) : null); 162 } 163 onRequestFinished()164 private void onRequestFinished() { 165 mCurrentKey = null; 166 mFetchReceiver = null; 167 } 168 getLoadingDrawable(Context context)169 private Drawable getLoadingDrawable(Context context) { 170 if (mLoadingDrawable == null) { 171 int color = context.getColor(R.color.loading_image_placeholder_color); 172 mLoadingDrawable = new ColorDrawable(color); 173 } 174 return mLoadingDrawable; 175 } 176 } 177