blob: f6402181af219f39f51cf5561a2164b3d138b81c [file] [log] [blame]
/*
* 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;
}
}