chromium/ui/android/java/src/org/chromium/ui/resources/ResourceManager.java

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.ui.resources;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.util.SparseArray;

import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.NativeMethods;

import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.display.DisplayAndroid;
import org.chromium.ui.resources.ResourceLoader.ResourceLoaderCallback;
import org.chromium.ui.resources.dynamics.BitmapDynamicResource;
import org.chromium.ui.resources.dynamics.DynamicResourceLoader;
import org.chromium.ui.resources.statics.StaticResourceLoader;
import org.chromium.ui.resources.system.SystemResourceLoader;

/**
 * The Java component of a manager for all static resources to be loaded and used by CC layers.
 * This class does not hold any resource state, but passes it directly to native as they are loaded.
 */
@JNINamespace("ui")
public class ResourceManager implements ResourceLoaderCallback {
    private final SparseArray<ResourceLoader> mResourceLoaders = new SparseArray<ResourceLoader>();
    private final SparseArray<SparseArray<LayoutResource>> mLoadedResources =
            new SparseArray<SparseArray<LayoutResource>>();

    private final float mPxToDp;

    private long mNativeResourceManagerPtr;

    private ResourceManager(
            Resources resources, int minScreenSideLength, long staticResourceManagerPtr) {
        mPxToDp = 1.f / resources.getDisplayMetrics().density;

        registerResourceLoader(
                new StaticResourceLoader(AndroidResourceType.STATIC, this, resources));
        registerResourceLoader(new DynamicResourceLoader(AndroidResourceType.DYNAMIC, this));
        registerResourceLoader(new DynamicResourceLoader(AndroidResourceType.DYNAMIC_BITMAP, this));
        registerResourceLoader(
                new SystemResourceLoader(AndroidResourceType.SYSTEM, this, minScreenSideLength));

        mNativeResourceManagerPtr = staticResourceManagerPtr;
    }

    /**
     * Creates an instance of a {@link ResourceManager}.
     * @param windowAndroid            A {@link WindowAndroid} instance to fetch a {@link Context}
     * and thus grab {@link Resources} from.
     * @param staticResourceManagerPtr A pointer to the native component of this class.
     * @return                         A new instance of a {@link ResourceManager}.
     */
    @CalledByNative
    private static ResourceManager create(
            WindowAndroid windowAndroid, long staticResourceManagerPtr) {
        Context context = windowAndroid.getContext().get();
        // This call should happen early enough (i.e. during construction) that this context should
        // not yet have been released.
        if (context == null) {
            throw new IllegalStateException("Context should not be null during initialization.");
        }

        DisplayAndroid displayAndroid = windowAndroid.getDisplay();
        int minScreenSideLength =
                Math.min(displayAndroid.getDisplayWidth(), displayAndroid.getDisplayHeight());

        Resources resources = context.getResources();
        return new ResourceManager(resources, minScreenSideLength, staticResourceManagerPtr);
    }

    /**
     * @return A reference to the {@link DynamicResourceLoader} that provides
     *         {@link Resource} objects to this class.
     */
    public DynamicResourceLoader getDynamicResourceLoader() {
        return (DynamicResourceLoader) mResourceLoaders.get(AndroidResourceType.DYNAMIC);
    }

    /**
     * @return A reference to the {@link DynamicResourceLoader} for bitmaps that provides
     *         {@link BitmapDynamicResource} objects to this class.
     */
    public DynamicResourceLoader getBitmapDynamicResourceLoader() {
        return (DynamicResourceLoader) mResourceLoaders.get(AndroidResourceType.DYNAMIC_BITMAP);
    }

    /**
     * Automatically loads any synchronous resources specified in |syncIds| and will start
     * asynchronous reads for any asynchronous resources specified in |asyncIds|.
     * @param type AndroidResourceType which will be loaded.
     * @param syncIds Resource ids which will be loaded synchronously.
     * @param asyncIds Resource ids which will be loaded asynchronously.
     */
    public void preloadResources(int type, int[] syncIds, int[] asyncIds) {
        ResourceLoader loader = mResourceLoaders.get(type);
        if (asyncIds != null) {
            for (Integer resId : asyncIds) {
                loader.preloadResource(resId);
            }
        }

        if (syncIds != null) {
            for (Integer resId : syncIds) {
                loader.loadResource(resId);
            }
        }
    }

