/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.platform

import android.annotation.TargetApi
import android.graphics.Bitmap
import android.graphics.BitmapFactory.Options
import android.os.Build
import androidx.core.util.Pools
import com.facebook.imagepipeline.memory.BitmapPool
import com.facebook.imageutils.BitmapUtil
import java.nio.ByteBuffer
import javax.annotation.concurrent.ThreadSafe

/** Bitmap decoder for ART VM (Android O and up). */
@TargetApi(Build.VERSION_CODES.O)
@ThreadSafe
class OreoDecoder(bitmapPool: BitmapPool, decodeBuffers: Pools.Pool<ByteBuffer>) :
    DefaultDecoder(bitmapPool, decodeBuffers) {

  override fun getBitmapSize(width: Int, height: Int, options: Options): Int =
      // If the color is wide gamut but the Bitmap Config doesn't use 8 bytes per pixel, the size of
      // the bitmap
      // needs to be computed manually to get the correct size.
      if (hasColorGamutMismatch(options)) width * height * 8
      else
          BitmapUtil.getSizeInByteForBitmap(
              width, height, options.inPreferredConfig ?: Bitmap.Config.ARGB_8888)

  companion object {
    /** Check if the color space has a wide color gamut and is consistent with the Bitmap config */
    private fun hasColorGamutMismatch(options: Options): Boolean =
        options.outColorSpace != null &&
            options.outColorSpace.isWideGamut &&
            options.inPreferredConfig != Bitmap.Config.RGBA_F16
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.platform

import android.graphics.Bitmap
import android.graphics.ColorSpace
import android.graphics.Rect
import com.facebook.common.references.CloseableReference
import com.facebook.imagepipeline.image.EncodedImage

interface PlatformDecoder {

  /**
   * Creates a bitmap from encoded bytes. Supports JPEG but callers should use
   * [decodeJPEGFromEncodedImage] for partial JPEGs. In addition, a region to decode can be supplied
   * in order to minimize memory usage. NOTE: Not all platform decoders necessarily support
   * supplying specific regions.
   *
   * Note: This needs to be kept because of dependencies issues.
   *
   * @param encodedImage the reference to the encoded image with the reference to the encoded bytes
   * @param bitmapConfig the [android.graphics.Bitmap.Config] used to create the decoded Bitmap
   * @param regionToDecode optional image region to decode or null to decode the whole image
   * @return the bitmap
   * @throws TooManyBitmapsException if the pool is full
   * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated
   */
  fun decodeFromEncodedImage(
      encodedImage: EncodedImage,
      bitmapConfig: Bitmap.Config,
      regionToDecode: Rect?
  ): CloseableReference<Bitmap>?

  /**
   * Creates a bitmap from encoded JPEG bytes. Supports a partial JPEG image. In addition, a region
   * to decode can be supplied in order to minimize memory usage. NOTE: Not all platform decoders
   * necessarily support supplying specific regions.
   *
   * Note: This needs to be kept because of dependencies issues.
   *
   * @param encodedImage the reference to the encoded image with the reference to the encoded bytes
   * @param bitmapConfig the [android.graphics.Bitmap.Config] used to create the decoded Bitmap
   * @param regionToDecode optional image region to decode or null to decode the whole image.
   * @param length the number of encoded bytes in the buffer
   * @return the bitmap
   * @throws TooManyBitmapsException if the pool is full
   * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated
   */
  fun decodeJPEGFromEncodedImage(
      encodedImage: EncodedImage,
      bitmapConfig: Bitmap.Config,
      regionToDecode: Rect?,
      length: Int
  ): CloseableReference<Bitmap>?

  /**
   * Creates a bitmap from encoded bytes. Supports JPEG but callers should use
   * [decodeJPEGFromEncodedImage] for partial JPEGs. In addition, a region to decode can be supplied
   * in order to minimize memory usage. NOTE: Not all platform decoders necessarily support
   * supplying specific regions.
   *
   * @param encodedImage the reference to the encoded image with the reference to the encoded bytes
   * @param bitmapConfig the [android.graphics.Bitmap.Config] used to create the decoded Bitmap
   * @param regionToDecode optional image region to decode or null to decode the whole image
   * @param colorSpace the target color space of the decoded bitmap, must be one of the named color
   *   space in [android.graphics.ColorSpace.Named]. If null, then SRGB color space is assumed if
   *   the SDK version >= 26.
   * @return the bitmap
   * @throws TooManyBitmapsException if the pool is full
   * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated
   */
  fun decodeFromEncodedImageWithColorSpace(
      encodedImage: EncodedImage,
      bitmapConfig: Bitmap.Config,
      regionToDecode: Rect?,
      colorSpace: ColorSpace?
  ): CloseableReference<Bitmap>?

  /**
   * Creates a bitmap from encoded JPEG bytes. Supports a partial JPEG image. In addition, a region
   * to decode can be supplied in order to minimize memory usage. NOTE: Not all platform decoders
   * necessarily support supplying specific regions.
   *
   * @param encodedImage the reference to the encoded image with the reference to the encoded bytes
   * @param bitmapConfig the [android.graphics.Bitmap.Config] used to create the decoded Bitmap
   * @param regionToDecode optional image region to decode or null to decode the whole image.
   * @param length the number of encoded bytes in the buffer
   * @param colorSpace the target color space of the decoded bitmap, must be one of the named color
   *   space in [android.graphics.ColorSpace.Named]. If null, then SRGB color space is assumed if
   *   the SDK version >= 26.
   * @return the bitmap
   * @throws TooManyBitmapsException if the pool is full
   * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated
   */
  fun decodeJPEGFromEncodedImageWithColorSpace(
      encodedImage: EncodedImage,
      bitmapConfig: Bitmap.Config,
      regionToDecode: Rect?,
      length: Int,
      colorSpace: ColorSpace?
  ): CloseableReference<Bitmap>?
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.platform

import android.os.Build
import androidx.core.util.Pools
import androidx.core.util.Pools.SynchronizedPool
import com.facebook.common.memory.DecodeBufferHelper
import com.facebook.imagepipeline.core.NativeCodeSetup
import com.facebook.imagepipeline.memory.FlexByteArrayPool
import com.facebook.imagepipeline.memory.PoolFactory
import java.lang.reflect.InvocationTargetException
import java.nio.ByteBuffer

object PlatformDecoderFactory {

  /**
   * Provide the implementation of the PlatformDecoder for the current platform using the provided
   * PoolFactory
   *
   * @param poolFactory The PoolFactory
   * @return The PlatformDecoder implementation
   */
  @JvmStatic
  @JvmOverloads
  fun buildPlatformDecoder(
      poolFactory: PoolFactory,
      gingerbreadDecoderEnabled: Boolean,
      useDecodeBufferHelper: Boolean = false
  ): PlatformDecoder =
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        OreoDecoder(poolFactory.bitmapPool, createPool(poolFactory, useDecodeBufferHelper))
      } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ||
          !NativeCodeSetup.getUseNativeCode()) {
        ArtDecoder(poolFactory.bitmapPool, createPool(poolFactory, useDecodeBufferHelper))
      } else {
        try {
          if (gingerbreadDecoderEnabled && Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            val clazz =
                Class.forName("com.facebook.imagepipeline.platform.GingerbreadPurgeableDecoder")
            clazz.getConstructor().newInstance() as PlatformDecoder
          } else {
            val clazz = Class.forName("com.facebook.imagepipeline.platform.KitKatPurgeableDecoder")
            clazz
                .getConstructor(FlexByteArrayPool::class.java)
                .newInstance(poolFactory.flexByteArrayPool) as PlatformDecoder
          }
        } catch (e: ClassNotFoundException) {
          throw RuntimeException("Wrong Native code setup, reflection failed.", e)
        } catch (e: IllegalAccessException) {
          throw RuntimeException("Wrong Native code setup, reflection failed.", e)
        } catch (e: NoSuchMethodException) {
          throw RuntimeException("Wrong Native code setup, reflection failed.", e)
        } catch (e: InvocationTargetException) {
          throw RuntimeException("Wrong Native code setup, reflection failed.", e)
        } catch (e: InstantiationException) {
          throw RuntimeException("Wrong Native code setup, reflection failed.", e)
        }
      }

  @JvmStatic
  fun createPool(poolFactory: PoolFactory, useDecodeBufferHelper: Boolean): Pools.Pool<ByteBuffer> {
    if (useDecodeBufferHelper) {
      return DecodeBufferHelper.INSTANCE
    }
    val maxNumThreads = poolFactory.flexByteArrayPoolMaxNumThreads
    val pool: Pools.Pool<ByteBuffer> = SynchronizedPool(maxNumThreads)
    for (i in 0 until maxNumThreads) {
      pool.release(ByteBuffer.allocate(DecodeBufferHelper.getRecommendedDecodeBufferSize()))
    }
    return pool
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.platform

import android.annotation.TargetApi
import android.graphics.Bitmap
import android.os.Build
import com.facebook.soloader.DoNotOptimize

@DoNotOptimize
internal class PreverificationHelper {
  @TargetApi(Build.VERSION_CODES.O)
  @DoNotOptimize
  fun /*package*/ shouldUseHardwareBitmapConfig(config: Bitmap.Config?): Boolean =
      config == Bitmap.Config.HARDWARE
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.postprocessors

import android.content.Context
import android.graphics.Bitmap
import android.renderscript.ScriptIntrinsicBlur
import com.facebook.cache.common.CacheKey
import com.facebook.cache.common.SimpleCacheKey
import com.facebook.common.internal.Preconditions
import com.facebook.imagepipeline.filter.IterativeBoxBlurFilter
import com.facebook.imagepipeline.filter.RenderScriptBlurFilter
import com.facebook.imagepipeline.request.BasePostprocessor
import java.util.Locale

/**
 * A java implementation of a blur post processor. This provide two different blurring algorithm,
 * one Gaussian blur using [ScriptIntrinsicBlur] for Android version >= 4.2 and the other one is an
 * in-place iterative box blur algorithm that runs faster than a traditional box blur.
 */
class BlurPostProcessor
/**
 * Creates an instance of [BlurPostProcessor].
 *
 * @param blurRadius The radius of the blur in range 0 < radius <= [ ]
 *   [RenderScriptBlurFilter.BLUR_MAX_RADIUS].
 * @param context A valid [Context].
 * @param iterations The number of iterations of the blurring algorithm > 0.
 */
@JvmOverloads
constructor(val blurRadius: Int, val context: Context, val iterations: Int = DEFAULT_ITERATIONS) :
    BasePostprocessor() {

  init {
    Preconditions.checkArgument(
        blurRadius > 0 && blurRadius <= RenderScriptBlurFilter.BLUR_MAX_RADIUS)
    Preconditions.checkArgument(iterations > 0)
  }

  private val cacheKey: CacheKey =
      SimpleCacheKey(
          if (canUseRenderScript) {
            String.format(null as Locale?, "IntrinsicBlur;%d", blurRadius)
          } else {
            String.format(null as Locale?, "IterativeBoxBlur;%d;%d", iterations, blurRadius)
          })

  override fun getPostprocessorCacheKey(): CacheKey = cacheKey

  override fun process(destBitmap: Bitmap, sourceBitmap: Bitmap) {
    if (canUseRenderScript) {
      RenderScriptBlurFilter.blurBitmap(destBitmap, sourceBitmap, context, blurRadius)
    } else {
      super.process(destBitmap, sourceBitmap)
    }
  }

  override fun process(bitmap: Bitmap) {
    IterativeBoxBlurFilter.boxBlurBitmapInPlace(bitmap, iterations, blurRadius)
  }

  companion object {
    private val canUseRenderScript = RenderScriptBlurFilter.canUseRenderScript()
    private const val DEFAULT_ITERATIONS = 3
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.postprocessors

import android.graphics.Bitmap
import com.facebook.cache.common.CacheKey
import com.facebook.cache.common.SimpleCacheKey
import com.facebook.imagepipeline.filter.XferRoundFilter
import com.facebook.imagepipeline.request.BasePostprocessor

/** Postprocessor that rounds a given image as a circle using non-native code. */
class RoundPostprocessor @JvmOverloads constructor(private val enableAntiAliasing: Boolean = true) :
    BasePostprocessor() {

  private val cacheKey: CacheKey = SimpleCacheKey("XferRoundFilter")

  override fun getPostprocessorCacheKey(): CacheKey = cacheKey

  override fun process(destBitmap: Bitmap, sourceBitmap: Bitmap) {
    XferRoundFilter.xferRoundBitmap(destBitmap, sourceBitmap, enableAntiAliasing)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers

/**
 * Base class for [NetworkFetcher].
 *
 * Intermediate results are propagated.
 *
 * {#code getExtraMap} returns null.
 */
abstract class BaseNetworkFetcher<FETCH_STATE : FetchState?> : NetworkFetcher<FETCH_STATE> {

  override fun shouldPropagate(fetchState: FETCH_STATE): Boolean = true

  override fun onFetchCompletion(fetchState: FETCH_STATE, byteSize: Int) {
    // no-op
  }

  override fun getExtraMap(fetchState: FETCH_STATE, byteSize: Int): Map<String, String>? = null
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers

/** Empty implementation of [ProducerContextCallbacks]. */
open class BaseProducerContextCallbacks : ProducerContextCallbacks {
  override fun onCancellationRequested() = Unit

  override fun onIsPrefetchChanged() = Unit

  override fun onIsIntermediateResultExpectedChanged() = Unit

  override fun onPriorityChanged() = Unit
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers

import com.facebook.cache.common.CacheKey
import com.facebook.common.references.CloseableReference
import com.facebook.imagepipeline.cache.CacheKeyFactory
import com.facebook.imagepipeline.cache.MemoryCache
import com.facebook.imagepipeline.image.CloseableImage

/** Bitmap memory cache producer that is read-only. */
class BitmapMemoryCacheGetProducer(
    memoryCache: MemoryCache<CacheKey, CloseableImage>,
    cacheKeyFactory: CacheKeyFactory,
    inputProducer: Producer<CloseableReference<CloseableImage>>
) : BitmapMemoryCacheProducer(memoryCache, cacheKeyFactory, inputProducer) {

  override fun wrapConsumer(
      consumer: Consumer<CloseableReference<CloseableImage>>,
      cacheKey: CacheKey,
      isMemoryCacheEnabled: Boolean
  ): Consumer<CloseableReference<CloseableImage>> =
      // since this cache is read-only, we can pass our consumer directly to the next producer
      consumer

  override fun getProducerName(): String = PRODUCER_NAME

  override fun getOriginSubcategory(): String = ORIGIN_SUBCATEGORY

  companion object {
    const val PRODUCER_NAME = "BitmapMemoryCacheGetProducer"
    private const val ORIGIN_SUBCATEGORY = "pipe_ui"
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers

import com.facebook.common.internal.ImmutableMap
import com.facebook.common.internal.Supplier
import com.facebook.common.logging.FLog
import com.facebook.common.memory.ByteArrayPool
import com.facebook.common.references.CloseableReference
import com.facebook.common.util.ExceptionWithNoStacktrace
import com.facebook.common.util.UriUtil
import com.facebook.fresco.middleware.HasExtraData
import com.facebook.imageformat.DefaultImageFormats
import com.facebook.imagepipeline.common.ImageDecodeOptions
import com.facebook.imagepipeline.core.CloseableReferenceFactory
import com.facebook.imagepipeline.decoder.DecodeException
import com.facebook.imagepipeline.decoder.ImageDecoder
import com.facebook.imagepipeline.decoder.ProgressiveJpegConfig
import com.facebook.imagepipeline.decoder.ProgressiveJpegParser
import com.facebook.imagepipeline.image.CloseableBitmap
import com.facebook.imagepipeline.image.CloseableImage
import com.facebook.imagepipeline.image.CloseableStaticBitmap
import com.facebook.imagepipeline.image.EncodedImage
import com.facebook.imagepipeline.image.ImmutableQualityInfo
import com.facebook.imagepipeline.image.QualityInfo
import com.facebook.imagepipeline.producers.JobScheduler.JobRunnable
import com.facebook.imagepipeline.request.ImageRequest
import com.facebook.imagepipeline.systrace.FrescoSystrace.traceSection
import com.facebook.imagepipeline.transcoder.DownsampleUtil
import com.facebook.imageutils.BitmapUtil
import java.lang.Exception
import java.util.HashMap
import java.util.concurrent.Executor
import javax.annotation.concurrent.GuardedBy

/**
 * Decodes images.
 *
 * Progressive JPEGs are decoded progressively as new data arrives.
 */
class DecodeProducer(
    val byteArrayPool: ByteArrayPool,
    val executor: Executor,
    val imageDecoder: ImageDecoder,
    val progressiveJpegConfig: ProgressiveJpegConfig,
    val downsampleEnabled: Boolean,
    val downsampleEnabledForNetwork: Boolean,
    val decodeCancellationEnabled: Boolean,
    val inputProducer: Producer<EncodedImage?>,
    val maxBitmapSize: Int,
    val closeableReferenceFactory: CloseableReferenceFactory,
    val reclaimMemoryRunnable: Runnable?,
    val recoverFromDecoderOOM: Supplier<Boolean>
) : Producer<CloseableReference<CloseableImage>> {

  override fun produceResults(
      consumer: Consumer<CloseableReference<CloseableImage>>,
      context: ProducerContext
  ) =
      traceSection("DecodeProducer#produceResults") {
        val imageRequest = context.imageRequest
        val progressiveDecoder =
            if (!UriUtil.isNetworkUri(imageRequest.sourceUri)) {
              LocalImagesProgressiveDecoder(
                  consumer, context, this.decodeCancellationEnabled, this.maxBitmapSize)
            } else {
              val jpegParser = ProgressiveJpegParser(this.byteArrayPool)
              NetworkImagesProgressiveDecoder(
                  consumer,
                  context,
                  jpegParser,
                  this.progressiveJpegConfig,
                  this.decodeCancellationEnabled,
                  this.maxBitmapSize)
            }
        this.inputProducer.produceResults(progressiveDecoder, context)
      }

  private abstract inner class ProgressiveDecoder(
      consumer: Consumer<CloseableReference<CloseableImage>>,
      private val producerContext: ProducerContext,
      decodeCancellationEnabled: Boolean,
      maxBitmapSize: Int
  ) : DelegatingConsumer<EncodedImage?, CloseableReference<CloseableImage>>(consumer) {
    private val TAG = "ProgressiveDecoder"
    private val producerListener: ProducerListener2 = producerContext.producerListener
    private val imageDecodeOptions: ImageDecodeOptions =
        producerContext.imageRequest.imageDecodeOptions

    /** @return true if producer is finished */
    @get:Synchronized @GuardedBy("this") private var isFinished: Boolean = false
    private val jobScheduler: JobScheduler
    protected var lastScheduledScanNumber = 0

    private fun maybeIncreaseSampleSize(encodedImage: EncodedImage) {
      if (encodedImage.imageFormat !== DefaultImageFormats.JPEG) {
        return
      }
      val pixelSize = BitmapUtil.getPixelSizeForBitmapConfig(imageDecodeOptions.bitmapConfig)
      val sampleSize =
          DownsampleUtil.determineSampleSizeJPEG(encodedImage, pixelSize, MAX_BITMAP_SIZE)
      encodedImage.sampleSize = sampleSize
    }

    public override fun onNewResultImpl(newResult: EncodedImage?, @Consumer.Status status: Int) =
        traceSection("DecodeProducer#onNewResultImpl") {
          val isLast = isLast(status)
          if (isLast) {
            if (newResult == null) {
              val cacheHit =
                  producerContext.getExtra<Boolean>(ProducerConstants.EXTRA_CACHED_VALUE_FOUND) ==
                      true
              if (!producerContext.imagePipelineConfig.experiments.cancelDecodeOnCacheMiss ||
                  producerContext.lowestPermittedRequestLevel ==
                      ImageRequest.RequestLevel.FULL_FETCH ||
                  cacheHit) {
                handleError(ExceptionWithNoStacktrace("Encoded image is null."))
                return
              }
            } else if (!newResult.isValid) {
              handleError(ExceptionWithNoStacktrace("Encoded image is not valid."))
              return
            }
          }
          if (!updateDecodeJob(newResult, status)) {
            return
          }
          val isPlaceholder = statusHasFlag(status, IS_PLACEHOLDER)
          if (isLast || isPlaceholder || producerContext.isIntermediateResultExpected) {
            jobScheduler.scheduleJob()
          }
        }

    override fun onProgressUpdateImpl(progress: Float) {
      super.onProgressUpdateImpl(progress * 0.99f)
    }

    public override fun onFailureImpl(t: Throwable) {
      handleError(t)
    }

    public override fun onCancellationImpl() {
      handleCancellation()
    }

    /** Updates the decode job. */
    protected open fun updateDecodeJob(ref: EncodedImage?, @Consumer.Status status: Int): Boolean =
        jobScheduler.updateJob(ref, status)

    /** Performs the decode synchronously. */
    private fun doDecode(
        encodedImage: EncodedImage,
        @Consumer.Status status: Int,
        lastScheduledScanNumber: Int
    ) {
      // do not run for partial results of anything except JPEG
      var status = status
      if (encodedImage.imageFormat !== DefaultImageFormats.JPEG && isNotLast(status)) {
        return
      }
      if (isFinished || !EncodedImage.isValid(encodedImage)) {
        return
      }
      val imageFormat = encodedImage.imageFormat
      val imageFormatStr =
          if (imageFormat != null) {
            imageFormat.name
          } else {
            "unknown"
          }
      val encodedImageSize = encodedImage.width.toString() + "x" + encodedImage.height
      val sampleSize = encodedImage.sampleSize.toString()
      val isLast = isLast(status)
      val isLastAndComplete = isLast && !statusHasFlag(status, IS_PARTIAL_RESULT)
      val isPlaceholder = statusHasFlag(status, IS_PLACEHOLDER)
      val resizeOptions = producerContext.imageRequest.resizeOptions
      val requestedSizeStr =
          if (resizeOptions != null) {
            resizeOptions.width.toString() + "x" + resizeOptions.height
          } else {
            "unknown"
          }
      try {
        val queueTime = jobScheduler.queuedTime
        val requestUri = producerContext.imageRequest.sourceUri.toString()
        val length =
            if (isLastAndComplete || isPlaceholder) encodedImage.size
            else getIntermediateImageEndOffset(encodedImage)
        val quality =
            if (isLastAndComplete || isPlaceholder) ImmutableQualityInfo.FULL_QUALITY
            else qualityInfo
        producerListener.onProducerStart(producerContext, PRODUCER_NAME)
        var image: CloseableImage? = null
        try {
          image =
              try {
                internalDecode(encodedImage, length, quality)
              } catch (e: DecodeException) {
                val failedEncodedImage = e.encodedImage
                FLog.w(
                    TAG,
                    "%s, {uri: %s, firstEncodedBytes: %s, length: %d}",
                    e.message,
                    requestUri,
                    failedEncodedImage.getFirstBytesAsHexString(
                        DECODE_EXCEPTION_MESSAGE_NUM_HEADER_BYTES),
                    failedEncodedImage.size)
                throw e
              }
          if (encodedImage.sampleSize != EncodedImage.DEFAULT_SAMPLE_SIZE) {
            status = status or IS_RESIZING_DONE
          }
        } catch (e: Exception) {
          val extraMap =
              getExtraMap(
                  image,
                  queueTime,
                  quality,
                  isLast,
                  imageFormatStr,
                  encodedImageSize,
                  requestedSizeStr,
                  sampleSize)
          producerListener.onProducerFinishWithFailure(producerContext, PRODUCER_NAME, e, extraMap)
          handleError(e)
          return
        }
        val extraMap =
            getExtraMap(
                image,
                queueTime,
                quality,
                isLast,
                imageFormatStr,
                encodedImageSize,
                requestedSizeStr,
                sampleSize)
        producerListener.onProducerFinishWithSuccess(producerContext, PRODUCER_NAME, extraMap)
        setImageExtras(encodedImage, image, lastScheduledScanNumber)
        handleResult(image, status)
      } finally {
        EncodedImage.closeSafely(encodedImage)
      }
    }

    /** This does not close the encodedImage * */
    private fun internalDecode(
        encodedImage: EncodedImage,
        length: Int,
        quality: QualityInfo
    ): CloseableImage? {
      val recover = reclaimMemoryRunnable != null && recoverFromDecoderOOM.get()
      val image =
          try {
            imageDecoder.decode(encodedImage, length, quality, imageDecodeOptions)
          } catch (e: OutOfMemoryError) {
            if (!recover) {
              throw e
            }

            reclaimMemoryRunnable?.run()
            System.gc()

            // Now we retry only once
            imageDecoder.decode(encodedImage, length, quality, imageDecodeOptions)
          }
      return image
    }

    private fun setImageExtras(
        encodedImage: EncodedImage,
        image: CloseableImage?,
        lastScheduledScanNumber: Int
    ) {
      producerContext.putExtra(HasExtraData.KEY_ENCODED_WIDTH, encodedImage.width)
      producerContext.putExtra(HasExtraData.KEY_ENCODED_HEIGHT, encodedImage.height)
      producerContext.putExtra(HasExtraData.KEY_ENCODED_SIZE, encodedImage.size)
      if (image is CloseableBitmap) {
        val bitmap = image.underlyingBitmap
        val config = if (bitmap == null) null else bitmap.config
        producerContext.putExtra(HasExtraData.KEY_BITMAP_CONFIG, config.toString())
      }
      image?.putExtras(producerContext.getExtras())
      producerContext.putExtra(HasExtraData.KEY_LAST_SCAN_NUMBER, lastScheduledScanNumber)
    }

    private fun getExtraMap(
        image: CloseableImage?,
        queueTime: Long,
        quality: QualityInfo,
        isFinal: Boolean,
        imageFormatName: String,
        encodedImageSize: String,
        requestImageSize: String,
        sampleSize: String
    ): Map<String, String>? {
      if (!producerListener.requiresExtraMap(producerContext, PRODUCER_NAME)) {
        return null
      }
      val queueStr = queueTime.toString()
      val qualityStr = quality.isOfGoodEnoughQuality.toString()
      val finalStr = isFinal.toString()
      var nonFatalErrorStr: String? = null
      if (image != null) {
        val nonFatalError = image.extras[NON_FATAL_DECODE_ERROR]
        if (nonFatalError != null) {
          nonFatalErrorStr = nonFatalError.toString()
        }
      }
      return if (image is CloseableStaticBitmap) {
        val bitmap = image.underlyingBitmap
        checkNotNull(bitmap)
        val sizeStr = bitmap.width.toString() + "x" + bitmap.height
        // We need this because the copyOf() utility method doesn't have a proper overload method
        // for all these parameters
        val tmpMap: MutableMap<String, String> = HashMap(8)
        tmpMap[EXTRA_BITMAP_SIZE] = sizeStr
        tmpMap[JobScheduler.QUEUE_TIME_KEY] = queueStr
        tmpMap[EXTRA_HAS_GOOD_QUALITY] = qualityStr
        tmpMap[EXTRA_IS_FINAL] = finalStr
        tmpMap[ENCODED_IMAGE_SIZE] = encodedImageSize
        tmpMap[EXTRA_IMAGE_FORMAT_NAME] = imageFormatName
        tmpMap[REQUESTED_IMAGE_SIZE] = requestImageSize
        tmpMap[SAMPLE_SIZE] = sampleSize
        tmpMap[EXTRA_BITMAP_BYTES] = bitmap.byteCount.toString() + ""
        if (nonFatalErrorStr != null) {
          tmpMap[NON_FATAL_DECODE_ERROR] = nonFatalErrorStr
        }
        ImmutableMap.copyOf(tmpMap)
      } else {
        val tmpMap: MutableMap<String, String> = HashMap(7)
        tmpMap[JobScheduler.QUEUE_TIME_KEY] = queueStr
        tmpMap[EXTRA_HAS_GOOD_QUALITY] = qualityStr
        tmpMap[EXTRA_IS_FINAL] = finalStr
        tmpMap[ENCODED_IMAGE_SIZE] = encodedImageSize
        tmpMap[EXTRA_IMAGE_FORMAT_NAME] = imageFormatName
        tmpMap[REQUESTED_IMAGE_SIZE] = requestImageSize
        tmpMap[SAMPLE_SIZE] = sampleSize
        if (nonFatalErrorStr != null) {
          tmpMap[NON_FATAL_DECODE_ERROR] = nonFatalErrorStr
        }
        ImmutableMap.copyOf(tmpMap)
      }
    }

    /**
     * Finishes if not already finished and `shouldFinish` is specified.
     *
     * If just finished, the intermediate image gets released.
     */
    private fun maybeFinish(shouldFinish: Boolean) {
      synchronized(this@ProgressiveDecoder) {
        if (!shouldFinish || isFinished) {
          return
        }
        consumer.onProgressUpdate(1.0f)
        isFinished = true
      }
      jobScheduler.clearJob()
    }

    /** Notifies consumer of new result and finishes if the result is final. */
    private fun handleResult(decodedImage: CloseableImage?, @Consumer.Status status: Int) {
      val decodedImageRef: CloseableReference<CloseableImage>? =
          closeableReferenceFactory.create(decodedImage)
      try {
        maybeFinish(isLast(status))
        consumer.onNewResult(decodedImageRef, status)
      } finally {
        CloseableReference.closeSafely(decodedImageRef)
      }
    }

    /** Notifies consumer about the failure and finishes. */
    private fun handleError(t: Throwable) {
      maybeFinish(true)
      consumer.onFailure(t)
    }

    /** Notifies consumer about the cancellation and finishes. */
    private fun handleCancellation() {
      maybeFinish(true)
      consumer.onCancellation()
    }

    protected abstract fun getIntermediateImageEndOffset(encodedImage: EncodedImage): Int

    protected abstract val qualityInfo: QualityInfo

    init {

      val job = JobRunnable { encodedImage, status ->
        if (encodedImage != null) {
          val request = producerContext.imageRequest
          producerContext.putExtra(HasExtraData.KEY_IMAGE_FORMAT, encodedImage.imageFormat.name)
          encodedImage.source = request.sourceUri?.toString()

          if (downsampleEnabled || !statusHasFlag(status, IS_RESIZING_DONE)) {
            if (downsampleEnabledForNetwork || !UriUtil.isNetworkUri(request.sourceUri)) {
              encodedImage.sampleSize =
                  DownsampleUtil.determineSampleSize(
                      request.rotationOptions, request.resizeOptions, encodedImage, maxBitmapSize)
            }
          }
          if (producerContext.imagePipelineConfig.experiments.downsampleIfLargeBitmap) {
            maybeIncreaseSampleSize(encodedImage)
          }
          doDecode(encodedImage, status, lastScheduledScanNumber)
        }
      }
      jobScheduler = JobScheduler(executor, job, imageDecodeOptions.minDecodeIntervalMs)
      producerContext.addCallbacks(
          object : BaseProducerContextCallbacks() {
            override fun onIsIntermediateResultExpectedChanged() {
              if (producerContext.isIntermediateResultExpected) {
                jobScheduler.scheduleJob()
              }
            }

            override fun onCancellationRequested() {
              if (decodeCancellationEnabled) {
                handleCancellation()
              }
            }
          })
    }
  }

  private inner class LocalImagesProgressiveDecoder(
      consumer: Consumer<CloseableReference<CloseableImage>>,
      producerContext: ProducerContext,
      decodeCancellationEnabled: Boolean,
      maxBitmapSize: Int
  ) : ProgressiveDecoder(consumer, producerContext, decodeCancellationEnabled, maxBitmapSize) {
    @Synchronized
    override fun updateDecodeJob(
        encodedImage: EncodedImage?,
        @Consumer.Status status: Int
    ): Boolean =
        if (isNotLast(status)) {
          false
        } else {
          super.updateDecodeJob(encodedImage, status)
        }

    override fun getIntermediateImageEndOffset(encodedImage: EncodedImage): Int = encodedImage.size

    override val qualityInfo: QualityInfo
      protected get() = ImmutableQualityInfo.of(0, false, false)
  }

  private inner class NetworkImagesProgressiveDecoder(
      consumer: Consumer<CloseableReference<CloseableImage>>,
      producerContext: ProducerContext,
      val progressiveJpegParser: ProgressiveJpegParser,
      val progressiveJpegConfig: ProgressiveJpegConfig,
      decodeCancellationEnabled: Boolean,
      maxBitmapSize: Int
  ) : ProgressiveDecoder(consumer, producerContext, decodeCancellationEnabled, maxBitmapSize) {
    @Synchronized
    override fun updateDecodeJob(
        encodedImage: EncodedImage?,
        @Consumer.Status status: Int
    ): Boolean {
      if (encodedImage == null) {
        return false
      }
      val ret = super.updateDecodeJob(encodedImage, status)
      if ((isNotLast(status) || statusHasFlag(status, IS_PARTIAL_RESULT)) &&
          !statusHasFlag(status, IS_PLACEHOLDER) &&
          EncodedImage.isValid(encodedImage) &&
          encodedImage.imageFormat === DefaultImageFormats.JPEG) {
        if (!this.progressiveJpegParser.parseMoreData(encodedImage)) {
          return false
        }
        val scanNum = this.progressiveJpegParser.bestScanNumber
        if (scanNum <= lastScheduledScanNumber) {
          // We have already decoded this scan, no need to do so again
          return false
        }
        if (scanNum < progressiveJpegConfig.getNextScanNumberToDecode(lastScheduledScanNumber) &&
            !this.progressiveJpegParser.isEndMarkerRead) {
          // We have not reached the minimum scan set by the configuration and there
          // are still more scans to be read (the end marker is not reached)
          return false
        }
        lastScheduledScanNumber = scanNum
      }
      return ret
    }

    override fun getIntermediateImageEndOffset(encodedImage: EncodedImage): Int =
        this.progressiveJpegParser.bestScanEndOffset

    override val qualityInfo: QualityInfo
      protected get() =
          progressiveJpegConfig.getQualityInfo(this.progressiveJpegParser.bestScanNumber)

    init {
      lastScheduledScanNumber = 0
    }
  }

  companion object {
    const val PRODUCER_NAME = "DecodeProducer"

    private const val DECODE_EXCEPTION_MESSAGE_NUM_HEADER_BYTES = 10

    // In recent versions of Android you cannot draw bitmap that is bigger than 100MB bytes:
    // https://web.archive.org/web/20191017003524/https://chromium.googlesource.com/android_tools/+/refs/heads/master/sdk/sources/android-25/android/view/DisplayListCanvas.java
    private const val MAX_BITMAP_SIZE = 100 * 1_024 * 1_024 // 100 MB

    // keys for extra map
    const val EXTRA_BITMAP_SIZE = ProducerConstants.EXTRA_BITMAP_SIZE
    const val EXTRA_HAS_GOOD_QUALITY = ProducerConstants.EXTRA_HAS_GOOD_QUALITY
    const val EXTRA_IS_FINAL = ProducerConstants.EXTRA_IS_FINAL
    const val EXTRA_IMAGE_FORMAT_NAME = ProducerConstants.EXTRA_IMAGE_FORMAT_NAME
    const val EXTRA_BITMAP_BYTES = ProducerConstants.EXTRA_BYTES
    const val ENCODED_IMAGE_SIZE = ProducerConstants.ENCODED_IMAGE_SIZE
    const val REQUESTED_IMAGE_SIZE = ProducerConstants.REQUESTED_IMAGE_SIZE
    const val SAMPLE_SIZE = ProducerConstants.SAMPLE_SIZE
    const val NON_FATAL_DECODE_ERROR = ProducerConstants.NON_FATAL_DECODE_ERROR
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers

import com.facebook.common.references.CloseableReference
import com.facebook.imagepipeline.image.CloseableImage
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeUnit.MILLISECONDS

/**
 * A ScheduledExecutorService is a significant dependency and we do not want to require it. If not
 * provided, this producer is a no-op.
 */
class DelayProducer(
    private val inputProducer: Producer<CloseableReference<CloseableImage>>,
    private val backgroundTasksExecutor: ScheduledExecutorService?
) : Producer<CloseableReference<CloseableImage>> {
  override fun produceResults(
      consumer: Consumer<CloseableReference<CloseableImage>>,
      context: ProducerContext
  ) {
    val request = context.imageRequest
    if (backgroundTasksExecutor != null) {
      backgroundTasksExecutor.schedule(
          Runnable { inputProducer.produceResults(consumer, context) },
          request.delayMs.toLong(),
          TimeUnit.MILLISECONDS)
    } else {
      inputProducer.produceResults(consumer, context)
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers

/** Delegating consumer. */
abstract class DelegatingConsumer<I, O>(val consumer: Consumer<O>) : BaseConsumer<I>() {

  override fun onFailureImpl(t: Throwable) {
    consumer.onFailure(t)
  }

  override fun onCancellationImpl() {
    consumer.onCancellation()
  }

  override fun onProgressUpdateImpl(progress: Float) {
    consumer.onProgressUpdate(progress)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers

import java.util.concurrent.Executor

class ExperimentalThreadHandoffProducerQueueImpl(executor: Executor?) : ThreadHandoffProducerQueue {

  private val executor: Executor = checkNotNull(executor)

  override fun addToQueueOrExecute(runnable: Runnable) {
    this.executor.execute(runnable)
  }

  override fun startQueueing() {
    throw UnsupportedOperationException()
  }

  override fun stopQueuing() {
    throw UnsupportedOperationException()
  }

  override fun remove(runnable: Runnable) {
    // NOOP
  }

  override fun isQueueing(): Boolean = false
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers

open class InternalProducerListener(
    val producerListener: ProducerListener?,
    val producerListener2: ProducerListener2?
) : ProducerListener2 {

  override fun onProducerStart(context: ProducerContext, producerName: String?) {
    producerListener?.onProducerStart(context.id, producerName)
    producerListener2?.onProducerStart(context, producerName)
  }

  override fun onProducerEvent(
      context: ProducerContext,
      producerName: String?,
      eventName: String?
  ) {
    producerListener?.onProducerEvent(context.id, producerName, eventName)
    producerListener2?.onProducerEvent(context, producerName, eventName)
  }

  override fun onProducerFinishWithSuccess(
      context: ProducerContext,
      producerName: String?,
      extraMap: Map<String?, String?>?
  ) {
    producerListener?.onProducerFinishWithSuccess(context.id, producerName, extraMap)
    producerListener2?.onProducerFinishWithSuccess(context, producerName, extraMap)
  }

  override fun onProducerFinishWithFailure(
      context: ProducerContext,
      producerName: String?,
      t: Throwable?,
      extraMap: Map<String?, String?>?
  ) {
    producerListener?.onProducerFinishWithFailure(context.id, producerName, t, extraMap)
    producerListener2?.onProducerFinishWithFailure(context, producerName, t, extraMap)
  }

  override fun onProducerFinishWithCancellation(
      context: ProducerContext,
      producerName: String?,
      extraMap: Map<String?, String?>?
  ) {
    producerListener?.onProducerFinishWithCancellation(context.id, producerName, extraMap)
    producerListener2?.onProducerFinishWithCancellation(context, producerName, extraMap)
  }

  override fun onUltimateProducerReached(
      context: ProducerContext,
      producerName: String?,
      successful: Boolean
  ) {
    producerListener?.onUltimateProducerReached(context.id, producerName, successful)
    producerListener2?.onUltimateProducerReached(context, producerName, successful)
  }

  override fun requiresExtraMap(context: ProducerContext, producerName: String?): Boolean {
    var required: Boolean? = producerListener?.requiresExtraMap(context.id)
    if (required != true) {
      required = producerListener2?.requiresExtraMap(context, producerName)
    }
    return required ?: false
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers

import com.facebook.imagepipeline.listener.RequestListener
import com.facebook.imagepipeline.listener.RequestListener2

class InternalRequestListener(
    private val requestListener: RequestListener?,
    private val requestListener2: RequestListener2?
) : InternalProducerListener(requestListener, requestListener2), RequestListener2 {

  override fun onRequestStart(producerContext: ProducerContext) {
    requestListener?.onRequestStart(
        producerContext.imageRequest,
        producerContext.callerContext,
        producerContext.id,
        producerContext.isPrefetch)
    requestListener2?.onRequestStart(producerContext)
  }

  override fun onRequestSuccess(producerContext: ProducerContext) {
    requestListener?.onRequestSuccess(
        producerContext.imageRequest, producerContext.id, producerContext.isPrefetch)
    requestListener2?.onRequestSuccess(producerContext)
  }

  override fun onRequestFailure(producerContext: ProducerContext, throwable: Throwable?) {
    requestListener?.onRequestFailure(
        producerContext.imageRequest, producerContext.id, throwable, producerContext.isPrefetch)
    requestListener2?.onRequestFailure(producerContext, throwable)
  }

  override fun onRequestCancellation(producerContext: ProducerContext) {
    requestListener?.onRequestCancellation(producerContext.id)
    requestListener2?.onRequestCancellation(producerContext)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers

import android.content.res.AssetFileDescriptor
import android.content.res.AssetManager
import com.facebook.common.memory.PooledByteBufferFactory
import com.facebook.imagepipeline.image.EncodedImage
import com.facebook.imagepipeline.request.ImageRequest
import java.io.IOException
import java.util.concurrent.Executor

/** Executes a local fetch from an asset. */
class LocalAssetFetchProducer(
    executor: Executor,
    pooledByteBufferFactory: PooledByteBufferFactory,
    private val assetManager: AssetManager
) : LocalFetchProducer(executor, pooledByteBufferFactory) {

  @Throws(IOException::class)
  override fun getEncodedImage(imageRequest: ImageRequest): EncodedImage? =
      getEncodedImage(
          assetManager.open(getAssetName(imageRequest), AssetManager.ACCESS_STREAMING),
          getLength(imageRequest))

  private fun getLength(imageRequest: ImageRequest): Int {
    var fd: AssetFileDescriptor? = null
    return try {
      fd = assetManager.openFd(getAssetName(imageRequest))
      fd.length.toInt()
    } catch (e: IOException) {
      -1
    } finally {
      try {
        fd?.close()
      } catch (ignored: IOException) {
        // There's nothing we can do with the exception when closing descriptor.
      }
    }
  }

  override fun getProducerName(): String = PRODUCER_NAME

  companion object {
    const val PRODUCER_NAME = "LocalAssetFetchProducer"

    private fun getAssetName(imageRequest: ImageRequest): String =
        imageRequest.sourceUri.path!!.substring(1)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers

import android.content.ContentResolver
import android.net.Uri
import android.os.ParcelFileDescriptor
import android.provider.ContactsContract
import android.provider.MediaStore
import com.facebook.common.memory.PooledByteBufferFactory
import com.facebook.common.util.UriUtil
import com.facebook.imagepipeline.image.EncodedImage
import com.facebook.imagepipeline.request.ImageRequest
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
import java.util.concurrent.Executor

/** Represents a local content Uri fetch producer. */
class LocalContentUriFetchProducer(
    executor: Executor,
    pooledByteBufferFactory: PooledByteBufferFactory,
    private val contentResolver: ContentResolver
) : LocalFetchProducer(executor, pooledByteBufferFactory) {

  @Throws(IOException::class)
  override fun getEncodedImage(imageRequest: ImageRequest): EncodedImage? {
    val uri = imageRequest.sourceUri
    if (UriUtil.isLocalContactUri(uri)) {
      val inputStream: InputStream?
      if (uri.toString().endsWith("/photo")) {
        inputStream = contentResolver.openInputStream(uri)
      } else if (uri.toString().endsWith("/display_photo")) {
        inputStream =
            try {
              val fd = contentResolver.openAssetFileDescriptor(uri, "r")
              checkNotNull(fd)
              fd!!.createInputStream()
            } catch (e: IOException) {
              throw IOException("Contact photo does not exist: $uri")
            }
      } else {
        inputStream = ContactsContract.Contacts.openContactPhotoInputStream(contentResolver, uri)
        if (inputStream == null) {
          throw IOException("Contact photo does not exist: $uri")
        }
      }
      checkNotNull(inputStream)
      // If a Contact URI is provided, use the special helper to open that contact's photo.
      return getEncodedImage(inputStream!!, EncodedImage.UNKNOWN_STREAM_SIZE)
    }
    if (UriUtil.isLocalCameraUri(uri)) {
      val cameraImage = getCameraImage(uri)
      if (cameraImage != null) {
        return cameraImage
      }
    }
    return getEncodedImage(
        checkNotNull(contentResolver.openInputStream(uri)), EncodedImage.UNKNOWN_STREAM_SIZE)
  }

  @Throws(IOException::class)
  private fun getCameraImage(uri: Uri): EncodedImage? {
    val parcelFileDescriptor: ParcelFileDescriptor? =
        try {
          contentResolver.openFileDescriptor(uri, "r")
        } catch (e: FileNotFoundException) {
          return null
        }
    checkNotNull(parcelFileDescriptor)
    val fd = parcelFileDescriptor!!.fileDescriptor
    val encodedImage =
        this.getEncodedImage(FileInputStream(fd), parcelFileDescriptor.statSize.toInt())
    parcelFileDescriptor.close()
    return encodedImage
  }

  override fun getProducerName(): String = PRODUCER_NAME

  companion object {
    const val PRODUCER_NAME = "LocalContentUriFetchProducer"
    private val PROJECTION =
        arrayOf(MediaStore.Images.Media._ID, MediaStore.Images.ImageColumns.DATA)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers

import com.facebook.common.memory.PooledByteBufferFactory
import com.facebook.imagepipeline.image.EncodedImage
import com.facebook.imagepipeline.request.ImageRequest
import java.io.FileInputStream
import java.io.IOException
import java.util.concurrent.Executor

/** Represents a local file fetch producer. */
class LocalFileFetchProducer(executor: Executor, pooledByteBufferFactory: PooledByteBufferFactory) :
    LocalFetchProducer(executor, pooledByteBufferFactory) {

  @Throws(IOException::class)
  override fun getEncodedImage(imageRequest: ImageRequest): EncodedImage? =
      getEncodedImage(
          FileInputStream(imageRequest.sourceFile.toString()),
          imageRequest.sourceFile.length().toInt())

  override fun getProducerName(): String = PRODUCER_NAME

  companion object {
    const val PRODUCER_NAME = "LocalFileFetchProducer"
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers

import android.content.res.AssetFileDescriptor
import android.content.res.Resources
import android.content.res.Resources.NotFoundException
import com.facebook.common.memory.PooledByteBufferFactory
import com.facebook.imagepipeline.image.EncodedImage
import com.facebook.imagepipeline.request.ImageRequest
import java.io.IOException
import java.util.concurrent.Executor

/** Executes a local fetch from a resource. */
class LocalResourceFetchProducer(
    executor: Executor,
    pooledByteBufferFactory: PooledByteBufferFactory,
    private val resources: Resources
) : LocalFetchProducer(executor, pooledByteBufferFactory) {

  @Throws(IOException::class)
  override fun getEncodedImage(imageRequest: ImageRequest): EncodedImage? =
      getEncodedImage(
          resources.openRawResource(getResourceId(imageRequest)), getLength(imageRequest))

  private fun getLength(imageRequest: ImageRequest): Int {
    var fd: AssetFileDescriptor? = null
    return try {
      fd = resources.openRawResourceFd(getResourceId(imageRequest))
      fd.length.toInt()
    } catch (e: NotFoundException) {
      -1
    } finally {
      try {
        fd?.close()
      } catch (ignored: IOException) {
        // There's nothing we can do with the exception when closing descriptor.
      }
    }
  }

  override fun getProducerName(): String = PRODUCER_NAME

  companion object {
    const val PRODUCER_NAME = "LocalResourceFetchProducer"

    private fun getResourceId(imageRequest: ImageRequest): Int {
      val path = imageRequest.sourceUri.path
      checkNotNull(path)
      return path!!.substring(1).toInt()
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

/** All the Producer implementation which compose the image pipeline. */
package com.facebook.imagepipeline.producers


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
package com.facebook.imagepipeline.producers

/**
 * Building block for image processing in the image pipeline.
 *
 * Execution of image request consists of multiple different tasks such as network fetch, disk
 * caching, memory caching, decoding, applying transformations etc. Producer<T> represents single
 * task whose result is an instance of T. Breaking entire request into sequence of Producers allows
 * us to construct different requests while reusing the same blocks.
 *
 * </T> * Producer supports multiple values and streaming.
 *
 * @param <T> </T>
 */
interface Producer<T> {
  /**
   * Start producing results for given context. Provided consumer is notified whenever progress is
   * made (new value is ready or error occurs).
   *
   * @param consumer
   * @param context
   */
  fun produceResults(consumer: Consumer<T>, context: ProducerContext)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers

/**
 * Constants to be used various [Producer]s for logging purposes in the extra maps for the
 * [com.facebook.imagepipeline.listener.RequestListener].
 *
 * The elements are package visible on purpose such that the individual producers create public
 * constants of the ones that they actually use.
 */
internal object ProducerConstants {
  const val EXTRA_CACHED_VALUE_FOUND = "cached_value_found"
  const val EXTRA_BITMAP_SIZE = "bitmapSize"
  const val EXTRA_HAS_GOOD_QUALITY = "hasGoodQuality"
  const val EXTRA_IS_FINAL = "isFinal"
  const val EXTRA_IMAGE_FORMAT_NAME = "imageFormat"
  const val EXTRA_BYTES = "byteCount"
  const val ENCODED_IMAGE_SIZE = "encodedImageSize"
  const val REQUESTED_IMAGE_SIZE = "requestedImageSize"
  const val SAMPLE_SIZE = "sampleSize"
  const val NON_FATAL_DECODE_ERROR = "non_fatal_decode_error"
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
package com.facebook.imagepipeline.producers

import com.facebook.fresco.middleware.HasExtraData
import com.facebook.imagepipeline.common.Priority
import com.facebook.imagepipeline.core.ImagePipelineConfigInterface
import com.facebook.imagepipeline.request.ImageRequest

/**
 * Used to pass context information to producers.
 *
 * Object implementing this interface is passed to all producers participating in pipeline request
 * {@see Producer#produceResults}. Its responsibility is to instruct producers which image should be
 * fetched/decoded/resized/cached etc. This class also handles request cancellation.
 *
 * In order to be notified when cancellation is requested, a producer should use the
 * `runOnCancellationRequested` method which takes an instance of Runnable and executes it when the
 * pipeline client cancels the image request.
 */
interface ProducerContext : HasExtraData {

  /** @return image request that is being executed */
  val imageRequest: ImageRequest

  /** @return id of this request */
  val id: String

  /** @return optional id of the UI component requesting the image */
  val uiComponentId: String?

  /** @return ProducerListener2 for producer's events */
  val producerListener: ProducerListener2

  /** @return the [Object] that indicates the caller's context */
  val callerContext: Any

  /** @return the lowest permitted [ImageRequest.RequestLevel] */
  val lowestPermittedRequestLevel: ImageRequest.RequestLevel

  /** @return true if the request is a prefetch, false otherwise. */
  val isPrefetch: Boolean

  /** @return priority of the request. */
  val priority: Priority

  /** @return true if request's owner expects intermediate results */
  val isIntermediateResultExpected: Boolean

  /**
   * Adds callbacks to the set of callbacks that are executed at various points during the
   * processing of a request.
   *
   * @param callbacks callbacks to be executed
   */
  fun addCallbacks(callbacks: ProducerContextCallbacks)

  val imagePipelineConfig: ImagePipelineConfigInterface

  /** Helper to set [HasExtraData.KEY_ORIGIN] and [HasExtraData.KEY_ORIGIN_SUBCATEGORY] */
  fun putOriginExtra(origin: String?, subcategory: String?)

  /** Helper to set [HasExtraData.KEY_ORIGIN] */
  fun putOriginExtra(origin: String?)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers

/** Callbacks that are called when something changes in a request sequence. */
interface ProducerContextCallbacks {

  /** Method that is called when a client cancels the request. */
  fun onCancellationRequested()

  /** Method that is called when a request is no longer a prefetch, or vice versa. */
  fun onIsPrefetchChanged()

  /** Method that is called when intermediate results start or stop being expected. */
  fun onIsIntermediateResultExpectedChanged()

  /** Method that is called when the priority of the request changes. */
  fun onPriorityChanged()
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers

import android.content.ContentResolver
import com.facebook.common.memory.PooledByteBufferFactory
import com.facebook.imagepipeline.image.EncodedImage
import com.facebook.imagepipeline.request.ImageRequest
import java.io.IOException
import java.util.concurrent.Executor

/**
 * The [QualifiedResourceFetchProducer] uses the [ContentResolver] to allow fetching resources that
 * might not be part of the application's package.
 */
class QualifiedResourceFetchProducer(
    executor: Executor,
    pooledByteBufferFactory: PooledByteBufferFactory,
    private val contentResolver: ContentResolver
) : LocalFetchProducer(executor, pooledByteBufferFactory) {

  @Throws(IOException::class)
  override fun getEncodedImage(imageRequest: ImageRequest): EncodedImage {
    return getEncodedImage(
        checkNotNull(contentResolver.openInputStream(imageRequest.sourceUri)) {
          "ContentResolver returned null InputStream"
        },
        EncodedImage.UNKNOWN_STREAM_SIZE)
  }

  override fun getProducerName(): String = PRODUCER_NAME

  companion object {
    const val PRODUCER_NAME = "QualifiedResourceFetchProducer"
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers

import com.facebook.common.memory.PooledByteBuffer
import com.facebook.common.references.CloseableReference
import com.facebook.imagepipeline.image.EncodedImage

/**
 * Remove image transform meta data producer
 *
 * Remove the [ImageTransformMetaData] object from the results passed down from the next producer,
 * and adds it to the result that it returns to the consumer.
 */
class RemoveImageTransformMetaDataProducer(private val inputProducer: Producer<EncodedImage?>) :
    Producer<CloseableReference<PooledByteBuffer>> {

  override fun produceResults(
      consumer: Consumer<CloseableReference<PooledByteBuffer>>,
      context: ProducerContext
  ) {
    inputProducer.produceResults(RemoveImageTransformMetaDataConsumer(consumer), context)
  }

  private inner class RemoveImageTransformMetaDataConsumer(
      consumer: Consumer<CloseableReference<PooledByteBuffer>>
  ) : DelegatingConsumer<EncodedImage?, CloseableReference<PooledByteBuffer>>(consumer) {
    override fun onNewResultImpl(newResult: EncodedImage?, @Consumer.Status status: Int) {
      var ret: CloseableReference<PooledByteBuffer>? = null
      try {
        if (EncodedImage.isValid(newResult)) {
          ret = newResult?.byteBufferRef
        }
        consumer.onNewResult(ret, status)
      } finally {
        CloseableReference.closeSafely(ret)
      }
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
package com.facebook.imagepipeline.producers

import com.facebook.common.executors.StatefulRunnable
import java.lang.Exception

/**
 * [StatefulRunnable] intended to be used by producers.
 *
 * Class implements common functionality related to handling producer instrumentation and resource
 * management.
 */
abstract class StatefulProducerRunnable<T>(
    private val consumer: Consumer<T>,
    private val producerListener: ProducerListener2,
    private val producerContext: ProducerContext,
    private val producerName: String
) : StatefulRunnable<T>() {
  override fun onSuccess(result: T?) {
    producerListener.onProducerFinishWithSuccess(
        producerContext,
        producerName,
        if (producerListener.requiresExtraMap(producerContext, producerName))
            getExtraMapOnSuccess(result)
        else null)
    consumer.onNewResult(result, Consumer.IS_LAST)
  }

  override fun onFailure(e: Exception) {
    producerListener.onProducerFinishWithFailure(
        producerContext,
        producerName,
        e,
        if (producerListener.requiresExtraMap(producerContext, producerName))
            getExtraMapOnFailure(e)
        else null)
    consumer.onFailure(e)
  }

  override fun onCancellation() {
    producerListener.onProducerFinishWithCancellation(
        producerContext,
        producerName,
        if (producerListener.requiresExtraMap(producerContext, producerName)) extraMapOnCancellation
        else null)
    consumer.onCancellation()
  }

  /** Create extra map for result */
  protected open fun getExtraMapOnSuccess(result: T?): Map<String?, String?>? {
    return null
  }

  /** Create extra map for exception */
  protected open fun getExtraMapOnFailure(exception: Exception?): Map<String, String>? {
    return null
  }

  /** Create extra map for cancellation */
  protected open val extraMapOnCancellation: Map<String, String>?
    get() = null

  abstract override fun disposeResult(result: T?)

  init {
    producerListener.onProducerStart(producerContext, producerName)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers

import android.os.Looper
import com.facebook.imagepipeline.instrumentation.FrescoInstrumenter
import com.facebook.imagepipeline.systrace.FrescoSystrace.traceSection
import java.lang.Exception

/** Uses ExecutorService to move further computation to different thread */
class ThreadHandoffProducer<T>(
    val inputProducer: Producer<T>,
    val threadHandoffProducerQueue: ThreadHandoffProducerQueue
) : Producer<T> {

  override fun produceResults(consumer: Consumer<T>, context: ProducerContext) {
    traceSection("ThreadHandoffProducer#produceResults") {
      val producerListener = context.producerListener
      if (shouldRunImmediately(context)) {
        producerListener.onProducerStart(context, PRODUCER_NAME)
        producerListener.onProducerFinishWithSuccess(context, PRODUCER_NAME, null)
        inputProducer.produceResults(consumer, context)
        return
      }
      val statefulRunnable: StatefulProducerRunnable<T> =
          object : StatefulProducerRunnable<T>(consumer, producerListener, context, PRODUCER_NAME) {
            override fun onSuccess(ignored: T?) {
              producerListener.onProducerFinishWithSuccess(context, PRODUCER_NAME, null)
              inputProducer.produceResults(consumer, context)
            }

            override fun disposeResult(ignored: T?) = Unit

            @Throws(Exception::class)
            override fun getResult(): T? {
              return null
            }
          }
      context.addCallbacks(
          object : BaseProducerContextCallbacks() {
            override fun onCancellationRequested() {
              statefulRunnable.cancel()
              threadHandoffProducerQueue.remove(statefulRunnable)
            }
          })
      threadHandoffProducerQueue.addToQueueOrExecute(
          FrescoInstrumenter.decorateRunnable(statefulRunnable, getInstrumentationTag(context)))
    }
  }

  companion object {
    const val PRODUCER_NAME = "BackgroundThreadHandoffProducer"

    private fun getInstrumentationTag(context: ProducerContext): String? {
      return if (FrescoInstrumenter.isTracing) "ThreadHandoffProducer_produceResults_" + context.id
      else null
    }

    private fun shouldRunImmediately(context: ProducerContext): Boolean {
      if (!context.imagePipelineConfig.experiments.handOffOnUiThreadOnly) {
        return false
      }
      return Looper.getMainLooper().thread !== Thread.currentThread()
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers

import java.util.ArrayDeque
import java.util.Deque
import java.util.concurrent.Executor

class ThreadHandoffProducerQueueImpl(private val executor: Executor) : ThreadHandoffProducerQueue {

  private var queueing = false
  private val runnableList: Deque<Runnable> = ArrayDeque()

  @Synchronized
  override fun addToQueueOrExecute(runnable: Runnable) {
    if (queueing) {
      runnableList.add(runnable)
    } else {
      this.executor.execute(runnable)
    }
  }

  @Synchronized
  override fun startQueueing() {
    queueing = true
  }

  @Synchronized
  override fun stopQueuing() {
    queueing = false
    execInQueue()
  }

  private fun execInQueue() {
    while (!runnableList.isEmpty()) {
      this.executor.execute(runnableList.pop())
    }
    runnableList.clear()
  }

  @Synchronized
  override fun remove(runnable: Runnable) {
    runnableList.remove(runnable)
  }

  @Synchronized override fun isQueueing(): Boolean = queueing
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers

import com.facebook.imagepipeline.common.ResizeOptions

/**
 * Implemented producers can be queried for whether they are likely to be able to produce a result
 * of the desired size.
 *
 * [produceResults(Consumer, ProducerContext)] may send a null image to the consumer, even if an
 * image is available, if the ultimate image is smaller than wanted. This may happen even if the
 * producer thought it would be able to satisfy the request.
 */
interface ThumbnailProducer<T> : Producer<T> {
  /**
   * Checks whether the producer may be able to produce images of the specified size. This makes no
   * promise about being able to produce images for a particular source, only generally being able
   * to produce output of the desired resolution.
   *
   * @param resizeOptions the resize options from the current request
   * @return true if the producer can meet these needs
   */
  fun canProvideImageForSize(resizeOptions: ResizeOptions?): Boolean
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.producers

import com.facebook.imagepipeline.common.ResizeOptions
import com.facebook.imagepipeline.image.EncodedImage
import com.facebook.imageutils.BitmapUtil

/**
 * Utility class to consistently check whether a given thumbnail size will be sufficient for a given
 * request with [com.facebook.imagepipeline.common.ResizeOptions].
 */
object ThumbnailSizeChecker {

  /**
   * The ratio between the requested size and the minimum thumbnail size which will be considered
   * big enough. This will allow a thumbnail which is actually 75% of the requested size to be used
   * and scaled up.
   */
  const val ACCEPTABLE_REQUESTED_TO_ACTUAL_SIZE_RATIO = 4.0f / 3
  private const val ROTATED_90_DEGREES_CLOCKWISE = 90
  private const val ROTATED_90_DEGREES_COUNTER_CLOCKWISE = 270

  /**
   * Checks whether the producer may be able to produce images of the specified size. This makes no
   * promise about being able to produce images for a particular source, only generally being able
   * to produce output of the desired resolution.
   *
   * @param width the desired width
   * @param height the desired height
   * @return true if the producer can meet these needs
   */
  @JvmStatic
  fun isImageBigEnough(width: Int, height: Int, resizeOptions: ResizeOptions?): Boolean =
      if (resizeOptions == null) {
        (getAcceptableSize(width) >= BitmapUtil.MAX_BITMAP_SIZE &&
            getAcceptableSize(height) >= BitmapUtil.MAX_BITMAP_SIZE.toInt())
      } else {
        (getAcceptableSize(width) >= resizeOptions.width &&
            getAcceptableSize(height) >= resizeOptions.height)
      }

  @JvmStatic
  fun isImageBigEnough(encodedImage: EncodedImage?, resizeOptions: ResizeOptions?): Boolean =
      if (encodedImage == null) {
        false
      } else {
        when (encodedImage.rotationAngle) {
          ROTATED_90_DEGREES_CLOCKWISE,
          ROTATED_90_DEGREES_COUNTER_CLOCKWISE -> // Swap width and height when checking size as
              // this will be rotated
              isImageBigEnough(encodedImage.height, encodedImage.width, resizeOptions)
          else -> isImageBigEnough(encodedImage.width, encodedImage.height, resizeOptions)
        }
      }

  @JvmStatic
  fun getAcceptableSize(size: Int): Int = (size * ACCEPTABLE_REQUESTED_TO_ACTUAL_SIZE_RATIO).toInt()
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.request

abstract class BaseRepeatedPostProcessor : BasePostprocessor(), RepeatedPostprocessor {

  @get:Synchronized private var callback: RepeatedPostprocessorRunner? = null

  @Synchronized
  override fun setCallback(runner: RepeatedPostprocessorRunner) {
    callback = runner
  }

  fun update() {
    val callback = callback
    callback?.update()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.request

interface HasImageRequest {
  val imageRequest: ImageRequest?
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

/**
 * This package contains abstractions and related implementations about the ImageRequest and related
 * attributes.
 */
package com.facebook.imagepipeline.request


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.request

/**
 * Use an instance of this interface to perform post-process operations that must be performed more
 * than once.
 */
interface RepeatedPostprocessor : Postprocessor {
  /**
   * Callback used to pass the postprocessor a reference to the object that will run the
   * postprocessor's `PostProcessor#process` method when the client requires.
   *
   * @param runner
   */
  fun setCallback(runner: RepeatedPostprocessorRunner)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.request

/** An instance of this class is used to run a postprocessor whenever the client requires. */
fun interface RepeatedPostprocessorRunner {
  fun update()
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.transcoder

import com.facebook.imageformat.ImageFormat
import com.facebook.imagepipeline.core.ImageTranscoderType
import com.facebook.imagepipeline.core.NativeCodeSetup
import com.facebook.imagepipeline.nativecode.NativeImageTranscoderFactory

/**
 * Class responsible of returning the correct [ImageTranscoder] given the [ImageFormat]. The custom
 * [ImageTranscoder], if any, will always be used first. If the image format is not supported, the
 * first fallback is NativeJpegTranscoder, otherwise [SimpleImageTranscoder] is used.
 */
class MultiImageTranscoderFactory(
    private val maxBitmapSize: Int,
    private val useDownSamplingRatio: Boolean,
    private val primaryImageTranscoderFactory: ImageTranscoderFactory?,
    @field:ImageTranscoderType @param:ImageTranscoderType private val imageTranscoderType: Int?,
    private val ensureTranscoderLibraryLoaded: Boolean
) : ImageTranscoderFactory {

  override fun createImageTranscoder(
      imageFormat: ImageFormat,
      isResizingEnabled: Boolean
  ): ImageTranscoder? {
    // Use custom ImageTranscoder, if any
    var imageTranscoder = getCustomImageTranscoder(imageFormat, isResizingEnabled)
    // Use ImageTranscoder based on type passed, if any
    if (imageTranscoder == null) {
      imageTranscoder = getImageTranscoderWithType(imageFormat, isResizingEnabled)
    }
    // First fallback using native ImageTranscoder
    if (imageTranscoder == null && NativeCodeSetup.getUseNativeCode()) {
      imageTranscoder = getNativeImageTranscoder(imageFormat, isResizingEnabled)
    }

    // Fallback to SimpleImageTranscoder if the format is not supported by native ImageTranscoder
    return imageTranscoder ?: getSimpleImageTranscoder(imageFormat, isResizingEnabled)
  }

  private fun getCustomImageTranscoder(
      imageFormat: ImageFormat,
      isResizingEnabled: Boolean
  ): ImageTranscoder? =
      primaryImageTranscoderFactory?.createImageTranscoder(imageFormat, isResizingEnabled)

  private fun getNativeImageTranscoder(
      imageFormat: ImageFormat,
      isResizingEnabled: Boolean
  ): ImageTranscoder? =
      NativeImageTranscoderFactory.getNativeImageTranscoderFactory(
              maxBitmapSize, useDownSamplingRatio, ensureTranscoderLibraryLoaded)
          .createImageTranscoder(imageFormat, isResizingEnabled)

  private fun getSimpleImageTranscoder(
      imageFormat: ImageFormat,
      isResizingEnabled: Boolean
  ): ImageTranscoder =
      SimpleImageTranscoderFactory(maxBitmapSize)
          .createImageTranscoder(imageFormat, isResizingEnabled)

  private fun getImageTranscoderWithType(
      imageFormat: ImageFormat,
      isResizingEnabled: Boolean
  ): ImageTranscoder? =
      if (imageTranscoderType == null) {
        null
      } else {
        when (imageTranscoderType) {
          ImageTranscoderType.NATIVE_TRANSCODER ->
              getNativeImageTranscoder(imageFormat, isResizingEnabled)
          ImageTranscoderType.JAVA_TRANSCODER ->
              getSimpleImageTranscoder(imageFormat, isResizingEnabled)
          else -> throw IllegalArgumentException("Invalid ImageTranscoderType")
        }
      }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.transcoder

import android.graphics.Bitmap
import android.graphics.Bitmap.CompressFormat
import android.graphics.BitmapFactory
import android.os.Build
import com.facebook.common.logging.FLog
import com.facebook.imageformat.DefaultImageFormats
import com.facebook.imageformat.ImageFormat
import com.facebook.imagepipeline.common.ResizeOptions
import com.facebook.imagepipeline.common.RotationOptions
import com.facebook.imagepipeline.image.EncodedImage
import java.io.OutputStream

/**
 * Image transcoder using only the Android API. Clients can use this if they don't want to use the
 * native implementation. This image transcoder requires more memory.
 */
class SimpleImageTranscoder(private val resizingEnabled: Boolean, private val maxBitmapSize: Int) :
    ImageTranscoder {

  override fun transcode(
      encodedImage: EncodedImage,
      outputStream: OutputStream,
      rotationOptions: RotationOptions?,
      resizeOptions: ResizeOptions?,
      outputFormat: ImageFormat?,
      quality: Int?
  ): ImageTranscodeResult {
    var rotationOptions = rotationOptions
    var quality = quality
    if (quality == null) {
      quality = JpegTranscoderUtils.DEFAULT_JPEG_QUALITY
    }
    if (rotationOptions == null) {
      rotationOptions = RotationOptions.autoRotate()
    }
    val sampleSize = getSampleSize(encodedImage, rotationOptions, resizeOptions)
    val options = BitmapFactory.Options()
    options.inSampleSize = sampleSize
    val resizedBitmap: Bitmap? =
        try {
          BitmapFactory.decodeStream(encodedImage.inputStream, null, options)
        } catch (oom: OutOfMemoryError) {
          FLog.e(TAG, "Out-Of-Memory during transcode", oom)
          return ImageTranscodeResult(TranscodeStatus.TRANSCODING_ERROR)
        }
    if (resizedBitmap == null) {
      FLog.e(TAG, "Couldn't decode the EncodedImage InputStream ! ")
      return ImageTranscodeResult(TranscodeStatus.TRANSCODING_ERROR)
    }
    val transformationMatrix =
        JpegTranscoderUtils.getTransformationMatrix(encodedImage, rotationOptions)
    var srcBitmap: Bitmap = resizedBitmap
    return try {
      if (transformationMatrix != null) {
        srcBitmap =
            Bitmap.createBitmap(
                resizedBitmap,
                0,
                0,
                resizedBitmap.width,
                resizedBitmap.height,
                transformationMatrix,
                false)
      }
      srcBitmap.compress(getOutputFormat(outputFormat), quality, outputStream)
      ImageTranscodeResult(
          if (sampleSize > DownsampleUtil.DEFAULT_SAMPLE_SIZE) TranscodeStatus.TRANSCODING_SUCCESS
          else TranscodeStatus.TRANSCODING_NO_RESIZING)
    } catch (oom: OutOfMemoryError) {
      FLog.e(TAG, "Out-Of-Memory during transcode", oom)
      ImageTranscodeResult(TranscodeStatus.TRANSCODING_ERROR)
    } finally {
      srcBitmap.recycle()
      resizedBitmap.recycle()
    }
  }

  override fun canResize(
      encodedImage: EncodedImage,
      rotationOptions: RotationOptions?,
      resizeOptions: ResizeOptions?
  ): Boolean {
    var rotationOptions = rotationOptions
    if (rotationOptions == null) {
      rotationOptions = RotationOptions.autoRotate()
    }
    return resizingEnabled &&
        DownsampleUtil.determineSampleSize(
            rotationOptions, resizeOptions, encodedImage, maxBitmapSize) >
            DownsampleUtil.DEFAULT_SAMPLE_SIZE
  }

  override fun canTranscode(imageFormat: ImageFormat): Boolean =
      imageFormat === DefaultImageFormats.HEIF || imageFormat === DefaultImageFormats.JPEG

  override val identifier: String = "SimpleImageTranscoder"

  private fun getSampleSize(
      encodedImage: EncodedImage,
      rotationOptions: RotationOptions,
      resizeOptions: ResizeOptions?
  ): Int {
    val sampleSize: Int =
        if (!resizingEnabled) {
          DownsampleUtil.DEFAULT_SAMPLE_SIZE
        } else {
          DownsampleUtil.determineSampleSize(
              rotationOptions, resizeOptions, encodedImage, maxBitmapSize)
        }
    return sampleSize
  }

  companion object {
    private const val TAG = "SimpleImageTranscoder"

    /**
     * Determine the [Bitmap.CompressFormat] given the [ImageFormat]. If no match is found, it
     * returns [Bitmap.CompressFormat#JPEG]
     *
     * @param format The [ImageFormat] used as input
     * @return The corresponding [Bitmap.CompressFormat]
     */
    private fun getOutputFormat(format: ImageFormat?): CompressFormat {
      if (format == null) {
        return CompressFormat.JPEG
      }
      return if (format === DefaultImageFormats.JPEG) {
        CompressFormat.JPEG
      } else if (format === DefaultImageFormats.PNG) {
        CompressFormat.PNG
      } else {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH &&
            DefaultImageFormats.isStaticWebpFormat(format)) {
          CompressFormat.WEBP
        } else {
          CompressFormat.JPEG
        }
      }
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.prioritization

import com.facebook.imagepipeline.common.Priority
import com.facebook.imagepipeline.common.Priority.Companion.getHigherPriority
import com.facebook.imagepipeline.common.Priority.HIGH
import com.facebook.imagepipeline.common.Priority.LOW
import com.facebook.imagepipeline.common.Priority.MEDIUM
import org.junit.Assert.assertEquals
import org.junit.Test

/** Tests for Priority enum */
class PriorityTest {
  @Test
  fun testGetHigherPriority() {
    assertEquals(Priority.HIGH, getHigherPriority(Priority.LOW, Priority.HIGH))
    assertEquals(Priority.HIGH, getHigherPriority(Priority.MEDIUM, Priority.HIGH))
    assertEquals(Priority.HIGH, getHigherPriority(Priority.HIGH, Priority.HIGH))
    assertEquals(Priority.HIGH, getHigherPriority(Priority.HIGH, Priority.MEDIUM))
    assertEquals(Priority.HIGH, getHigherPriority(Priority.HIGH, Priority.LOW))
    assertEquals(Priority.MEDIUM, getHigherPriority(Priority.LOW, Priority.MEDIUM))
    assertEquals(Priority.MEDIUM, getHigherPriority(Priority.MEDIUM, Priority.MEDIUM))
    assertEquals(Priority.MEDIUM, getHigherPriority(Priority.MEDIUM, Priority.LOW))
    assertEquals(Priority.LOW, getHigherPriority(Priority.LOW, Priority.LOW))
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.backends.okhttp3

import android.content.Context
import com.facebook.imagepipeline.core.ImagePipelineConfig
import com.facebook.imagepipeline.core.ImagePipelineConfig.Companion.newBuilder
import okhttp3.OkHttpClient

/**
 * Factory for getting an [com.facebook.imagepipeline.core.ImagePipelineConfig] that uses
 * [OkHttpNetworkFetcher].
 */
object OkHttpImagePipelineConfigFactory {
  @JvmStatic
  fun newBuilder(context: Context, okHttpClient: OkHttpClient): ImagePipelineConfig.Builder =
      newBuilder(context).setNetworkFetcher(OkHttpNetworkFetcher(okHttpClient))
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.backends.okhttp3

import android.os.Looper
import android.os.SystemClock
import com.facebook.imagepipeline.backends.okhttp3.OkHttpNetworkFetcher.OkHttpNetworkFetchState
import com.facebook.imagepipeline.common.BytesRange
import com.facebook.imagepipeline.common.BytesRange.Companion.fromContentRangeHeader
import com.facebook.imagepipeline.image.EncodedImage
import com.facebook.imagepipeline.producers.BaseNetworkFetcher
import com.facebook.imagepipeline.producers.BaseProducerContextCallbacks
import com.facebook.imagepipeline.producers.Consumer
import com.facebook.imagepipeline.producers.FetchState
import com.facebook.imagepipeline.producers.NetworkFetcher
import com.facebook.imagepipeline.producers.ProducerContext
import java.io.IOException
import java.lang.Exception
import java.util.concurrent.Executor
import okhttp3.CacheControl
import okhttp3.Call
import okhttp3.Callback
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody

/**
 * Network fetcher that uses OkHttp 3 as a backend.
 *
 * @param callFactory custom [Call.Factory] for fetching image from the network
 * @param cancellationExecutor executor on which fetching cancellation is performed if cancellation
 *   is requested from the UI Thread
 * @param disableOkHttpCache true if network requests should not be cached by OkHttp
 */
open class OkHttpNetworkFetcher
@JvmOverloads
constructor(
    private val callFactory: Call.Factory,
    private val cancellationExecutor: Executor,
    disableOkHttpCache: Boolean = true
) : BaseNetworkFetcher<OkHttpNetworkFetchState>() {

  /** @param okHttpClient client to use */
  constructor(
      okHttpClient: OkHttpClient
  ) : this(okHttpClient, okHttpClient.dispatcher().executorService())

  class OkHttpNetworkFetchState(
      consumer: Consumer<EncodedImage>,
      producerContext: ProducerContext
  ) : FetchState(consumer, producerContext) {
    @JvmField var submitTime: Long = 0
    @JvmField var responseTime: Long = 0
    @JvmField var fetchCompleteTime: Long = 0
  }

  private val cacheControl: CacheControl? =
      if (disableOkHttpCache) CacheControl.Builder().noStore().build() else null

  override fun createFetchState(
      consumer: Consumer<EncodedImage>,
      context: ProducerContext
  ): OkHttpNetworkFetchState = OkHttpNetworkFetchState(consumer, context)

  override fun fetch(fetchState: OkHttpNetworkFetchState, callback: NetworkFetcher.Callback) {
    fetchState.submitTime = SystemClock.elapsedRealtime()
    val uri = fetchState.uri
    try {
      val requestBuilder = Request.Builder().url(uri.toString()).get()
      cacheControl?.let(requestBuilder::cacheControl)

      fetchState.context.imageRequest.bytesRange?.let {
        requestBuilder.addHeader("Range", it.toHttpRangeHeaderValue())
      }
      fetchWithRequest(fetchState, callback, requestBuilder.build())
    } catch (e: Exception) {
      // handle error while creating the request
      callback.onFailure(e)
    }
  }

  override fun onFetchCompletion(fetchState: OkHttpNetworkFetchState, byteSize: Int) {
    fetchState.fetchCompleteTime = SystemClock.elapsedRealtime()
  }

  override fun getExtraMap(fetchState: OkHttpNetworkFetchState, byteSize: Int) =
      mapOf(
          QUEUE_TIME to (fetchState.responseTime - fetchState.submitTime).toString(),
          FETCH_TIME to (fetchState.fetchCompleteTime - fetchState.responseTime).toString(),
          TOTAL_TIME to (fetchState.fetchCompleteTime - fetchState.submitTime).toString(),
          IMAGE_SIZE to byteSize.toString())

  protected open fun fetchWithRequest(
      fetchState: OkHttpNetworkFetchState,
      callback: NetworkFetcher.Callback,
      request: Request
  ) {
    val call = callFactory.newCall(request)
    fetchState.context.addCallbacks(
        object : BaseProducerContextCallbacks() {
          override fun onCancellationRequested() {
            if (Looper.myLooper() != Looper.getMainLooper()) {
              call.cancel()
            } else {
              cancellationExecutor.execute { call.cancel() }
            }
          }
        })
    call.enqueue(
        object : Callback {
          @Throws(IOException::class)
          override fun onResponse(call: Call, response: Response) {
            fetchState.responseTime = SystemClock.elapsedRealtime()
            val responseBody: ResponseBody? = response.body()
            responseBody?.use { body ->
              try {
                if (!response.isSuccessful) {
                  handleException(call, IOException("Unexpected HTTP code $response"), callback)
                  return@use
                }
                val responseRange = fromContentRangeHeader(response.header("Content-Range"))
                if (responseRange != null &&
                    !(responseRange.from == 0 &&
                        responseRange.to == BytesRange.TO_END_OF_CONTENT)) {
                  // Only treat as a partial image if the range is not all of the content
                  fetchState.responseBytesRange = responseRange
                  fetchState.onNewResultStatusFlags = Consumer.IS_PARTIAL_RESULT
                }
                val contentLength =
                    if (body.contentLength() < 0) 0 else body.contentLength().toInt()

                callback.onResponse(body.byteStream(), contentLength)
              } catch (e: Exception) {
                handleException(call, e, callback)
              }
            }
                ?: handleException(call, IOException("Response body null: $response"), callback)
          }

          override fun onFailure(call: Call, e: IOException) = handleException(call, e, callback)
        })
  }

  /**
   * Handles exceptions.
   *
   * OkHttp notifies callers of cancellations via an IOException. If IOException is caught after
   * request cancellation, then the exception is interpreted as successful cancellation and
   * onCancellation is called. Otherwise onFailure is called.
   */
  private fun handleException(call: Call, e: Exception, callback: NetworkFetcher.Callback) {
    if (call.isCanceled) {
      callback.onCancellation()
    } else {
      callback.onFailure(e)
    }
  }

  private companion object {
    private const val QUEUE_TIME = "queue_time"
    private const val FETCH_TIME = "fetch_time"
    private const val TOTAL_TIME = "total_time"
    private const val IMAGE_SIZE = "image_size"
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.binaryresource

import java.io.IOException
import java.io.InputStream

/*
 * Interface representing a sequence of bytes that abstracts underlying source (e.g. file).
 *
 *
It represents a non-volatile resource whenever it exists and it can be read multiple times.
 * Since it is stream based, performing transformations like encryption can be done by a
 * simple wrapper, instead of writing the decrypted content of the original file into a new file.
 *
 *
Inspired partly by Guava's ByteSource class, but does not use its implementation.
 */

interface BinaryResource {

  /**
   * Opens a new [InputStream] for reading from this source. This method should return a new,
   * independent stream each time it is called.
   *
   * The caller is responsible for ensuring that the returned stream is closed.
   *
   * @throws IOException if an I/O error occurs in the process of opening the stream
   */
  @Throws(IOException::class) fun openStream(): InputStream

  /**
   * Reads the full contents of this byte source as a byte array.
   *
   * @throws IOException if an I/O error occurs in the process of reading from this source
   */
  @Throws(IOException::class) fun read(): ByteArray

  /**
   * Returns the size of this source in bytes. This may be a heavyweight operation that will open a
   * stream, read (or [skip][InputStream.skip], if possible) to the end of the stream and return the
   * total number of bytes that were read.
   *
   * For some sources, such as a file, this method may use a more efficient implementation. Note
   * that in such cases, it is *possible* that this method will return a different number of bytes
   * than would be returned by reading all of the bytes (for example, some special files may return
   * a size of 0 despite actually having content when read).
   *
   * In either case, if this is a mutable source such as a file, the size it returns may not be the
   * same number of bytes a subsequent read would return.
   *
   * @throws IOException if an I/O error occurs in the process of reading the size of this source
   */
  fun size(): Long
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.binaryresource

import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.InputStream

/** A trivial implementation of BinaryResource that wraps a byte array */
class ByteArrayBinaryResource(private val bytes: ByteArray) : BinaryResource {

  override fun size(): Long = bytes.size.toLong()

  @Throws(IOException::class) override fun openStream(): InputStream = ByteArrayInputStream(bytes)

  /**
   * Get the underlying byte array
   *
   * @return the underlying byte array of this resource
   */
  override fun read(): ByteArray = bytes
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.binaryresource

import com.facebook.common.internal.Files
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.io.InputStream

/*
 * Implementation of BinaryResource based on a real file. @see BinaryResource for more details.
 */

class FileBinaryResource private constructor(val file: File) : BinaryResource {

  @Throws(IOException::class) override fun openStream(): InputStream = FileInputStream(file)

  override fun size(): Long = file.length() // 0L if file doesn't exist

  @Throws(IOException::class) override fun read(): ByteArray = Files.toByteArray(file)

  override fun equals(other: Any?): Boolean {
    if (other == null || other !is FileBinaryResource) {
      return false
    }
    return file == other.file
  }

  override fun hashCode(): Int = file.hashCode()

  companion object {
    /*
     * Factory method to create a wrapping BinaryResource without explicitly taking care of null.
     * If the supplied file is null, instead of BinaryResource, null is returned.
     */
    @JvmStatic
    fun createOrNull(file: File?): FileBinaryResource? {
      return file?.let { FileBinaryResource(it) }
    }

    @JvmStatic
    fun create(file: File): FileBinaryResource {
      return FileBinaryResource(file)
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.cache.common

interface HasDebugData {
  /** Return a string describing this object and to be used for debugging or logging */
  val debugData: String?
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.callercontext

/** Verifies CallerContext */
fun interface CallerContextVerifier {
  fun verifyCallerContext(callerContext: Any?, isPrefetch: Boolean)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.drawable.base

/**
 * A drawable can implement this fun interface to be notified when it might be convenient to drop
 * its caches in order conserve memory. This is best effort and the Drawable should not depend on it
 * being called.
 */
interface DrawableWithCaches {
  /** Informs the Drawable to drop its caches. */
  fun dropCaches()
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.animated.factory

import android.content.Context
import com.facebook.imagepipeline.decoder.ImageDecoder
import com.facebook.imagepipeline.drawable.DrawableFactory
import javax.annotation.concurrent.NotThreadSafe

@NotThreadSafe
interface AnimatedFactory {
  fun getAnimatedDrawableFactory(context: Context?): DrawableFactory?

  val gifDecoder: ImageDecoder?
  val webPDecoder: ImageDecoder?
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.animated.factory

import com.facebook.cache.common.CacheKey
import com.facebook.common.executors.SerialExecutorService
import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory
import com.facebook.imagepipeline.cache.AnimatedCache
import com.facebook.imagepipeline.cache.CountingMemoryCache
import com.facebook.imagepipeline.core.ExecutorSupplier
import com.facebook.imagepipeline.image.CloseableImage
import java.util.concurrent.ExecutorService

object AnimatedFactoryProvider {
  private var implLoaded = false
  private var impl: AnimatedFactory? = null

  @JvmStatic
  fun getAnimatedFactory(
      platformBitmapFactory: PlatformBitmapFactory?,
      executorSupplier: ExecutorSupplier?,
      backingCache: CountingMemoryCache<CacheKey?, CloseableImage?>?,
      animatedCache: AnimatedCache,
      downscaleFrameToDrawableDimensions: Boolean,
      useBalancedAnimationStrategy: Boolean,
      animationFpsLimit: Int,
      serialExecutorService: ExecutorService?
  ): AnimatedFactory? {
    if (!implLoaded) {
      try {
        val clazz = Class.forName("com.facebook.fresco.animation.factory.AnimatedFactoryV2Impl")
        val constructor =
            clazz.getConstructor(
                PlatformBitmapFactory::class.java,
                ExecutorSupplier::class.java,
                CountingMemoryCache::class.java,
                AnimatedCache::class.java,
                java.lang.Boolean.TYPE,
                java.lang.Boolean.TYPE,
                Integer.TYPE,
                SerialExecutorService::class.java)
        impl =
            constructor.newInstance(
                platformBitmapFactory,
                executorSupplier,
                backingCache,
                animatedCache,
                downscaleFrameToDrawableDimensions,
                useBalancedAnimationStrategy,
                animationFpsLimit,
                serialExecutorService) as AnimatedFactory
      } catch (e: Throwable) {
        // Head in the sand
      }
      if (impl != null) {
        implLoaded = true
      }
    }
    return impl
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

/** Implementation for the factory classes of the animation framework for the image pipeline */
package com.facebook.imagepipeline.animated.factory