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

package com.facebook

import android.util.Log
import com.facebook.internal.FacebookRequestErrorClassification
import com.facebook.internal.Logger
import com.facebook.internal.Utility
import java.io.IOException
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.MalformedURLException
import java.net.URL
import java.util.Locale
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import org.json.JSONTokener

/**
 * Encapsulates the response, successful or otherwise, of a call to the Facebook platform.
 *
 * @param graphRequest The request that this response is for.
 * @param connection The HttpURLConnection that this response was generated from. If the response
 * was retrieved from the cache, this will be null.
 * @param rawResponse The server response as a String that this response is for.
 * @param graphObject The response returned for this request, if it's in object form.
 * @param graphObjectArray:The response returned for this request, if it's in array form.
 * @param error Information about any errors that may have occurred during the request.
 */
class GraphResponse
internal constructor(
    val request: GraphRequest,
    val connection: HttpURLConnection?,
    val rawResponse: String?,
    private val graphObject: JSONObject?,
    private val graphObjectArray: JSONArray?,
    val error: FacebookRequestError?
) {

  constructor(
      request: GraphRequest,
      connection: HttpURLConnection?,
      rawResponse: String,
      graphObject: JSONObject?
  ) : this(request, connection, rawResponse, graphObject, null, null)

  constructor(
      request: GraphRequest,
      connection: HttpURLConnection?,
      rawResponse: String,
      graphObjects: JSONArray
  ) : this(request, connection, rawResponse, null, graphObjects, null)

  constructor(
      request: GraphRequest,
      connection: HttpURLConnection?,
      error: FacebookRequestError
  ) : this(request, connection, null, null, null, error)

  /** Indicates whether paging is being done forward or backward. */
  enum class PagingDirection {
    /** Indicates that paging is being performed in the forward direction. */
    NEXT,

    /** Indicates that paging is being performed in the backward direction. */
    PREVIOUS
  }

  /**
   * The response returned for this request, if it's in object form.
   *
   * @return the returned JSON object, or null if none was returned (or if the result was a JSON
   * array)
   */
  fun getJSONObject(): JSONObject? = graphObject

  /** The response returned for this request, if it's in object form. Otherwise it's null */
  val jsonObject: JSONObject? = graphObject

  /**
   * The response returned for this request, if it's in array form.
   *
   * @return the returned JSON array, or null if none was returned (or if the result was a JSON
   * object)
   */
  fun getJSONArray(): JSONArray? = graphObjectArray

  /** The response returned for this request, if it's in array form. Otherwise it's null */
  val jsonArray: JSONArray? = graphObjectArray

  /**
   * If a Response contains results that contain paging information, returns a new Request that will
   * retrieve the next page of results, in whichever direction is desired. If no paging information
   * is available, returns null.
   *
   * @param direction enum indicating whether to page forward or backward
   * @return a Request that will retrieve the next page of results in the desired direction, or null
   * if no paging information is available
   */
  fun getRequestForPagedResults(direction: PagingDirection): GraphRequest? {
    var link: String? = null
    if (graphObject != null) {
      val pagingInfo = graphObject.optJSONObject("paging")
      if (pagingInfo != null) {
        link =
            if (direction == PagingDirection.NEXT) {
              pagingInfo.optString("next")
            } else {
              pagingInfo.optString("previous")
            }
      }
    }
    if (Utility.isNullOrEmpty(link)) {
      return null
    }
    if (link != null && link == request.urlForSingleRequest) {
      // We got the same "next" link as we just tried to retrieve. This could happen if cached
      // data is invalid. All we can do in this case is pretend we have finished.
      return null
    }
    val pagingRequest: GraphRequest
    pagingRequest =
        try {
          GraphRequest(request.accessToken, URL(link))
        } catch (e: MalformedURLException) {
          return null
        }
    return pagingRequest
  }

  /** Provides a debugging string for this response. */
  override fun toString(): String {
    val responseCode: String =
        try {
          String.format(Locale.US, "%d", connection?.responseCode ?: 200)
        } catch (e: IOException) {
          "unknown"
        }
    return StringBuilder()
        .append("{Response: ")
        .append(" responseCode: ")
        .append(responseCode)
        .append(", graphObject: ")
        .append(graphObject)
        .append(", error: ")
        .append(error)
        .append("}")
        .toString()
  }

  companion object {
    private val TAG: String? = GraphResponse::class.java.canonicalName
    /**
     * Property name of non-JSON results in the GraphObject. Certain calls to Facebook result in a
     * non-JSON response (e.g., the string literal "true" or "false"). To present a consistent way
     * of accessing results, these are represented as a GraphObject with a single string property
     * with this name.
     */
    const val NON_JSON_RESPONSE_PROPERTY = "FACEBOOK_NON_JSON_RESULT"

    // From v2.1 of the Graph API, write endpoints will now return valid JSON with the result as the
    // value for the "success" key
    const val SUCCESS_KEY = "success"

    private const val CODE_KEY = "code"
    private const val BODY_KEY = "body"

    private const val RESPONSE_LOG_TAG = "Response"

    @SuppressWarnings("resource")
    @JvmStatic
    internal fun fromHttpConnection(
        connection: HttpURLConnection,
        requests: GraphRequestBatch
    ): List<GraphResponse> {
      var stream: InputStream? = null
      return try {
        if (!FacebookSdk.isFullyInitialized()) {
          val msg = "GraphRequest can't be used when Facebook SDK isn't fully initialized"
          Log.e(TAG, msg)
          throw FacebookException(msg)
        }
        stream =
            if (connection.responseCode >= 400) {
              connection.errorStream
            } else {
              connection.inputStream
            }
        createResponsesFromStream(stream, connection, requests)
      } catch (facebookException: FacebookException) {
        Logger.log(
            LoggingBehavior.REQUESTS, RESPONSE_LOG_TAG, "Response <Error>: %s", facebookException)
        constructErrorResponses(requests, connection, facebookException)
      } catch (exception: Exception) {
        // Note due to bugs various android devices some devices can throw a
        // SecurityException or NoSuchAlgorithmException. Make sure to handle these
        // exceptions here.
        Logger.log(LoggingBehavior.REQUESTS, RESPONSE_LOG_TAG, "Response <Error>: %s", exception)
        constructErrorResponses(requests, connection, FacebookException(exception))
      } finally {
        Utility.closeQuietly(stream)
      }
    }

    @JvmStatic
    @Throws(FacebookException::class, JSONException::class, IOException::class)
    internal fun createResponsesFromStream(
        stream: InputStream?,
        connection: HttpURLConnection?,
        requests: GraphRequestBatch
    ): List<GraphResponse> {
      val responseString = Utility.readStreamToString(stream)
      Logger.log(
          LoggingBehavior.INCLUDE_RAW_RESPONSES,
          RESPONSE_LOG_TAG,
          "Response (raw)\n  Size: %d\n  Response:\n%s\n",
          responseString.length,
          responseString)
      return createResponsesFromString(responseString, connection, requests)
    }

    @JvmStatic
    @Throws(FacebookException::class, JSONException::class, IOException::class)
    internal fun createResponsesFromString(
        responseString: String,
        connection: HttpURLConnection?,
        requests: GraphRequestBatch
    ): List<GraphResponse> {
      val tokener = JSONTokener(responseString)
      val resultObject = tokener.nextValue()
      val responses = createResponsesFromObject(connection, requests, resultObject)
      Logger.log(
          LoggingBehavior.REQUESTS,
          RESPONSE_LOG_TAG,
          "Response\n  Id: %s\n  Size: %d\n  Responses:\n%s\n",
          requests.id,
          responseString.length,
          responses)
      return responses
    }

    @Throws(FacebookException::class, JSONException::class)
    private fun createResponsesFromObject(
        connection: HttpURLConnection?,
        requests: List<GraphRequest>,
        sourceObject: Any
    ): List<GraphResponse> {
      var obj = sourceObject
      val numRequests = requests.size
      val responses: MutableList<GraphResponse> = ArrayList(numRequests)
      val originalResult = obj
      if (numRequests == 1) {
        val request = requests[0]
        try {
          // Single request case -- the entire response is the result, wrap it as "body" so we
          // can handle it the same as we do in the batched case. We get the response code
          // from the actual HTTP response, as opposed to the batched case where it is
          // returned as a "code" element.
          val jsonObject = JSONObject()
          jsonObject.put(BODY_KEY, obj)
          val responseCode = connection?.responseCode ?: 200
          jsonObject.put(CODE_KEY, responseCode)
          val jsonArray = JSONArray()
          jsonArray.put(jsonObject)

          // Pretend we got an array of 1 back.
          obj = jsonArray
        } catch (e: JSONException) {
          responses.add(GraphResponse(request, connection, FacebookRequestError(connection, e)))
        } catch (e: IOException) {
          responses.add(GraphResponse(request, connection, FacebookRequestError(connection, e)))
        }
      }
      if (obj !is JSONArray || obj.length() != numRequests) {
        throw FacebookException("Unexpected number of results")
      }
      val jsonArray = obj
      for (i in 0 until jsonArray.length()) {
        val request = requests[i]
        try {
          val obj = jsonArray[i]
          responses.add(createResponseFromObject(request, connection, obj, originalResult))
        } catch (e: JSONException) {
          responses.add(GraphResponse(request, connection, FacebookRequestError(connection, e)))
        } catch (e: FacebookException) {
          responses.add(GraphResponse(request, connection, FacebookRequestError(connection, e)))
        }
      }
      return responses
    }

    @Throws(JSONException::class)
    private fun createResponseFromObject(
        request: GraphRequest,
        connection: HttpURLConnection?,
        sourceObject: Any,
        originalResult: Any
    ): GraphResponse {
      var obj = sourceObject
      if (obj is JSONObject) {
        val jsonObject = obj
        val error =
            FacebookRequestError.checkResponseAndCreateError(jsonObject, originalResult, connection)
        if (error != null) {
          Log.e(TAG, error.toString())
          if (error.errorCode == FacebookRequestErrorClassification.EC_INVALID_TOKEN &&
              Utility.isCurrentAccessToken(request.accessToken)) {
            if (error.subErrorCode != FacebookRequestErrorClassification.ESC_APP_INACTIVE) {
              AccessToken.setCurrentAccessToken(null)
            } else if (AccessToken.getCurrentAccessToken()?.isExpired == false) {
              AccessToken.expireCurrentAccessToken()
            }
          }
          return GraphResponse(request, connection, error)
        }
        val body = Utility.getStringPropertyAsJSON(jsonObject, BODY_KEY, NON_JSON_RESPONSE_PROPERTY)
        if (body is JSONObject) {
          return GraphResponse(request, connection, body.toString(), body)
        } else if (body is JSONArray) {
          return GraphResponse(request, connection, body.toString(), body)
        }
        // We didn't get a body we understand how to handle, so pretend we got nothing.
        obj = JSONObject.NULL
      }
      return if (obj === JSONObject.NULL) {
        GraphResponse(request, connection, obj.toString(), null as JSONObject?)
      } else {
        throw FacebookException(
            "Got unexpected object type in response, class: " + obj.javaClass.simpleName)
      }
    }

    /**
     * Build GraphResponse with Error
     *
     * @param requests original graph requests
     * @param connection request url connection
     * @param error error included in response
     *
     * @return graph response with error
     */
    @JvmStatic
    fun constructErrorResponses(
        requests: List<GraphRequest>,
        connection: HttpURLConnection?,
        error: FacebookException?
    ): List<GraphResponse> {
      return requests.map { GraphResponse(it, connection, FacebookRequestError(connection, error)) }
    }
  }
}


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

package com.facebook

