/*
 * 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.
 */

/**
 * Abstraction we use in order to allocate memory for bitmaps in different OS versions. It also
 * contains implementation of the Releaser which is responsible to close the specific
 * CloseableReference when not used anymore.
 */
package com.facebook.imagepipeline.bitmaps


/*
 * 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.cache

import android.graphics.Bitmap
import com.facebook.common.references.CloseableReference
import com.facebook.common.util.ByteConstants.MB
import com.facebook.imageutils.BitmapUtil
import java.io.Closeable
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit

/** Store frames from animations which are being displayed at this moment */
class AnimatedCache private constructor(memoryMB: Int) {

  private val sizeBytes = memoryMB * MB

  /** % Memory for animations which were recently used, but they are not running now */
  private val evictionRatio = if (memoryMB < 90) 0.0f else 0.30f

  /** 10% Memory is the maximum size of an animation */
  private val maxCacheEntrySize = sizeBytes.times(0.1).toInt()

  private val lruCache =
      LruCountingMemoryCache<String, AnimationFrames>(
          { it.sizeBytes },
          { it.suggestedTrimRatio },
          {
            MemoryCacheParams(
                maxCacheSize = sizeBytes,
                maxCacheEntries = Int.MAX_VALUE,
                maxEvictionQueueSize = sizeBytes.times(evictionRatio).toInt(),
                maxEvictionQueueEntries = EVICTION_QUEUE,
                maxCacheEntrySize = maxCacheEntrySize,
                paramsCheckIntervalMs = TimeUnit.SECONDS.toMillis(5))
          },
          null,
          false,
          false,
      )

  fun getSize(key: String): Int = lruCache.sizeInBytes

  fun findAnimation(key: String): CloseableReference<AnimationFrames>? = lruCache[key]

  fun saveAnimation(
      key: String,
      animationFrames: AnimationFrames
  ): CloseableReference<AnimationFrames>? {
    return lruCache.cache(key, CloseableReference.of(animationFrames))
  }

  fun removeAnimation(key: String) {
    lruCache[key]?.close()
  }

  companion object {
    private const val EVICTION_QUEUE = 50
    private var instance: AnimatedCache? = null

    @JvmStatic
    fun getInstance(memoryMB: Int): AnimatedCache =
        instance ?: AnimatedCache(memoryMB).also { instance = it }
  }
}

/**
 * Represents the bitmaps of one animation.
 *
 * @param bitmapsByFrame are amount of final bitmap that we will render given a frame index. These
 *   amount could be less than original animation if the animation were reduced
 * @param realToCompressIndexMap If animation was reduced, this map describes the equivalence
 *   between original frame number and reduced frame number
 */
class AnimationFrames(
    bitmapsByFrame: Map<Int, CloseableReference<Bitmap>>,
    private val realToCompressIndexMap: Map<Int, Int>
) : Closeable {
  val frames = ConcurrentHashMap(bitmapsByFrame)

  /** Calculate the size of animation */
  val sizeBytes: Int =
      bitmapsByFrame.values.sumOf {
        if (it.isValid) {
          BitmapUtil.getSizeInBytes(it.get())
        } else 0
      }

  /**
   * Return the bitmap (if exists) given a the animation frame.
   *
   * @param frameIndex Frame index in original animation (not reduced animation frame number).
   * @return Bitmap for [frameIndex] if it is available
   */
  fun getFrame(frameIndex: Int): CloseableReference<Bitmap>? {
    val frame =
        if (realToCompressIndexMap.isEmpty()) {
          frames[frameIndex]
        } else {
          val reducedIndex = realToCompressIndexMap[frameIndex] ?: return null
          frames[reducedIndex]
        }

    return if (frame?.isValid == true) frame else null
  }

  /** Release the stored bitmaps */
  override fun close() {
    frames.values.forEach { it.close() }
    frames.clear()
  }
}


/*
 * 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.cache

import com.facebook.cache.common.CacheKey
import com.facebook.common.internal.Supplier
import com.facebook.common.memory.MemoryTrimmableRegistry
import com.facebook.imagepipeline.cache.CountingMemoryCache.EntryStateObserver
import com.facebook.imagepipeline.cache.MemoryCache.CacheTrimStrategy
import com.facebook.imagepipeline.image.CloseableImage

fun interface BitmapMemoryCacheFactory {
  fun create(
      bitmapMemoryCacheParamsSupplier: Supplier<MemoryCacheParams>,
      memoryTrimmableRegistry: MemoryTrimmableRegistry,
      trimStrategy: CacheTrimStrategy,
      storeEntrySize: Boolean,
      ignoreSizeMismatch: Boolean,
      observer: EntryStateObserver<CacheKey>?
  ): CountingMemoryCache<CacheKey, CloseableImage>
}


/*
 * 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.cache

import android.os.Build
import com.facebook.common.logging.FLog
import com.facebook.common.memory.MemoryTrimType
import com.facebook.imagepipeline.cache.MemoryCache.CacheTrimStrategy

/**
 * CountingMemoryCache eviction strategy appropriate for bitmap caches.
 *
 * If run on KitKat or below, then this TrimStrategy behaves exactly as
 * NativeMemoryCacheTrimStrategy. If run on Lollipop, then BitmapMemoryCacheTrimStrategy will trim
 * cache in one additional case: when OnCloseToDalvikHeapLimit trim type is received, cache's
 * eviction queue will be trimmed according to OnCloseToDalvikHeapLimit's suggested trim ratio.
 */
class BitmapMemoryCacheTrimStrategy : CacheTrimStrategy {

  override fun getTrimRatio(trimType: MemoryTrimType): Double =
      when (trimType) {
        MemoryTrimType.OnCloseToDalvikHeapLimit ->
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
              MemoryTrimType.OnCloseToDalvikHeapLimit.suggestedTrimRatio
            } else {
              // On pre-lollipop versions we keep bitmaps on the native heap, so no need to trim
              // here
              // as it wouldn't help Dalvik heap anyway.
              0.0
            }
        MemoryTrimType.OnAppBackgrounded,
        MemoryTrimType.OnSystemMemoryCriticallyLowWhileAppInForeground,
        MemoryTrimType.OnSystemLowMemoryWhileAppInForeground,
        MemoryTrimType.OnSystemLowMemoryWhileAppInBackground -> 1.0
        else -> {
          FLog.wtf(TAG, "unknown trim type: %s", trimType)
          0.0
        }
      }

  companion object {
    private const val TAG = "BitmapMemoryCacheTrimStrategy"
  }
}


/*
 * 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.cache

import java.util.LinkedHashSet
import javax.annotation.concurrent.ThreadSafe

@ThreadSafe
class BoundedLinkedHashSet<E>(maxSize: Int) {

  private val maxSize: Int = maxSize
  private val linkedHashSet: LinkedHashSet<E> = LinkedHashSet(maxSize)

  @Synchronized operator fun contains(o: E): Boolean = linkedHashSet.contains(o)

  @Synchronized
  fun add(key: E): Boolean {
    if (linkedHashSet.size == this.maxSize) {
      linkedHashSet.remove(linkedHashSet.iterator().next())
    }
    linkedHashSet.remove(key)
    return linkedHashSet.add(key)
  }
}


/*
 * 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.cache

import android.graphics.Bitmap
import com.facebook.common.references.CloseableReference
import java.util.ArrayList
import java.util.HashMap
import kotlin.jvm.JvmField

/** Inspects values cached in bitmap memory cache. */
class CountingMemoryCacheInspector<K, V>(
    private val countingBitmapCache: CountingMemoryCache<K, V>
) {

  /** Cache entry info for use by dumpers. */
  class DumpInfoEntry<K, V>(key: K, valueRef: CloseableReference<V>?) {
    // The key is immutable, so it's safe to store that directly
    @JvmField val key: K

    // The value
    @JvmField val value: CloseableReference<V>?

    fun release() {
      CloseableReference.closeSafely(value)
    }

    init {
      this.key = checkNotNull(key)
      value = CloseableReference.cloneOrNull(valueRef)
    }
  }

  /** Info about the status of the cache for use by dumpers. */
  class DumpInfo<K, V>(size: Int, lruSize: Int, params: MemoryCacheParams) {
    @JvmField val maxSize: Int = params.maxCacheSize

    @JvmField val maxEntriesCount: Int = params.maxCacheEntries

    @JvmField val maxEntrySize: Int = params.maxCacheEntrySize

    @JvmField val size: Int

    @JvmField val lruSize: Int

    @JvmField val lruEntries: MutableList<DumpInfoEntry<K, V>>

    @JvmField val sharedEntries: MutableList<DumpInfoEntry<K, V>>

    @JvmField val otherEntries: MutableMap<Bitmap, Any>

    fun release() {
      for (entry in lruEntries) {
        entry.release()
      }
      for (entry in sharedEntries) {
        entry.release()
      }
    }

    init {

      this.size = size
      this.lruSize = lruSize
      lruEntries = ArrayList()
      sharedEntries = ArrayList()
      otherEntries = HashMap()
    }
  }

  /**
   * Iterates through all entries cached in counting cache and builds snapshot of its content. This
   * should be used by tools that need to know current content of given cache.
   *
   * Caller should call release method on returned DumpInfo after it is done with examining cache
   * contents
   */
  fun dumpCacheContent(): DumpInfo<K, V> {
    synchronized(countingBitmapCache) {
      val dumpInfo =
          DumpInfo<K, V>(
              countingBitmapCache.sizeInBytes,
              countingBitmapCache.evictionQueueSizeInBytes,
              countingBitmapCache.memoryCacheParams)
      val maybeCachedEntries = countingBitmapCache.cachedEntries ?: return dumpInfo
      val cachedEntries: List<Map.Entry<K, CountingMemoryCache.Entry<K, V>>> =
          maybeCachedEntries.getMatchingEntries(null)
      for ((_, entry) in cachedEntries) {
        val dumpEntry = DumpInfoEntry(entry.key, entry.valueRef)
        if (entry.clientCount > 0) {
          dumpInfo.sharedEntries.add(dumpEntry)
        } else {
          dumpInfo.lruEntries.add(dumpEntry)
        }
      }
      val otherEntries = countingBitmapCache.otherEntries
      if (otherEntries != null) {
        for (entry in otherEntries.entries) {
          if (entry != null && !entry.key.isRecycled) {
            dumpInfo.otherEntries[entry.key] = entry.value
          }
        }
      }
      return dumpInfo
    }
  }
}


/*
 * 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.cache

import com.facebook.cache.common.HasDebugData
import com.facebook.common.internal.Predicate
import com.facebook.common.memory.MemoryTrimType
import com.facebook.common.memory.MemoryTrimmable
import com.facebook.common.references.CloseableReference

/**
 * Interface for the image pipeline memory cache.
 *
 * @param <K> the key type
 * @param <V> the value type </V></K>
 */
interface MemoryCache<K, V> : MemoryTrimmable, HasDebugData {

  /** Interface used to specify the trimming strategy for the cache. */
  fun interface CacheTrimStrategy {
    fun getTrimRatio(trimType: MemoryTrimType): Double
  }

  /**
   * Caches the the given key-value pair.
   *
   * The cache returns a new copy of the provided reference which should be used instead of the
   * original one. The client should close the returned reference when it is not required anymore.
   *
   * If the cache failed to cache the given value, then the null reference is returned.
   *
   * @param key
   * @param value
   * @return a new reference to be used, or null if the caching failed
   */
  fun cache(key: K, value: CloseableReference<V>): CloseableReference<V>?

