ResourceAccessors.java

/*
 * Copyright 2021 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 androidx.wear.tiles.renderer.internal;

import android.annotation.SuppressLint;
import android.graphics.drawable.Drawable;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.concurrent.futures.ResolvableFuture;
import androidx.wear.tiles.proto.ResourceProto;
import androidx.wear.tiles.proto.ResourceProto.AndroidImageResourceByResId;
import androidx.wear.tiles.proto.ResourceProto.InlineImageResource;

import com.google.common.util.concurrent.ListenableFuture;

/**
 * Class for accessing resources. Delegates the actual work to different types of accessor classes,
 * and allows each type of accessor to be configured individually, as well as instantiation from
 * common accessor implementations.
 */
public class ResourceAccessors {
    private final ResourceProto.Resources mProtoResources;

    @Nullable
    private final AndroidImageResourceByResIdAccessor mAndroidImageResourceByResIdAccessor;

    @Nullable private final InlineImageResourceAccessor mInlineImageResourceAccessor;

    ResourceAccessors(
            ResourceProto.Resources protoResources,
            @Nullable AndroidImageResourceByResIdAccessor androidImageResourceByResIdAccessor,
            @Nullable InlineImageResourceAccessor inlineImageResourceAccessor) {
        this.mProtoResources = protoResources;
        this.mAndroidImageResourceByResIdAccessor = androidImageResourceByResIdAccessor;
        this.mInlineImageResourceAccessor = inlineImageResourceAccessor;
    }

    /** Exception thrown when accessing resources. */
    public static final class ResourceAccessException extends Exception {
        public ResourceAccessException(@NonNull String description) {
            super(description);
        }

        public ResourceAccessException(@NonNull String description, @NonNull Exception cause) {
            super(description, cause);
        }
    }

    /** Interface that can provide a Drawable for an AndroidImageResourceByResId */
    public interface AndroidImageResourceByResIdAccessor {
        /** Get the drawable as specified by {@code resource}. */
        @NonNull
        ListenableFuture<Drawable> getDrawable(@NonNull AndroidImageResourceByResId resource);
    }

    /** Interface that can provide a Drawable for an InlineImageResource */
    public interface InlineImageResourceAccessor {
        /** Get the drawable as specified by {@code resource}. */
        @NonNull
        ListenableFuture<Drawable> getDrawable(@NonNull InlineImageResource resource);
    }

    /** Get an empty builder to build {@link ResourceAccessors} with. */
    @NonNull
    public static Builder builder(@NonNull ResourceProto.Resources protoResources) {
        return new Builder(protoResources);
    }

    /** Get the drawable corresponding to the given resource ID. */
    @NonNull
    @SuppressLint("RestrictedApi") // TODO(b/183006740): Remove when prefix check is fixed.
    public ListenableFuture<Drawable> getDrawable(@NonNull String protoResourceId) {
        ResolvableFuture<Drawable> errorFuture = ResolvableFuture.create();
        ResourceProto.ImageResource imageResource =
                mProtoResources.getIdToImageMap().get(protoResourceId);

        if (imageResource == null) {
            errorFuture.setException(
                    new IllegalArgumentException(
                            "Resource " + protoResourceId + " is not defined in resources bundle"));
            return errorFuture;
        }

        if (imageResource.hasAndroidResourceByResid()
                && mAndroidImageResourceByResIdAccessor != null) {
            AndroidImageResourceByResIdAccessor accessor = mAndroidImageResourceByResIdAccessor;
            return accessor.getDrawable(imageResource.getAndroidResourceByResid());
        }

        if (imageResource.hasInlineResource() && mInlineImageResourceAccessor != null) {
            InlineImageResourceAccessor accessor = mInlineImageResourceAccessor;
            return accessor.getDrawable(imageResource.getInlineResource());
        }

        errorFuture.setException(
                new IllegalArgumentException(
                        new ResourceAccessException("Can't find accessor for image resource.")));
        return errorFuture;
    }

    /** Builder for ResourceProviders */
    public static final class Builder {
        private final ResourceProto.Resources mProtoResources;
        @Nullable private AndroidImageResourceByResIdAccessor mAndroidImageResourceByResIdAccessor;
        @Nullable private InlineImageResourceAccessor mInlineImageResourceAccessor;

        Builder(ResourceProto.Resources protoResources) {
            this.mProtoResources = protoResources;
        }

        /** Set the resource loader for {@link AndroidImageResourceByResIdAccessor} resources. */
        @NonNull
        @SuppressLint("MissingGetterMatchingBuilder")
        public Builder setAndroidImageResourceByResIdAccessor(
                @NonNull AndroidImageResourceByResIdAccessor accessor) {
            mAndroidImageResourceByResIdAccessor = accessor;
            return this;
        }

        /** Set the resource loader for {@link InlineImageResourceAccessor} resources. */
        @NonNull
        @SuppressLint("MissingGetterMatchingBuilder")
        public Builder setInlineImageResourceAccessor(
                @NonNull InlineImageResourceAccessor accessor) {
            mInlineImageResourceAccessor = accessor;
            return this;
        }

        /** Build a {@link ResourceAccessors} instance. */
        @NonNull
        public ResourceAccessors build() {
            return new ResourceAccessors(
                    mProtoResources,
                    mAndroidImageResourceByResIdAccessor,
                    mInlineImageResourceAccessor);
        }
    }
}