/** Enumeration of HTTP methods supported by Request */
enum class HttpMethod {
  /** Use HTTP method "GET" for the request */
  GET,

  /** Use HTTP method "POST" for the request */
  POST,

  /** Use HTTP method "DELETE" for the request */
  DELETE
}


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

package com.facebook

import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import com.facebook.internal.Logger.Companion.log
import java.util.Date
import kotlin.collections.ArrayList
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject

internal class LegacyTokenHelper
@JvmOverloads
constructor(context: Context, cacheKey: String? = null) {
  private val cacheKey: String
  private val cache: SharedPreferences

  init {
    var context = context
    this.cacheKey = if (cacheKey == null || cacheKey.isEmpty()) DEFAULT_CACHE_KEY else cacheKey

    // If the application context is available, use that. However, if it isn't
    // available (possibly because of a context that was created manually), use
    // the passed in context directly.
    val applicationContext = context.applicationContext
    context = applicationContext ?: context
    cache = context.getSharedPreferences(this.cacheKey, Context.MODE_PRIVATE)
  }

  fun load(): Bundle? {
    val settings = Bundle()
    val allCachedEntries = cache.all
    for (key in allCachedEntries.keys) {
      try {
        deserializeKey(key, settings)
      } catch (e: JSONException) {
        // Error in the cache. So consider it corrupted and return null
        log(
            LoggingBehavior.CACHE,
            Log.WARN,
            TAG,
            "Error reading cached value for key: '$key' -- $e")
        return null
      }
    }
    return settings
  }

  fun save(bundle: Bundle) {
    val editor = cache.edit()
    for (key in bundle.keySet()) {
      try {
        serializeKey(key, bundle, editor)
      } catch (e: JSONException) {
        // Error in the bundle. Don't store a partial cache.
        log(LoggingBehavior.CACHE, Log.WARN, TAG, "Error processing value for key: '$key' -- $e")

        // Bypass the commit and just return. This cancels the entire edit transaction
        return
      }
    }
    editor.apply()
  }

  /** Clears out all token information stored in this cache. */
  fun clear() {
    cache.edit().clear().apply()
  }

  @Throws(JSONException::class)
  private fun serializeKey(key: String, bundle: Bundle, editor: SharedPreferences.Editor) {
    val value =
        bundle[key] ?: // Cannot serialize null values.
        return
    var supportedType: String? = null
    var jsonArray: JSONArray? = null
    val json = JSONObject()
    when (value) {
      is Byte -> {
        supportedType = TYPE_BYTE
        json.put(JSON_VALUE, value.toInt())
      }
      is Short -> {
        supportedType = TYPE_SHORT
        json.put(JSON_VALUE, value.toInt())
      }
      is Int -> {
        supportedType = TYPE_INTEGER
        json.put(JSON_VALUE, value.toInt())
      }
      is Long -> {
        supportedType = TYPE_LONG
        json.put(JSON_VALUE, value.toLong())
      }
      is Float -> {
        supportedType = TYPE_FLOAT
        json.put(JSON_VALUE, value.toDouble())
      }
      is Double -> {
        supportedType = TYPE_DOUBLE
        json.put(JSON_VALUE, value.toDouble())
      }
      is Boolean -> {
        supportedType = TYPE_BOOLEAN
        json.put(JSON_VALUE, value as Boolean)
      }
      is Char -> {
        supportedType = TYPE_CHAR
        json.put(JSON_VALUE, value.toString())
      }
      is String -> {
        supportedType = TYPE_STRING
        json.put(JSON_VALUE, value)
      }
      is Enum<*> -> {
        supportedType = TYPE_ENUM
        json.put(JSON_VALUE, value.toString())
        json.put(JSON_VALUE_ENUM_TYPE, value.javaClass.name)
      }
      else -> {
        // Optimistically create a JSONArray. If not an array type, we can null
        // it out later
        jsonArray = JSONArray()
        when (value) {
          is ByteArray -> {
            supportedType = TYPE_BYTE_ARRAY
            for (v in value) {
              jsonArray.put(v.toInt())
            }
          }
          is ShortArray -> {
            supportedType = TYPE_SHORT_ARRAY
            for (v in value) {
              jsonArray.put(v.toInt())
            }
          }
          is IntArray -> {
            supportedType = TYPE_INTEGER_ARRAY
            for (v in value) {
              jsonArray.put(v)
            }
          }
          is LongArray -> {
            supportedType = TYPE_LONG_ARRAY
            for (v in value) {
              jsonArray.put(v)
            }
          }
          is FloatArray -> {
            supportedType = TYPE_FLOAT_ARRAY
            for (v in value) {
              jsonArray.put(v.toDouble())
            }
          }
          is DoubleArray -> {
            supportedType = TYPE_DOUBLE_ARRAY
            for (v in value) {
              jsonArray.put(v)
            }
          }
          is BooleanArray -> {
            supportedType = TYPE_BOOLEAN_ARRAY
            for (v in value) {
              jsonArray.put(v)
            }
          }
          is CharArray -> {
            supportedType = TYPE_CHAR_ARRAY
            for (v in value) {
              jsonArray.put(v.toString())
            }
          }
          is List<*> -> {
            supportedType = TYPE_STRING_LIST
            val stringList = value as List<String>
            for (v in stringList) {
              jsonArray.put(v ?: JSONObject.NULL)
            }
          }
          else -> {
            // Unsupported type. Clear out the array as a precaution even though
            // it is redundant with the null supportedType.
            jsonArray = null
          }
        }
      }
    }
    if (supportedType != null) {
      json.put(JSON_VALUE_TYPE, supportedType)
      if (jsonArray != null) {
        // If we have an array, it has already been converted to JSON. So use
        // that instead.
        json.putOpt(JSON_VALUE, jsonArray)
      }
      val jsonString = json.toString()
      editor.putString(key, jsonString)
    }
  }

  @Throws(JSONException::class)
  private fun deserializeKey(key: String, bundle: Bundle) {
    val jsonString = checkNotNull(cache.getString(key, "{}"))
    val json = JSONObject(jsonString)
    val valueType = json.getString(JSON_VALUE_TYPE)
    if (valueType == TYPE_BOOLEAN) {
      bundle.putBoolean(key, json.getBoolean(JSON_VALUE))
    } else if (valueType == TYPE_BOOLEAN_ARRAY) {
      val jsonArray = json.getJSONArray(JSON_VALUE)
      val array = BooleanArray(jsonArray.length())
      for (i in array.indices) {
        array[i] = jsonArray.getBoolean(i)
      }
      bundle.putBooleanArray(key, array)
    } else if (valueType == TYPE_BYTE) {
      bundle.putByte(key, json.getInt(JSON_VALUE).toByte())
    } else if (valueType == TYPE_BYTE_ARRAY) {
      val jsonArray = json.getJSONArray(JSON_VALUE)
      val array = ByteArray(jsonArray.length())
      for (i in array.indices) {
        array[i] = jsonArray.getInt(i).toByte()
      }
      bundle.putByteArray(key, array)
    } else if (valueType == TYPE_SHORT) {
      bundle.putShort(key, json.getInt(JSON_VALUE).toShort())
    } else if (valueType == TYPE_SHORT_ARRAY) {
      val jsonArray = json.getJSONArray(JSON_VALUE)
      val array = ShortArray(jsonArray.length())
      for (i in array.indices) {
        array[i] = jsonArray.getInt(i).toShort()
      }
      bundle.putShortArray(key, array)
    } else if (valueType == TYPE_INTEGER) {
      bundle.putInt(key, json.getInt(JSON_VALUE))
    } else if (valueType == TYPE_INTEGER_ARRAY) {
      val jsonArray = json.getJSONArray(JSON_VALUE)
      val array = IntArray(jsonArray.length())
      for (i in array.indices) {
        array[i] = jsonArray.getInt(i)
      }
      bundle.putIntArray(key, array)
    } else if (valueType == TYPE_LONG) {
      bundle.putLong(key, json.getLong(JSON_VALUE))
    } else if (valueType == TYPE_LONG_ARRAY) {
      val jsonArray = json.getJSONArray(JSON_VALUE)
      val array = LongArray(jsonArray.length())
      for (i in array.indices) {
        array[i] = jsonArray.getLong(i)
      }
      bundle.putLongArray(key, array)
    } else if (valueType == TYPE_FLOAT) {
      bundle.putFloat(key, json.getDouble(JSON_VALUE).toFloat())
    } else if (valueType == TYPE_FLOAT_ARRAY) {
      val jsonArray = json.getJSONArray(JSON_VALUE)
      val array = FloatArray(jsonArray.length())
      for (i in array.indices) {
        array[i] = jsonArray.getDouble(i).toFloat()
      }
      bundle.putFloatArray(key, array)
    } else if (valueType == TYPE_DOUBLE) {
      bundle.putDouble(key, json.getDouble(JSON_VALUE))
    } else if (valueType == TYPE_DOUBLE_ARRAY) {
      val jsonArray = json.getJSONArray(JSON_VALUE)
      val array = DoubleArray(jsonArray.length())
      for (i in array.indices) {
        array[i] = jsonArray.getDouble(i)
      }
      bundle.putDoubleArray(key, array)
    } else if (valueType == TYPE_CHAR) {
      val charString = json.getString(JSON_VALUE)
      if (charString != null && charString.length == 1) {
        bundle.putChar(key, charString[0])
      }
    } else if (valueType == TYPE_CHAR_ARRAY) {
      val jsonArray = json.getJSONArray(JSON_VALUE)
      val array = CharArray(jsonArray.length())
      for (i in array.indices) {
        val charString = jsonArray.getString(i)
        if (charString != null && charString.length == 1) {
          array[i] = charString[0]
        }
      }
      bundle.putCharArray(key, array)
    } else if (valueType == TYPE_STRING) {
      bundle.putString(key, json.getString(JSON_VALUE))
    } else if (valueType == TYPE_STRING_LIST) {
      val jsonArray = json.getJSONArray(JSON_VALUE)
      val numStrings = jsonArray.length()
      val stringList = ArrayList<String?>(numStrings)
      for (i in 0 until numStrings) {
        val jsonStringValue = jsonArray[i]
        stringList.add(
            i, if (jsonStringValue === JSONObject.NULL) null else jsonStringValue as String)
      }
      bundle.putStringArrayList(key, stringList)
    } else if (valueType == TYPE_ENUM) {
      try {
        val enumType = json.getString(JSON_VALUE_ENUM_TYPE)
        val enumClass = Class.forName(enumType) as Class<out Enum<*>>
        val enumValue = java.lang.Enum.valueOf(enumClass, json.getString(JSON_VALUE))
        bundle.putSerializable(key, enumValue)
      } catch (e: ClassNotFoundException) {} catch (e: IllegalArgumentException) {}
    }
  }

  companion object {
    /**
     * The key used by AccessTokenCache to store the token value in the Bundle during load and save.
     */
    const val TOKEN_KEY = "com.facebook.TokenCachingStrategy.Token"

    /**
     * The key used by AccessTokenCache to store the expiration date value in the Bundle during load
     * and save.
     */
    const val EXPIRATION_DATE_KEY = "com.facebook.TokenCachingStrategy.ExpirationDate"

    /**
     * The key used by AccessTokenCache to store the last refresh date value in the Bundle during
     * load and save.
     */
    const val LAST_REFRESH_DATE_KEY = "com.facebook.TokenCachingStrategy.LastRefreshDate"

    /**
     * The key used by AccessTokenCache to store an enum indicating the source of the token in the
     * Bundle during load and save.
     */
    const val TOKEN_SOURCE_KEY = "com.facebook.TokenCachingStrategy.AccessTokenSource"

    /**
     * The key used by AccessTokenCache to store the list of permissions granted by the token in the
     * Bundle during load and save.
     */
    const val PERMISSIONS_KEY = "com.facebook.TokenCachingStrategy.Permissions"

    /**
     * The key used by AccessTokenCache to store the list of permissions declined by the user in the
     * token in the Bundle during load and save.
     */
    const val DECLINED_PERMISSIONS_KEY = "com.facebook.TokenCachingStrategy.DeclinedPermissions"
    const val EXPIRED_PERMISSIONS_KEY = "com.facebook.TokenCachingStrategy.ExpiredPermissions"
    const val APPLICATION_ID_KEY = "com.facebook.TokenCachingStrategy.ApplicationId"
    private const val INVALID_BUNDLE_MILLISECONDS = Long.MIN_VALUE
    private const val IS_SSO_KEY = "com.facebook.TokenCachingStrategy.IsSSO"
    const val DEFAULT_CACHE_KEY = "com.facebook.SharedPreferencesTokenCachingStrategy.DEFAULT_KEY"
    private val TAG = LegacyTokenHelper::class.java.simpleName
    private const val JSON_VALUE_TYPE = "valueType"
    private const val JSON_VALUE = "value"
    private const val JSON_VALUE_ENUM_TYPE = "enumType"
    private const val TYPE_BOOLEAN = "bool"
    private const val TYPE_BOOLEAN_ARRAY = "bool[]"
    private const val TYPE_BYTE = "byte"
    private const val TYPE_BYTE_ARRAY = "byte[]"
    private const val TYPE_SHORT = "short"
    private const val TYPE_SHORT_ARRAY = "short[]"
    private const val TYPE_INTEGER = "int"
    private const val TYPE_INTEGER_ARRAY = "int[]"
    private const val TYPE_LONG = "long"
    private const val TYPE_LONG_ARRAY = "long[]"
    private const val TYPE_FLOAT = "float"
    private const val TYPE_FLOAT_ARRAY = "float[]"
    private const val TYPE_DOUBLE = "double"
    private const val TYPE_DOUBLE_ARRAY = "double[]"
    private const val TYPE_CHAR = "char"
    private const val TYPE_CHAR_ARRAY = "char[]"
    private const val TYPE_STRING = "string"
    private const val TYPE_STRING_LIST = "stringList"
    private const val TYPE_ENUM = "enum"

    @JvmStatic
    fun hasTokenInformation(bundle: Bundle?): Boolean {
      if (bundle == null) {
        return false
      }
      val token = bundle.getString(TOKEN_KEY)
      if (token == null || token.isEmpty()) {
        return false
      }
      val expiresMilliseconds = bundle.getLong(EXPIRATION_DATE_KEY, 0L)
      return expiresMilliseconds != 0L
    }

    @JvmStatic
    fun getToken(bundle: Bundle): String? {
      return bundle.getString(TOKEN_KEY)
    }

    @JvmStatic
    fun putToken(bundle: Bundle, value: String) {
      bundle.putString(TOKEN_KEY, value)
    }

    @JvmStatic
    fun getExpirationDate(bundle: Bundle): Date? {
      return getDate(bundle, EXPIRATION_DATE_KEY)
    }

    @JvmStatic
    fun putExpirationDate(bundle: Bundle, value: Date) {
      putDate(bundle, EXPIRATION_DATE_KEY, value)
    }

    @JvmStatic
    fun getExpirationMilliseconds(bundle: Bundle): Long {
      return bundle.getLong(EXPIRATION_DATE_KEY)
    }

    @JvmStatic
    fun putExpirationMilliseconds(bundle: Bundle, value: Long) {
      bundle.putLong(EXPIRATION_DATE_KEY, value)
    }

    @JvmStatic
    fun getPermissions(bundle: Bundle): Set<String>? {
      val arrayList = bundle.getStringArrayList(PERMISSIONS_KEY) ?: return null
      return HashSet(arrayList)
    }

    @JvmStatic
    fun putPermissions(bundle: Bundle, value: Collection<String>) {
      bundle.putStringArrayList(PERMISSIONS_KEY, ArrayList(value))
    }

    @JvmStatic
    fun putDeclinedPermissions(bundle: Bundle, value: Collection<String>) {
      bundle.putStringArrayList(DECLINED_PERMISSIONS_KEY, ArrayList(value))
    }

    @JvmStatic
    fun putExpiredPermissions(bundle: Bundle, value: Collection<String>) {
      bundle.putStringArrayList(EXPIRED_PERMISSIONS_KEY, ArrayList(value))
    }

    @JvmStatic
    fun getSource(bundle: Bundle): AccessTokenSource? {
      return if (bundle.containsKey(TOKEN_SOURCE_KEY)) {
        bundle.getSerializable(TOKEN_SOURCE_KEY) as AccessTokenSource?
      } else {
        val isSSO = bundle.getBoolean(IS_SSO_KEY)
        if (isSSO) AccessTokenSource.FACEBOOK_APPLICATION_WEB else AccessTokenSource.WEB_VIEW
      }
    }

    @JvmStatic
    fun putSource(bundle: Bundle, value: AccessTokenSource) {
      bundle.putSerializable(TOKEN_SOURCE_KEY, value)
    }

    @JvmStatic
    fun getLastRefreshDate(bundle: Bundle): Date? {
      return getDate(bundle, LAST_REFRESH_DATE_KEY)
    }

    @JvmStatic
    fun putLastRefreshDate(bundle: Bundle, value: Date) {
      putDate(bundle, LAST_REFRESH_DATE_KEY, value)
    }

    @JvmStatic
    fun getLastRefreshMilliseconds(bundle: Bundle): Long {
      return bundle.getLong(LAST_REFRESH_DATE_KEY)
    }

    @JvmStatic
    fun putLastRefreshMilliseconds(bundle: Bundle, value: Long) {
      bundle.putLong(LAST_REFRESH_DATE_KEY, value)
    }

    @JvmStatic
    fun getApplicationId(bundle: Bundle): String? {
      return bundle.getString(APPLICATION_ID_KEY)
    }

    @JvmStatic
    fun putApplicationId(bundle: Bundle, value: String?) {
      bundle.putString(APPLICATION_ID_KEY, value)
    }

    private fun getDate(bundle: Bundle?, key: String?): Date? {
      if (bundle == null) {
        return null
      }
      val n = bundle.getLong(key, INVALID_BUNDLE_MILLISECONDS)
      return if (n == INVALID_BUNDLE_MILLISECONDS) {
        null
      } else Date(n)
    }

    private fun putDate(bundle: Bundle, key: String?, date: Date) {
      bundle.putLong(key, date.time)
    }
  }
}


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