  /**
   * Gets the item with the given key, or null if there is no such item.
   *
   * @param key
   * @return a reference to the cached value, or null if the item was not found
   */
  operator fun get(key: K): CloseableReference<V>?

  /**
   * Gets the item with the given key for debug purposes. For instance, for LRU caches this will not
   * change the LRU order. Use [get(K)] instead.
   *
   * @param key
   * @return a cached value or null if the item was not found
   */
  fun inspect(key: K): V?

  /**
   * Probes whether the object corresponding to the key is in the cache. Note that the act of
   * probing touches the item (if present in cache), thus changing its LRU timestamp.
   *
   * @param key
   */
  fun probe(key: K)

  /**
   * Removes all the items from the cache whose keys match the specified predicate.
   *
   * @param predicate returns true if an item with the given key should be removed
   * @return number of the items removed from the cache
   */
  fun removeAll(predicate: Predicate<K>): Int

  /**
   * Find if any of the items from the cache whose keys match the specified predicate.
   *
   * @param predicate returns true if an item with the given key matches
   * @return true if the predicate was found in the cache, false otherwise
   */
  operator fun contains(predicate: Predicate<K>): Boolean

  /**
   * Check if the cache contains an item for the given key.
   *
   * @param key
   * @return true if the key was found in the cache, false otherwise
   */
  operator fun contains(key: K): Boolean

  /** Gets the total number of all currently cached items. */
  val count: Int

  /** Gets the total size in bytes of all currently cached items. */
  val sizeInBytes: Int
}


/*
 * 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.cache

import java.util.concurrent.TimeUnit
import kotlin.jvm.JvmField

/** Configuration for a memory cache. */
class MemoryCacheParams
/**
 * Pass arguments to control the cache's behavior in the constructor.
 *
 * @param maxCacheSize The maximum size of the cache, in bytes.
 * @param maxCacheEntries The maximum number of items that can live in the cache.
 * @param maxEvictionQueueSize The eviction queue is an area of memory that stores items ready for
 *   eviction but have not yet been deleted. This is the maximum size of that queue in bytes.
 * @param maxEvictionQueueEntries The maximum number of entries in the eviction queue.
 * @param maxCacheEntrySize The maximum size of a single cache entry.
 */
@JvmOverloads
constructor(
    @JvmField val maxCacheSize: Int,
    @JvmField val maxCacheEntries: Int,
    @JvmField val maxEvictionQueueSize: Int,
    @JvmField val maxEvictionQueueEntries: Int,
    @JvmField val maxCacheEntrySize: Int,
    @JvmField val paramsCheckIntervalMs: Long = TimeUnit.MINUTES.toMillis(5)
) {
  /**
   * Pass arguments to control the cache's behavior in the constructor.
   *
   * @param maxCacheSize The maximum size of the cache, in bytes.
   * @param maxCacheEntries The maximum number of items that can live in the cache.
   * @param maxEvictionQueueSize The eviction queue is an area of memory that stores items ready for
   *   eviction but have not yet been deleted. This is the maximum size of that queue in bytes.
   * @param maxEvictionQueueEntries The maximum number of entries in the eviction queue.
   * @param maxCacheEntrySize The maximum size of a single cache entry.
   * @param paramsCheckIntervalMs Interval between checking parameters for updated values in ms.
   */
}


/*
 * 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.
 */

/** Memory cache implementations for image pipeline. */
package com.facebook.imagepipeline.cache


/*
 * 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.cache

/** Interface used to get the information about the values. */
fun interface ValueDescriptor<V> {
  /** Returns the size in bytes of the given value. */
  fun getSizeInBytes(value: V): Int
}


/*
 * 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.common

import com.facebook.common.internal.Preconditions
import java.util.Locale
import java.util.regex.Pattern
import javax.annotation.concurrent.Immutable

/**
 * A representation of the range of bytes requested or contained in a piece of content.
 *
 * This is based on the spec of the HTTP "Content-Range" header so includes methods to parse and
 * output appropriate strings for the header.
 *
 * The header spec is at https://tools.ietf.org/html/rfc2616#section-14.16
 *
 * As per that spec, the from and to values are inclusive. Requesting the first 100 bytes of an
 * image can be achieved by calling `BytesRange.toMax(99)`.
 *
 * This might be useful because you want to limit the maximum size of a specific download or it
 * might be that you already have part of the image data and only want the remainder.
 *
 * These objects are currently only respected in image requests and only taken from responses if
 * `ImagePipelineExperiments.isPartialImageCachingEnabled()` is true in the image pipeline config.
 * It is also dependent on a `NetworkFetcher` which writes and reads these headers.
 */
@Immutable
data class BytesRange(
    @JvmField
    /** The first byte of the range. Values begin at 0. */
    val from: Int,
    @JvmField
    /**
     * The final byte of the range inclusive, or [.TO_END_OF_CONTENT] if it reaches to the end.
     *
     * If not TO_END_OF_CONTENT, values are inclusive. e.g. for the first 100 bytes this is 99.
     */
    val to: Int
) {

  fun toHttpRangeHeaderValue(): String {
    return String.format(null as Locale?, "bytes=%s-%s", valueOrEmpty(from), valueOrEmpty(to))
  }

  /**
   * Checks whether a provided range is within this one.
   *
   * @return true if the provided range is within this one, false if given null
   */
  operator fun contains(compare: BytesRange?): Boolean {
    return if (compare == null) false else from <= compare.from && compare.to <= to
  }

  override fun toString(): String {
    return String.format(null as Locale?, "%s-%s", valueOrEmpty(from), valueOrEmpty(to))
  }

  companion object {
    const val TO_END_OF_CONTENT = Int.MAX_VALUE
    private val headerParsingRegEx: Pattern by lazy { Pattern.compile("[-/ ]") }

    private fun valueOrEmpty(n: Int): String {
      return if (n == TO_END_OF_CONTENT) "" else n.toString()
    }

    /**
     * Specifies a bytes range to request only the bytes from a specified index up to the end of the
     * data.
     *
     * @param from the first byte to request, must be positive or zero
     */
    @JvmStatic
    fun from(from: Int): BytesRange {
      Preconditions.checkArgument(from >= 0)
      return BytesRange(from, TO_END_OF_CONTENT)
    }

    /**
     * Specifies a bytes range to request no more than a specified number of bytes. If the encoded
     * image is less than the requested maximum you may still get the entire result.
     *
     * @param to the maximum byte to be requested, must be positive
     */
    @JvmStatic
    fun toMax(to: Int): BytesRange {
      Preconditions.checkArgument(to > 0)
      return BytesRange(0, to)
    }

    /**
     * Creates an instance of BytesRange by parsing the value of a returned HTTP "Content-Range"
     * header.
     *
     * If the range runs to the end of the available content, the end of the range will be set to
     * TO_END_OF_CONTENT.
     *
     * The header spec is at https://tools.ietf.org/html/rfc2616#section-14.16
     *
     * @param header
     * @return the parsed range
     * @throws IllegalArgumentException if the header is non-null but fails to match the format per
     *   the spec
     */
    @JvmStatic
    @Throws(IllegalArgumentException::class)
    fun fromContentRangeHeader(header: String?): BytesRange? {
      if (header == null) {
        return null
      }
      try {
        val headerParts = headerParsingRegEx.split(header)
        Preconditions.checkArgument(headerParts.size == 4)
        Preconditions.checkArgument(headerParts[0] == "bytes")
        val from = headerParts[1].toInt()
        val to = headerParts[2].toInt()
        val length = headerParts[3].toInt()
        Preconditions.checkArgument(to > from)
        Preconditions.checkArgument(length > to)
        if (to < length - 1) {
          return BytesRange(from, to)
        } else {
          return BytesRange(from, TO_END_OF_CONTENT)
        }
      } catch (x: IllegalArgumentException) {
        throw IllegalArgumentException(
            String.format(null as Locale?, "Invalid Content-Range header value: \"%s\"", header), x)
      }
    }
  }
}


/*
 * 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.
 */

/** Classes which are commonly used into the image pipeline. */
package com.facebook.imagepipeline.common


/*
 * 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.common

/** Priority levels recognized by the image pipeline. */
enum class Priority {

  /**
   * NOTE: DO NOT CHANGE ORDERING OF THOSE CONSTANTS UNDER ANY CIRCUMSTANCES. Doing so will make
   * ordering incorrect.
   */
  /** Lowest priority level. Used for prefetches of non-visible images. */
  LOW,

  /** Medium priority level. Used for warming of images that might soon get visible. */
  MEDIUM,

  /** Highest priority level. Used for images that are currently visible on screen. */
  HIGH;

  companion object {
    /**
     * Gets the higher priority among the two.
     *
     * @param priority1
     * @param priority2
     * @return higher priority
     */
    @JvmStatic
    fun getHigherPriority(priority1: Priority, priority2: Priority): Priority {
      return if (priority1.ordinal > priority2.ordinal) {
        priority1
      } else {
        priority2
      }
    }
  }
}


/*
 * 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.common

import com.facebook.common.util.HashCodeUtil
import com.facebook.imageutils.BitmapUtil
import java.util.Locale
import kotlin.jvm.JvmField

/**
 * Options for resizing.
 *
 * Describes the target bounds for the image (width, height) in pixels, as well as the downscaling
 * policy to employ.
 */
