| /* |
| * Copyright 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.car.apps.common.imaging; |
| |
| |
| import static com.android.internal.util.Preconditions.checkNotNull; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.content.Context; |
| import android.graphics.drawable.ColorDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.net.Uri; |
| import android.util.Size; |
| |
| import com.android.car.apps.common.R; |
| import com.android.car.apps.common.UriUtils; |
| |
| import java.util.Objects; |
| import java.util.function.BiConsumer; |
| import java.util.function.Consumer; |
| |
| /** |
| * A helper class to bind an image to a UI element, updating the image when needed. |
| * @param <T> see {@link ImageRef}. |
| */ |
| public class ImageBinder<T extends ImageBinder.ImageRef> { |
| |
| public enum PlaceholderType { |
| /** For elements that don't want to display a placeholder (like tabs). */ |
| NONE, |
| /** A placeholder displayed in the foreground, typically has more details. */ |
| FOREGROUND, |
| /** A placeholder displayed in the background, typically has less details. */ |
| BACKGROUND |
| } |
| |
| /** |
| * Interface to define keys for identifying images. |
| */ |
| public interface ImageRef { |
| |
| /** Returns whether the given {@link ImageRef} and this one reference the same image. */ |
| boolean equals(Context context, Object other); |
| |
| /** Returns the uri to use to retrieve the image. */ |
| @Nullable Uri getImageURI(); |
| |
| /** For when the image ref doesn't always use a uri. */ |
| default @Nullable Drawable getImage(Context context) { |
| return null; |
| } |
| |
| /** Returns a placeholder for images that can't be found. */ |
| Drawable getPlaceholder(Context context, @NonNull PlaceholderType type); |
| } |
| |
| private final PlaceholderType mPlaceholderType; |
| private final Size mMaxImageSize; |
| @Nullable |
| private final Consumer<Drawable> mClient; |
| |
| private T mCurrentRef; |
| private ImageKey mCurrentKey; |
| private BiConsumer<ImageKey, Drawable> mFetchReceiver; |
| private Drawable mLoadingDrawable; |
| |
| |
| public ImageBinder(@NonNull PlaceholderType type, @NonNull Size maxImageSize, |
| @NonNull Consumer<Drawable> consumer) { |
| mPlaceholderType = checkNotNull(type, "Need a type"); |
| mMaxImageSize = checkNotNull(maxImageSize, "Need a size"); |
| mClient = checkNotNull(consumer, "Cannot bind a null consumer"); |
| } |
| |
| protected ImageBinder(@NonNull PlaceholderType type, @NonNull Size maxImageSize) { |
| mPlaceholderType = checkNotNull(type, "Need a type"); |
| mMaxImageSize = checkNotNull(maxImageSize, "Need a size"); |
| mClient = null; |
| } |
| |
| protected void setDrawable(@Nullable Drawable drawable) { |
| if (mClient != null) { |
| mClient.accept(drawable); |
| } |
| } |
| |
| /** Fetches a new image if needed. */ |
| public void setImage(Context context, @Nullable T newRef) { |
| if (isSameImage(context, newRef)) { |
| return; |
| } |
| |
| prepareForNewBinding(context); |
| |
| mCurrentRef = newRef; |
| |
| if (mCurrentRef == null) { |
| setDrawable(null); |
| } else { |
| Drawable image = mCurrentRef.getImage(context); |
| if (image != null) { |
| setDrawable(image); |
| return; |
| } |
| |
| mFetchReceiver = (key, drawable) -> { |
| if (Objects.equals(mCurrentKey, key)) { |
| Drawable displayed = |
| (drawable == null && mPlaceholderType != PlaceholderType.NONE) |
| ? mCurrentRef.getPlaceholder(context, mPlaceholderType) |
| : drawable; |
| setDrawable(displayed); |
| onRequestFinished(); |
| } |
| }; |
| |
| if (UriUtils.isEmpty(mCurrentRef.getImageURI())) { |
| mCurrentKey = null; |
| mFetchReceiver.accept(null, null); |
| } else { |
| mCurrentKey = new ImageKey(mCurrentRef.getImageURI(), mMaxImageSize); |
| getImageFetcher(context).getImage(context, mCurrentKey, mFetchReceiver); |
| } |
| } |
| } |
| |
| private boolean isSameImage(Context context, @Nullable T newRef) { |
| if (mCurrentRef == null && newRef == null) return true; |
| |
| if (mCurrentRef != null && newRef != null) { |
| return mCurrentRef.equals(context, newRef); |
| } |
| |
| return false; |
| } |
| |
| private LocalImageFetcher getImageFetcher(Context context) { |
| return LocalImageFetcher.getInstance(context); |
| } |
| |
| protected void prepareForNewBinding(Context context) { |
| if (mCurrentKey != null) { |
| getImageFetcher(context).cancelRequest(mCurrentKey, mFetchReceiver); |
| onRequestFinished(); |
| } |
| setDrawable(mPlaceholderType != PlaceholderType.NONE ? getLoadingDrawable(context) : null); |
| } |
| |
| private void onRequestFinished() { |
| mCurrentKey = null; |
| mFetchReceiver = null; |
| } |
| |
| private Drawable getLoadingDrawable(Context context) { |
| if (mLoadingDrawable == null) { |
| int color = context.getColor(R.color.loading_image_placeholder_color); |
| mLoadingDrawable = new ColorDrawable(color); |
| } |
| return mLoadingDrawable; |
| } |
| } |