package com.facebook

/**
 * Specifies different categories of logging messages that can be generated.
 *
 * @see FacebookSdk.addLoggingBehavior
 */
enum class LoggingBehavior {
  /** Indicates that HTTP requests and a summary of responses should be logged. */
  REQUESTS,

  /**
   * Indicates that access tokens should be logged as part of the request logging; normally they are
   * not.
   */
  INCLUDE_ACCESS_TOKENS,

  /** Indicates that the entire raw HTTP response for each request should be logged. */
  INCLUDE_RAW_RESPONSES,

  /** Indicates that cache operations should be logged. */
  CACHE,

  /** Indicates the App Events-related operations should be logged. */
  APP_EVENTS,

  /**
   * Indicates that likely developer errors should be logged. (This is set by default in
   * LoggingBehavior.)
   */
  DEVELOPER_ERRORS,

  /**
   * Log debug warnings from API response, e.g. when friends fields requested, but user_friends
   * permission isn't granted.
   */
  GRAPH_API_DEBUG_WARNING,

  /**
   * Log warnings from API response, e.g. when requested feature will be deprecated in next version
   * of API. Info is the lowest level of severity, using it will result in logging all
   * GRAPH_API_DEBUG levels.
   */
  GRAPH_API_DEBUG_INFO
}


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

package com.facebook

import android.net.Uri
import android.os.Parcel
import android.os.Parcelable
import android.util.Log
import com.facebook.internal.ImageRequest.Companion.getProfilePictureUri
import com.facebook.internal.Utility
import com.facebook.internal.Utility.getGraphMeRequestWithCacheAsync
import com.facebook.internal.Validate.notNullOrEmpty
import org.json.JSONException
import org.json.JSONObject

/** This class represents a basic Facebook profile. */
class Profile : Parcelable {
  /**
   * Getter for the id of the profile.
   *
   * @return id of the profile.
   */
  val id: String?

  /**
   * Getter for the first name of the profile.
   *
   * @return the first name of the profile.
   */
  val firstName: String?

  /**
   * Getter for the middle name of the profile.
   *
   * @return the middle name of the profile.
   */
  val middleName: String?

  /**
   * Getter for the last name of the profile.
   *
   * @return the last name of the profile.
   */
  val lastName: String?

  /**
   * Getter for the name of the profile.
   *
   * @return the name of the profile.
   */
  val name: String?

  /**
   * Getter for the link of the profile.
   *
   * @return the link of the profile.
   */
  val linkUri: Uri?

  /**
   * Getter for the picture URI of the profile
   *
   * @return the picture URI of the profile.
   */
  val pictureUri: Uri?

  /**
   * Constructor.
   *
   * @param id The id of the profile.
   * @param firstName The first name of the profile. Can be null.
   * @param middleName The middle name of the profile. Can be null.
   * @param lastName The last name of the profile. Can be null.
   * @param name The name of the profile. Can be null.
   * @param linkUri The link for this profile. Can be null.
   */
  @JvmOverloads
  constructor(
      id: String?,
      firstName: String?,
      middleName: String?,
      lastName: String?,
      name: String?,
      linkUri: Uri?,
      pictureUri: Uri? = null,
  ) {
    notNullOrEmpty(id, "id")
    this.id = id
    this.firstName = firstName
    this.middleName = middleName
    this.lastName = lastName
    this.name = name
    this.linkUri = linkUri
    this.pictureUri = pictureUri
  }

  /**
   * Getter for the Uri of the profile picture.
   *
   * @param width The desired width for the profile picture.
   * @param height The desired height for the profile picture.
   * @return The Uri of the profile picture.
   */
  fun getProfilePictureUri(width: Int, height: Int): Uri {
    // Only Instagram users will have non-null profile picture URIs
    if (pictureUri != null) {
      return pictureUri
    }
    val accessToken =
        if (AccessToken.isCurrentAccessTokenActive()) AccessToken.getCurrentAccessToken()?.token
        else ""
    return getProfilePictureUri(id, width, height, accessToken)
  }

  override fun equals(other: Any?): Boolean {
    if (this === other) {
      return true
    }
    if (other !is Profile) {
      return false
    }

    return (id == null && other.id == null || id == other.id) &&
        (firstName == null && other.firstName == null || firstName == other.firstName) &&
        (middleName == null && other.middleName == null || middleName == other.middleName) &&
        (lastName == null && other.lastName == null || lastName == other.lastName) &&
        (name == null && other.name == null || name == other.name) &&
        (linkUri == null && other.linkUri == null || linkUri == other.linkUri) &&
        (pictureUri == null && other.pictureUri == null || pictureUri == other.pictureUri)
  }

  override fun hashCode(): Int {
    var result = 17
    result = result * 31 + id.hashCode()
    if (firstName != null) {
      result = result * 31 + firstName.hashCode()
    }
    if (middleName != null) {
      result = result * 31 + middleName.hashCode()
    }
    if (lastName != null) {
      result = result * 31 + lastName.hashCode()
    }
    if (name != null) {
      result = result * 31 + name.hashCode()
    }
    if (linkUri != null) {
      result = result * 31 + linkUri.hashCode()
    }
    if (pictureUri != null) {
      result = result * 31 + pictureUri.hashCode()
    }
    return result
  }