class ResizeOptions
@JvmOverloads
constructor(
    /* target width (in pixels) */
    @JvmField val width: Int,
    /* target height (in pixels) */
    @JvmField val height: Int,
    /* max supported bitmap size (in pixels), defaults to BitmapUtil.MAX_BITMAP_SIZE */
    @JvmField val maxBitmapSize: Float = BitmapUtil.MAX_BITMAP_SIZE,
    /* round-up fraction for resize process, defaults to DEFAULT_ROUNDUP_FRACTION */
    @JvmField val roundUpFraction: Float = DEFAULT_ROUNDUP_FRACTION
) {

  init {
    check(width > 0)
    check(height > 0)
  }

  override fun hashCode(): Int = HashCodeUtil.hashCode(width, height)

  override fun equals(other: Any?): Boolean {
    if (other === this) {
      return true
    }
    return other is ResizeOptions && width == other.width && height == other.height
  }

  override fun toString(): String = String.format(null as Locale?, "%dx%d", width, height)

  companion object {

    const val DEFAULT_ROUNDUP_FRACTION = 2.0f / 3

    /** @return new ResizeOptions, if the width and height values are valid, and null otherwise */
    @JvmStatic
    fun forDimensions(width: Int, height: Int): ResizeOptions? =
        if (width <= 0 || height <= 0) {
          null
        } else {
          ResizeOptions(width, height)
        }

    /** @return new ResizeOptions, if the width and height values are valid, and null otherwise */
    @JvmStatic
    fun forSquareSize(size: Int): ResizeOptions? =
        if (size <= 0) {
          null
        } else {
          ResizeOptions(size, 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.imagepipeline.common

import androidx.annotation.IntDef
import com.facebook.common.util.HashCodeUtil
import java.util.Locale

/**
 * Options for rotation.
 *
 * These options are applied to JPEG images only.
 *
 * Describes how the image should be rotated, whether following image meta-data or a specified
 * amount.
 *
 * These options are only relevant for JPEG images. Fresco doesn't support rotation of other image
 * formats.
 *
 * The options also include whether the rotation can be deferred until the bitmap is rendered. This
 * should be be false if a post-processor is used which needs to operate on the bitmap correctly
 * oriented but can otherwise generally be true, particularly if using drawee.
 */
class RotationOptions
private constructor(
    @field:Rotation @param:Rotation private val rotation: Int,
    private val deferUntilRendered: Boolean
) {

  @IntDef(
      flag = false,
      value =
          [
              NO_ROTATION,
              ROTATE_90,
              ROTATE_180,
              ROTATE_270,
              USE_EXIF_ROTATION_ANGLE,
              DISABLE_ROTATION])
  @Retention(AnnotationRetention.SOURCE)
  private annotation class Rotation

  @IntDef(flag = false, value = [NO_ROTATION, ROTATE_90, ROTATE_180, ROTATE_270])
  @Retention(AnnotationRetention.SOURCE)
  annotation class RotationAngle

  fun useImageMetadata(): Boolean = rotation == USE_EXIF_ROTATION_ANGLE

  fun rotationEnabled(): Boolean = rotation != DISABLE_ROTATION

  /**
   * Gets the explicit angle to rotate to, if one was set.
   *
   * @throws IllegalStateException if the instance was create using one of the `autoRotate()`
   *   constructors.
   */
  @get:RotationAngle
  val forcedAngle: Int
    get() {
      check(!useImageMetadata()) { "Rotation is set to use EXIF" }
      return rotation
    }

  fun canDeferUntilRendered(): Boolean = deferUntilRendered

  override fun hashCode(): Int = HashCodeUtil.hashCode(rotation, deferUntilRendered)

  override fun equals(other: Any?): Boolean {
    if (other === this) {
      return true
    }
    if (other !is RotationOptions) {
      return false
    }
    val that = other
    return rotation == that.rotation && deferUntilRendered == that.deferUntilRendered
  }

  override fun toString(): String =
      String.format(null as Locale?, "%d defer:%b", rotation, deferUntilRendered)

  companion object {
    const val NO_ROTATION = 0
    const val ROTATE_90 = 90
    const val ROTATE_180 = 180
    const val ROTATE_270 = 270
    private const val USE_EXIF_ROTATION_ANGLE = -1
    private const val DISABLE_ROTATION = -2
    private val ROTATION_OPTIONS_AUTO_ROTATE = RotationOptions(USE_EXIF_ROTATION_ANGLE, false)
    private val ROTATION_OPTIONS_DISABLE_ROTATION = RotationOptions(DISABLE_ROTATION, false)
    private val ROTATION_OPTIONS_ROTATE_AT_RENDER_TIME =
        RotationOptions(USE_EXIF_ROTATION_ANGLE, true)

    /**
     * Creates a new set of rotation options for JPEG images to use the rotation angle in the image
     * metadata.
     *
     * This is the default option for requests which don't specify rotation options.
     *
     * The rotation will not be deferred for defensiveness but that can improve performance. To
     * defer, use [autoRotateAtRenderTime()].
     */
    @JvmStatic fun autoRotate(): RotationOptions = ROTATION_OPTIONS_AUTO_ROTATE

    /** Creates a new set of rotation options for JPEG images to load image without any rotation. */
    @JvmStatic fun disableRotation(): RotationOptions = ROTATION_OPTIONS_DISABLE_ROTATION

    /**
     * Creates a new set of rotation options for JPEG images to use the rotation angle in the image
     * metadata.
     *
     * The rotation may be deferred until the image is rendered.
     */
    @JvmStatic
    fun autoRotateAtRenderTime(): RotationOptions = ROTATION_OPTIONS_ROTATE_AT_RENDER_TIME

    /**
     * Creates a new set of rotation options to use a specific rotation angle.
     *
     * The rotation will be carried out in the pipeline.
     *
     * @param angle the angle to rotate - valid values are 0, 90, 180 and 270
     */
    @JvmStatic
    fun forceRotation(@RotationAngle angle: Int): RotationOptions = RotationOptions(angle, 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.common

import androidx.annotation.IntDef

/**
 * This is the interface we use in order to define different types of Uri an ImageRequest can have.
 */
@Retention(AnnotationRetention.SOURCE)
@IntDef(
    SourceUriType.SOURCE_TYPE_UNKNOWN,
    SourceUriType.SOURCE_TYPE_NETWORK,
    SourceUriType.SOURCE_TYPE_LOCAL_FILE,
    SourceUriType.SOURCE_TYPE_LOCAL_VIDEO_FILE,
    SourceUriType.SOURCE_TYPE_LOCAL_IMAGE_FILE,
    SourceUriType.SOURCE_TYPE_LOCAL_CONTENT,
    SourceUriType.SOURCE_TYPE_LOCAL_ASSET,
    SourceUriType.SOURCE_TYPE_LOCAL_RESOURCE,
    SourceUriType.SOURCE_TYPE_DATA,
    SourceUriType.SOURCE_TYPE_QUALIFIED_RESOURCE)
annotation class SourceUriType {
  companion object {
    const val SOURCE_TYPE_UNKNOWN = -1
    const val SOURCE_TYPE_NETWORK = 0
    const val SOURCE_TYPE_LOCAL_FILE = 1
    const val SOURCE_TYPE_LOCAL_VIDEO_FILE = 2
    const val SOURCE_TYPE_LOCAL_IMAGE_FILE = 3
    const val SOURCE_TYPE_LOCAL_CONTENT = 4
    const val SOURCE_TYPE_LOCAL_ASSET = 5
    const val SOURCE_TYPE_LOCAL_RESOURCE = 6
    const val SOURCE_TYPE_DATA = 7
    const val SOURCE_TYPE_QUALIFIED_RESOURCE = 8
  }
}


/*
 * 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.common

import java.lang.RuntimeException

/** Thrown if a bitmap pool cap or other limit on the number of bitmaps is exceeded. */
class TooManyBitmapsException(detailMessage: String?) : RuntimeException(detailMessage)


/*
 * 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.core

import android.os.Process
import java.util.concurrent.Executor
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService

/**
 * Basic implementation of [ExecutorSupplier].
 *
 * Provides one thread pool for the CPU-bound operations and another thread pool for the IO-bound
 * operations.
 */
class DefaultExecutorSupplier(numCpuBoundThreads: Int) : ExecutorSupplier {

  private val ioBoundExecutor: Executor =
      Executors.newFixedThreadPool(
          NUM_IO_BOUND_THREADS,
          PriorityThreadFactory(Process.THREAD_PRIORITY_BACKGROUND, "FrescoIoBoundExecutor", true))
  private val decodeExecutor: Executor =
      Executors.newFixedThreadPool(
          numCpuBoundThreads,
          PriorityThreadFactory(Process.THREAD_PRIORITY_BACKGROUND, "FrescoDecodeExecutor", true))
  private val backgroundExecutor: Executor =
      Executors.newFixedThreadPool(
          numCpuBoundThreads,
          PriorityThreadFactory(
              Process.THREAD_PRIORITY_BACKGROUND, "FrescoBackgroundExecutor", true))
  private val lightWeightBackgroundExecutor: Executor =
      Executors.newFixedThreadPool(
          NUM_LIGHTWEIGHT_BACKGROUND_THREADS,
          PriorityThreadFactory(
              Process.THREAD_PRIORITY_BACKGROUND, "FrescoLightWeightBackgroundExecutor", true))
  private val backgroundScheduledExecutorService: ScheduledExecutorService =
      Executors.newScheduledThreadPool(
          numCpuBoundThreads,
          PriorityThreadFactory(
              Process.THREAD_PRIORITY_BACKGROUND, "FrescoBackgroundExecutor", true))

  override fun forLocalStorageRead(): Executor = ioBoundExecutor

  override fun forLocalStorageWrite(): Executor = ioBoundExecutor

  override fun forDecode(): Executor = decodeExecutor

  override fun forBackgroundTasks(): Executor = backgroundExecutor

  override fun scheduledExecutorServiceForBackgroundTasks(): ScheduledExecutorService? =
      backgroundScheduledExecutorService

  override fun forLightweightBackgroundTasks(): Executor = lightWeightBackgroundExecutor

  override fun forThumbnailProducer(): Executor = ioBoundExecutor

  companion object {
    // Allows for simultaneous reads and writes.
    private const val NUM_IO_BOUND_THREADS = 2
    private const val NUM_LIGHTWEIGHT_BACKGROUND_THREADS = 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.
 */

/** Supplier implementations for Executors and thread priorities */
package com.facebook.imagepipeline.core


/*
 * 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.core

import android.os.Process
import java.util.concurrent.ThreadFactory
import java.util.concurrent.atomic.AtomicInteger

/** ThreadFactory that applies a priority to the threads it creates. */
class PriorityThreadFactory
/**
 * Creates a new PriorityThreadFactory with a given priority.
 *
 * This value should be set to a value compatible with [android.os.Process#setThreadPriority], not
 * [Thread#setPriority].
 */
@JvmOverloads
constructor(
    private val threadPriority: Int,
    private val prefix: String = "PriorityThreadFactory",
    private val addThreadNumber: Boolean = true
) : ThreadFactory {
  private val threadNumber = AtomicInteger(1)

  override fun newThread(runnable: Runnable): Thread {
    val wrapperRunnable = Runnable {
      try {
        Process.setThreadPriority(threadPriority)
      } catch (t: Throwable) {
        // just to be safe
      }
      runnable.run()
    }
    val name: String =
        if (addThreadNumber) {
          "${prefix}-${threadNumber.getAndIncrement()}"
        } else {
          prefix
        }
    return Thread(wrapperRunnable, name)
  }
}


/*
 * 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.decoder

import com.facebook.imagepipeline.common.ImageDecodeOptions
import com.facebook.imagepipeline.image.CloseableImage
import com.facebook.imagepipeline.image.EncodedImage
import com.facebook.imagepipeline.image.QualityInfo

/** Image decoder interface. Takes an [EncodedImage] and creates a [CloseableImage]. */
fun interface ImageDecoder {
  fun decode(
      encodedImage: EncodedImage,
      length: Int,
      qualityInfo: QualityInfo,
      options: ImageDecodeOptions
  ): CloseableImage?
}


/*
 * 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.
 */

/** Common image decoder classes. */
package com.facebook.imagepipeline.decoder


/*
 * 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.drawable

import android.graphics.drawable.Drawable
import com.facebook.imagepipeline.image.CloseableImage

/** Drawable factory to create Drawables for given images. */
interface DrawableFactory {

  /**
   * Returns true if the factory can create a Drawable for the given image.
   *
   * @param image the image to check
   * @return true if a Drawable can be created
   */
  fun supportsImageType(image: CloseableImage): Boolean

  /**
   * Create a drawable for the given image. It is guaranteed that this method is only called if
   * [supportsImageType(CloseableImage)] returned true.
   *
   * @param image the image to create the drawable for
   * @return the Drawable for the image or null if an error occurred
   */
  fun createDrawable(image: CloseableImage): Drawable?
}


/*
 * 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.
 */

/** Common drawable classes. */
package com.facebook.imagepipeline.drawable


/*
 * 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.
 */

/** CloseableReference implementation for objects used into image pipeline. */
package com.facebook.imagepipeline.image


/*
 * 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.instrumentation

import com.facebook.imagepipeline.instrumentation.FrescoInstrumenter.Instrumenter
import com.facebook.infer.annotation.PropagatesNullable

/**
 * Utility class that provides hooks to capture execution of different units of work. Client code
 * can specify a custom [Instrumenter] that will receive ad-hoc updates when work that has to be
 * executed across threads gets moved around.
 */
object FrescoInstrumenter {

  @Volatile private var instance: Instrumenter? = null

  /**
   * Allows to provide a [Instrumenter] that will receive units of work updates.
   *
   * @param instrumenter to be notified or null to reset.
   */
  @JvmStatic
  fun provide(instrumenter: Instrumenter?) {
    instance = instrumenter
  }

  @get:JvmStatic
  val isTracing: Boolean
    get() {
      val instrumenter = instance ?: return false
      return instrumenter.isTracing
    }

  @JvmStatic
  fun onBeforeSubmitWork(tag: String?): Any? {
    val instrumenter = instance
    return if (instrumenter == null || tag == null) {
      null
    } else {
      instrumenter.onBeforeSubmitWork(tag)
    }
  }

  @JvmStatic
  fun onBeginWork(token: Any?, tag: String?): Any? {
    val instrumenter = instance
    return if (instrumenter == null || token == null) {
      null
    } else {
      instrumenter.onBeginWork(token, tag)
    }
  }

  @JvmStatic
  fun onEndWork(token: Any?) {
    val instrumenter = instance
    if (instrumenter == null || token == null) {
      return
    }
    instrumenter.onEndWork(token)
  }

  @JvmStatic
  fun markFailure(token: Any?, th: Throwable) {
    val instrumenter = instance
    if (instrumenter == null || token == null) {
      return
    }
    instrumenter.markFailure(token, th)
  }

  @JvmStatic
  fun decorateRunnable(@PropagatesNullable runnable: Runnable?, tag: String?): Runnable? {
    val instrumenter = instance
    if (instrumenter == null || runnable == null) {
      return runnable
    }
    val nonNullTag = tag ?: ""
    return instrumenter.decorateRunnable(runnable, nonNullTag)
  }

  /** Allows to capture unit of works across different threads. */
  interface Instrumenter {
    /**
     * Allows to know in advance if the custom instrumenter desires to receive continuation updates.
     * This can be used to avoid un-necessary work if the subscriber will not use the information
     * provided.
     *
     * @return true to specify interest in handling the updates, false otherwise.
     */
    val isTracing: Boolean

    /**
     * Called before scheduling a new unit work.
     *
     * @param tag name.
     * @return a token object that allows to track work units.
     */
    fun onBeforeSubmitWork(tag: String): Any?

    /**
     * Captures the beginning of the continuation for stolen work.
     *
     * @param token returned by [Instrumenter#onBeforeSubmitWork].
     * @param tag optional name.
     */
    fun onBeginWork(token: Any, tag: String?): Any?

    /**
     * Captures the end of the continuation for stolen work.
     *
     * @param token returned by [Instrumenter#onBeginWork].
     */
    fun onEndWork(token: Any)

    /**
     * Reports a failure while executing work.
     *
     * <note>[Instrumenter#onEndWork(Object)] still needs to be invoked.
     *
     * @param token returned by [Instrumenter#onBeginWork(Object, String)].
     * @param th containing the failure. </note>
     */
    fun markFailure(token: Any, th: Throwable)

    /**
     * Called when a unit of work is about to be scheduled.
     *
     * @param runnable that will be executed.
     * @param tag name.
     * @return the wrapped input runnable or just the input one.
     */
    fun decorateRunnable(runnable: Runnable, tag: String): Runnable?
  }
}


/*
 * 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.nativecode

import com.facebook.imagepipeline.transcoder.ImageTranscoderFactory
import java.lang.reflect.InvocationTargetException

/** Returns the native [ImageTranscoderFactory] if it is instantiable via reflection */
object NativeImageTranscoderFactory {
  @JvmStatic
  fun getNativeImageTranscoderFactory(
      maxBitmapSize: Int,
      useDownSamplingRatio: Boolean,
      ensureTranscoderLibraryLoaded: Boolean
  ): ImageTranscoderFactory =
      try {
        Class.forName("com.facebook.imagepipeline.nativecode.NativeJpegTranscoderFactory")
            .getConstructor(Integer.TYPE, java.lang.Boolean.TYPE, java.lang.Boolean.TYPE)
            .newInstance(maxBitmapSize, useDownSamplingRatio, ensureTranscoderLibraryLoaded)
            as ImageTranscoderFactory
      } catch (e: NoSuchMethodException) {
        throw RuntimeException(
            "Dependency ':native-imagetranscoder' is needed to use the default native image transcoder.",
            e)
      } catch (e: SecurityException) {
        throw RuntimeException(
            "Dependency ':native-imagetranscoder' is needed to use the default native image transcoder.",
            e)
      } catch (e: InstantiationException) {
        throw RuntimeException(
            "Dependency ':native-imagetranscoder' is needed to use the default native image transcoder.",
            e)
      } catch (e: InvocationTargetException) {
        throw RuntimeException(
            "Dependency ':native-imagetranscoder' is needed to use the default native image transcoder.",
            e)
      } catch (e: IllegalAccessException) {
        throw RuntimeException(
            "Dependency ':native-imagetranscoder' is needed to use the default native image transcoder.",
            e)
      } catch (e: IllegalArgumentException) {
        throw RuntimeException(
            "Dependency ':native-imagetranscoder' is needed to use the default native image transcoder.",
            e)
      } catch (e: ClassNotFoundException) {
        throw RuntimeException(
            "Dependency ':native-imagetranscoder' is needed to use the default native image transcoder.",
            e)
      }
}


/*
 * 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.nativecode

import com.facebook.imageformat.ImageFormat
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream

/** The abstraction for WebpTranscoder */
interface WebpTranscoder {

  /** @return true if given type of WebP is supported natively by the framework */
  fun isWebpNativelySupported(webpFormat: ImageFormat): Boolean

  /** Transcodes webp image given by input stream into jpeg. */
  @Throws(IOException::class)
  fun transcodeWebpToJpeg(inputStream: InputStream, outputStream: OutputStream, quality: Int)

  /** Transcodes Webp image given by input stream into png. */
  @Throws(IOException::class)
  fun transcodeWebpToPng(inputStream: InputStream, outputStream: OutputStream)
}


/*
 * 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.systrace

import android.os.Build
import android.os.Trace
import com.facebook.imagepipeline.systrace.FrescoSystrace.ArgsBuilder
import com.facebook.imagepipeline.systrace.FrescoSystrace.Systrace
import com.facebook.imagepipelinebase.BuildConfig

class DefaultFrescoSystrace : Systrace {

  override fun beginSection(name: String) {
    if (isTracing()) {
      Trace.beginSection(name)
    }
  }

  override fun beginSectionWithArgs(name: String): ArgsBuilder =
      if (isTracing()) {
        DefaultArgsBuilder(name)
      } else {
        FrescoSystrace.NO_OP_ARGS_BUILDER
      }

  override fun endSection() {
    if (isTracing()) {
      Trace.endSection()
    }
  }

  override fun isTracing(): Boolean =
      BuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2

  /**
   * Handles adding args to a systrace section by naively appending them to the section name. This
   * functionality has a more intelligent implementation when using a tracer that writes directly to
   * ftrace instead of using Android's Trace class.
   */
  private class DefaultArgsBuilder(name: String) : ArgsBuilder {

    private val stringBuilder: StringBuilder = StringBuilder(name)

    override fun flush() {
      // 127 is the max name length according to
      // https://developer.android.com/reference/android/os/Trace.html
      if (stringBuilder.length > 127) {
        stringBuilder.setLength(127)
      }

      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
        Trace.beginSection(stringBuilder.toString())
      }
    }

    override fun arg(key: String, value: Any) = apply { appendArgument(key, value) }

    override fun arg(key: String, value: Int) = apply { appendArgument(key, value) }

    override fun arg(key: String, value: Long) = apply { appendArgument(key, value) }

    override fun arg(key: String, value: Double) = apply { appendArgument(key, value) }

    private fun appendArgument(key: String, value: Any) =
        stringBuilder.append(';').append("$key=$value")
  }
}


/*
 * 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.systrace

/**
 * This is intended as a hook into `android.os.Trace`, but allows you to provide your own
 * functionality. Use it as
 *
 * `FrescoSystrace.beginSection("tag"); ... FrescoSystrace.endSection(); ` As a default, it simply
 * calls `android.os.Trace` (see [DefaultFrescoSystrace]). You may supply your own with
 * [FrescoSystrace.provide].
 */
object FrescoSystrace {

  @JvmField
  /** Convenience implementation of ArgsBuilder to use when we aren't tracing. */
  val NO_OP_ARGS_BUILDER: ArgsBuilder = NoOpArgsBuilder()

  @JvmStatic
  fun provide(instance: Systrace?) {
    _instance = instance
  }

  @JvmStatic fun beginSection(name: String) = instance.beginSection(name)

  @JvmStatic
  fun beginSectionWithArgs(name: String): ArgsBuilder = instance.beginSectionWithArgs(name)

  @JvmStatic
  fun endSection() {
    instance.endSection()
  }

  inline fun <T> traceSection(name: String, block: () -> T): T {
    if (!isTracing()) {
      return block()
    }

    beginSection(name)
    try {
      return block()
    } finally {
      endSection()
    }
  }

  @JvmStatic fun isTracing(): Boolean = instance.isTracing()

  private var _instance: Systrace? = null
  private val instance: Systrace
    get() {
      return _instance
          ?: synchronized(FrescoSystrace::class.java) {
            val systrace = DefaultFrescoSystrace()
            _instance = systrace
            systrace
          }
    }

  interface Systrace {
    fun beginSection(name: String)

    fun beginSectionWithArgs(name: String): ArgsBuilder

    fun endSection()

    fun isTracing(): Boolean
  }

  /** Object that accumulates arguments. */
  interface ArgsBuilder {
    /**
     * Write the full message to the Systrace buffer.
     *
     * You must call this to log the trace message.
     */
    fun flush()

    /**
     * Logs an argument whose value is any object. It will be stringified with [ ][String.valueOf].
     */
    fun arg(key: String, value: Any): ArgsBuilder

    /** Logs an argument whose value is an int. It will be stringified with [ ][String.valueOf]. */
    fun arg(key: String, value: Int): ArgsBuilder

    /** Logs an argument whose value is a long. It will be stringified with [ ][String.valueOf]. */
    fun arg(key: String, value: Long): ArgsBuilder

    /**
     * Logs an argument whose value is a double. It will be stringified with [ ][String.valueOf].
     */
    fun arg(key: String, value: Double): ArgsBuilder
  }

  private class NoOpArgsBuilder : ArgsBuilder {
    override fun flush() = Unit

    override fun arg(key: String, value: Any): ArgsBuilder = this

    override fun arg(key: String, value: Int): ArgsBuilder = this

    override fun arg(key: String, value: Long): ArgsBuilder = this

    override fun arg(key: String, value: Double): ArgsBuilder = this
  }
}


/*
 * 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 androidx.annotation.VisibleForTesting
import com.facebook.common.logging.FLog
import com.facebook.imageformat.DefaultImageFormats
import com.facebook.imagepipeline.common.ResizeOptions
import com.facebook.imagepipeline.common.RotationOptions
import com.facebook.imagepipeline.image.EncodedImage
import kotlin.math.pow

object DownsampleUtil {

  const val DEFAULT_SAMPLE_SIZE = 1
  private const val INTERVAL_ROUNDING = 1.0f / 3

  /**
   * Get the factor between the dimensions of the encodedImage (actual image) and the ones of the
   * imageRequest (requested size).
   *
   * @param rotationOptions the rotations options of the request
   * @param resizeOptions the resize options of the request
   * @param encodedImage the encoded image with the actual dimensions
   * @param maxBitmapSize the maximum supported bitmap size (in pixels) when not specified in the
   *   encoded image resizeOptions.
   * @return
   */
  @JvmStatic
  fun determineSampleSize(
      rotationOptions: RotationOptions,
      resizeOptions: ResizeOptions?,
      encodedImage: EncodedImage,
      maxBitmapSize: Int
  ): Int {
    if (!EncodedImage.isMetaDataAvailable(encodedImage)) {
      return DEFAULT_SAMPLE_SIZE
    }
    val ratio = determineDownsampleRatio(rotationOptions, resizeOptions, encodedImage)
    var sampleSize: Int =
        if (encodedImage.imageFormat === DefaultImageFormats.JPEG) {
          ratioToSampleSizeJPEG(ratio)
        } else {
          ratioToSampleSize(ratio)
        }

    // Check the case when the dimension of the downsampled image is still larger than the max
    // possible dimension for an image.
    val maxDimension = Math.max(encodedImage.height, encodedImage.width)
    val computedMaxBitmapSize = resizeOptions?.maxBitmapSize ?: maxBitmapSize.toFloat()
    while (maxDimension / sampleSize > computedMaxBitmapSize) {
      if (encodedImage.imageFormat === DefaultImageFormats.JPEG) {
        sampleSize *= 2
      } else {
        sampleSize++
      }
    }
    return sampleSize
  }

  @JvmStatic
  fun determineSampleSizeJPEG(
      encodedImage: EncodedImage,
      pixelSize: Int,
      maxBitmapSizeInBytes: Int
  ): Int {
    var sampleSize = encodedImage.sampleSize
    val base = encodedImage.width * encodedImage.height * pixelSize
    while (base / sampleSize / sampleSize > maxBitmapSizeInBytes) {
      sampleSize *= 2
    }
    return sampleSize
  }

  @JvmStatic
  @VisibleForTesting
  fun determineDownsampleRatio(
      rotationOptions: RotationOptions,
      resizeOptions: ResizeOptions?,
      encodedImage: EncodedImage
  ): Float {
    check(EncodedImage.isMetaDataAvailable(encodedImage))
    if (resizeOptions == null ||
        resizeOptions.height <= 0 ||
        resizeOptions.width <= 0 ||
        encodedImage.width == 0 ||
        encodedImage.height == 0) {
      return 1.0f
    }
    val rotationAngle = getRotationAngle(rotationOptions, encodedImage)
    val swapDimensions = rotationAngle == 90 || rotationAngle == 270
    val widthAfterRotation = if (swapDimensions) encodedImage.height else encodedImage.width
    val heightAfterRotation = if (swapDimensions) encodedImage.width else encodedImage.height
    val widthRatio = resizeOptions.width.toFloat() / widthAfterRotation
    val heightRatio = resizeOptions.height.toFloat() / heightAfterRotation
    val ratio = widthRatio.coerceAtLeast(heightRatio)
    FLog.v(
        "DownsampleUtil",
        "Downsample - Specified size: %dx%d, image size: %dx%d " +
            "ratio: %.1f x %.1f, ratio: %.3f",
        resizeOptions.width,
        resizeOptions.height,
        widthAfterRotation,
        heightAfterRotation,
        widthRatio,
        heightRatio,
        ratio)
    return ratio
  }

  @JvmStatic
  @VisibleForTesting
  fun ratioToSampleSize(ratio: Float): Int {
    if (ratio > 0.5f + 0.5f * INTERVAL_ROUNDING) {
      return 1 // should have resized
    }
    var sampleSize = 2
    while (true) {
      val intervalLength = 1.0 / (sampleSize.toDouble().pow(2.0) - sampleSize)
      val compare = 1.0 / sampleSize + intervalLength * INTERVAL_ROUNDING
      if (compare <= ratio) {
        return sampleSize - 1
      }
      sampleSize++
    }
  }

  @JvmStatic
  @VisibleForTesting
  fun ratioToSampleSizeJPEG(ratio: Float): Int {
    if (ratio > 0.5f + 0.5f * INTERVAL_ROUNDING) {
      return 1 // should have resized
    }
    var sampleSize = 2
    while (true) {
      val intervalLength = 1.0 / (2 * sampleSize)
      val compare = 1.0 / (2 * sampleSize) + intervalLength * INTERVAL_ROUNDING
      if (compare <= ratio) {
        return sampleSize
      }
      sampleSize *= 2
    }
  }

  private fun getRotationAngle(rotationOptions: RotationOptions, encodedImage: EncodedImage): Int {
    if (!rotationOptions.useImageMetadata()) {
      return 0
    }
    val rotationAngle = encodedImage.rotationAngle
    check(rotationAngle == 0 || rotationAngle == 90 || rotationAngle == 180 || rotationAngle == 270)
    return rotationAngle
  }

  @JvmStatic
  @VisibleForTesting
  fun roundToPowerOfTwo(sampleSize: Int): Int {
    var compare = 1
    while (true) {
      if (compare >= sampleSize) {
        return compare
      }
      compare *= 2
    }
  }
}


/*
 * 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.common.ResizeOptions
import com.facebook.imagepipeline.common.RotationOptions
import com.facebook.imagepipeline.image.EncodedImage
import java.io.IOException
import java.io.OutputStream

/** The abstraction for an image transcoder */
interface ImageTranscoder {

  /**
   * Transcodes an image to match the specified rotation and resize options. The most common-use
   * case is to create a resized version of an input image to make subsequent decodings faster.
   *
   * @param encodedImage The [EncodedImage] that will be transcoded.
   * @param outputStream The [OutputStream] where the newly created image is written to.
   * @param rotationOptions The [RotationOptions] used when transcoding the image.
   * @param resizeOptions The [ResizeOptions] used when transcoding the image.
   * @param outputFormat The desired [ImageFormat] of the newly created image. If this is null the
   *   same format as the input image will be used.
   * @param quality The desired quality of the newly created image. If this is null, the default
   *   quality of the transcoder will be applied.
   * @return The [ImageTranscodeResult] generated when encoding the image.
   * @throws IOException if I/O error happens when reading or writing the images.
   */
  @Throws(IOException::class)
  fun transcode(
      encodedImage: EncodedImage,
      outputStream: OutputStream,
      rotationOptions: RotationOptions?,
      resizeOptions: ResizeOptions?,
      outputFormat: ImageFormat?,
      quality: Int?
  ): ImageTranscodeResult

  /**
   * Whether the input image is resized to make subsequent decodings faster.
   *
   * @param encodedImage The [EncodedImage] that will be transcoded.
   * @param rotationOptions The [RotationOptions] used when transcoding the image.
   * @param resizeOptions The [ResizeOptions] used when transcoding the image.
   * @return true if the image is resized, else false.
   */
  fun canResize(
      encodedImage: EncodedImage,
      rotationOptions: RotationOptions?,
      resizeOptions: ResizeOptions?
  ): Boolean

  /**
   * Whether the input [ImageFormat] can be transcoded by the image transcoder.
   *
   * @param imageFormat The [ImageFormat] that will be transcoded.
   * @return true if this image format is handled by the image transcoder, else false.
   */
  fun canTranscode(imageFormat: ImageFormat): Boolean

  /**
   * Gets the identifier of the image transcoder. This is mostly used for logging purposes.
   *
   * @return the identifier of the image transcoder.
   */
  val identifier: 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.transcoder

import java.util.Locale

/** Result returned by an [ImageTranscoder] when transcoding an image. */
class ImageTranscodeResult(
    @field:TranscodeStatus @get:TranscodeStatus @param:TranscodeStatus val transcodeStatus: Int
) {
  override fun toString(): String = String.format(null as Locale?, "Status: %d", transcodeStatus)
}


/*
 * 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.Matrix
import android.media.ExifInterface
import androidx.annotation.VisibleForTesting
import com.facebook.common.internal.ImmutableList
import com.facebook.imagepipeline.common.ResizeOptions
import com.facebook.imagepipeline.common.RotationOptions
import com.facebook.imagepipeline.image.EncodedImage

object JpegTranscoderUtils {

  private const val FULL_ROUND = 360
  const val MIN_QUALITY = 0
  const val MAX_QUALITY = 100
  const val MIN_SCALE_NUMERATOR = 1
  const val MAX_SCALE_NUMERATOR = 16
  const val SCALE_DENOMINATOR = 8

  /**
   * Inverted EXIF orientations in clockwise rotation order. Rotating 90 degrees clockwise gets you
   * the next item in the list
   */
  @JvmField //
  val INVERTED_EXIF_ORIENTATIONS =
      ImmutableList.of(
          ExifInterface.ORIENTATION_FLIP_HORIZONTAL,
          ExifInterface.ORIENTATION_TRANSVERSE,
          ExifInterface.ORIENTATION_FLIP_VERTICAL,
          ExifInterface.ORIENTATION_TRANSPOSE)

  @VisibleForTesting const val DEFAULT_JPEG_QUALITY = 85

  /**
   * @return true if and only if given number of degrees is allowed rotation angle, that is it is
   *   equal to 0, 90, 180 or 270
   */
  @JvmStatic
  fun isRotationAngleAllowed(degrees: Int): Boolean =
      degrees >= 0 && degrees <= 270 && degrees % 90 == 0

  /** @return true if and only if given value is a valid EXIF orientation */
  @JvmStatic
  fun isExifOrientationAllowed(exifOrientation: Int): Boolean =
      when (exifOrientation) {
        ExifInterface.ORIENTATION_NORMAL,
        ExifInterface.ORIENTATION_ROTATE_90,
        ExifInterface.ORIENTATION_ROTATE_180,
        ExifInterface.ORIENTATION_ROTATE_270,
        ExifInterface.ORIENTATION_FLIP_HORIZONTAL,
        ExifInterface.ORIENTATION_FLIP_VERTICAL,
        ExifInterface.ORIENTATION_TRANSPOSE,
        ExifInterface.ORIENTATION_TRANSVERSE -> true
        else -> false
      }

  @JvmStatic
  fun getSoftwareNumerator(
      rotationOptions: RotationOptions,
      resizeOptions: ResizeOptions?,
      encodedImage: EncodedImage,
      resizingEnabled: Boolean
  ): Int {
    if (!resizingEnabled) {
      return SCALE_DENOMINATOR
    }
    if (resizeOptions == null) {
      return SCALE_DENOMINATOR
    }
    val rotationAngle = getRotationAngle(rotationOptions, encodedImage)
    var exifOrientation = ExifInterface.ORIENTATION_UNDEFINED
    if (INVERTED_EXIF_ORIENTATIONS.contains(encodedImage.exifOrientation)) {
      exifOrientation = getForceRotatedInvertedExifOrientation(rotationOptions, encodedImage)
    }
    val swapDimensions =
        rotationAngle == 90 ||
            rotationAngle == 270 ||
            exifOrientation == ExifInterface.ORIENTATION_TRANSPOSE ||
            exifOrientation == ExifInterface.ORIENTATION_TRANSVERSE
    val widthAfterRotation = if (swapDimensions) encodedImage.height else encodedImage.width
    val heightAfterRotation = if (swapDimensions) encodedImage.width else encodedImage.height
    val ratio = determineResizeRatio(resizeOptions, widthAfterRotation, heightAfterRotation)
    val numerator = roundNumerator(ratio, resizeOptions.roundUpFraction)
    if (numerator > SCALE_DENOMINATOR) {
      return SCALE_DENOMINATOR
    }
    return if (numerator < 1) 1 else numerator
  }

  @JvmStatic
  fun getRotationAngle(rotationOptions: RotationOptions, encodedImage: EncodedImage): Int {
    if (!rotationOptions.rotationEnabled()) {
      return RotationOptions.NO_ROTATION
    }
    val rotationFromMetadata = extractOrientationFromMetadata(encodedImage)
    return if (rotationOptions.useImageMetadata()) {
      rotationFromMetadata
    } else {
      (rotationFromMetadata + rotationOptions.forcedAngle) % FULL_ROUND
    }
  }

  /**
   * Get an inverted exif orientation (2, 4, 5, 7) but adjusted to take the force rotation angle
   * into consideration
   *
   * @throws IllegalArgumentException if encoded image passed doesn't have an inverted EXIF
   *   orientation
   */
  @JvmStatic
  fun getForceRotatedInvertedExifOrientation(
      rotationOptions: RotationOptions,
      encodedImage: EncodedImage
  ): Int {
    val exifOrientation = encodedImage.exifOrientation
    val index = INVERTED_EXIF_ORIENTATIONS.indexOf(exifOrientation)
    require(index >= 0) { "Only accepts inverted exif orientations" }
    var forcedAngle = RotationOptions.NO_ROTATION
    if (!rotationOptions.useImageMetadata()) {
      forcedAngle = rotationOptions.forcedAngle
    }
    val timesToRotate = forcedAngle / 90
    return INVERTED_EXIF_ORIENTATIONS[(index + timesToRotate) % INVERTED_EXIF_ORIENTATIONS.size]
  }

  @JvmStatic
  @VisibleForTesting
  fun determineResizeRatio(resizeOptions: ResizeOptions?, width: Int, height: Int): Float {
    if (resizeOptions == null) {
      return 1.0f
    }
    val widthRatio = resizeOptions.width.toFloat() / width
    val heightRatio = resizeOptions.height.toFloat() / height
    var ratio = Math.max(widthRatio, heightRatio)
    if (width * ratio > resizeOptions.maxBitmapSize) {
      ratio = resizeOptions.maxBitmapSize / width
    }
    if (height * ratio > resizeOptions.maxBitmapSize) {
      ratio = resizeOptions.maxBitmapSize / height
    }
    return ratio
  }

  @JvmStatic
  @VisibleForTesting
  fun roundNumerator(maxRatio: Float, roundUpFraction: Float): Int =
      (roundUpFraction + maxRatio * SCALE_DENOMINATOR).toInt()

  /**
   * This method calculate the ratio in case the downsampling was enabled
   *
   * @param downsampleRatio The ratio from downsampling
   * @return The ratio to use for software resize using the downsampling limitation
   */
  @JvmStatic
  @VisibleForTesting
  fun calculateDownsampleNumerator(downsampleRatio: Int): Int =
      Math.max(1, SCALE_DENOMINATOR / downsampleRatio)

  /**
   * Compute the transformation matrix needed to rotate the image. If no transformation is needed,
   * it returns null.
   *
   * @param encodedImage The [EncodedImage] used when computing the matrix.
   * @param rotationOptions The [RotationOptions] used when computing the matrix
   * @return The transformation matrix, or null if no transformation is required.
   */
  @JvmStatic
  fun getTransformationMatrix(
      encodedImage: EncodedImage,
      rotationOptions: RotationOptions
  ): Matrix? {
    var transformationMatrix: Matrix? = null
    if (INVERTED_EXIF_ORIENTATIONS.contains(encodedImage.exifOrientation)) {
      // Use exif orientation to rotate since we can't use the rotation angle for inverted exif
      // orientations
      val exifOrientation = getForceRotatedInvertedExifOrientation(rotationOptions, encodedImage)
      transformationMatrix = getTransformationMatrixFromInvertedExif(exifOrientation)
    } else {
      // Use actual rotation angle in degrees to rotate
      val rotationAngle = getRotationAngle(rotationOptions, encodedImage)
      if (rotationAngle != 0) {
        transformationMatrix = Matrix()
        transformationMatrix.setRotate(rotationAngle.toFloat())
      }
    }
    return transformationMatrix
  }

  /**
   * Returns the transformation matrix if the orientation corresponds to one present in
   * [ ][.INVERTED_EXIF_ORIENTATIONS], else null.
   *
   * @param orientation the exif orientation
   * @return the transformation matrix if inverted orientation, else null.
   */
  private fun getTransformationMatrixFromInvertedExif(orientation: Int): Matrix? {
    val matrix = Matrix()
    when (orientation) {
      ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.setScale(-1f, 1f)
      ExifInterface.ORIENTATION_TRANSVERSE -> {
        matrix.setRotate(-90f)
        matrix.postScale(-1f, 1f)
      }
      ExifInterface.ORIENTATION_FLIP_VERTICAL -> {
        matrix.setRotate(180f)
        matrix.postScale(-1f, 1f)
      }
      ExifInterface.ORIENTATION_TRANSPOSE -> {
        matrix.setRotate(90f)
        matrix.postScale(-1f, 1f)
      }
      else -> return null
    }
    return matrix
  }

  private fun extractOrientationFromMetadata(encodedImage: EncodedImage): Int =
      when (encodedImage.rotationAngle) {
        RotationOptions.ROTATE_90,
        RotationOptions.ROTATE_180,
        RotationOptions.ROTATE_270 -> encodedImage.rotationAngle
        else -> 0
      }
}


/*
 * 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.transformation

import android.graphics.Bitmap

/**
 * In-place bitmap transformation. This interface is similar to Postprocessors, however, it only
 * allows in-place bitmap transformations that are applied immediately after the bitmap has been
 * decoded.
 *
 * NOTE: The original bitmap will not be copied and only the transformed bitmap will be cached in
 * the bitmap memory cache. If the same image is requested without the transformation, it will be
 * decoded again.
 */
interface BitmapTransformation {

  /**
   * Perform an in-place bitmap transformation.
   *
   * @param bitmap the bitmap to transform
   */
  fun transform(bitmap: Bitmap)

  /**
   * Specify whether the transformation modifies alpha support (transparent images).
   *
   * @return true if the alpha channel is needed
   */
  fun modifiesTransparency(): 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.transformation

/** Indicates that the bitmap transformation performs rounding. */
interface CircularTransformation


/*
 * 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.transformation

import android.graphics.Bitmap
import com.facebook.common.references.CloseableReference

object TransformationUtils {
  @JvmStatic
  fun maybeApplyTransformation(
      transformation: BitmapTransformation?,
      bitmapReference: CloseableReference<Bitmap>?
  ): Boolean {

    if (transformation == null || bitmapReference == null) {
      return false
    }
    val bitmap = bitmapReference.get()
    if (transformation.modifiesTransparency()) {
      bitmap.setHasAlpha(true)
    }
    transformation.transform(bitmap)
    return true
  }
}


/*
 * 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.imageutils

import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.ColorSpace
import android.net.Uri
import android.os.Build
import android.util.Pair
import androidx.core.util.Pools
import com.facebook.common.memory.DecodeBufferHelper
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.lang.NullPointerException
import java.lang.UnsupportedOperationException
import java.nio.ByteBuffer

/** This class contains utility method for Bitmap */
object BitmapUtil {

  private const val POOL_SIZE = 12
  private val DECODE_BUFFERS: Pools.SynchronizedPool<ByteBuffer?> by lazy {
    Pools.SynchronizedPool(POOL_SIZE)
  }

  /** Bytes per pixel definitions */
  const val ALPHA_8_BYTES_PER_PIXEL = 1
  const val ARGB_4444_BYTES_PER_PIXEL = 2
  const val ARGB_8888_BYTES_PER_PIXEL = 4
  const val RGB_565_BYTES_PER_PIXEL = 2
  const val RGBA_F16_BYTES_PER_PIXEL = 8
  const val MAX_BITMAP_SIZE = 2_048f
  private var useDecodeBufferHelper = false

  /** @return size in bytes of the underlying bitmap */
  @JvmStatic
  @SuppressLint("NewApi")
  fun getSizeInBytes(bitmap: Bitmap?): Int {
    if (bitmap == null) {
      return 0
    }

    // There's a known issue in KitKat where getAllocationByteCount() can throw an NPE. This was
    // apparently fixed in MR1: http://bit.ly/1IvdRpd. So we do a version check here, and
    // catch any potential NPEs just to be safe.
    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
      try {
        return bitmap.allocationByteCount
      } catch (npe: NullPointerException) {
        // Swallow exception and try fallbacks.
      }
    }
    return bitmap.byteCount
  }

  /**
   * Decodes only the bounds of an image and returns its width and height or null if the size can't
   * be determined
   *
   * @param bytes the input byte array of the image
   * @return dimensions of the image
   */
  @JvmStatic
  fun decodeDimensions(bytes: ByteArray?): Pair<Int, Int>? =
      // wrapping with ByteArrayInputStream is cheap and we don't have duplicate implementation
      decodeDimensions(ByteArrayInputStream(bytes))

  /**
   * Decodes the bounds of an image from its Uri and returns a pair of the dimensions
   *
   * @param uri the Uri of the image
   * @return dimensions of the image
   */
  @JvmStatic
  fun decodeDimensions(uri: Uri): Pair<Int, Int>? {
    checkNotNull(uri)
    val options = BitmapFactory.Options()
    options.inJustDecodeBounds = true
    BitmapFactory.decodeFile(uri.path, options)
    return if (options.outWidth == -1 || options.outHeight == -1) null
    else Pair(options.outWidth, options.outHeight)
  }

  /**
   * Decodes the bounds of an image and returns its width and height or null if the size can't be
   * determined
   *
   * @param inputStream the InputStream containing the image data
   * @return dimensions of the image
   */
  @JvmStatic
  fun decodeDimensions(inputStream: InputStream?): Pair<Int, Int>? {
    checkNotNull(inputStream)
    val byteBuffer = obtainByteBuffer()
    val options = BitmapFactory.Options()
    options.inJustDecodeBounds = true
    return try {
      options.inTempStorage = byteBuffer.array()
      BitmapFactory.decodeStream(inputStream, null, options)
      if (options.outWidth == -1 || options.outHeight == -1) null
      else Pair(options.outWidth, options.outHeight)
    } finally {
      releaseByteBuffer(byteBuffer)
    }
  }

  /**
   * Decodes the bounds of an image and returns its width and height or null if the size can't be
   * determined. It also recovers the color space of the image, or null if it can't be determined.
   *
   * @param inputStream the InputStream containing the image data
   * @return the metadata of the image
   */
  @JvmStatic
  fun decodeDimensionsAndColorSpace(inputStream: InputStream?): ImageMetaData {
    checkNotNull(inputStream)
    val byteBuffer = obtainByteBuffer()
    val options = BitmapFactory.Options()
    options.inJustDecodeBounds = true
    return try {
      options.inTempStorage = byteBuffer.array()
      BitmapFactory.decodeStream(inputStream, null, options)
      val colorSpace: ColorSpace? =
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) options.outColorSpace else null

      ImageMetaData(options.outWidth, options.outHeight, colorSpace)
    } finally {
      releaseByteBuffer(byteBuffer)
    }
  }

  /**
   * Returns the amount of bytes used by a pixel in a specific [ ]
   *
   * @param bitmapConfig the [android.graphics.Bitmap.Config] for which the size in byte will be
   *   returned
   * @return
   */
  @SuppressLint("NewApi")
  @JvmStatic
  fun getPixelSizeForBitmapConfig(bitmapConfig: Bitmap.Config?): Int =
      when (bitmapConfig) {
        Bitmap.Config.ARGB_8888 -> ARGB_8888_BYTES_PER_PIXEL
        Bitmap.Config.ALPHA_8 -> ALPHA_8_BYTES_PER_PIXEL
        Bitmap.Config.ARGB_4444 -> ARGB_4444_BYTES_PER_PIXEL
        Bitmap.Config.RGB_565 -> RGB_565_BYTES_PER_PIXEL
        Bitmap.Config.RGBA_F16 -> RGBA_F16_BYTES_PER_PIXEL
        Bitmap.Config.HARDWARE ->
            ARGB_8888_BYTES_PER_PIXEL // We assume ARGB_8888 is used underneath
        else -> throw UnsupportedOperationException("The provided Bitmap.Config is not supported")
      }

  /**
   * Returns the size in byte of an image with specific size and [ ]
   *
   * @param width the width of the image
   * @param height the height of the image
   * @param bitmapConfig the [android.graphics.Bitmap.Config] for which the size in byte will be
   *   returned
   * @return calculated size in bytes
   * @throws IllegalArgumentException if width or height is less than or equal to zero
   * @throws IllegalStateException if calculated size overflows Integer datatype
   */
  @JvmStatic
  fun getSizeInByteForBitmap(width: Int, height: Int, bitmapConfig: Bitmap.Config?): Int {
    require(width > 0) { "width must be > 0, width is: $width" }
    require(height > 0) { "height must be > 0, height is: $height" }
    val pixelSize = getPixelSizeForBitmapConfig(bitmapConfig)
    val size = width * height * pixelSize
    check(size > 0) {
      "size must be > 0: size: $size, width: $width, height: $height, pixelSize: $pixelSize"
    }
    return size
  }

  private fun acquireByteBuffer(): ByteBuffer? =
      if (useDecodeBufferHelper) {
        DecodeBufferHelper.INSTANCE.acquire()
      } else {
        DECODE_BUFFERS.acquire()
      }

  private fun releaseByteBuffer(byteBuffer: ByteBuffer) {
    if (!useDecodeBufferHelper) {
      DECODE_BUFFERS.release(byteBuffer)
    }
  }

  @JvmStatic
  fun setUseDecodeBufferHelper(useDecodeBufferHelper: Boolean) {
    this.useDecodeBufferHelper = useDecodeBufferHelper
  }

  private fun obtainByteBuffer(): ByteBuffer {
    return acquireByteBuffer()
        ?: ByteBuffer.allocate(DecodeBufferHelper.getRecommendedDecodeBufferSize())
  }
}


/*
 * 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.imageutils

import android.media.ExifInterface
import android.os.Build
import androidx.annotation.RequiresApi
import com.facebook.common.logging.FLog
import com.facebook.imageutils.HeifExifUtil.HeifExifUtilAndroidN
import com.facebook.soloader.DoNotOptimize
import java.io.IOException
import java.io.InputStream

/**
 * Specialization of EXIF utilities for HEIF files. The specialization allows us to restrict
 * ourselves to Android N where [ExifInterface] can wrap around an input stream. Actual support for
 * HEIF was only added in Android P.
 */
object HeifExifUtil {

  const val TAG = "HeifExifUtil"

  @JvmStatic
  fun getOrientation(inputStream: InputStream?): Int {
    return if (inputStream == null) {
      FLog.d(TAG, "Trying to read Heif Exif from null inputStream -> ignoring")
      ExifInterface.ORIENTATION_UNDEFINED
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
      HeifExifUtilAndroidN.getOrientation(inputStream)
    } else {
      FLog.d(TAG, "Trying to read Heif Exif information before Android N -> ignoring")
      ExifInterface.ORIENTATION_UNDEFINED
    }
  }

  @DoNotOptimize
  private object HeifExifUtilAndroidN {
    @JvmStatic
    @RequiresApi(api = Build.VERSION_CODES.N)
    fun getOrientation(inputStream: InputStream): Int =
        try {
          val exifInterface = ExifInterface(inputStream)
          exifInterface.getAttributeInt(
              ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
        } catch (e: IOException) {
          FLog.d(TAG, "Failed reading Heif Exif orientation -> ignoring", e)
          ExifInterface.ORIENTATION_UNDEFINED
        }
  }
}


/*
 * 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.imageutils

import android.graphics.ColorSpace
import android.util.Pair

/** Wrapper class representing the recovered meta data of an image when decoding. */
class ImageMetaData(width: Int, height: Int, val colorSpace: ColorSpace?) {

  val dimensions: Pair<Int, Int>? = if (width == -1 || height == -1) null else Pair(width, height)
}


/*
 * 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.imageutils

import android.media.ExifInterface
import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.InputStream

/** Util for obtaining information from JPEG file. */
object JfifUtil {

  /**
   * Definitions of jpeg markers as well as overall description of jpeg file format can be found
   * here: [Recommendation T.81](http://www.w3.org/Graphics/JPEG/itu-t81.pdf)
   */
  const val MARKER_FIRST_BYTE = 0xFF
  const val MARKER_ESCAPE_BYTE = 0x00
  const val MARKER_SOI = 0xD8
  const val MARKER_TEM = 0x01
  const val MARKER_EOI = 0xD9
  const val MARKER_SOS = 0xDA
  const val MARKER_APP1 = 0xE1
  const val MARKER_SOFn = 0xC0
  const val MARKER_RST0 = 0xD0
  const val MARKER_RST7 = 0xD7
  const val APP1_EXIF_MAGIC = 0x45786966

  /**
   * Determines auto-rotate angle based on orientation information.
   *
   * @param orientation orientation information, one of {1, 3, 6, 8}.
   * @return orientation: 1/3/6/8 -> 0/180/90/270.
   */
  @JvmStatic
  fun getAutoRotateAngleFromOrientation(orientation: Int): Int =
      TiffUtil.getAutoRotateAngleFromOrientation(orientation)

  /**
   * Gets orientation information from jpeg byte array.
   *
   * @param jpeg the input byte array of jpeg image
   * @return orientation: 1/8/3/6. Returns 0 if there is no valid orientation information.
   */
  @JvmStatic
  fun getOrientation(jpeg: ByteArray?): Int =
      // wrapping with ByteArrayInputStream is cheap and we don't have duplicate implementation
      getOrientation(ByteArrayInputStream(jpeg))

  /**
   * Get orientation information from jpeg input stream.
   *
   * @param inputStream the input stream of jpeg image
   * @return orientation: 1/8/3/6. Returns {@value
   * * android.media.ExifInterface#ORIENTATION_UNDEFINED} if there is no valid orientation
   *   information.
   */
  @JvmStatic
  fun getOrientation(inputStream: InputStream): Int =
      try {
        val length = moveToAPP1EXIF(inputStream)
        if (length == 0) {
          ExifInterface.ORIENTATION_UNDEFINED
        } else {
          TiffUtil.readOrientationFromTIFF(inputStream, length)
        }
      } catch (ioe: IOException) {
        ExifInterface.ORIENTATION_UNDEFINED
      }

  /**
   * Reads the content of the input stream until specified marker is found. Marker will be consumed
   * and the input stream will be positioned after the specified marker.
   *
   * @param inputStream the input stream to read bytes from
   * @param markerToFind the marker we are looking for
   * @return boolean: whether or not we found the expected marker from input stream.
   */
  @JvmStatic
  @Throws(IOException::class)
  fun moveToMarker(inputStream: InputStream, markerToFind: Int): Boolean {
    checkNotNull(inputStream)
    // ISO/IEC 10918-1:1993(E)
    while (StreamProcessor.readPackedInt(inputStream, 1, false) == MARKER_FIRST_BYTE) {
      var marker = MARKER_FIRST_BYTE
      while (marker == MARKER_FIRST_BYTE) {
        marker = StreamProcessor.readPackedInt(inputStream, 1, false)
      }
      if (markerToFind == MARKER_SOFn && isSOFn(marker)) {
        return true
      }
      if (marker == markerToFind) {
        return true
      }

      // Check if the marker is SOI or TEM. These two don't have length field, so we skip it.
      if (marker == MARKER_SOI || marker == MARKER_TEM) {
        continue
      }

      // Check if the marker is EOI or SOS. We will stop reading since metadata markers don't
      // come after these two markers.
      if (marker == MARKER_EOI || marker == MARKER_SOS) {
        return false
      }

      // read block length
      // subtract 2 as length contain SIZE field we just read
      val length = StreamProcessor.readPackedInt(inputStream, 2, false) - 2
      // Skip other markers.
      inputStream.skip(length.toLong())
    }
    return false
  }

  private fun isSOFn(marker: Int): Boolean =
      // There are no SOF4, SOF8, SOF12
      when (marker) {
        0xC0,
        0xC1,
        0xC2,
        0xC3,
        0xC5,
        0xC6,
        0xC7,
        0xC9,
        0xCA,
        0xCB,
        0xCD,
        0xCE,
        0xCF -> true
        else -> false
      }

  /**
   * Positions the given input stream to the beginning of the EXIF data in the JPEG APP1 block.
   *
   * @param inputStream the input stream of jpeg image
   * @return length of EXIF data
   */
  @Throws(IOException::class)
  private fun moveToAPP1EXIF(inputStream: InputStream): Int {
    if (moveToMarker(inputStream, MARKER_APP1)) {
      // read block length
      // subtract 2 as length contain SIZE field we just read
      var length = StreamProcessor.readPackedInt(inputStream, 2, false) - 2
      if (length > 6) {
        val magic = StreamProcessor.readPackedInt(inputStream, 4, false)
        length -= 4
        val zero = StreamProcessor.readPackedInt(inputStream, 2, false)
        length -= 2
        if (magic == APP1_EXIF_MAGIC && zero == 0) {
          // JEITA CP-3451 Exif Version 2.2
          return length
        }
      }
    }
    return 0
  }
}


/*
 * 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.fresco.middleware

typealias Extras = Map<String, Any?>

interface HasExtraData {

  fun <E> putExtra(key: String, value: E?)

  fun <E> getExtra(key: String): E?

  fun <E> getExtra(key: String, valueIfNotFound: E? = null): E?

  fun getExtras(): Extras

  fun putExtras(extras: Extras)

  companion object {
    const val KEY_ID = "id"
    const val KEY_ENCODED_SIZE = "encoded_size"
    const val KEY_ENCODED_WIDTH = "encoded_width"
    const val KEY_ENCODED_HEIGHT = "encoded_height"
    const val KEY_URI_SOURCE = "uri_source"
    const val KEY_IMAGE_FORMAT = "image_format"
    const val KEY_BITMAP_CONFIG = "bitmap_config"
    const val KEY_IS_ROUNDED = "is_rounded"
    const val KEY_NON_FATAL_DECODE_ERROR = "non_fatal_decode_error"
    const val KEY_MODIFIED_URL = "modified_url"

    const val KEY_ORIGIN = "origin"
    const val KEY_ORIGIN_SUBCATEGORY = "origin_sub"

    /* number of deduped request in BitmapMemoryCacheKeyMultiplexProducer */
    const val KEY_MULTIPLEX_BITMAP_COUNT = "multiplex_bmp_cnt"
    /* number of deduped request in EncodedCacheKeyMultiplexProducer */
    const val KEY_MULTIPLEX_ENCODED_COUNT = "multiplex_enc_cnt"
    const val KEY_LAST_SCAN_NUMBER = "last_scan_num"

    const val KEY_IMAGE_SOURCE_EXTRAS = "image_source_extras"
    const val KEY_COLOR_SPACE = "image_color_space"
  }
}


/*
 * 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.fresco.middleware

import android.graphics.PointF
import android.graphics.Rect
import android.net.Uri
import com.facebook.fresco.ui.common.ControllerListener2.Extras

object MiddlewareUtils {
  @JvmStatic
  fun obtainExtras(
      componentAttribution: Map<String, Any>,
      shortcutAttribution: Map<String, Any>,
      dataSourceExtras: Map<String, Any>?,
      imageSourceExtras: Map<String, Any>?,
      viewportDimensions: Rect?,
      scaleType: String?,
      focusPoint: PointF?,
      imageExtras: Map<String, Any?>?,
      callerContext: Any?,
      mainUri: Uri?
  ): Extras {
    val extras = Extras()
    if (viewportDimensions != null) {
      extras.viewportWidth = viewportDimensions.width()
      extras.viewportHeight = viewportDimensions.height()
    }
    extras.scaleType = scaleType
    if (focusPoint != null) {
      extras.focusX = focusPoint.x
      extras.focusY = focusPoint.y
    }
    extras.callerContext = callerContext
    extras.mainUri = mainUri
    extras.datasourceExtras = dataSourceExtras
    extras.imageExtras = imageExtras
    extras.shortcutExtras = shortcutAttribution
    extras.componentExtras = componentAttribution
    extras.imageSourceExtras = imageSourceExtras
    return extras
  }
}


/*
 * 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.fresco.samples.showcase

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import com.facebook.fresco.samples.showcase.common.dpToPx

/** A base Kotlin class for ShowcaseFragment */
abstract class BaseShowcaseKotlinFragment(
    @LayoutRes private val layoutResId: Int = R.layout.fragment_scrolling_linear_layout
) : BaseShowcaseFragment() {

  override fun onCreateView(
      inflater: LayoutInflater,
      container: ViewGroup?,
      savedInstanceState: Bundle?
  ): View? = inflater.inflate(layoutResId, container, false)

  fun Int.dpToPx(): Int = this.dpToPx(requireContext())

  fun Float.dpToPx(): Float = this.dpToPx(requireContext())
}


/*
 * 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.fresco.samples.showcase

data class ExampleCategory(val name: String, val examples: List<ExampleItem>)


/*
 * 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.fresco.samples.showcase

import com.facebook.fresco.samples.showcase.drawee.*
import com.facebook.fresco.samples.showcase.drawee.transition.DraweeTransitionFragment
import com.facebook.fresco.samples.showcase.imageformat.color.ImageFormatColorFragment
import com.facebook.fresco.samples.showcase.imageformat.datauri.ImageFormatDataUriFragment
import com.facebook.fresco.samples.showcase.imageformat.gif.ImageFormatGifFragment
import com.facebook.fresco.samples.showcase.imageformat.keyframes.ImageFormatKeyframesFragment
import com.facebook.fresco.samples.showcase.imageformat.override.ImageFormatOverrideExample
import com.facebook.fresco.samples.showcase.imageformat.pjpeg.ImageFormatProgressiveJpegFragment
import com.facebook.fresco.samples.showcase.imageformat.svg.ImageFormatSvgFragment
import com.facebook.fresco.samples.showcase.imageformat.webp.ImageFormatWebpFragment
import com.facebook.fresco.samples.showcase.imagepipeline.*
import com.facebook.fresco.samples.showcase.misc.WelcomeFragment
import com.facebook.fresco.samples.showcase.settings.SettingsFragment
import com.facebook.fresco.samples.showcase.vito.*
import com.facebook.fresco.samples.showcase.vito.ninepatch.LithoNinePatchSample
import com.facebook.fresco.samples.showcase.vito.renderer.RendererColorFilterExampleFragment
import com.facebook.fresco.samples.showcase.vito.renderer.RendererFadeExampleFragment
import com.facebook.fresco.samples.showcase.vito.renderer.RendererShapeExampleFragment
import com.facebook.fresco.samples.showcase.vito.renderer.VitoLayerExample

object ExampleDatabase {

  val welcome = ExampleItem("Welcome") { WelcomeFragment() }
  val settings = ExampleItem("Settings", "Settings") { SettingsFragment() }

  val examples =
      listOf(
          ExampleCategory(
              "Drawee",
              listOf(
                  ExampleItem("Simple Drawee") { DraweeSimpleFragment() },
                  ExampleItem("Media Provider") { DraweeMediaPickerFragment() },
                  ExampleItem("Scale Type") { DraweeScaleTypeFragment() },
                  ExampleItem("Simple DraweeSpan") { DraweeSpanSimpleTextFragment() },
                  ExampleItem("Rounded Corners") { DraweeRoundedCornersFragment() },
                  ExampleItem("Placeholder, Progress, Failure") { DraweeHierarchyFragment() },
                  ExampleItem("Rotation") { DraweeRotationFragment() },
                  ExampleItem("Recycler View") { DraweeRecyclerViewFragment() },
                  ExampleItem("Drawee Transition") { DraweeTransitionFragment() },
                  ExampleItem("Retaining Data Source Supplier") {
                    RetainingDataSourceSupplierFragment()
                  },
                  ExampleItem("Multi URI") { MultiUriFragment() })),
          ExampleCategory(
              "Image Pipeline",
              listOf(
                  ExampleItem("Notifications") { ImagePipelineNotificationFragment() },
                  ExampleItem("Post-Processor") { ImagePipelinePostProcessorFragment() },
                  ExampleItem("Prefetch") { ImagePipelinePrefetchFragment() },
                  ExampleItem("Resizing") { ImagePipelineResizingFragment() },
                  ExampleItem("Qualified Resource URI") {
                    ImagePipelineQualifiedResourceFragment()
                  },
                  ExampleItem("Partial Image Loading") { PartialRequestFragment() },
                  ExampleItem("Platform Bitmap Factory") { ImagePipelineBitmapFactoryFragment() },
                  ExampleItem("Region Decoding") { ImagePipelineRegionDecodingFragment() })),
          ExampleCategory(
              "Image Formats",
              listOf(
                  ExampleItem("Progressive JPEG") { ImageFormatProgressiveJpegFragment() },
                  ExampleItem("Color") { ImageFormatColorFragment() },
                  ExampleItem("GIF") { ImageFormatGifFragment() },
                  ExampleItem("WebP") { ImageFormatWebpFragment() },
                  ExampleItem("SVG") { ImageFormatSvgFragment() },
                  ExampleItem("Keyframes") { ImageFormatKeyframesFragment() },
                  ExampleItem("Decoder Override") { ImageFormatOverrideExample() },
                  ExampleItem("Data URI") { ImageFormatDataUriFragment() })),
          ExampleCategory(
              "Fresco Vito",
              listOf(
                  ExampleItem(
                      "Vito Litho: Simple",
                      FrescoVitoLithoSimpleExample,
                      "Simple Fresco Vito Litho component."),
                  ExampleItem("Vito Litho: Region decoding") {
                    FrescoVitoLithoRegionDecodeFragment()
                  },
                  ExampleItem("Vito Litho: Image Options configurator") {
                    FrescoVitoLithoImageOptionsConfigFragment()
                  },
                  ExampleItem("Vito Litho: Sections (RecyclerView)") {
                    FrescoVitoLithoSectionsFragment()
                  },
                  ExampleItem(
                      "Vito Litho: Listener",
                      FrescoVitoLithoListenerExample,
                      "Vito Litho component with listener."),
                  ExampleItem("Vito Litho: Gallery") { FrescoVitoLithoGalleryFragment() },
                  ExampleItem("Vito View: Simple") { VitoViewSimpleFragment() },
                  ExampleItem("Vito View: Recycler") { VitoViewRecyclerFragment() },
                  ExampleItem("Vito View: Prefetch") { VitoViewPrefetchFragment() },
                  ExampleItem("Vito View: Kotlin Extensions") { VitoViewKtxFragment() },
                  ExampleItem(
                      "Vito Litho: Slideshow",
                      LithoSlideshowSample(),
                      "Litho slideshow that fades between images"),
                  ExampleItem(
                      "Vito Litho: NinePatch", LithoNinePatchSample(), "Litho nine patch example"),
                  ExampleItem("Vito Text Span") { VitoSpanFragment() },
              )),
          ExampleCategory(
              "Vito Renderer",
              listOf(
                  ExampleItem("Renderer: Shapes") { RendererShapeExampleFragment() },
                  ExampleItem("Renderer: Color Filters") { RendererColorFilterExampleFragment() },
                  ExampleItem("Renderer: Fading") { RendererFadeExampleFragment() },
                  ExampleItem("Layers: Scaling") { VitoLayerExample() })))
}


/*
 * 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.fresco.samples.showcase

import androidx.fragment.app.Fragment

data class ExampleItem(
    val title: String,
    val helpText: String? = null,
    val backstackTag: String? = null,
    val createFragment: () -> Fragment
) {
  constructor(
      title: String,
      lithoSample: LithoSample,
      helpText: String? = null,
      backstackTag: String? = null
  ) : this(title, helpText, backstackTag, { LithoSampleHostFragment(lithoSample, helpText) })

  val itemId = title.hashCode()
}


/*
 * 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.fresco.samples.showcase

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.facebook.fresco.vito.tools.liveeditor.ImageSelector
import com.facebook.fresco.vito.tools.liveeditor.LiveEditorUiUtils
import com.google.android.material.bottomsheet.BottomSheetDialogFragment

class ImageOptionsBottomSheet(val imageSelector: ImageSelector) : BottomSheetDialogFragment() {

  private val uiUtils = LiveEditorUiUtils(imageSelector.currentEditor)

  override fun onCreateView(
      inflater: LayoutInflater,
      container: ViewGroup?,
      savedInstanceState: Bundle?
  ): View {
    imageSelector.removeHighlight(requireContext())
    return uiUtils.createView(requireContext())
  }

  companion object {
    @JvmStatic
    fun newInstance(imageSelector: ImageSelector, bundle: Bundle): ImageOptionsBottomSheet {
      val fragment = ImageOptionsBottomSheet(imageSelector)
      fragment.arguments = bundle
      return fragment
    }
  }
}


/*
 * 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.fresco.samples.showcase

import com.facebook.fresco.samples.showcase.misc.ImageUriProvider
import com.facebook.litho.Component
import com.facebook.litho.ComponentContext

interface LithoSample {
  fun createLithoComponent(
      c: ComponentContext,
      uris: ImageUriProvider,
      callerContext: Any
  ): Component
}


/*
 * 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.fresco.samples.showcase

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.facebook.fresco.samples.showcase.databinding.FragmentLithoHostBinding
import com.facebook.litho.ComponentContext
import com.facebook.litho.LithoView

class LithoSampleHostFragment(
    private val lithoSample: LithoSample,
    private val helpText: String? = null
) : BaseShowcaseFragment() {

  private var _binding: FragmentLithoHostBinding? = null
  private val binding
    get() = _binding!!

  override fun onCreateView(
      inflater: LayoutInflater,
      container: ViewGroup?,
      savedInstanceState: Bundle?
  ): View {
    _binding = FragmentLithoHostBinding.inflate(inflater, container, false)
    return binding.root
  }

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    binding.textHelp.text = helpText
    val c = ComponentContext(context)
    val lithoView =
        LithoView.create(c, lithoSample.createLithoComponent(c, sampleUris(), "Example"))
    binding.container.addView(lithoView)
  }

  override fun onDestroyView() {
    super.onDestroyView()
    _binding = null
  }
}