    /**
     * @param resType The type of the Android resource.
     * @param resId   The id of the Android resource.
     * @return The corresponding {@link LayoutResource}.
     */
    public LayoutResource getResource(@AndroidResourceType int resType, int resId) {
        SparseArray<LayoutResource> bucket = mLoadedResources.get(resType);
        return bucket != null ? bucket.get(resId) : null;
    }

    @SuppressWarnings("cast")
    @Override
    public void onResourceLoaded(@AndroidResourceType int resType, int resId, Resource resource) {
        if (resource == null) return;
        Bitmap bitmap = resource.getBitmap();
        if (bitmap == null) {
            if (resource.shouldRemoveResourceOnNullBitmap() && mNativeResourceManagerPtr != 0) {
                ResourceManagerJni.get()
                        .removeResource(
                                mNativeResourceManagerPtr, ResourceManager.this, resType, resId);
            }
            return;
        }

        saveMetadataForLoadedResource(resType, resId, resource);

        if (mNativeResourceManagerPtr == 0) return;

        ResourceManagerJni.get()
                .onResourceReady(
                        mNativeResourceManagerPtr,
                        ResourceManager.this,
                        resType,
                        resId,
                        bitmap,
                        resource.getBitmapSize().width(),
                        resource.getBitmapSize().height(),
                        resource.createNativeResource());
    }

    @Override
    public void onResourceUnregistered(@AndroidResourceType int resType, int resId) {
        // Only remove dynamic resources that were unregistered.
        if (resType != AndroidResourceType.DYNAMIC_BITMAP
                && resType != AndroidResourceType.DYNAMIC) {
            return;
        }

        if (mNativeResourceManagerPtr == 0) return;

        ResourceManagerJni.get()
                .removeResource(mNativeResourceManagerPtr, ResourceManager.this, resType, resId);
    }

    /** Clear the cache of tinted assets that the native manager holds. */
    public void clearTintedResourceCache() {
        if (mNativeResourceManagerPtr == 0) return;
        ResourceManagerJni.get()
                .clearTintedResourceCache(mNativeResourceManagerPtr, ResourceManager.this);
    }

    private void saveMetadataForLoadedResource(
            @AndroidResourceType int resType, int resId, Resource resource) {
        SparseArray<LayoutResource> bucket = mLoadedResources.get(resType);
        if (bucket == null) {
            bucket = new SparseArray<LayoutResource>();
            mLoadedResources.put(resType, bucket);
        }
        bucket.put(resId, new LayoutResource(mPxToDp, resource));
    }

    @CalledByNative
    private void destroy() {
        assert mNativeResourceManagerPtr != 0;
        mNativeResourceManagerPtr = 0;
    }

    @CalledByNative
    private void resourceRequested(int resType, int resId) {
        ResourceLoader loader = mResourceLoaders.get(resType);
        if (loader != null) loader.loadResource(resId);
    }

    @CalledByNative
    private void preloadResource(int resType, int resId) {
        ResourceLoader loader = mResourceLoaders.get(resType);
        if (loader != null) loader.preloadResource(resId);
    }

    @CalledByNative
    private long getNativePtr() {
        return mNativeResourceManagerPtr;
    }

    private void registerResourceLoader(ResourceLoader loader) {
        mResourceLoaders.put(loader.getResourceType(), loader);
    }

    @NativeMethods
    interface Natives {
        void onResourceReady(
                long nativeResourceManagerImpl,
                ResourceManager caller,
                int resType,
                int resId,
                Bitmap bitmap,
                int width,
                int height,
                long nativeResource);

        void removeResource(
                long nativeResourceManagerImpl, ResourceManager caller, int resType, int resId);

        void clearTintedResourceCache(long nativeResourceManagerImpl, ResourceManager caller);
    }
}