  fun toJSONObject(): JSONObject? {
    val jsonObject = JSONObject()
    try {
      jsonObject.put(ID_KEY, id)
      jsonObject.put(FIRST_NAME_KEY, firstName)
      jsonObject.put(MIDDLE_NAME_KEY, middleName)
      jsonObject.put(LAST_NAME_KEY, lastName)
      jsonObject.put(NAME_KEY, name)
      if (linkUri != null) {
        jsonObject.put(LINK_URI_KEY, linkUri.toString())
      }
      if (pictureUri != null) {
        jsonObject.put(PICTURE_URI_KEY, pictureUri.toString())
      }
    } catch (_: JSONException) {
      return null
    }
    return jsonObject
  }

  internal constructor(jsonObject: JSONObject) {
    id = jsonObject.optString(ID_KEY, null)
    firstName = jsonObject.optString(FIRST_NAME_KEY, null)
    middleName = jsonObject.optString(MIDDLE_NAME_KEY, null)
    lastName = jsonObject.optString(LAST_NAME_KEY, null)
    name = jsonObject.optString(NAME_KEY, null)
    val linkUriString = jsonObject.optString(LINK_URI_KEY, null)
    linkUri = if (linkUriString == null) null else Uri.parse(linkUriString)
    val pictureUriString = jsonObject.optString(PICTURE_URI_KEY, null)
    pictureUri = if (pictureUriString == null) null else Uri.parse(pictureUriString)
  }

  private constructor(source: Parcel) {
    id = source.readString()
    firstName = source.readString()
    middleName = source.readString()
    lastName = source.readString()
    name = source.readString()
    val linkUriString = source.readString()
    linkUri = if (linkUriString == null) null else Uri.parse(linkUriString)
    val pictureUriString = source.readString()
    pictureUri = if (pictureUriString == null) null else Uri.parse(pictureUriString)
  }

  override fun describeContents(): Int = 0

  override fun writeToParcel(dest: Parcel, flags: Int) {
    dest.writeString(id)
    dest.writeString(firstName)
    dest.writeString(middleName)
    dest.writeString(lastName)
    dest.writeString(name)
    dest.writeString(linkUri?.toString())
    dest.writeString(pictureUri?.toString())
  }

  companion object {
    private val TAG = Profile::class.java.simpleName
    private const val ID_KEY = "id"
    private const val FIRST_NAME_KEY = "first_name"
    private const val MIDDLE_NAME_KEY = "middle_name"
    private const val LAST_NAME_KEY = "last_name"
    private const val NAME_KEY = "name"
    private const val LINK_URI_KEY = "link_uri"
    // Used exclusively for Instagram profiles
    private const val PICTURE_URI_KEY = "picture_uri"

    /**
     * Getter for the profile that is currently logged in to the application.
     *
     * @return The profile that is currently logged in to the application.
     */
    @JvmStatic
    fun getCurrentProfile(): Profile? {
      return ProfileManager.getInstance().currentProfile
    }
    /**
     * Setter for the profile that is currently logged in to the application. If the access token is
     * invalidated, the current profile will not be updated. It's only updated when there is an
     * explicit logout, login or when permissions change via the [ ].
     *
     * @param profile The profile that is currently logged in to the application.
     */
    @JvmStatic
    fun setCurrentProfile(profile: Profile?) {
      ProfileManager.getInstance().currentProfile = profile
    }

    /**
     * Fetches and sets the current profile from the current access token.
     *
     * This should only be called from the UI thread.
     */
    @JvmStatic
    fun fetchProfileForCurrentAccessToken() {
      val accessToken = AccessToken.getCurrentAccessToken() ?: return
      if (!AccessToken.isCurrentAccessTokenActive()) {
        setCurrentProfile(null)
        return
      }
      getGraphMeRequestWithCacheAsync(
          accessToken.token,
          object : Utility.GraphMeRequestWithCacheCallback {
            override fun onSuccess(userInfo: JSONObject?) {
              val id = userInfo?.optString("id")
              if (id == null) {
                Log.w(TAG, "No user ID returned on Me request")
                return
              }
              val link = userInfo.optString("link")
              val picture = userInfo.optString("profile_picture", null)
              val profile =
                  Profile(
                      id,
                      userInfo.optString("first_name"),
                      userInfo.optString("middle_name"),
                      userInfo.optString("last_name"),
                      userInfo.optString("name"),
                      if (link != null) Uri.parse(link) else null,
                      if (picture != null) Uri.parse(picture) else null)
              setCurrentProfile(profile)
            }

            override fun onFailure(error: FacebookException?) {
              Log.e(TAG, "Got unexpected exception: $error")
              return
            }
          })
    }

    @JvmField
    val CREATOR: Parcelable.Creator<Profile> =
        object : Parcelable.Creator<Profile> {
          override fun createFromParcel(source: Parcel): Profile {
            return Profile(source)
          }

          override fun newArray(size: Int): Array<Profile?> {
            return arrayOfNulls(size)
          }
        }
  }
}


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

package com.facebook

import android.content.Context
import android.content.SharedPreferences
import org.json.JSONException
import org.json.JSONObject

class ProfileCache {
  private val sharedPreferences: SharedPreferences
  fun load(): Profile? {
    val jsonString = sharedPreferences.getString(CACHED_PROFILE_KEY, null)
    if (jsonString != null) {
      try {
        val jsonObject = JSONObject(jsonString)
        return Profile(jsonObject)
      } catch (e: JSONException) {
        // Can't recover
      }
    }
    return null
  }

  fun save(profile: Profile) {
    val jsonObject = profile.toJSONObject()
    if (jsonObject != null) {
      sharedPreferences.edit().putString(CACHED_PROFILE_KEY, jsonObject.toString()).apply()
    }
  }

  fun clear() {
    sharedPreferences.edit().remove(CACHED_PROFILE_KEY).apply()
  }

  companion object {
    const val CACHED_PROFILE_KEY = "com.facebook.ProfileManager.CachedProfile"
    const val SHARED_PREFERENCES_NAME = "com.facebook.AccessTokenManager.SharedPreferences"
  }

  init {
    sharedPreferences =
        FacebookSdk.getApplicationContext()
            .getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
  }
}


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

package com.facebook

import android.content.Intent
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.facebook.internal.Utility.areObjectsEqual

class ProfileManager
internal constructor(
    private val localBroadcastManager: LocalBroadcastManager,
    private val profileCache: ProfileCache
) {
  private var currentProfileField: Profile? = null
  var currentProfile: Profile?
    get() = currentProfileField
    set(value) = setCurrentProfile(value, true)

  /**
   * load profile from cache and set it to current profile
   *
   * @return if successfully load profile
   */
  fun loadCurrentProfile(): Boolean {
    val profile = profileCache.load()
    if (profile != null) {
      setCurrentProfile(profile, false)
      return true
    }
    return false
  }

  private fun setCurrentProfile(currentProfile: Profile?, writeToCache: Boolean) {
    val oldProfile = this.currentProfileField
    this.currentProfileField = currentProfile
    if (writeToCache) {
      if (currentProfile != null) {
        profileCache.save(currentProfile)
      } else {
        profileCache.clear()
      }
    }
    if (!areObjectsEqual(oldProfile, currentProfile)) {
      sendCurrentProfileChangedBroadcast(oldProfile, currentProfile)
    }
  }

  private fun sendCurrentProfileChangedBroadcast(oldProfile: Profile?, currentProfile: Profile?) {
    val intent = Intent(ACTION_CURRENT_PROFILE_CHANGED)
    intent.putExtra(EXTRA_OLD_PROFILE, oldProfile)
    intent.putExtra(EXTRA_NEW_PROFILE, currentProfile)
    localBroadcastManager.sendBroadcast(intent)
  }

  companion object {
    const val ACTION_CURRENT_PROFILE_CHANGED = "com.facebook.sdk.ACTION_CURRENT_PROFILE_CHANGED"
    const val EXTRA_OLD_PROFILE = "com.facebook.sdk.EXTRA_OLD_PROFILE"
    const val EXTRA_NEW_PROFILE = "com.facebook.sdk.EXTRA_NEW_PROFILE"

    @Volatile private lateinit var instance: ProfileManager
    @JvmStatic
    @Synchronized
    fun getInstance(): ProfileManager {
      if (!this::instance.isInitialized) {
        val applicationContext = FacebookSdk.getApplicationContext()
        val localBroadcastManager = LocalBroadcastManager.getInstance(applicationContext)
        instance = ProfileManager(localBroadcastManager, ProfileCache())
      }
      return instance
    }
  }
}


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

package com.facebook

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.facebook.internal.Validate.sdkInitialized

/**
 * This class can be extended to receive notifications of profile changes. The [ ][.stopTracking]
 * method should be called in the onDestroy() method of the receiving Activity or Fragment.
 */
abstract class ProfileTracker {
  private val receiver: BroadcastReceiver
  private val broadcastManager: LocalBroadcastManager

  /**
   * Gets whether the tracker is tracking the current access token.
   *
   * @return true if the tracker is the tracking the current access token, false if not
   */
  var isTracking = false
    private set

  /**
   * The method that will be called when the profile changes.
   *
   * @param oldProfile The profile before the change.
   * @param currentProfile The new profile.
   */
  protected abstract fun onCurrentProfileChanged(oldProfile: Profile?, currentProfile: Profile?)

  /** Starts tracking the current profile. */
  fun startTracking() {
    if (isTracking) {
      return
    }
    addBroadcastReceiver()
    isTracking = true
  }

  /** Stops tracking the current profile. */
  fun stopTracking() {
    if (!isTracking) {
      return
    }
    broadcastManager.unregisterReceiver(receiver)
    isTracking = false
  }

  private inner class ProfileBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
      if (ProfileManager.ACTION_CURRENT_PROFILE_CHANGED == intent.action) {
        val oldProfile: Profile? = intent.getParcelableExtra(ProfileManager.EXTRA_OLD_PROFILE)
        val newProfile: Profile? = intent.getParcelableExtra(ProfileManager.EXTRA_NEW_PROFILE)
        onCurrentProfileChanged(oldProfile, newProfile)
      }
    }
  }

  private fun addBroadcastReceiver() {
    val filter = IntentFilter()
    filter.addAction(ProfileManager.ACTION_CURRENT_PROFILE_CHANGED)
    broadcastManager.registerReceiver(receiver, filter)
  }

  /** Constructor. */
  init {
    sdkInitialized()
    receiver = ProfileBroadcastReceiver()
    broadcastManager = LocalBroadcastManager.getInstance(FacebookSdk.getApplicationContext())
    startTracking()
  }
}


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

package com.facebook

import android.os.Handler
import java.io.OutputStream

class ProgressNoopOutputStream(private val callbackHandler: Handler?) :
    OutputStream(), RequestOutputStream {
  private val progressMap: MutableMap<GraphRequest, RequestProgress> = hashMapOf()
  private var currentRequest: GraphRequest? = null
  private var currentRequestProgress: RequestProgress? = null
  var maxProgress = 0
    private set

  override fun setCurrentRequest(currentRequest: GraphRequest?) {
    this.currentRequest = currentRequest
    currentRequestProgress = if (currentRequest != null) progressMap[currentRequest] else null
  }

  fun getProgressMap(): Map<GraphRequest, RequestProgress> = progressMap

  /** Add size to currentRequestProgress's process size */
  fun addProgress(size: Long) {
    val currentRequest = this.currentRequest ?: return
    if (currentRequestProgress == null) {
      val requestProgress = RequestProgress(callbackHandler, currentRequest)
      currentRequestProgress = requestProgress
      progressMap[currentRequest] = requestProgress
    }
    currentRequestProgress?.addToMax(size)
    maxProgress += size.toInt()
  }

  override fun write(buffer: ByteArray) {
    addProgress(buffer.size.toLong())
  }

  override fun write(buffer: ByteArray, offset: Int, length: Int) {
    addProgress(length.toLong())
  }

  override fun write(oneByte: Int) {
    addProgress(1)
  }
}


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

package com.facebook

import java.io.FilterOutputStream
import java.io.IOException
import java.io.OutputStream

class ProgressOutputStream(
    out: OutputStream,
    private val requests: GraphRequestBatch,
    private val progressMap: Map<GraphRequest, RequestProgress>,
    val maxProgress: Long
) : FilterOutputStream(out), RequestOutputStream {
  private val threshold: Long = FacebookSdk.getOnProgressThreshold()
  var batchProgress: Long = 0
    private set
  private var lastReportedProgress: Long = 0
  private var currentRequestProgress: RequestProgress? = null

  private fun addProgress(size: Long) {
    currentRequestProgress?.addProgress(size)
    batchProgress += size
    if (batchProgress >= lastReportedProgress + threshold || batchProgress >= maxProgress) {
      reportBatchProgress()
    }
  }

  private fun reportBatchProgress() {
    if (batchProgress > lastReportedProgress) {
      for (callback in requests.callbacks) {
        if (callback is GraphRequestBatch.OnProgressCallback) {
          requests.callbackHandler?.post {
            callback.onBatchProgress(requests, batchProgress, maxProgress)
          }
              ?: callback.onBatchProgress(requests, batchProgress, maxProgress)
        }
      }
      lastReportedProgress = batchProgress
    }
  }

  override fun setCurrentRequest(request: GraphRequest?) {
    currentRequestProgress = if (request != null) progressMap[request] else null
  }

  @Throws(IOException::class)
  override fun write(buffer: ByteArray) {
    out.write(buffer)
    addProgress(buffer.size.toLong())
  }

  @Throws(IOException::class)
  override fun write(buffer: ByteArray, offset: Int, length: Int) {
    out.write(buffer, offset, length)
    addProgress(length.toLong())
  }

  @Throws(IOException::class)
  override fun write(oneByte: Int) {
    out.write(oneByte)
    addProgress(1)
  }

  @Throws(IOException::class)
  override fun close() {
    super.close()
    for (p in progressMap.values) {
      p.reportProgress()
    }
    reportBatchProgress()
  }
}


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

package com.facebook

internal fun interface RequestOutputStream {
  fun setCurrentRequest(r: GraphRequest?)
}


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

package com.facebook

import android.os.Handler

class RequestProgress
constructor(private val callbackHandler: Handler?, private val request: GraphRequest) {
  private val threshold = FacebookSdk.getOnProgressThreshold()
  var progress: Long = 0
    private set
  private var lastReportedProgress: Long = 0
  var maxProgress: Long = 0
    private set

  /**
   * Increase process size
   *
   * @param size to increase
   */
  fun addProgress(size: Long) {
    progress += size
    if (progress >= lastReportedProgress + threshold || progress >= maxProgress) {
      reportProgress()
    }
  }

  /**
   * Increase maxProgress size
   *
   * @param size to increase
   */
  fun addToMax(size: Long) {
    maxProgress += size
  }

  /** Send requests if process size larger than lastReportedProgress size */
  fun reportProgress() {
    if (progress > lastReportedProgress) {
      val callback = request.callback
      if (maxProgress > 0 && callback is GraphRequest.OnProgressCallback) {
        // Keep copies to avoid threading issues
        val progressCopy = progress
        val maxProgressCopy = maxProgress
        callbackHandler?.post { callback.onProgress(progressCopy, maxProgressCopy) }
            ?: callback.onProgress(progressCopy, maxProgressCopy)
        lastReportedProgress = progress
      }
    }
  }
}


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

package com.facebook

import android.content.Context
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import com.facebook.GraphRequest.Companion.newGraphPathRequest
import com.facebook.appevents.InternalAppEventsLogger
import com.facebook.internal.AttributionIdentifiers.Companion.getAttributionIdentifiers
import com.facebook.internal.FetchedAppSettingsManager.queryAppSettings
import com.facebook.internal.Utility.isAutoAppLinkSetup
import com.facebook.internal.Utility.logd
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import java.util.concurrent.atomic.AtomicBoolean
import org.json.JSONException
import org.json.JSONObject

@AutoHandleExceptions
internal object UserSettingsManager {
  private val TAG = UserSettingsManager::class.java.name
  private val isInitialized = AtomicBoolean(false)
  private val isFetchingCodelessStatus = AtomicBoolean(false)
  private const val EVENTS_CODELESS_SETUP_ENABLED = "auto_event_setup_enabled"
  private const val TIMEOUT_7D =
      (7 * 24 * 60 * 60 * 1000 // Millisecond
          )
          .toLong()
  private const val ADVERTISER_ID_KEY = "advertiser_id"
  private const val APPLICATION_FIELDS = GraphRequest.FIELDS_PARAM
  private val autoInitEnabled = UserSetting(true, FacebookSdk.AUTO_INIT_ENABLED_PROPERTY)
  private val autoLogAppEventsEnabled =
      UserSetting(true, FacebookSdk.AUTO_LOG_APP_EVENTS_ENABLED_PROPERTY)
  private val advertiserIDCollectionEnabled =
      UserSetting(true, FacebookSdk.ADVERTISER_ID_COLLECTION_ENABLED_PROPERTY)
  private val codelessSetupEnabled = UserSetting(false, EVENTS_CODELESS_SETUP_ENABLED)

  // Monitor enabled user setting from AndroidManifest
  private val monitorEnabled = UserSetting(true, FacebookSdk.MONITOR_ENABLED_PROPERTY)

  // Cache
  private const val USER_SETTINGS = "com.facebook.sdk.USER_SETTINGS"
  private const val USER_SETTINGS_BITMASK = "com.facebook.sdk.USER_SETTINGS_BITMASK"
  private lateinit var userSettingPref: SharedPreferences

  // Parameter names of settings in cache
  private const val LAST_TIMESTAMP = "last_timestamp"
  private const val VALUE = "value"

  // Warning message for App Event Flags
  private const val AUTOLOG_APPEVENT_NOT_SET_WARNING =
      ("Please set a value for AutoLogAppEventsEnabled. Set the flag to TRUE if you want " +
          "to collect app install, app launch and in-app purchase events automatically. To " +
          "request user consent before collecting data, set the flag value to FALSE, then " +
          "change to TRUE once user consent is received. " +
          "Learn more: https://developers.facebook.com/docs/app-events/getting-started-app-events-android#disable-auto-events.")
  private const val ADVERTISERID_COLLECTION_NOT_SET_WARNING =
      ("You haven't set a value for AdvertiserIDCollectionEnabled. Set the flag to TRUE " +
          "if you want to collect Advertiser ID for better advertising and analytics " +
          "results. To request user consent before collecting data, set the flag value to " +
          "FALSE, then change to TRUE once user consent is received. " +
          "Learn more: https://developers.facebook.com/docs/app-events/getting-started-app-events-android#disable-auto-events.")
  private const val ADVERTISERID_COLLECTION_FALSE_WARNING =
      ("The value for AdvertiserIDCollectionEnabled is currently set to FALSE so you're " +
          "sending app events without collecting Advertiser ID. This can affect the quality " +
          "of your advertising and analytics results.")

  // Warning message for Auto App Link Setting
  private const val AUTO_APP_LINK_WARNING =
      "You haven't set the Auto App Link URL scheme: fb<YOUR APP ID> in AndroidManifest"

  private fun initializeIfNotInitialized() {
    if (!FacebookSdk.isInitialized()) {
      return
    }
    if (!isInitialized.compareAndSet(false, true)) {
      return
    }
    userSettingPref =
        FacebookSdk.getApplicationContext()
            .getSharedPreferences(USER_SETTINGS, Context.MODE_PRIVATE)
    initializeUserSetting(autoLogAppEventsEnabled, advertiserIDCollectionEnabled, autoInitEnabled)
    initializeCodelessSetupEnabledAsync()
    logWarnings()
    logIfSDKSettingsChanged()
  }

  private fun initializeUserSetting(vararg userSettings: UserSetting) {
    for (userSetting in userSettings) {
      if (userSetting === codelessSetupEnabled) {
        initializeCodelessSetupEnabledAsync()
      } else {
        if (userSetting.value == null) {
          readSettingFromCache(userSetting)
          if (userSetting.value == null) {
            loadSettingFromManifest(userSetting)
          }
        } else {
          // if flag has been set before initialization, load setting to cache
          writeSettingToCache(userSetting)
        }
      }
    }
  }

  private fun initializeCodelessSetupEnabledAsync() {
    readSettingFromCache(codelessSetupEnabled)
    val currTime = System.currentTimeMillis()
    if (codelessSetupEnabled.value != null && currTime - codelessSetupEnabled.lastTS < TIMEOUT_7D) {
      return
    } else {
      codelessSetupEnabled.value = null
      codelessSetupEnabled.lastTS = 0
    }
    if (!isFetchingCodelessStatus.compareAndSet(false, true)) {
      return
    }
    // fetch data through Graph request if cache is unavailable
    FacebookSdk.getExecutor().execute {
      if (advertiserIDCollectionEnabled.getValue()) {
        val appSettings = queryAppSettings(FacebookSdk.getApplicationId(), false)
        if (appSettings != null && appSettings.codelessEventsEnabled) {
          var advertiserId: String? = null
          val context = FacebookSdk.getApplicationContext()
          val identifiers = getAttributionIdentifiers(context)
          if (identifiers != null && identifiers.androidAdvertiserId != null) {
            advertiserId = identifiers.androidAdvertiserId
          }
          if (advertiserId != null) {
            val codelessSettingsParams = Bundle()
            codelessSettingsParams.putString(ADVERTISER_ID_KEY, advertiserId)
            codelessSettingsParams.putString(APPLICATION_FIELDS, EVENTS_CODELESS_SETUP_ENABLED)

            val codelessRequest = newGraphPathRequest(null, "app", null)
            codelessRequest.parameters = codelessSettingsParams
            val response = codelessRequest.executeAndWait().getJSONObject()

            if (response != null) {
              codelessSetupEnabled.value = response.optBoolean(EVENTS_CODELESS_SETUP_ENABLED, false)
              codelessSetupEnabled.lastTS = currTime
              writeSettingToCache(codelessSetupEnabled)
            }
          }
        }
      }
      isFetchingCodelessStatus.set(false)
    }
  }

  private fun writeSettingToCache(userSetting: UserSetting) {
    validateInitialized()
    try {
      val jsonObject = JSONObject()
      jsonObject.put(VALUE, userSetting.value)
      jsonObject.put(LAST_TIMESTAMP, userSetting.lastTS)
      userSettingPref.edit().putString(userSetting.key, jsonObject.toString()).apply()
      logIfSDKSettingsChanged()
    } catch (e: Exception) {
      logd(TAG, e)
    }
  }

  private fun readSettingFromCache(userSetting: UserSetting) {
    validateInitialized()
    try {
      val settingStr = userSettingPref.getString(userSetting.key, "") ?: ""
      if (settingStr.isNotEmpty()) {
        val setting = JSONObject(settingStr)
        userSetting.value = setting.getBoolean(VALUE)
        userSetting.lastTS = setting.getLong(LAST_TIMESTAMP)
      }
    } catch (je: JSONException) {
      logd(TAG, je)
    }
  }

  private fun loadSettingFromManifest(userSetting: UserSetting) {
    validateInitialized()
    try {
      val ctx = FacebookSdk.getApplicationContext()
      val ai = ctx.packageManager.getApplicationInfo(ctx.packageName, PackageManager.GET_META_DATA)
      if (ai?.metaData != null && ai.metaData.containsKey(userSetting.key)) {
        // default value should not be used
        userSetting.value = ai.metaData.getBoolean(userSetting.key, userSetting.defaultVal)
      }
    } catch (e: PackageManager.NameNotFoundException) {
      logd(TAG, e)
    }
  }

  private fun logWarnings() {
    try {
      val ctx = FacebookSdk.getApplicationContext()
      val ai = ctx.packageManager.getApplicationInfo(ctx.packageName, PackageManager.GET_META_DATA)
      if (ai?.metaData != null) {
        // Log warnings for App Event Flags
        if (!ai.metaData.containsKey(FacebookSdk.AUTO_LOG_APP_EVENTS_ENABLED_PROPERTY)) {
          Log.w(TAG, AUTOLOG_APPEVENT_NOT_SET_WARNING)
        }
        if (!ai.metaData.containsKey(FacebookSdk.ADVERTISER_ID_COLLECTION_ENABLED_PROPERTY)) {
          Log.w(TAG, ADVERTISERID_COLLECTION_NOT_SET_WARNING)
        }
        if (!getAdvertiserIDCollectionEnabled()) {
          Log.w(TAG, ADVERTISERID_COLLECTION_FALSE_WARNING)
        }
      }
    } catch (e: PackageManager.NameNotFoundException) {
      /* no op */
    }
  }

  private fun logIfSDKSettingsChanged() {
    if (!isInitialized.get()) {
      return
    }
    if (!FacebookSdk.isInitialized()) {
      return
    }
    val ctx = FacebookSdk.getApplicationContext()
    var bitmask = 0
    var bit = 0
    bitmask = bitmask or ((if (autoInitEnabled.getValue()) 1 else 0) shl bit++)
    bitmask = bitmask or ((if (autoLogAppEventsEnabled.getValue()) 1 else 0) shl bit++)
    bitmask = bitmask or ((if (advertiserIDCollectionEnabled.getValue()) 1 else 0) shl bit++)
    bitmask = bitmask or ((if (monitorEnabled.getValue()) 1 else 0) shl bit++)
    val previousBitmask = userSettingPref.getInt(USER_SETTINGS_BITMASK, 0)
    if (previousBitmask != bitmask) {
      userSettingPref.edit().putInt(USER_SETTINGS_BITMASK, bitmask).apply()
      var initialBitmask = 0
      var usageBitmask = 0
      try {
        val ai =
            ctx.packageManager.getApplicationInfo(ctx.packageName, PackageManager.GET_META_DATA)
        if (ai?.metaData != null) {
          val keys =
              arrayOf(
                  FacebookSdk.AUTO_INIT_ENABLED_PROPERTY,
                  FacebookSdk.AUTO_LOG_APP_EVENTS_ENABLED_PROPERTY,
                  FacebookSdk.ADVERTISER_ID_COLLECTION_ENABLED_PROPERTY,
                  FacebookSdk.MONITOR_ENABLED_PROPERTY)
          val defaultValues = booleanArrayOf(true, true, true, true)
          for (i in keys.indices) {
            usageBitmask = usageBitmask or ((if (ai.metaData.containsKey(keys[i])) 1 else 0) shl i)
            val initialValue = ai.metaData.getBoolean(keys[i], defaultValues[i])
            initialBitmask = initialBitmask or ((if (initialValue) 1 else 0) shl i)
          }
        }
      } catch (e: PackageManager.NameNotFoundException) {
        /* no op */
      }
      val logger = InternalAppEventsLogger(ctx)
      val parameters = Bundle()
      parameters.putInt("usage", usageBitmask)
      parameters.putInt("initial", initialBitmask)
      parameters.putInt("previous", previousBitmask)
      parameters.putInt("current", bitmask)
      logger.logChangedSettingsEvent(parameters)
    }
  }

  @JvmStatic
  fun logIfAutoAppLinkEnabled() {
    try {
      val ctx = FacebookSdk.getApplicationContext()
      val ai = ctx.packageManager.getApplicationInfo(ctx.packageName, PackageManager.GET_META_DATA)
      if (ai?.metaData != null &&
          ai.metaData.getBoolean("com.facebook.sdk.AutoAppLinkEnabled", false)) {
        val logger = InternalAppEventsLogger(ctx)
        val params = Bundle()
        if (!isAutoAppLinkSetup) {
          params.putString("SchemeWarning", AUTO_APP_LINK_WARNING)
          Log.w(TAG, AUTO_APP_LINK_WARNING)
        }
        logger.logEvent("fb_auto_applink", params)
      }
    } catch (e: PackageManager.NameNotFoundException) {
      /* no op */
    }
  }

  /** Sanity check that if UserSettingsManager initialized successfully */
  private fun validateInitialized() {
    if (!isInitialized.get()) {
      throw FacebookSdkNotInitializedException(
          "The UserSettingManager has not been initialized successfully")
    }
  }

  @JvmStatic
  fun setAutoInitEnabled(flag: Boolean) {
    autoInitEnabled.value = flag
    autoInitEnabled.lastTS = System.currentTimeMillis()
    if (isInitialized.get()) {
      writeSettingToCache(autoInitEnabled)
    } else {
      initializeIfNotInitialized()
    }
  }

  @JvmStatic
  fun getAutoInitEnabled(): Boolean {
    initializeIfNotInitialized()
    return autoInitEnabled.getValue()
  }

  @JvmStatic
  fun setAutoLogAppEventsEnabled(flag: Boolean) {
    autoLogAppEventsEnabled.value = flag
    autoLogAppEventsEnabled.lastTS = System.currentTimeMillis()
    if (isInitialized.get()) {
      writeSettingToCache(autoLogAppEventsEnabled)
    } else {
      initializeIfNotInitialized()
    }
  }

  @JvmStatic
  fun getAutoLogAppEventsEnabled(): Boolean {
    initializeIfNotInitialized()
    return autoLogAppEventsEnabled.getValue()
  }

  @JvmStatic
  fun setAdvertiserIDCollectionEnabled(flag: Boolean) {
    advertiserIDCollectionEnabled.value = flag
    advertiserIDCollectionEnabled.lastTS = System.currentTimeMillis()
    if (isInitialized.get()) {
      writeSettingToCache(advertiserIDCollectionEnabled)
    } else {
      initializeIfNotInitialized()
    }
  }

  @JvmStatic
  fun getAdvertiserIDCollectionEnabled(): Boolean {
    initializeIfNotInitialized()
    return advertiserIDCollectionEnabled.getValue()
  }

  @JvmStatic
  fun getCodelessSetupEnabled(): Boolean {
    initializeIfNotInitialized()
    return codelessSetupEnabled.getValue()
  }

  @JvmStatic
  fun setMonitorEnabled(flag: Boolean) {
    monitorEnabled.value = flag
    monitorEnabled.lastTS = System.currentTimeMillis()
    if (isInitialized.get()) {
      writeSettingToCache(monitorEnabled)
    } else {
      initializeIfNotInitialized()
    }
  }

  @JvmStatic
  fun getMonitorEnabled(): Boolean {
    initializeIfNotInitialized()
    return monitorEnabled.getValue()
  }

  private class UserSetting(var defaultVal: Boolean, var key: String) {
    var value: Boolean? = null
    var lastTS: Long = 0
    fun getValue(): Boolean {
      return value ?: defaultVal
    }
  }
}


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

package com.facebook.appevents

import androidx.annotation.RestrictTo
import com.facebook.AccessToken
import com.facebook.FacebookSdk
import com.facebook.internal.Utility.areObjectsEqual
import com.facebook.internal.Utility.isNullOrEmpty
import java.io.ObjectStreamException
import java.io.Serializable

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
class AccessTokenAppIdPair(accessTokenString: String?, val applicationId: String) : Serializable {
  val accessTokenString: String? = if (isNullOrEmpty(accessTokenString)) null else accessTokenString

  constructor(accessToken: AccessToken) : this(accessToken.token, FacebookSdk.getApplicationId())

  override fun hashCode(): Int {
    return (accessTokenString?.hashCode() ?: 0) xor (applicationId.hashCode() ?: 0)
  }

  override fun equals(o: Any?): Boolean {
    if (o !is AccessTokenAppIdPair) {
      return false
    }
    return areObjectsEqual(o.accessTokenString, accessTokenString) &&
        areObjectsEqual(o.applicationId, applicationId)
  }

  internal class SerializationProxyV1
  constructor(private val accessTokenString: String?, private val appId: String) : Serializable {
    @Throws(ObjectStreamException::class)
    private fun readResolve(): Any {
      return AccessTokenAppIdPair(accessTokenString, appId)
    }

    companion object {
      private const val serialVersionUID = -2488473066578201069L
    }
  }
  @Throws(ObjectStreamException::class)
  private fun writeReplace(): Any {
    return SerializationProxyV1(accessTokenString, applicationId)
  }

  companion object {
    private const val serialVersionUID = 1L
  }
}


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

package com.facebook.appevents

import android.preference.PreferenceManager
import android.util.Log
import com.facebook.FacebookSdk
import com.facebook.appevents.InternalAppEventsLogger.Companion.getAnalyticsExecutor
import com.facebook.appevents.internal.AppEventUtility.assertIsNotMainThread
import java.util.concurrent.locks.ReentrantReadWriteLock

internal object AnalyticsUserIDStore {
  private val TAG = AnalyticsUserIDStore::class.java.simpleName
  private const val ANALYTICS_USER_ID_KEY = "com.facebook.appevents.AnalyticsUserIDStore.userID"
  private val lock = ReentrantReadWriteLock()
  private var userID: String? = null

  @Volatile private var initialized = false
  @JvmStatic
  fun initStore() {
    if (initialized) {
      return
    }
    getAnalyticsExecutor().execute { initAndWait() }
  }

  @JvmStatic
  fun setUserID(id: String?) {
    assertIsNotMainThread()
    if (!initialized) {
      Log.w(TAG, "initStore should have been called before calling setUserID")
      initAndWait()
    }
    getAnalyticsExecutor().execute {
      lock.writeLock().lock()
      try {
        userID = id
        val sharedPreferences =
            PreferenceManager.getDefaultSharedPreferences(FacebookSdk.getApplicationContext())
        val editor = sharedPreferences.edit()
        editor.putString(ANALYTICS_USER_ID_KEY, userID)
        editor.apply()
      } finally {
        lock.writeLock().unlock()
      }
    }
  }

  @JvmStatic
  fun getUserID(): String? {
    if (!initialized) {
      Log.w(TAG, "initStore should have been called before calling setUserID")
      initAndWait()
    }
    lock.readLock().lock()
    return try {
      userID
    } finally {
      lock.readLock().unlock()
    }
  }

  private fun initAndWait() {
    if (initialized) {
      return
    }
    lock.writeLock().lock()
    try {
      if (initialized) {
        return
      }
      val sharedPreferences =
          PreferenceManager.getDefaultSharedPreferences(FacebookSdk.getApplicationContext())
      userID = sharedPreferences.getString(ANALYTICS_USER_ID_KEY, null)
      initialized = true
    } finally {
      lock.writeLock().unlock()
    }
  }
}


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

package com.facebook.appevents

import android.os.Build
import android.os.Bundle
import androidx.annotation.RestrictTo
import com.facebook.FacebookException
import com.facebook.LoggingBehavior
import com.facebook.appevents.eventdeactivation.EventDeactivationManager.processDeprecatedParameters
import com.facebook.appevents.integrity.IntegrityManager
import com.facebook.appevents.internal.AppEventUtility.bytesToHex
import com.facebook.appevents.internal.Constants
import com.facebook.appevents.restrictivedatafilter.RestrictiveDataManager.processEvent
import com.facebook.appevents.restrictivedatafilter.RestrictiveDataManager.processParameters
import com.facebook.internal.Logger.Companion.log
import com.facebook.internal.Utility.logd
import java.io.ObjectStreamException
import java.io.Serializable
import java.io.UnsupportedEncodingException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.Locale
import java.util.UUID
import org.json.JSONException
import org.json.JSONObject

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
class AppEvent : Serializable {
  val jsonObject: JSONObject
  val isImplicit: Boolean
  private val inBackground: Boolean
  val name: String
  private val checksum: String?

  @Throws(JSONException::class, FacebookException::class)
  constructor(
      contextName: String,
      eventName: String,
      valueToSum: Double?,
      parameters: Bundle?,
      isImplicitlyLogged: Boolean,
      isInBackground: Boolean,
      currentSessionId: UUID?
  ) {
    isImplicit = isImplicitlyLogged
    inBackground = isInBackground
    name = eventName
    jsonObject =
        getJSONObjectForAppEvent(contextName, eventName, valueToSum, parameters, currentSessionId)
    checksum = calculateChecksum()
  }

  private constructor(
      jsonString: String,
      isImplicit: Boolean,
      inBackground: Boolean,
      checksum: String?
  ) {
    jsonObject = JSONObject(jsonString)
    this.isImplicit = isImplicit
    name = jsonObject.optString(Constants.EVENT_NAME_EVENT_KEY)
    this.checksum = checksum
    this.inBackground = inBackground
  }

  fun getIsImplicit(): Boolean = isImplicit

  fun getJSONObject(): JSONObject = jsonObject

  // for old events we don't have a checksum
  val isChecksumValid: Boolean
    get() =
        if (checksum == null) {
          // for old events we don't have a checksum
          true
        } else calculateChecksum() == checksum

  private fun getJSONObjectForAppEvent(
      contextName: String,
      eventName: String,
      valueToSum: Double?,
      parameters: Bundle?,
      currentSessionId: UUID?
  ): JSONObject {
    var eventName = eventName
    validateIdentifier(eventName)
    val eventObject = JSONObject()
    eventName = processEvent(eventName)
    eventObject.put(Constants.EVENT_NAME_EVENT_KEY, eventName)
    eventObject.put(Constants.EVENT_NAME_MD5_EVENT_KEY, md5Checksum(eventName))
    eventObject.put(Constants.LOG_TIME_APP_EVENT_KEY, System.currentTimeMillis() / 1000)
    eventObject.put("_ui", contextName)
    if (currentSessionId != null) {
      eventObject.put("_session_id", currentSessionId)
    }
    if (parameters != null) {
      val processedParam = validateParameters(parameters)
      for (key in processedParam.keys) {
        eventObject.put(key, processedParam[key])
      }
    }
    if (valueToSum != null) {
      eventObject.put(AppEventsConstants.EVENT_PARAM_VALUE_TO_SUM, valueToSum.toDouble())
    }
    if (inBackground) {
      eventObject.put("_inBackground", "1")
    }
    if (isImplicit) {
      eventObject.put("_implicitlyLogged", "1")
    } else {
      log(LoggingBehavior.APP_EVENTS, "AppEvents", "Created app event '%s'", eventObject.toString())
    }
    return eventObject
  }

  private fun validateParameters(parameters: Bundle): Map<String, String?> {
    val paramMap: MutableMap<String, String> = hashMapOf()
    for (key in parameters.keySet()) {
      validateIdentifier(key)
      val value = parameters[key]
      if (value !is String && value !is Number) {
        throw FacebookException(
            String.format(
                "Parameter value '%s' for key '%s' should be a string" + " or a numeric type.",
                value,
                key))
      }
      paramMap[key] = value.toString()
    }
    IntegrityManager.processParameters(paramMap)
    processParameters(paramMap as MutableMap<String, String?>, name)
    processDeprecatedParameters(paramMap as MutableMap<String, String?>, name)
    return paramMap
  }

  internal class SerializationProxyV2
  constructor(
      private val jsonString: String,
      private val isImplicit: Boolean,
      private val inBackground: Boolean,
      private val checksum: String?
  ) : Serializable {
    @Throws(JSONException::class, ObjectStreamException::class)
    private fun readResolve(): Any {
      return AppEvent(jsonString, isImplicit, inBackground, checksum)
    }

    companion object {
      private const val serialVersionUID = 20160803001L
    }
  }

  @Throws(ObjectStreamException::class)
  private fun writeReplace(): Any {
    return SerializationProxyV2(jsonObject.toString(), isImplicit, inBackground, checksum)
  }

  override fun toString(): String {
    return String.format(
        "\"%s\", implicit: %b, json: %s",
        jsonObject.optString("_eventName"),
        isImplicit,
        jsonObject.toString())
  }

  private fun calculateChecksum(): String {
    // jsonObject.toString() doesn't guarantee order of the keys on KitKat
    // (API Level 19) and below as JSONObject used HashMap internally,
    // starting Android API Level 20+, JSONObject changed to use LinkedHashMap
    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
      return md5Checksum(jsonObject.toString())
    }
    val keys = arrayListOf<String>()
    val iterator = jsonObject.keys()
    while (iterator.hasNext()) {
      keys.add(iterator.next())
    }
    keys.sort()
    val sb = StringBuilder()
    for (key in keys) {
      sb.append(key).append(" = ").append(jsonObject.optString(key)).append('\n')
    }
    return md5Checksum(sb.toString())
  }

  companion object {
    private const val serialVersionUID = 1L
    private val validatedIdentifiers = HashSet<String>()
    private const val MAX_IDENTIFIER_LENGTH = 40

    // throw exception if not valid.
    private fun validateIdentifier(identifier: String) {

      // Identifier should be 40 chars or less, and only have 0-9A-Za-z, underscore, hyphen,
      // and space (but no hyphen or space in the first position).
      var identifier: String? = identifier
      val regex = "^[0-9a-zA-Z_]+[0-9a-zA-Z _-]*$"
      if (identifier == null || identifier.isEmpty() || identifier.length > MAX_IDENTIFIER_LENGTH) {
        if (identifier == null) {
          identifier = "<None Provided>"
        }
        throw FacebookException(
            String.format(
                Locale.ROOT,
                "Identifier '%s' must be less than %d characters",
                identifier,
                MAX_IDENTIFIER_LENGTH))
      }
      var alreadyValidated: Boolean
      synchronized(validatedIdentifiers) {
        alreadyValidated = validatedIdentifiers.contains(identifier)
      }
      if (!alreadyValidated) {
        if (identifier.matches(Regex(regex))) {
          synchronized(validatedIdentifiers) { validatedIdentifiers.add(identifier) }
        } else {
          throw FacebookException(
              String.format(
                  "Skipping event named '%s' due to illegal name - must be " +
                      "under 40 chars and alphanumeric, _, - or space, and " +
                      "not start with a space or hyphen.",
                  identifier))
        }
      }
    }

    private fun md5Checksum(toHash: String): String {
      val hash: String
      try {
        val digest = MessageDigest.getInstance("MD5")
        var bytes = toHash.toByteArray(charset("UTF-8"))
        digest.update(bytes, 0, bytes.size)
        bytes = digest.digest()
        hash = bytesToHex(bytes)
      } catch (e: NoSuchAlgorithmException) {
        logd("Failed to generate checksum: ", e)
        return "0"
      } catch (e: UnsupportedEncodingException) {
        logd("Failed to generate checksum: ", e)
        return "1"
      }
      return hash
    }
  }
}


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

package com.facebook.appevents

import com.facebook.FacebookSdk
import com.facebook.internal.AttributionIdentifiers.Companion.getAttributionIdentifiers
import java.util.HashMap

internal class AppEventCollection {
  private val stateMap: HashMap<AccessTokenAppIdPair, SessionEventsState> = HashMap()

  @Synchronized
  fun addPersistedEvents(persistedEvents: PersistedEvents?) {
    if (persistedEvents == null) {
      return
    }
    for (entry in persistedEvents.entrySet()) {
      val state = getSessionEventsState(entry.key)
      if (state != null) {
        for (appEvent in entry.value) {
          state.addEvent(appEvent)
        }
      }
    }
  }

  @Synchronized
  fun addEvent(accessTokenAppIdPair: AccessTokenAppIdPair, appEvent: AppEvent) {
    getSessionEventsState(accessTokenAppIdPair)?.addEvent(appEvent)
  }

  @Synchronized fun keySet(): Set<AccessTokenAppIdPair> = stateMap.keys

  @Synchronized
  operator fun get(accessTokenAppIdPair: AccessTokenAppIdPair): SessionEventsState? {
    return stateMap[accessTokenAppIdPair]
  }

  @get:Synchronized
  val eventCount: Int
    get() {
      var count = 0
      for (sessionEventsState in stateMap.values) {
        count += sessionEventsState.accumulatedEventCount
      }
      return count
    }

  @Synchronized
  private fun getSessionEventsState(accessTokenAppId: AccessTokenAppIdPair): SessionEventsState? {
    var eventsState = stateMap[accessTokenAppId]
    if (eventsState == null) {
      val context = FacebookSdk.getApplicationContext()

      // Retrieve attributionId, but we will only send it if attribution is supported for the
      // app.
      val identifier = getAttributionIdentifiers(context)
      if (identifier != null) {
        eventsState =
            SessionEventsState(identifier, AppEventsLogger.getAnonymousAppDeviceGUID(context))
      }
    }
    if (eventsState == null) {
      return null
    }
    stateMap[accessTokenAppId] = eventsState
    return eventsState
  }
}


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

package com.facebook.appevents

import android.util.Log
import com.facebook.FacebookSdk
import com.facebook.appevents.internal.AppEventUtility.assertIsNotMainThread
import com.facebook.internal.Utility.closeQuietly
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.ObjectStreamClass

/** Responsible for reading and writing app events to/from disk */
internal object AppEventDiskStore {
  private val TAG = AppEventDiskStore::class.java.name
  private const val PERSISTED_EVENTS_FILENAME = "AppEventsLogger.persistedevents"

  // Only call from singleThreadExecutor
  @Synchronized
  @JvmStatic
  fun readAndClearStore(): PersistedEvents {
    assertIsNotMainThread()
    var ois: MovedClassObjectInputStream? = null
    var persistedEvents: PersistedEvents? = null
    val context = FacebookSdk.getApplicationContext()
    try {
      val inputStream: InputStream = context.openFileInput(PERSISTED_EVENTS_FILENAME)
      ois = MovedClassObjectInputStream(BufferedInputStream(inputStream))
      persistedEvents = ois.readObject() as PersistedEvents
    } catch (e: FileNotFoundException) {
      // Expected if we never persisted any events.
    } catch (e: Exception) {
      Log.w(TAG, "Got unexpected exception while reading events: ", e)
    } finally {
      closeQuietly(ois)
      try {
        // Note: We delete the store before we store the events; this means we'd
        // prefer to lose some events in the case of exception rather than
        // potentially log them twice.
        // Always delete this file after the above try catch to recover from read
        // errors.
        context.getFileStreamPath(PERSISTED_EVENTS_FILENAME).delete()
      } catch (ex: Exception) {
        Log.w(TAG, "Got unexpected exception when removing events file: ", ex)
      }
    }
    if (persistedEvents == null) {
      persistedEvents = PersistedEvents()
    }
    return persistedEvents
  }

  // Only call from singleThreadExecutor
  @JvmStatic
  internal fun saveEventsToDisk(eventsToPersist: PersistedEvents?) {
    var oos: ObjectOutputStream? = null
    val context = FacebookSdk.getApplicationContext()
    try {
      oos =
          ObjectOutputStream(
              BufferedOutputStream(context.openFileOutput(PERSISTED_EVENTS_FILENAME, 0)))
      oos.writeObject(eventsToPersist)
    } catch (t: Throwable) {
      Log.w(TAG, "Got unexpected exception while persisting events: ", t)
      try {
        context.getFileStreamPath(PERSISTED_EVENTS_FILENAME).delete()
      } catch (innerException: Exception) {
        // ignore
      }
    } finally {
      closeQuietly(oos)
    }
  }

  private class MovedClassObjectInputStream(inputStream: InputStream?) :
      ObjectInputStream(inputStream) {
    @Throws(IOException::class, ClassNotFoundException::class)
    override fun readClassDescriptor(): ObjectStreamClass {
      var resultClassDescriptor = super.readClassDescriptor()
      if (resultClassDescriptor.name ==
          ACCESS_TOKEN_APP_ID_PAIR_SERIALIZATION_PROXY_V1_CLASS_NAME) {
        resultClassDescriptor =
            ObjectStreamClass.lookup(AccessTokenAppIdPair.SerializationProxyV1::class.java)
      } else if (resultClassDescriptor.name == APP_EVENT_SERIALIZATION_PROXY_V1_CLASS_NAME) {
        resultClassDescriptor = ObjectStreamClass.lookup(AppEvent.SerializationProxyV2::class.java)
      }
      return resultClassDescriptor
    }

    companion object {
      private const val ACCESS_TOKEN_APP_ID_PAIR_SERIALIZATION_PROXY_V1_CLASS_NAME =
          "com.facebook.appevents.AppEventsLogger\$AccessTokenAppIdPair\$SerializationProxyV1"
      private const val APP_EVENT_SERIALIZATION_PROXY_V1_CLASS_NAME =
          "com.facebook.appevents.AppEventsLogger\$AppEvent\$SerializationProxyV2"
    }
  }
}


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

package com.facebook.appevents

import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.facebook.FacebookSdk
import com.facebook.GraphRequest
import com.facebook.GraphRequest.Companion.newPostRequest
import com.facebook.GraphResponse
import com.facebook.LoggingBehavior
import com.facebook.appevents.AppEventDiskStore.readAndClearStore
import com.facebook.appevents.AppEventStore.persistEvents
import com.facebook.appevents.InternalAppEventsLogger.Companion.getPushNotificationsRegistrationId
import com.facebook.appevents.cloudbridge.AppEventsCAPIManager
import com.facebook.appevents.cloudbridge.AppEventsConversionsAPITransformerWebRequests
import com.facebook.internal.FetchedAppSettingsManager.queryAppSettings
import com.facebook.internal.Logger.Companion.log
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import org.json.JSONArray
import org.json.JSONException

@AutoHandleExceptions
internal object AppEventQueue {
  private val TAG = AppEventQueue::class.java.name
  private val NUM_LOG_EVENTS_TO_TRY_TO_FLUSH_AFTER = 100
  private const val FLUSH_PERIOD_IN_SECONDS = 15
  private const val NO_CONNECTIVITY_ERROR_CODE = -1

  @Volatile private var appEventCollection = AppEventCollection()
  private val singleThreadExecutor = Executors.newSingleThreadScheduledExecutor()
  private var scheduledFuture: ScheduledFuture<*>? = null

  // Only call for the singleThreadExecutor
  private val flushRunnable = Runnable {
    scheduledFuture = null
    if (AppEventsLogger.getFlushBehavior() != AppEventsLogger.FlushBehavior.EXPLICIT_ONLY) {
      flushAndWait(FlushReason.TIMER)
    }
  }

  @JvmStatic
  fun persistToDisk() {
    singleThreadExecutor.execute {
      persistEvents(appEventCollection)
      appEventCollection = AppEventCollection()
    }
  }

  @JvmStatic
  fun flush(reason: FlushReason) {
    singleThreadExecutor.execute { flushAndWait(reason) }
  }

  @JvmStatic
  fun add(accessTokenAppId: AccessTokenAppIdPair, appEvent: AppEvent) {
    singleThreadExecutor.execute {
      appEventCollection.addEvent(accessTokenAppId, appEvent)
      if (AppEventsLogger.getFlushBehavior() != AppEventsLogger.FlushBehavior.EXPLICIT_ONLY &&
          appEventCollection.eventCount > NUM_LOG_EVENTS_TO_TRY_TO_FLUSH_AFTER) {
        flushAndWait(FlushReason.EVENT_THRESHOLD)
      } else if (scheduledFuture == null) {
        scheduledFuture =
            singleThreadExecutor.schedule(
                flushRunnable, FLUSH_PERIOD_IN_SECONDS.toLong(), TimeUnit.SECONDS)
      }
    }
  }

  @JvmStatic
  fun getKeySet(): Set<AccessTokenAppIdPair> {
    // This is safe to call outside of the singleThreadExecutor since
    // the appEventCollection is volatile and the modifying methods within the
    // class are synchronized.
    return appEventCollection.keySet()
  }

  @JvmStatic
  fun flushAndWait(reason: FlushReason) {
    // Read and send any persisted events
    val result = readAndClearStore()
    // Add any of the persisted app events to our list of events to send
    appEventCollection.addPersistedEvents(result)
    val flushResults: FlushStatistics?
    flushResults =
        try {
          sendEventsToServer(reason, appEventCollection)
        } catch (e: Exception) {
          Log.w(TAG, "Caught unexpected exception while flushing app events: ", e)
          return
        }
    if (flushResults != null) {
      val intent = Intent(AppEventsLogger.ACTION_APP_EVENTS_FLUSHED)
      intent.putExtra(AppEventsLogger.APP_EVENTS_EXTRA_NUM_EVENTS_FLUSHED, flushResults.numEvents)
      intent.putExtra(AppEventsLogger.APP_EVENTS_EXTRA_FLUSH_RESULT, flushResults.result)
      val context = FacebookSdk.getApplicationContext()
      LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
    }
  }

  @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
  @JvmStatic
  fun sendEventsToServer(
      reason: FlushReason,
      appEventCollection: AppEventCollection
  ): FlushStatistics? {
    val flushResults = FlushStatistics()
    val requestsToExecute = buildRequests(appEventCollection, flushResults)
    if (requestsToExecute.isNotEmpty()) {
      log(
          LoggingBehavior.APP_EVENTS,
          TAG,
          "Flushing %d events due to %s.",
          flushResults.numEvents,
          reason.toString())
      for (request in requestsToExecute) {
        // Execute the request synchronously. Callbacks will take care of handling errors
        // and updating our final overall result.
        request.executeAndWait()
      }
      return flushResults
    }
    return null
  }

  @JvmStatic
  fun buildRequests(
      appEventCollection: AppEventCollection,
      flushResults: FlushStatistics
  ): List<GraphRequest> {
    val context = FacebookSdk.getApplicationContext()
    val limitEventUsage = FacebookSdk.getLimitEventAndDataUsage(context)
    val requestsToExecute = ArrayList<GraphRequest>()
    for (accessTokenAppId in appEventCollection.keySet()) {
      val request =
          buildRequestForSession(
              accessTokenAppId,
              checkNotNull(appEventCollection[accessTokenAppId]),
              limitEventUsage,
              flushResults)

      if (request != null) {
        requestsToExecute.add(request)
        if (AppEventsCAPIManager.isEnabled) {
          AppEventsConversionsAPITransformerWebRequests.transformGraphRequestAndSendToCAPIGEndPoint(
              request)
        }
      }
    }
    return requestsToExecute
  }

  @JvmStatic
  fun buildRequestForSession(
      accessTokenAppId: AccessTokenAppIdPair,
      appEvents: SessionEventsState,
      limitEventUsage: Boolean,
      flushState: FlushStatistics
  ): GraphRequest? {
    val applicationId = accessTokenAppId.applicationId
    val fetchedAppSettings = queryAppSettings(applicationId, false)
    val postRequest =
        newPostRequest(null, String.format("%s/activities", applicationId), null, null)
    postRequest.setForceApplicationRequest(true)
    var requestParameters = postRequest.parameters
    if (requestParameters == null) {
      requestParameters = Bundle()
    }
    requestParameters.putString("access_token", accessTokenAppId.accessTokenString)
    val pushNotificationsRegistrationId = getPushNotificationsRegistrationId()
    if (pushNotificationsRegistrationId != null) {
      requestParameters.putString("device_token", pushNotificationsRegistrationId)
    }
    val installReferrer = AppEventsLoggerImpl.getInstallReferrer()
    if (installReferrer != null) {
      requestParameters.putString("install_referrer", installReferrer)
    }
    postRequest.parameters = requestParameters
    var supportsImplicitLogging = false
    if (fetchedAppSettings != null) {
      supportsImplicitLogging = fetchedAppSettings.supportsImplicitLogging()
    }
    val numEvents =
        appEvents.populateRequest(
            postRequest,
            FacebookSdk.getApplicationContext(),
            supportsImplicitLogging,
            limitEventUsage)
    if (numEvents == 0) {
      return null
    }
    flushState.numEvents += numEvents
    postRequest.callback =
        GraphRequest.Callback { response ->
          handleResponse(accessTokenAppId, postRequest, response, appEvents, flushState)
        }
    return postRequest
  }

  @JvmStatic
  fun handleResponse(
      accessTokenAppId: AccessTokenAppIdPair,
      request: GraphRequest,
      response: GraphResponse,
      appEvents: SessionEventsState,
      flushState: FlushStatistics
  ) {
    val error = response.error
    var resultDescription = "Success"
    var flushResult = FlushResult.SUCCESS
    if (error != null) {
      if (error.errorCode == NO_CONNECTIVITY_ERROR_CODE) {
        resultDescription = "Failed: No Connectivity"
        flushResult = FlushResult.NO_CONNECTIVITY
      } else {
        resultDescription =
            String.format(
                "Failed:\n  Response: %s\n  Error %s", response.toString(), error.toString())
        flushResult = FlushResult.SERVER_ERROR
      }
    }
    if (FacebookSdk.isLoggingBehaviorEnabled(LoggingBehavior.APP_EVENTS)) {
      val eventsJsonString = request.tag as String?
      val prettyPrintedEvents: String
      prettyPrintedEvents =
          try {
            val jsonArray = JSONArray(eventsJsonString)
            jsonArray.toString(2)
          } catch (exc: JSONException) {
            "<Can't encode events for debug logging>"
          }
      log(
          LoggingBehavior.APP_EVENTS,
          TAG,
          "Flush completed\nParams: %s\n  Result: %s\n  Events JSON: %s",
          request.graphObject.toString(),
          resultDescription,
          prettyPrintedEvents)
    }
    appEvents.clearInFlightAndStats(error != null)
    if (flushResult === FlushResult.NO_CONNECTIVITY) {
      // We may call this for multiple requests in a batch, which is slightly inefficient
      // since in principle we could call it once for all failed requests, but the impact is
      // likely to be minimal. We don't call this for other server errors, because if an event
      // failed because it was malformed, etc., continually retrying it will cause subsequent
      // events to not be logged either.
      FacebookSdk.getExecutor().execute { persistEvents(accessTokenAppId, appEvents) }
    }
    if (flushResult !== FlushResult.SUCCESS) {
      // We assume that connectivity issues are more significant to report than server issues.
      if (flushState.result !== FlushResult.NO_CONNECTIVITY) {
        flushState.result = flushResult
      }
    }
  }
}