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

import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.util.Base64
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.Button
import android.widget.CheckBox
import android.widget.DatePicker
import android.widget.EditText
import android.widget.ImageView
import android.widget.RadioButton
import android.widget.RadioGroup
import android.widget.RatingBar
import android.widget.Spinner
import android.widget.Switch
import android.widget.TextView
import android.widget.TimePicker
import androidx.annotation.RestrictTo
import com.facebook.appevents.codeless.internal.SensitiveUserDataUtils.isSensitiveUserData
import com.facebook.appevents.internal.ViewHierarchyConstants.ADAPTER_VIEW_ITEM_BITMASK
import com.facebook.appevents.internal.ViewHierarchyConstants.BUTTON_BITMASK
import com.facebook.appevents.internal.ViewHierarchyConstants.CHECKBOX_BITMASK
import com.facebook.appevents.internal.ViewHierarchyConstants.CHILDREN_VIEW_KEY
import com.facebook.appevents.internal.ViewHierarchyConstants.CLASS_NAME_KEY
import com.facebook.appevents.internal.ViewHierarchyConstants.CLASS_TYPE_BITMASK_KEY
import com.facebook.appevents.internal.ViewHierarchyConstants.CLICKABLE_VIEW_BITMASK
import com.facebook.appevents.internal.ViewHierarchyConstants.DESC_KEY
import com.facebook.appevents.internal.ViewHierarchyConstants.DIMENSION_HEIGHT_KEY
import com.facebook.appevents.internal.ViewHierarchyConstants.DIMENSION_KEY
import com.facebook.appevents.internal.ViewHierarchyConstants.DIMENSION_LEFT_KEY
import com.facebook.appevents.internal.ViewHierarchyConstants.DIMENSION_SCROLL_X_KEY
import com.facebook.appevents.internal.ViewHierarchyConstants.DIMENSION_SCROLL_Y_KEY
import com.facebook.appevents.internal.ViewHierarchyConstants.DIMENSION_TOP_KEY
import com.facebook.appevents.internal.ViewHierarchyConstants.DIMENSION_VISIBILITY_KEY
import com.facebook.appevents.internal.ViewHierarchyConstants.DIMENSION_WIDTH_KEY
import com.facebook.appevents.internal.ViewHierarchyConstants.HINT_KEY
import com.facebook.appevents.internal.ViewHierarchyConstants.ICON_BITMAP
import com.facebook.appevents.internal.ViewHierarchyConstants.ID_KEY
import com.facebook.appevents.internal.ViewHierarchyConstants.IMAGEVIEW_BITMASK
import com.facebook.appevents.internal.ViewHierarchyConstants.INPUT_BITMASK
import com.facebook.appevents.internal.ViewHierarchyConstants.IS_USER_INPUT_KEY
import com.facebook.appevents.internal.ViewHierarchyConstants.LABEL_BITMASK
import com.facebook.appevents.internal.ViewHierarchyConstants.PICKER_BITMASK
import com.facebook.appevents.internal.ViewHierarchyConstants.RADIO_GROUP_BITMASK
import com.facebook.appevents.internal.ViewHierarchyConstants.RATINGBAR_BITMASK
import com.facebook.appevents.internal.ViewHierarchyConstants.REACT_NATIVE_BUTTON_BITMASK
import com.facebook.appevents.internal.ViewHierarchyConstants.SWITCH_BITMASK
import com.facebook.appevents.internal.ViewHierarchyConstants.TAG_KEY
import com.facebook.appevents.internal.ViewHierarchyConstants.TEXTVIEW_BITMASK
import com.facebook.appevents.internal.ViewHierarchyConstants.TEXT_IS_BOLD
import com.facebook.appevents.internal.ViewHierarchyConstants.TEXT_IS_ITALIC
import com.facebook.appevents.internal.ViewHierarchyConstants.TEXT_KEY
import com.facebook.appevents.internal.ViewHierarchyConstants.TEXT_SIZE
import com.facebook.appevents.internal.ViewHierarchyConstants.TEXT_STYLE
import com.facebook.internal.Utility.coerceValueIfNullOrEmpty
import com.facebook.internal.Utility.logd
import com.facebook.internal.Utility.sha256hash
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import java.io.ByteArrayOutputStream
import java.lang.ref.WeakReference
import java.lang.reflect.Field
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject

@AutoHandleExceptions
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
object ViewHierarchy {
  private val TAG = ViewHierarchy::class.java.canonicalName

  // React Native class names
  private const val CLASS_RCTROOTVIEW = "com.facebook.react.ReactRootView"
  private const val CLASS_RCTVIEWGROUP = "com.facebook.react.views.view.ReactViewGroup"
  private const val CLASS_TOUCHTARGETHELPER = "com.facebook.react.uimanager.TouchTargetHelper"

  // TouchTargetHelper method names
  private const val METHOD_FIND_TOUCHTARGET_VIEW = "findTouchTargetView"
  private const val ICON_MAX_EDGE_LENGTH = 44
  private var RCTRootViewReference = WeakReference<View?>(null)
  private var methodFindTouchTargetView: Method? = null

  @JvmStatic
  fun getParentOfView(view: View?): ViewGroup? {
    if (null == view) {
      return null
    }
    val parent = view.parent
    return if (parent is ViewGroup) {
      parent
    } else null
  }

  @JvmStatic
  fun getChildrenOfView(view: View?): List<View> {
    val children = ArrayList<View>()
    if (view is ViewGroup) {
      val count = view.childCount
      for (i in 0 until count) {
        children.add(view.getChildAt(i))
      }
    }
    return children
  }

  @JvmStatic
  fun updateBasicInfoOfView(view: View, json: JSONObject) {
    try {
      val text = getTextOfView(view)
      val hint = getHintOfView(view)
      val tag = view.tag
      val description = view.contentDescription
      json.put(CLASS_NAME_KEY, view.javaClass.canonicalName)
      json.put(CLASS_TYPE_BITMASK_KEY, getClassTypeBitmask(view))
      json.put(ID_KEY, view.id)
      if (!isSensitiveUserData(view)) {
        json.put(TEXT_KEY, coerceValueIfNullOrEmpty(sha256hash(text), ""))
      } else {
        json.put(TEXT_KEY, "")
        json.put(IS_USER_INPUT_KEY, true)
      }
      json.put(HINT_KEY, coerceValueIfNullOrEmpty(sha256hash(hint), ""))
      if (tag != null) {
        json.put(TAG_KEY, coerceValueIfNullOrEmpty(sha256hash(tag.toString()), ""))
      }
      if (description != null) {
        json.put(DESC_KEY, coerceValueIfNullOrEmpty(sha256hash(description.toString()), ""))
      }
      val dimension = getDimensionOfView(view)
      json.put(DIMENSION_KEY, dimension)
    } catch (e: JSONException) {
      logd(TAG, e)
    }
  }

  @JvmStatic
  fun updateAppearanceOfView(view: View, json: JSONObject, displayDensity: Float) {
    try {
      val textStyle = JSONObject()
      if (view is TextView) {
        val textView = view
        val typeface = textView.typeface
        if (typeface != null) {
          textStyle.put(TEXT_SIZE, textView.textSize.toDouble())
          textStyle.put(TEXT_IS_BOLD, typeface.isBold)
          textStyle.put(TEXT_IS_ITALIC, typeface.isItalic)
          json.put(TEXT_STYLE, textStyle)
        }
      }
      if (view is ImageView) {
        val drawable = view.drawable
        if (drawable is BitmapDrawable) {
          if (view.getHeight() / displayDensity <= ICON_MAX_EDGE_LENGTH &&
              view.getWidth() / displayDensity <= ICON_MAX_EDGE_LENGTH) {
            val bitmap = drawable.bitmap
            if (bitmap != null) {
              val byteArrayOutputStream = ByteArrayOutputStream()
              bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream)
              val byteArray = byteArrayOutputStream.toByteArray()
              val encoded = Base64.encodeToString(byteArray, Base64.DEFAULT)
              json.put(ICON_BITMAP, encoded)
            }
          }
        }
      }
    } catch (e: JSONException) {
      logd(TAG, e)
    }
  }

  @JvmStatic
  fun getDictionaryOfView(view: View): JSONObject {
    if (view.javaClass.name == CLASS_RCTROOTVIEW) {
      RCTRootViewReference = WeakReference(view)
    }
    val json = JSONObject()
    try {
      updateBasicInfoOfView(view, json)
      val childViews = JSONArray()
      val children = getChildrenOfView(view)
      for (i in children.indices) {
        val child = children[i]
        val childInfo = getDictionaryOfView(child)
        childViews.put(childInfo)
      }
      json.put(CHILDREN_VIEW_KEY, childViews)
    } catch (e: JSONException) {
      Log.e(TAG, "Failed to create JSONObject for view.", e)
    }
    return json
  }

  @JvmStatic
  fun getClassTypeBitmask(view: View): Int {
    var bitmask = 0
    if (view is ImageView) {
      bitmask = bitmask or (1 shl IMAGEVIEW_BITMASK)
    }
    if (view.isClickable) {
      bitmask = bitmask or (1 shl CLICKABLE_VIEW_BITMASK)
    }
    if (isAdapterViewItem(view)) {
      bitmask = bitmask or (1 shl ADAPTER_VIEW_ITEM_BITMASK)
    }
    if (view is TextView) {
      bitmask = bitmask or (1 shl LABEL_BITMASK)
      bitmask = bitmask or (1 shl TEXTVIEW_BITMASK)
      if (view is Button) {
        bitmask = bitmask or (1 shl BUTTON_BITMASK)
        if (view is Switch) {
          bitmask = bitmask or (1 shl SWITCH_BITMASK)
        } else if (view is CheckBox) {
          bitmask = bitmask or (1 shl CHECKBOX_BITMASK)
        }
      }
      if (view is EditText) {
        bitmask = bitmask or (1 shl INPUT_BITMASK)
      }
    } else if (view is Spinner || view is DatePicker) {
      bitmask = bitmask or (1 shl PICKER_BITMASK)
    } else if (view is RatingBar) {
      bitmask = bitmask or (1 shl RATINGBAR_BITMASK)
    } else if (view is RadioGroup) {
      bitmask = bitmask or (1 shl RADIO_GROUP_BITMASK)
    } else if (view is ViewGroup && isRCTButton(view, RCTRootViewReference.get())) {
      bitmask = bitmask or (1 shl REACT_NATIVE_BUTTON_BITMASK)
    }
    return bitmask
  }

  @JvmStatic
  private fun isAdapterViewItem(view: View): Boolean {
    val parent = view.parent
    if (parent is AdapterView<*>) {
      return true
    }
    var nestedScrollingChildClass = getExistingClass("android.support.v4.view.NestedScrollingChild")
    if (nestedScrollingChildClass != null && nestedScrollingChildClass.isInstance(parent)) {
      return true
    }
    nestedScrollingChildClass = getExistingClass("androidx.core.view.NestedScrollingChild")
    return nestedScrollingChildClass != null && nestedScrollingChildClass.isInstance(parent)
  }

  @JvmStatic
  fun getTextOfView(view: View?): String {
    var textObj: Any? = null
    if (view is TextView) {
      textObj = view.text
      if (view is Switch) {
        val isOn = view.isChecked
        textObj = if (isOn) "1" else "0"
      }
    } else if (view is Spinner) {
      if (view.count > 0) {
        val selectedItem = view.selectedItem
        if (selectedItem != null) {
          textObj = selectedItem.toString()
        }
      }
    } else if (view is DatePicker) {
      val y = view.year
      val m = view.month
      val d = view.dayOfMonth
      textObj = String.format("%04d-%02d-%02d", y, m, d)
    } else if (view is TimePicker) {
      val h = view.currentHour
      val m = view.currentMinute
      textObj = String.format("%02d:%02d", h, m)
    } else if (view is RadioGroup) {
      val checkedId = view.checkedRadioButtonId
      val childCount = view.childCount
      for (i in 0 until childCount) {
        val child = view.getChildAt(i)
        if (child.id == checkedId && child is RadioButton) {
          textObj = child.text
          break
        }
      }
    } else if (view is RatingBar) {
      val rating = view.rating
      textObj = rating.toString()
    }
    return textObj?.toString() ?: ""
  }

  @JvmStatic
  fun getHintOfView(view: View?): String {
    var hintObj: CharSequence? = null
    if (view is EditText) {
      hintObj = view.hint
    } else if (view is TextView) {
      hintObj = view.hint
    }
    return hintObj?.toString() ?: ""
  }

  private fun getDimensionOfView(view: View): JSONObject {
    val dimension = JSONObject()
    try {
      dimension.put(DIMENSION_TOP_KEY, view.top)
      dimension.put(DIMENSION_LEFT_KEY, view.left)
      dimension.put(DIMENSION_WIDTH_KEY, view.width)
      dimension.put(DIMENSION_HEIGHT_KEY, view.height)
      dimension.put(DIMENSION_SCROLL_X_KEY, view.scrollX)
      dimension.put(DIMENSION_SCROLL_Y_KEY, view.scrollY)
      dimension.put(DIMENSION_VISIBILITY_KEY, view.visibility)
    } catch (e: JSONException) {
      Log.e(TAG, "Failed to create JSONObject for dimension.", e)
    }
    return dimension
  }

  @JvmStatic
  fun getExistingOnClickListener(view: View?): View.OnClickListener? {
    try {
      val listenerInfoField = Class.forName("android.view.View").getDeclaredField("mListenerInfo")
      if (listenerInfoField != null) {
        listenerInfoField.isAccessible = true
      }
      val listenerObj = listenerInfoField[view] ?: return null
      var listener: View.OnClickListener? = null
      val listenerField =
          Class.forName("android.view.View\$ListenerInfo").getDeclaredField("mOnClickListener")
      if (listenerField != null) {
        listenerField.isAccessible = true
        listener = listenerField[listenerObj] as View.OnClickListener
      }
      return listener
    } catch (e: NoSuchFieldException) {
      /* no op */
    } catch (e: ClassNotFoundException) {
      /* no op */
    } catch (e: IllegalAccessException) {
      /* no op */
    }
    return null
  }

  @JvmStatic
  fun setOnClickListener(view: View, newListener: View.OnClickListener?) {
    try {
      var listenerInfoField: Field? = null
      var listenerField: Field? = null
      try {
        listenerInfoField = Class.forName("android.view.View").getDeclaredField("mListenerInfo")
        listenerField =
            Class.forName("android.view.View\$ListenerInfo").getDeclaredField("mOnClickListener")
      } catch (e: ClassNotFoundException) {
        /* no op */
      } catch (e: NoSuchFieldException) {
        /* no op */
      }
      if (listenerInfoField == null || listenerField == null) {
        view.setOnClickListener(newListener)
        return
      }
      listenerInfoField.isAccessible = true
      listenerField.isAccessible = true
      var listenerObj: Any? = null
      try {
        listenerInfoField.isAccessible = true
        listenerObj = listenerInfoField[view]
      } catch (e: IllegalAccessException) {
        /* no op */
      }
      if (listenerObj == null) {
        view.setOnClickListener(newListener)
        return
      }
      listenerField[listenerObj] = newListener
    } catch (e: Exception) {
      /* no op */
    }
  }

  @JvmStatic
  fun getExistingOnTouchListener(view: View?): View.OnTouchListener? {
    try {
      val listenerInfoField = Class.forName("android.view.View").getDeclaredField("mListenerInfo")
      if (listenerInfoField != null) {
        listenerInfoField.isAccessible = true
      }
      val listenerObj = listenerInfoField[view] ?: return null
      var listener: View.OnTouchListener? = null
      val listenerField =
          Class.forName("android.view.View\$ListenerInfo").getDeclaredField("mOnTouchListener")
      if (listenerField != null) {
        listenerField.isAccessible = true
        listener = listenerField[listenerObj] as View.OnTouchListener
      }
      return listener
    } catch (e: NoSuchFieldException) {
      logd(TAG, e)
    } catch (e: ClassNotFoundException) {
      logd(TAG, e)
    } catch (e: IllegalAccessException) {
      logd(TAG, e)
    }
    return null
  }

  private fun getTouchReactView(location: FloatArray?, RCTRootView: View?): View? {
    initTouchTargetHelperMethods()
    if (null == methodFindTouchTargetView || null == RCTRootView) {
      return null
    }
    try {
      val nativeTargetView =
          checkNotNull(methodFindTouchTargetView).invoke(null, location, RCTRootView) as View
      if (nativeTargetView != null && nativeTargetView.id > 0) {
        return nativeTargetView.parent as View
      }
    } catch (e: IllegalAccessException) {
      logd(TAG, e)
    } catch (e: InvocationTargetException) {
      logd(TAG, e)
    }
    return null
  }

  fun isRCTButton(view: View, RCTRootView: View?): Boolean {
    // React Native Button and Touchable components are all ReactViewGroup
    val className = view.javaClass.name
    if (className == CLASS_RCTVIEWGROUP) {
      val location = getViewLocationOnScreen(view)
      val touchTargetView = getTouchReactView(location, RCTRootView)
      return touchTargetView != null && touchTargetView.id == view.id
    }
    return false
  }

  private fun isRCTRootView(view: View): Boolean {
    return view.javaClass.name == CLASS_RCTROOTVIEW
  }

  @JvmStatic
  fun findRCTRootView(view: View?): View? {
    var view = view
    while (null != view) {
      if (isRCTRootView(view)) {
        return view
      }
      val viewParent = view.parent
      view =
          if (viewParent is View) {
            viewParent
          } else {
            break
          }
    }
    return null
  }

  private fun getViewLocationOnScreen(view: View): FloatArray {
    val location = IntArray(2)
    view.getLocationOnScreen(location)
    return floatArrayOf(location[0].toFloat(), location[1].toFloat())
  }

  private fun initTouchTargetHelperMethods() {
    if (null != methodFindTouchTargetView) {
      return
    }
    try {
      val RCTTouchTargetHelper = Class.forName(CLASS_TOUCHTARGETHELPER)
      methodFindTouchTargetView =
          RCTTouchTargetHelper.getDeclaredMethod(
              METHOD_FIND_TOUCHTARGET_VIEW, FloatArray::class.java, ViewGroup::class.java)
      checkNotNull(methodFindTouchTargetView).isAccessible = true
    } catch (e: ClassNotFoundException) {
      logd(TAG, e)
    } catch (e: NoSuchMethodException) {
      logd(TAG, e)
    }
  }

  private fun getExistingClass(className: String): Class<*>? {
    return try {
      Class.forName(className)
    } catch (e: ClassNotFoundException) {
      null
    }
  }
}


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

import androidx.annotation.RestrictTo
import com.facebook.FacebookSdk
import com.facebook.appevents.AppEvent
import com.facebook.internal.FetchedAppSettingsManager.queryAppSettings
import com.facebook.internal.Utility.convertJSONArrayToList
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import java.lang.Exception
import java.util.ArrayList
import java.util.HashSet
import org.json.JSONObject

@AutoHandleExceptions
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
object EventDeactivationManager {
  private var enabled = false
  private val deprecatedParamFilters: MutableList<DeprecatedParamFilter> = ArrayList()
  private val deprecatedEvents: MutableSet<String> = HashSet()

  @JvmStatic
  fun enable() {
    enabled = true
    initialize()
  }

  @Synchronized
  private fun initialize() {
    try {
      val settings = queryAppSettings(FacebookSdk.getApplicationId(), false) ?: return
      val eventFilterResponse = settings.restrictiveDataSetting
      if (eventFilterResponse !== null && eventFilterResponse.isNotEmpty()) {
        val jsonObject = JSONObject(eventFilterResponse)
        deprecatedParamFilters.clear()
        val keys = jsonObject.keys()
        while (keys.hasNext()) {
          val key = keys.next()
          val json = jsonObject.getJSONObject(key)
          if (json != null) {
            if (json.optBoolean("is_deprecated_event")) {
              deprecatedEvents.add(key)
            } else {
              val deprecatedParamJsonArray = json.optJSONArray("deprecated_param")
              val deprecatedParamFilter = DeprecatedParamFilter(key, ArrayList())
              if (deprecatedParamJsonArray != null) {
                deprecatedParamFilter.deprecateParams =
                    convertJSONArrayToList(deprecatedParamJsonArray)
              }
              deprecatedParamFilters.add(deprecatedParamFilter)
            }
          }
        }
      }
    } catch (e: Exception) {
      /* swallow */
    }
  }

  @JvmStatic
  fun processEvents(events: MutableList<AppEvent>) {
    if (!enabled) {
      return
    }
    val iterator = events.iterator()
    while (iterator.hasNext()) {
      val event = iterator.next()
      if (deprecatedEvents.contains(event.name)) {
        iterator.remove()
      }
    }
  }

  @JvmStatic
  fun processDeprecatedParameters(parameters: MutableMap<String, String?>, eventName: String) {
    if (!enabled) {
      return
    }
    val keys: List<String> = ArrayList(parameters.keys)
    val deprecatedParamFiltersCopy: List<DeprecatedParamFilter> = ArrayList(deprecatedParamFilters)
    for (filter in deprecatedParamFiltersCopy) {
      if (filter.eventName != eventName) {
        continue
      }
      for (key in keys) {
        if (filter.deprecateParams.contains(key)) {
          parameters.remove(key)
        }
      }
    }
  }

  internal class DeprecatedParamFilter(var eventName: String, var deprecateParams: List<String>)
}


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

import android.app.Activity
import android.app.Application
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import com.facebook.FacebookSdk.getApplicationContext
import com.facebook.FacebookSdk.getExecutor
import com.facebook.appevents.internal.AutomaticAnalyticsLogger.isImplicitPurchaseLoggingEnabled
import com.facebook.appevents.internal.AutomaticAnalyticsLogger.logPurchase
import java.util.concurrent.atomic.AtomicBoolean
import org.json.JSONException
import org.json.JSONObject

object InAppPurchaseActivityLifecycleTracker {
  private val TAG = InAppPurchaseActivityLifecycleTracker::class.java.canonicalName
  private const val SERVICE_INTERFACE_NAME =
      "com.android.vending.billing.IInAppBillingService\$Stub"
  private const val BILLING_ACTIVITY_NAME = "com.android.billingclient.api.ProxyBillingActivity"
  private val isTracking = AtomicBoolean(false)
  private var hasBillingService: Boolean? = null
  private var hasBillingActivity: Boolean? = null
  private lateinit var serviceConnection: ServiceConnection
  private lateinit var callbacks: Application.ActivityLifecycleCallbacks
  private lateinit var intent: Intent
  private var inAppBillingObj: Any? = null

  /** Start iap logging if enable, initialize billing service if not */
  @JvmStatic
  fun startIapLogging() {
    initializeIfNotInitialized()
    if (hasBillingService == false) {
      return
    }
    if (isImplicitPurchaseLoggingEnabled()) {
      startTracking()
    }
  }

  private fun initializeIfNotInitialized() {
    if (hasBillingService != null) {
      return
    }

    hasBillingService = InAppPurchaseUtils.getClass(SERVICE_INTERFACE_NAME) != null
    if (hasBillingService == false) {
      return
    }

    hasBillingActivity = InAppPurchaseUtils.getClass(BILLING_ACTIVITY_NAME) != null

    InAppPurchaseEventManager.clearSkuDetailsCache()

    intent =
        Intent("com.android.vending.billing.InAppBillingService.BIND")
            .setPackage("com.android.vending")
    serviceConnection =
        object : ServiceConnection {
          override fun onServiceConnected(name: ComponentName, service: IBinder) {
            inAppBillingObj =
                InAppPurchaseEventManager.asInterface(getApplicationContext(), service)
          }

          override fun onServiceDisconnected(name: ComponentName) = Unit
        }
    callbacks =
        object : Application.ActivityLifecycleCallbacks {
          override fun onActivityResumed(activity: Activity) {
            try {
              getExecutor().execute {
                val context = getApplicationContext()
                val purchasesInapp =
                    InAppPurchaseEventManager.getPurchasesInapp(context, inAppBillingObj)
                logPurchase(context, purchasesInapp, false)
                val purchasesSubs =
                    InAppPurchaseEventManager.getPurchasesSubs(context, inAppBillingObj)
                logPurchase(context, purchasesSubs, true)
              }
            } catch (ep: Exception) {
              /*no op*/
            }
          }

          override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = Unit
          override fun onActivityStarted(activity: Activity) = Unit
          override fun onActivityPaused(activity: Activity) = Unit
          override fun onActivityStopped(activity: Activity) {
            try {
              if (hasBillingActivity == true && activity.localClassName == BILLING_ACTIVITY_NAME) {
                getExecutor().execute {
                  val context = getApplicationContext()
                  var purchases =
                      InAppPurchaseEventManager.getPurchasesInapp(context, inAppBillingObj)
                  if (purchases.isEmpty()) {
                    purchases =
                        InAppPurchaseEventManager.getPurchaseHistoryInapp(context, inAppBillingObj)
                  }
                  logPurchase(context, purchases, false)
                }
              }
            } catch (ep: Exception) {
              /*no op*/
            }
          }

          override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit
          override fun onActivityDestroyed(activity: Activity) = Unit
        }
  }

  private fun startTracking() {
    if (!isTracking.compareAndSet(false, true)) {
      return
    }
    val context = getApplicationContext()
    if (context is Application) {
      context.registerActivityLifecycleCallbacks(callbacks)
      context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
    }
  }

  private fun logPurchase(context: Context, purchases: ArrayList<String>, isSubscription: Boolean) {
    if (purchases.isEmpty()) {
      return
    }
    val purchaseMap = hashMapOf<String, String>()
    val skuList = arrayListOf<String>()
    for (purchase in purchases) {
      try {
        val purchaseJson = JSONObject(purchase)
        val sku = purchaseJson.getString("productId")
        purchaseMap[sku] = purchase
        skuList.add(sku)
      } catch (e: JSONException) {
        Log.e(TAG, "Error parsing in-app purchase data.", e)
      }
    }
    val skuDetailsMap =
        InAppPurchaseEventManager.getSkuDetails(context, skuList, inAppBillingObj, isSubscription)
    for ((key, value) in skuDetailsMap) {
      purchaseMap[key]?.let { logPurchase(it, value, isSubscription) }
    }
  }
}


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

import android.content.Context
import androidx.annotation.RestrictTo
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions

@AutoHandleExceptions
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
object InAppPurchaseAutoLogger {
  private const val BILLING_CLIENT_PURCHASE_NAME = "com.android.billingclient.api.Purchase"

  @JvmStatic
  fun startIapLogging(context: Context) {
    // check if the app has IAP with Billing Lib
    if (InAppPurchaseUtils.getClass(BILLING_CLIENT_PURCHASE_NAME) == null) {
      return
    }
    val billingClientWrapper =
        InAppPurchaseBillingClientWrapper.getOrCreateInstance(context) ?: return
    if (InAppPurchaseBillingClientWrapper.isServiceConnected.get()) {
      if (InAppPurchaseLoggerManager.eligibleQueryPurchaseHistory()) {
        billingClientWrapper.queryPurchaseHistory("inapp") { logPurchase() }
      } else {
        billingClientWrapper.queryPurchase("inapp") { logPurchase() }
      }
    }
  }

  private fun logPurchase() {
    InAppPurchaseLoggerManager.filterPurchaseLogging(
        InAppPurchaseBillingClientWrapper.purchaseDetailsMap,
        InAppPurchaseBillingClientWrapper.skuDetailsMap)
    InAppPurchaseBillingClientWrapper.purchaseDetailsMap.clear()
  }
}


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

import android.content.Context
import androidx.annotation.RestrictTo
import com.facebook.appevents.iap.InAppPurchaseSkuDetailsWrapper.Companion.getOrCreateInstance
import com.facebook.appevents.iap.InAppPurchaseUtils.getClass
import com.facebook.appevents.iap.InAppPurchaseUtils.getMethod
import com.facebook.appevents.iap.InAppPurchaseUtils.invokeMethod
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArraySet
import java.util.concurrent.atomic.AtomicBoolean
import org.json.JSONException
import org.json.JSONObject

@AutoHandleExceptions
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
class InAppPurchaseBillingClientWrapper
private constructor(
    private val context: Context,
    private val billingClient: Any,
    private val billingClientClazz: Class<*>,
    private val purchaseResultClazz: Class<*>,
    private val purchaseClazz: Class<*>,
    private val skuDetailsClazz: Class<*>,
    private val purchaseHistoryRecordClazz: Class<*>,
    private val skuDetailsResponseListenerClazz: Class<*>,
    private val purchaseHistoryResponseListenerClazz: Class<*>,
    private val queryPurchasesMethod: Method,
    private val getPurchaseListMethod: Method,
    private val getOriginalJsonMethod: Method,
    private val getOriginalJsonSkuMethod: Method,
    private val getOriginalJsonPurchaseHistoryMethod: Method,
    private val querySkuDetailsAsyncMethod: Method,
    private val queryPurchaseHistoryAsyncMethod: Method,
    private val inAppPurchaseSkuDetailsWrapper: InAppPurchaseSkuDetailsWrapper
) {
  private val historyPurchaseSet: MutableSet<String?> = CopyOnWriteArraySet()
  fun queryPurchaseHistory(skuType: String, queryPurchaseHistoryRunnable: Runnable) {
    queryPurchaseHistoryAsync(skuType) {
      querySkuDetailsAsync(IN_APP, ArrayList(historyPurchaseSet), queryPurchaseHistoryRunnable)
    }
  }

  fun queryPurchase(skuType: String, querySkuRunnable: Runnable) {
    // TODO (T67568885): support subs
    val purchaseResult =
        invokeMethod(billingClientClazz, queryPurchasesMethod, billingClient, IN_APP)
    val purchaseObjects =
        invokeMethod(purchaseResultClazz, getPurchaseListMethod, purchaseResult) as? List<*>
            ?: return
    try {
      val skuIDs: MutableList<String?> = arrayListOf()
      for (purchaseObject in purchaseObjects) {
        val purchaseJsonStr =
            invokeMethod(purchaseClazz, getOriginalJsonMethod, purchaseObject) as? String
                ?: continue
        val purchaseJson = JSONObject(purchaseJsonStr)
        if (purchaseJson.has(PRODUCT_ID)) {
          val skuID = purchaseJson.getString(PRODUCT_ID)
          skuIDs.add(skuID)
          purchaseDetailsMap[skuID] = purchaseJson
        }
      }
      querySkuDetailsAsync(skuType, skuIDs, querySkuRunnable)
    } catch (je: JSONException) {
      /* swallow */
    }
  }

  private fun querySkuDetailsAsync(skuType: String, skuIDs: List<String?>, runnable: Runnable) {
    val listenerObj =
        Proxy.newProxyInstance(
            skuDetailsResponseListenerClazz.classLoader,
            arrayOf(skuDetailsResponseListenerClazz),
            SkuDetailsResponseListenerWrapper(runnable))
    val skuDetailsParams = inAppPurchaseSkuDetailsWrapper.getSkuDetailsParams(skuType, skuIDs)
    invokeMethod(
        billingClientClazz,
        querySkuDetailsAsyncMethod,
        billingClient,
        skuDetailsParams,
        listenerObj)
  }

  private fun queryPurchaseHistoryAsync(skuType: String, runnable: Runnable) {
    val listenerObj =
        Proxy.newProxyInstance(
            purchaseHistoryResponseListenerClazz.classLoader,
            arrayOf(purchaseHistoryResponseListenerClazz),
            PurchaseHistoryResponseListenerWrapper(runnable))
    invokeMethod(
        billingClientClazz, queryPurchaseHistoryAsyncMethod, billingClient, skuType, listenerObj)
  }

  private fun startConnection() {
    val listenerClazz = getClass(CLASSNAME_BILLING_CLIENT_STATE_LISTENER) ?: return
    val method = getMethod(billingClientClazz, METHOD_START_CONNECTION, listenerClazz) ?: return
    val listenerObj =
        Proxy.newProxyInstance(
            listenerClazz.classLoader, arrayOf(listenerClazz), BillingClientStateListenerWrapper())
    invokeMethod(billingClientClazz, method, billingClient, listenerObj)
  }

  @AutoHandleExceptions
  internal class BillingClientStateListenerWrapper : InvocationHandler {
    override fun invoke(proxy: Any, m: Method, args: Array<Any>?): Any? {
      if (m.name == METHOD_ON_BILLING_SETUP_FINISHED) {
        isServiceConnected.set(true)
      } else if (m.name.endsWith(METHOD_ON_BILLING_SERVICE_DISCONNECTED)) {
        isServiceConnected.set(false)
      }
      return null
    }
  }

  @AutoHandleExceptions
  internal class PurchasesUpdatedListenerWrapper : InvocationHandler {
    // dummy function, no need to implement onPurchasesUpdated
    override fun invoke(proxy: Any, m: Method, args: Array<Any>?): Any? = null
  }

  @AutoHandleExceptions
  internal inner class PurchaseHistoryResponseListenerWrapper(var runnable: Runnable) :
      InvocationHandler {
    override fun invoke(proxy: Any, method: Method, args: Array<Any>?): Any? {
      if (method.name == METHOD_ON_PURCHASE_HISTORY_RESPONSE) {
        val purchaseHistoryRecordListObject = args?.get(1)
        if (purchaseHistoryRecordListObject != null && purchaseHistoryRecordListObject is List<*>) {
          getPurchaseHistoryRecord(purchaseHistoryRecordListObject)
        }
      }
      return null
    }

    private fun getPurchaseHistoryRecord(purchaseHistoryRecordList: List<*>) {
      for (purchaseHistoryObject in purchaseHistoryRecordList) {
        try {
          val purchaseHistoryJsonRaw =
              invokeMethod(
                  purchaseHistoryRecordClazz,
                  getOriginalJsonPurchaseHistoryMethod,
                  purchaseHistoryObject)
                  as? String
                  ?: continue
          val purchaseHistoryJson = JSONObject(purchaseHistoryJsonRaw)
          val packageName = context.packageName
          purchaseHistoryJson.put(PACKAGE_NAME, packageName)
          if (purchaseHistoryJson.has(PRODUCT_ID)) {
            val skuID = purchaseHistoryJson.getString(PRODUCT_ID)
            historyPurchaseSet.add(skuID)
            purchaseDetailsMap[skuID] = purchaseHistoryJson
          }
        } catch (e: Exception) {
          /* swallow */
        }
      }
      runnable.run()
    }
  }

  @AutoHandleExceptions
  internal inner class SkuDetailsResponseListenerWrapper(var runnable: Runnable) :
      InvocationHandler {
    override fun invoke(proxy: Any, m: Method, args: Array<Any>?): Any? {
      if (m.name == METHOD_ON_SKU_DETAILS_RESPONSE) {
        val skuDetailsObj = args?.get(1)
        if (skuDetailsObj != null && skuDetailsObj is List<*>) {
          parseSkuDetails(skuDetailsObj)
        }
      }
      return null
    }

    fun parseSkuDetails(skuDetailsObjectList: List<*>) {
      for (skuDetail in skuDetailsObjectList) {
        try {
          val skuDetailJson =
              invokeMethod(skuDetailsClazz, getOriginalJsonSkuMethod, skuDetail) as? String
                  ?: continue
          val skuJson = JSONObject(skuDetailJson)
          if (skuJson.has(PRODUCT_ID)) {
            val skuID = skuJson.getString(PRODUCT_ID)
            skuDetailsMap[skuID] = skuJson
          }
        } catch (e: Exception) {
          /* swallow */
        }
      }
      runnable.run()
    }
  }

  companion object {
    private val initialized = AtomicBoolean(false)
    private var instance: InAppPurchaseBillingClientWrapper? = null
    val isServiceConnected = AtomicBoolean(false)

    // Use ConcurrentHashMap because purchase values may be updated in different threads
    val purchaseDetailsMap: MutableMap<String, JSONObject> = ConcurrentHashMap()
    val skuDetailsMap: MutableMap<String, JSONObject> = ConcurrentHashMap()
    private const val IN_APP = "inapp"
    private const val PRODUCT_ID = "productId"
    private const val PACKAGE_NAME = "packageName"

    // Class names
    private const val CLASSNAME_BILLING_CLIENT = "com.android.billingclient.api.BillingClient"
    private const val CLASSNAME_PURCHASE = "com.android.billingclient.api.Purchase"
    private const val CLASSNAME_PURCHASES_RESULT =
        "com.android.billingclient.api.Purchase\$PurchasesResult"
    private const val CLASSNAME_SKU_DETAILS = "com.android.billingclient.api.SkuDetails"
    private const val CLASSNAME_PURCHASE_HISTORY_RECORD =
        "com.android.billingclient.api.PurchaseHistoryRecord"
    private const val CLASSNAME_SKU_DETAILS_RESPONSE_LISTENER =
        "com.android.billingclient.api.SkuDetailsResponseListener"
    private const val CLASSNAME_PURCHASE_HISTORY_RESPONSE_LISTENER =
        "com.android.billingclient.api.PurchaseHistoryResponseListener"
    private const val CLASSNAME_BILLING_CLIENT_BUILDER =
        "com.android.billingclient.api.BillingClient\$Builder"
    private const val CLASSNAME_PURCHASE_UPDATED_LISTENER =
        "com.android.billingclient.api.PurchasesUpdatedListener"
    private const val CLASSNAME_BILLING_CLIENT_STATE_LISTENER =
        "com.android.billingclient.api.BillingClientStateListener"

    // Method names
    private const val METHOD_QUERY_PURCHASES = "queryPurchases"
    private const val METHOD_GET_PURCHASE_LIST = "getPurchasesList"
    private const val METHOD_GET_ORIGINAL_JSON = "getOriginalJson"
    private const val METHOD_QUERY_SKU_DETAILS_ASYNC = "querySkuDetailsAsync"
    private const val METHOD_QUERY_PURCHASE_HISTORY_ASYNC = "queryPurchaseHistoryAsync"
    private const val METHOD_NEW_BUILDER = "newBuilder"
    private const val METHOD_ENABLE_PENDING_PURCHASES = "enablePendingPurchases"
    private const val METHOD_SET_LISTENER = "setListener"
    private const val METHOD_BUILD = "build"
    private const val METHOD_START_CONNECTION = "startConnection"
    private const val METHOD_ON_BILLING_SETUP_FINISHED = "onBillingSetupFinished"
    private const val METHOD_ON_BILLING_SERVICE_DISCONNECTED = "onBillingServiceDisconnected"
    private const val METHOD_ON_PURCHASE_HISTORY_RESPONSE = "onPurchaseHistoryResponse"
    private const val METHOD_ON_SKU_DETAILS_RESPONSE = "onSkuDetailsResponse"
    @Synchronized
    @JvmStatic
    fun getOrCreateInstance(context: Context): InAppPurchaseBillingClientWrapper? {
      if (initialized.get()) {
        return instance
      }
      createInstance(context)
      initialized.set(true)
      return instance
    }

    private fun createInstance(context: Context) {
      val inAppPurchaseSkuDetailsWrapper = getOrCreateInstance() ?: return
      val billingClientClazz = getClass(CLASSNAME_BILLING_CLIENT)
      val purchaseClazz = getClass(CLASSNAME_PURCHASE)
      val purchaseResultClazz = getClass(CLASSNAME_PURCHASES_RESULT)
      val skuDetailsClazz = getClass(CLASSNAME_SKU_DETAILS)
      val purchaseHistoryRecordClazz = getClass(CLASSNAME_PURCHASE_HISTORY_RECORD)
      val skuDetailsResponseListenerClazz = getClass(CLASSNAME_SKU_DETAILS_RESPONSE_LISTENER)
      val purchaseHistoryResponseListenerClazz =
          getClass(CLASSNAME_PURCHASE_HISTORY_RESPONSE_LISTENER)
      if (billingClientClazz == null ||
          purchaseResultClazz == null ||
          purchaseClazz == null ||
          skuDetailsClazz == null ||
          skuDetailsResponseListenerClazz == null ||
          purchaseHistoryRecordClazz == null ||
          purchaseHistoryResponseListenerClazz == null) {
        return
      }
      val queryPurchasesMethod =
          getMethod(billingClientClazz, METHOD_QUERY_PURCHASES, String::class.java)
      val getPurchaseListMethod = getMethod(purchaseResultClazz, METHOD_GET_PURCHASE_LIST)
      val getOriginalJsonMethod = getMethod(purchaseClazz, METHOD_GET_ORIGINAL_JSON)
      val getOriginalJsonSkuMethod = getMethod(skuDetailsClazz, METHOD_GET_ORIGINAL_JSON)
      val getOriginalJsonPurchaseHistoryMethod =
          getMethod(purchaseHistoryRecordClazz, METHOD_GET_ORIGINAL_JSON)
      val querySkuDetailsAsyncMethod =
          getMethod(
              billingClientClazz,
              METHOD_QUERY_SKU_DETAILS_ASYNC,
              inAppPurchaseSkuDetailsWrapper.skuDetailsParamsClazz,
              skuDetailsResponseListenerClazz)
      val queryPurchaseHistoryAsyncMethod =
          getMethod(
              billingClientClazz,
              METHOD_QUERY_PURCHASE_HISTORY_ASYNC,
              String::class.java,
              purchaseHistoryResponseListenerClazz)
      if (queryPurchasesMethod == null ||
          getPurchaseListMethod == null ||
          getOriginalJsonMethod == null ||
          getOriginalJsonSkuMethod == null ||
          getOriginalJsonPurchaseHistoryMethod == null ||
          querySkuDetailsAsyncMethod == null ||
          queryPurchaseHistoryAsyncMethod == null) {
        return
      }
      val billingClient = createBillingClient(context, billingClientClazz) ?: return
      instance =
          InAppPurchaseBillingClientWrapper(
              context,
              billingClient,
              billingClientClazz,
              purchaseResultClazz,
              purchaseClazz,
              skuDetailsClazz,
              purchaseHistoryRecordClazz,
              skuDetailsResponseListenerClazz,
              purchaseHistoryResponseListenerClazz,
              queryPurchasesMethod,
              getPurchaseListMethod,
              getOriginalJsonMethod,
              getOriginalJsonSkuMethod,
              getOriginalJsonPurchaseHistoryMethod,
              querySkuDetailsAsyncMethod,
              queryPurchaseHistoryAsyncMethod,
              inAppPurchaseSkuDetailsWrapper)
      (instance as InAppPurchaseBillingClientWrapper).startConnection()
    }

    private fun createBillingClient(context: Context?, billingClientClazz: Class<*>): Any? {
      // 0. pre-check
      val builderClazz = getClass(CLASSNAME_BILLING_CLIENT_BUILDER)
      val listenerClazz = getClass(CLASSNAME_PURCHASE_UPDATED_LISTENER)
      if (builderClazz == null || listenerClazz == null) {
        return null
      }
      val newBuilderMethod = getMethod(billingClientClazz, METHOD_NEW_BUILDER, Context::class.java)
      val enablePendingPurchasesMethod = getMethod(builderClazz, METHOD_ENABLE_PENDING_PURCHASES)
      val setListenerMethod = getMethod(builderClazz, METHOD_SET_LISTENER, listenerClazz)
      val buildMethod = getMethod(builderClazz, METHOD_BUILD)
      if (newBuilderMethod == null ||
          enablePendingPurchasesMethod == null ||
          setListenerMethod == null ||
          buildMethod == null) {
        return null
      }

      // 1. newBuilder(context)
      var builder: Any? =
          invokeMethod(billingClientClazz, newBuilderMethod, null, context) ?: return null

      // 2. setListener(listener)
      val listenerObj =
          Proxy.newProxyInstance(
              listenerClazz.classLoader, arrayOf(listenerClazz), PurchasesUpdatedListenerWrapper())
      builder = invokeMethod(builderClazz, setListenerMethod, builder, listenerObj)
      if (builder == null) {
        return null
      }

      // 3. enablePendingPurchases() or  4. build()
      builder = invokeMethod(builderClazz, enablePendingPurchasesMethod, builder)
      return if (builder == null) {
        null
      } else invokeMethod(builderClazz, buildMethod, builder)
    }
  }
}


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

import android.content.Context
import android.os.Bundle
import android.os.IBinder
import androidx.annotation.RestrictTo
import com.facebook.FacebookSdk.getApplicationContext
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import java.lang.reflect.Method
import org.json.JSONException
import org.json.JSONObject

@AutoHandleExceptions
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
object InAppPurchaseEventManager {
  private val methodMap: HashMap<String, Method> = hashMapOf()
  private val classMap: HashMap<String, Class<*>> = hashMapOf()
  private const val CACHE_CLEAR_TIME_LIMIT_SEC = 7 * 24 * 60 * 60 // 7 days

  // Sku detail cache setting
  private const val SKU_DETAIL_EXPIRE_TIME_SEC = 12 * 60 * 60 // 12 h

  // Purchase types
  private const val SUBSCRIPTION = "subs"
  private const val INAPP = "inapp"

  // Purchase setting
  private const val PURCHASE_EXPIRE_TIME_SEC = 24 * 60 * 60 // 24 h
  private const val PURCHASE_STOP_QUERY_TIME_SEC = 20 * 60 // 20 min
  private const val MAX_QUERY_PURCHASE_NUM = 30

  // Class names
  private const val IN_APP_BILLING_SERVICE_STUB =
      "com.android.vending.billing.IInAppBillingService\$Stub"
  private const val IN_APP_BILLING_SERVICE = "com.android.vending.billing.IInAppBillingService"

  // Method names
  private const val AS_INTERFACE = "asInterface"
  private const val GET_SKU_DETAILS = "getSkuDetails"
  private const val GET_PURCHASES = "getPurchases"
  private const val GET_PURCHASE_HISTORY = "getPurchaseHistory"
  private const val IS_BILLING_SUPPORTED = "isBillingSupported"

  // Other names
  private const val ITEM_ID_LIST = "ITEM_ID_LIST"
  private const val RESPONSE_CODE = "RESPONSE_CODE"
  private const val DETAILS_LIST = "DETAILS_LIST"
  private const val INAPP_PURCHASE_DATA_LIST = "INAPP_PURCHASE_DATA_LIST"
  private const val INAPP_CONTINUATION_TOKEN = "INAPP_CONTINUATION_TOKEN"
  private const val LAST_CLEARED_TIME = "LAST_CLEARED_TIME"
  private val PACKAGE_NAME = getApplicationContext().packageName
  private const val SKU_DETAILS_STORE = "com.facebook.internal.SKU_DETAILS"
  private const val PURCHASE_INAPP_STORE = "com.facebook.internal.PURCHASE"
  private val skuDetailSharedPrefs =
      getApplicationContext().getSharedPreferences(SKU_DETAILS_STORE, Context.MODE_PRIVATE)
  private val purchaseInappSharedPrefs =
      getApplicationContext().getSharedPreferences(PURCHASE_INAPP_STORE, Context.MODE_PRIVATE)

  @JvmStatic
  fun asInterface(context: Context, service: IBinder?): Any? {
    val args = arrayOf<Any?>(service)
    return invokeMethod(context, IN_APP_BILLING_SERVICE_STUB, AS_INTERFACE, null, args)
  }

  @JvmStatic
  fun getSkuDetails(
      context: Context,
      skuList: ArrayList<String>,
      inAppBillingObj: Any?,
      isSubscription: Boolean
  ): Map<String, String> {
    val skuDetailsMap = readSkuDetailsFromCache(skuList)
    val unresolvedSkuList = arrayListOf<String>()
    for (sku in skuList) {
      if (!skuDetailsMap.containsKey(sku)) {
        unresolvedSkuList.add(sku)
      }
    }
    skuDetailsMap.putAll(
        getSkuDetailsFromGoogle(context, unresolvedSkuList, inAppBillingObj, isSubscription))
    return skuDetailsMap
  }

  private fun getSkuDetailsFromGoogle(
      context: Context,
      skuList: ArrayList<String>,
      inAppBillingObj: Any?,
      isSubscription: Boolean
  ): Map<String, String> {
    val skuDetailsMap: MutableMap<String, String> = mutableMapOf()
    if (inAppBillingObj == null || skuList.isEmpty()) {
      return skuDetailsMap
    }
    val querySkus = Bundle()
    querySkus.putStringArrayList(ITEM_ID_LIST, skuList)
    val args = arrayOf(3, PACKAGE_NAME, if (isSubscription) SUBSCRIPTION else INAPP, querySkus)
    val result =
        invokeMethod(context, IN_APP_BILLING_SERVICE, GET_SKU_DETAILS, inAppBillingObj, args)
    if (result != null) {
      val bundle = result as Bundle
      val response = bundle.getInt(RESPONSE_CODE)
      if (response == 0) {
        val skuDetailsList = bundle.getStringArrayList(DETAILS_LIST)
        if (skuDetailsList != null && skuList.size == skuDetailsList.size) {
          for (i in skuList.indices) {
            skuDetailsMap[skuList[i]] = skuDetailsList[i]
          }
        }
        writeSkuDetailsToCache(skuDetailsMap)
      }
    }
    return skuDetailsMap
  }

  private fun readSkuDetailsFromCache(skuList: ArrayList<String>): MutableMap<String, String> {
    val skuDetailsMap: MutableMap<String, String> = mutableMapOf()
    val nowSec = System.currentTimeMillis() / 1000L
    for (sku in skuList) {
      val rawString = skuDetailSharedPrefs.getString(sku, null)
      if (rawString != null) {
        val splitted = rawString.split(";", limit = 2)
        val timeSec = splitted[0].toLong()
        if (nowSec - timeSec < SKU_DETAIL_EXPIRE_TIME_SEC) {
          skuDetailsMap[sku] = splitted[1]
        }
      }
    }
    return skuDetailsMap
  }

  private fun writeSkuDetailsToCache(skuDetailsMap: Map<String, String>) {
    val nowSec = System.currentTimeMillis() / 1000L
    val editor = skuDetailSharedPrefs.edit()
    for ((key, value) in skuDetailsMap) {
      editor.putString(key, "$nowSec;$value")
    }
    editor.apply()
  }

  private fun isBillingSupported(context: Context, inAppBillingObj: Any?, type: String): Boolean {
    if (inAppBillingObj == null) {
      return false
    }
    val args = arrayOf<Any?>(3, PACKAGE_NAME, type)
    val result =
        invokeMethod(context, IN_APP_BILLING_SERVICE, IS_BILLING_SUPPORTED, inAppBillingObj, args)
    return result != null && result as Int == 0
  }

  @JvmStatic
  fun getPurchasesInapp(context: Context, inAppBillingObj: Any?): ArrayList<String> {
    return filterPurchases(getPurchases(context, inAppBillingObj, INAPP))
  }

  @JvmStatic
  fun getPurchasesSubs(context: Context, inAppBillingObj: Any?): ArrayList<String> {
    return filterPurchases(getPurchases(context, inAppBillingObj, SUBSCRIPTION))
  }

  private fun getPurchases(
      context: Context,
      inAppBillingObj: Any?,
      type: String
  ): ArrayList<String> {
    val purchases = arrayListOf<String>()
    if (inAppBillingObj == null) {
      return purchases
    }
    if (isBillingSupported(context, inAppBillingObj, type)) {
      var continuationToken: String? = null
      var queriedPurchaseNum = 0
      do {
        val args = arrayOf<Any?>(3, PACKAGE_NAME, type, continuationToken)
        val result =
            invokeMethod(context, IN_APP_BILLING_SERVICE, GET_PURCHASES, inAppBillingObj, args)
        continuationToken = null
        if (result != null) {
          val purchaseDetails = result as Bundle
          val response = purchaseDetails.getInt(RESPONSE_CODE)
          if (response == 0) {
            val details = purchaseDetails.getStringArrayList(INAPP_PURCHASE_DATA_LIST)
            if (details != null) {
              queriedPurchaseNum += details.size
              purchases.addAll(details)
              continuationToken = purchaseDetails.getString(INAPP_CONTINUATION_TOKEN)
            } else {
              break
            }
          }
        }
      } while (queriedPurchaseNum < MAX_QUERY_PURCHASE_NUM && continuationToken != null)
    }
    return purchases
  }

  fun hasFreeTrialPeirod(skuDetail: String): Boolean {
    try {
      val skuDetailsJson = JSONObject(skuDetail)
      val freeTrialPeriod = skuDetailsJson.optString("freeTrialPeriod")
      return freeTrialPeriod != null && freeTrialPeriod.isNotEmpty()
    } catch (e: JSONException) {
      /*no op*/
    }
    return false
  }

  @JvmStatic
  fun getPurchaseHistoryInapp(context: Context, inAppBillingObj: Any?): ArrayList<String> {
    var purchases = ArrayList<String>()
    if (inAppBillingObj == null) {
      return purchases
    }
    val iapClass = getClass(context, IN_APP_BILLING_SERVICE) ?: return purchases
    getMethod(iapClass, GET_PURCHASE_HISTORY) ?: return purchases
    purchases = getPurchaseHistory(context, inAppBillingObj, INAPP)
    return filterPurchases(purchases)
  }

  private fun getPurchaseHistory(
      context: Context,
      inAppBillingObj: Any,
      type: String
  ): ArrayList<String> {
    val purchases = arrayListOf<String>()
    if (isBillingSupported(context, inAppBillingObj, type)) {
      var continuationToken: String? = null
      var queriedPurchaseNum = 0
      var reachTimeLimit = false
      do {
        val args = arrayOf(6, PACKAGE_NAME, type, continuationToken, Bundle())
        continuationToken = null
        val result =
            invokeMethod(
                context, IN_APP_BILLING_SERVICE, GET_PURCHASE_HISTORY, inAppBillingObj, args)
        if (result != null) {
          val nowSec = System.currentTimeMillis() / 1000L
          val purchaseDetails = result as Bundle
          val response = purchaseDetails.getInt(RESPONSE_CODE)
          if (response == 0) {
            val details = purchaseDetails.getStringArrayList(INAPP_PURCHASE_DATA_LIST) ?: continue
            for (detail in details) {
              try {
                val detailJson = JSONObject(detail)
                val purchaseTimeSec = detailJson.getLong("purchaseTime") / 1000L
                if (nowSec - purchaseTimeSec > PURCHASE_STOP_QUERY_TIME_SEC) {
                  reachTimeLimit = true
                  break
                } else {
                  purchases.add(detail)
                  queriedPurchaseNum++
                }
              } catch (e: JSONException) {
                /*no op*/
              }
            }
            continuationToken = purchaseDetails.getString(INAPP_CONTINUATION_TOKEN)
          }
        }
      } while (queriedPurchaseNum < MAX_QUERY_PURCHASE_NUM &&
          continuationToken != null &&
          !reachTimeLimit)
    }
    return purchases
  }

  private fun filterPurchases(purchases: ArrayList<String>): ArrayList<String> {
    val filteredPurchase = ArrayList<String>()
    val editor = purchaseInappSharedPrefs.edit()
    val nowSec = System.currentTimeMillis() / 1000L
    for (purchase in purchases) {
      try {
        val purchaseJson = JSONObject(purchase)
        val sku = purchaseJson.getString("productId")
        val purchaseTimeMillis = purchaseJson.getLong("purchaseTime")
        val purchaseToken = purchaseJson.getString("purchaseToken")
        if (nowSec - purchaseTimeMillis / 1000L > PURCHASE_EXPIRE_TIME_SEC) {
          continue
        }
        val historyPurchaseToken = purchaseInappSharedPrefs.getString(sku, "")
        if (historyPurchaseToken == purchaseToken) {
          continue
        }
        editor.putString(sku, purchaseToken) // write new purchase into cache
        filteredPurchase.add(purchase)
      } catch (e: JSONException) {
        /*no op*/
      }
    }
    editor.apply()
    return filteredPurchase
  }

  private fun getMethod(classObj: Class<*>, methodName: String): Method? {
    var method = methodMap[methodName]
    if (method != null) {
      return method
    }

    var paramTypes: Array<Class<*>>? = null
    when (methodName) {
      AS_INTERFACE -> paramTypes = arrayOf(IBinder::class.java)
      GET_SKU_DETAILS ->
          paramTypes =
              arrayOf(Integer.TYPE, String::class.java, String::class.java, Bundle::class.java)
      IS_BILLING_SUPPORTED ->
          paramTypes = arrayOf(Integer.TYPE, String::class.java, String::class.java)
      GET_PURCHASES ->
          paramTypes =
              arrayOf(Integer.TYPE, String::class.java, String::class.java, String::class.java)
      GET_PURCHASE_HISTORY ->
          paramTypes =
              arrayOf(
                  Integer.TYPE,
                  String::class.java,
                  String::class.java,
                  String::class.java,
                  Bundle::class.java)
    }
    val parameterTypes = paramTypes
    method =
        if (parameterTypes == null) InAppPurchaseUtils.getDeclaredMethod(classObj, methodName, null)
        else InAppPurchaseUtils.getDeclaredMethod(classObj, methodName, *parameterTypes)
    if (method != null) methodMap[methodName] = method

    return method
  }

  private fun getClass(context: Context, className: String): Class<*>? {
    var classObj = classMap[className]
    if (classObj != null) {
      return classObj
    }
    classObj = InAppPurchaseUtils.getClassFromContext(context, className)
    if (classObj != null) {
      classMap[className] = classObj
    }
    return classObj
  }

  /** Internal variant of invoke method, uses internal version of getClass and getMethod */
  private fun invokeMethod(
      context: Context,
      className: String,
      methodName: String,
      obj: Any?,
      args: Array<Any?>
  ): Any? {
    var obj = obj
    val classObj = getClass(context, className) ?: return null
    val method = getMethod(classObj, methodName) ?: return null
    return InAppPurchaseUtils.invokeMethod(classObj, method, obj, *args)
  }

  @JvmStatic
  fun clearSkuDetailsCache() {
    val nowSec = System.currentTimeMillis() / 1000L

    // Sku details cache
    val lastClearedTimeSec = skuDetailSharedPrefs.getLong(LAST_CLEARED_TIME, 0)
    if (lastClearedTimeSec == 0L) {
      skuDetailSharedPrefs.edit().putLong(LAST_CLEARED_TIME, nowSec).apply()
    } else if (nowSec - lastClearedTimeSec > CACHE_CLEAR_TIME_LIMIT_SEC) {
      skuDetailSharedPrefs.edit().clear().putLong(LAST_CLEARED_TIME, nowSec).apply()
    }
  }
}


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

import android.content.Context
import android.content.SharedPreferences
import androidx.annotation.RestrictTo
import androidx.annotation.VisibleForTesting
import com.facebook.FacebookSdk.getApplicationContext
import com.facebook.appevents.internal.AutomaticAnalyticsLogger.logPurchase
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import java.lang.Exception
import java.util.HashMap
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArraySet
import org.json.JSONObject

@AutoHandleExceptions
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
object InAppPurchaseLoggerManager {
  private lateinit var sharedPreferences: SharedPreferences
  private val cachedPurchaseSet: MutableSet<String> = CopyOnWriteArraySet()
  private val cachedPurchaseMap: MutableMap<String, Long> = ConcurrentHashMap()
  private const val PURCHASE_TIME = "purchaseTime"
  private const val PRODUCT_DETAILS_STORE = "com.facebook.internal.iap.PRODUCT_DETAILS"
  private const val LAST_CLEARED_TIME = "LAST_CLEARED_TIME"
  private const val PURCHASE_DETAILS_SET = "PURCHASE_DETAILS_SET"
  private const val LAST_QUERY_PURCHASE_HISTORY_TIME = "LAST_QUERY_PURCHASE_HISTORY_TIME"
  private const val CACHE_CLEAR_TIME_LIMIT_SEC = 7 * 24 * 60 * 60 // 7 days
  private const val PURCHASE_IN_CACHE_INTERVAL = 24 * 60 * 60 // 1 day
  private fun readPurchaseCache() {
    // clear cached purchases logged by lib 1
    val cachedSkuSharedPref =
        getApplicationContext()
            .getSharedPreferences("com.facebook.internal.SKU_DETAILS", Context.MODE_PRIVATE)
    val cachedPurchaseSharedPref =
        getApplicationContext()
            .getSharedPreferences("com.facebook.internal.PURCHASE", Context.MODE_PRIVATE)
    if (cachedSkuSharedPref.contains("LAST_CLEARED_TIME")) {
      cachedSkuSharedPref.edit().clear().apply()
      cachedPurchaseSharedPref.edit().clear().apply()
    }
    sharedPreferences =
        getApplicationContext().getSharedPreferences(PRODUCT_DETAILS_STORE, Context.MODE_PRIVATE)
    cachedPurchaseSet.addAll(
        sharedPreferences.getStringSet(PURCHASE_DETAILS_SET, hashSetOf()) ?: hashSetOf())

    // Construct purchase de-dup map.
    for (purchaseHistory in cachedPurchaseSet) {
      val splitPurchase = purchaseHistory.split(";", limit = 2)
      cachedPurchaseMap[splitPurchase[0]] = splitPurchase[1].toLong()
    }

    // Clean up cache every 7 days, and only keep recent 1 day purchases
    clearOutdatedProductInfoInCache()
  }

  @JvmStatic
  fun filterPurchaseLogging(
      purchaseDetailsMap: MutableMap<String, JSONObject>,
      skuDetailsMap: Map<String, JSONObject?>
  ) {
    readPurchaseCache()
    val loggingReadyMap: Map<String, String> =
        constructLoggingReadyMap(cacheDeDupPurchase(purchaseDetailsMap), skuDetailsMap)
    logPurchases(loggingReadyMap)
  }

  private fun logPurchases(purchaseDetailsMap: Map<String, String>) {
    for ((purchaseDetails, skuDetails) in purchaseDetailsMap) {
      if (purchaseDetails != null && skuDetails != null) {
        logPurchase(purchaseDetails, skuDetails, false)
      }
    }
  }

  @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
  internal fun cacheDeDupPurchase(
      purchaseDetailsMap: MutableMap<String, JSONObject>
  ): Map<String, JSONObject> {
    val nowSec = System.currentTimeMillis() / 1000L
    val tempPurchaseDetailsMap: Map<String, JSONObject> = purchaseDetailsMap.toMap()
    for ((key, purchaseJson) in tempPurchaseDetailsMap) {
      try {
        if (purchaseJson.has("purchaseToken")) {
          val purchaseToken = purchaseJson.getString("purchaseToken")
          if (cachedPurchaseMap.containsKey(purchaseToken)) {
            purchaseDetailsMap.remove(key)
          } else {
            cachedPurchaseSet.add("$purchaseToken;$nowSec")
          }
        }
      } catch (e: Exception) {
        /* swallow */
      }
    }
    sharedPreferences.edit().putStringSet(PURCHASE_DETAILS_SET, cachedPurchaseSet).apply()
    return HashMap(purchaseDetailsMap)
  }

  @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
  internal fun clearOutdatedProductInfoInCache() {
    val nowSec = System.currentTimeMillis() / 1000L
    val lastClearedTimeSec = sharedPreferences.getLong(LAST_CLEARED_TIME, 0)
    if (lastClearedTimeSec == 0L) {
      sharedPreferences.edit().putLong(LAST_CLEARED_TIME, nowSec).apply()
    } else if (nowSec - lastClearedTimeSec > CACHE_CLEAR_TIME_LIMIT_SEC) {
      val tempPurchaseMap: Map<String, Long> = cachedPurchaseMap.toMap()
      for ((purchaseToken, historyPurchaseTime) in tempPurchaseMap) {
        if (nowSec - historyPurchaseTime > PURCHASE_IN_CACHE_INTERVAL) {
          cachedPurchaseSet.remove("$purchaseToken;$historyPurchaseTime")
          cachedPurchaseMap.remove(purchaseToken)
        }
      }
      sharedPreferences
          .edit()
          .putStringSet(PURCHASE_DETAILS_SET, cachedPurchaseSet)
          .putLong(LAST_CLEARED_TIME, nowSec)
          .apply()
    }
  }

  @JvmStatic
  fun eligibleQueryPurchaseHistory(): Boolean {
    readPurchaseCache()
    val nowSec = System.currentTimeMillis() / 1000L
    val lastQueryPurchaseHistoryTime =
        sharedPreferences.getLong(LAST_QUERY_PURCHASE_HISTORY_TIME, 0)
    if (lastQueryPurchaseHistoryTime != 0L &&
        nowSec - lastQueryPurchaseHistoryTime < PURCHASE_IN_CACHE_INTERVAL) {
      return false
    }
    sharedPreferences.edit().putLong(LAST_QUERY_PURCHASE_HISTORY_TIME, nowSec).apply()
    return true
  }

  @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
  internal fun constructLoggingReadyMap(
      purchaseDetailsMap: Map<String, JSONObject>,
      skuDetailsMap: Map<String, JSONObject?>
  ): Map<String, String> {
    val nowSec = System.currentTimeMillis() / 1000L
    val purchaseResultMap: MutableMap<String, String> = mutableMapOf()
    for ((key, purchaseDetail) in purchaseDetailsMap) {
      val skuDetail = skuDetailsMap[key]
      if (purchaseDetail != null && purchaseDetail.has(PURCHASE_TIME)) {
        try {
          val purchaseTime = purchaseDetail.getLong(PURCHASE_TIME)
          // Purchase is too old (more than 24h) to log
          if (nowSec - purchaseTime / 1000L > PURCHASE_IN_CACHE_INTERVAL) {
            continue
          }
          if (skuDetail != null) {
            purchaseResultMap[purchaseDetail.toString()] = skuDetail.toString()
          }
        } catch (e: Exception) {
          /* swallow */
        }
      }
    }
    return purchaseResultMap
  }
}


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

import android.content.pm.PackageManager
import androidx.annotation.RestrictTo
import com.facebook.FacebookSdk.getApplicationContext
import com.facebook.internal.FeatureManager
import com.facebook.internal.FeatureManager.isEnabled
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import java.util.concurrent.atomic.AtomicBoolean

@AutoHandleExceptions
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
object InAppPurchaseManager {
  private const val GOOGLE_BILLINGCLIENT_VERSION = "com.google.android.play.billingclient.version"
  private val enabled = AtomicBoolean(false)

  @JvmStatic
  fun enableAutoLogging() {
    enabled.set(true)
    startTracking()
  }

  @JvmStatic
  fun startTracking() {
    if (enabled.get()) {
      if (usingBillingLib2Plus() && isEnabled(FeatureManager.Feature.IapLoggingLib2)) {
        InAppPurchaseAutoLogger.startIapLogging(getApplicationContext())
      } else {
        InAppPurchaseActivityLifecycleTracker.startIapLogging()
      }
    }
  }

  private fun usingBillingLib2Plus(): Boolean {
    return try {
      val context = getApplicationContext()
      val info =
          context.packageManager.getApplicationInfo(
              context.packageName, PackageManager.GET_META_DATA)
      if (info != null) {
        val version = info.metaData.getString(GOOGLE_BILLINGCLIENT_VERSION)
        val versionArray = if (version === null) return false else version.split(".", limit = 3)
        return versionArray[0].toInt() >= 2
      }
      false
    } catch (e: Exception) {
      false
    }
  }
}


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

import androidx.annotation.RestrictTo
import com.facebook.appevents.iap.InAppPurchaseUtils.getClass
import com.facebook.appevents.iap.InAppPurchaseUtils.getMethod
import com.facebook.appevents.iap.InAppPurchaseUtils.invokeMethod
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import java.lang.reflect.Method
import java.util.concurrent.atomic.AtomicBoolean

@AutoHandleExceptions
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
class InAppPurchaseSkuDetailsWrapper(
    val skuDetailsParamsClazz: Class<*>,
    private val builderClazz: Class<*>,
    private val newBuilderMethod: Method,
    private val setTypeMethod: Method,
    private val setSkusListMethod: Method,
    private val buildMethod: Method
) {
  fun getSkuDetailsParams(skuType: String?, skuIDs: List<String?>?): Any? {
    // 1. newBuilder()
    var builder: Any? = invokeMethod(skuDetailsParamsClazz, newBuilderMethod, null) ?: return null

    // 2. setType(skuType)
    builder = invokeMethod(builderClazz, setTypeMethod, builder, skuType)
    if (builder == null) {
      return null
    }

    // 3. setSkusList(skuIDs)
    builder = invokeMethod(builderClazz, setSkusListMethod, builder, skuIDs)
    return if (builder == null) {
      null
    } else invokeMethod(builderClazz, buildMethod, builder)

    // 4. build()
  }

  companion object {
    private var instance: InAppPurchaseSkuDetailsWrapper? = null
    private val initialized = AtomicBoolean(false)
    private const val CLASSNAME_SKU_DETAILS_PARAMS =
        "com.android.billingclient.api.SkuDetailsParams"
    private const val CLASSNAME_SKU_DETAILS_PARAMS_BUILDER =
        "com.android.billingclient.api.SkuDetailsParams\$Builder"
    private const val METHOD_NEW_BUILDER = "newBuilder"
    private const val METHOD_SET_TYPE = "setType"
    private const val METHOD_SET_SKU_LIST = "setSkusList"
    private const val METHOD_BUILD = "build"

    @JvmStatic
    fun getOrCreateInstance(): InAppPurchaseSkuDetailsWrapper? {
      if (initialized.get()) {
        return instance
      }
      createInstance()
      initialized.set(true)
      return instance
    }

    private fun createInstance() {
      val skuDetailsParamsClazz = getClass(CLASSNAME_SKU_DETAILS_PARAMS)
      val builderClazz = getClass(CLASSNAME_SKU_DETAILS_PARAMS_BUILDER)
      if (skuDetailsParamsClazz == null || builderClazz == null) {
        return
      }
      val newBuilderMethod = getMethod(skuDetailsParamsClazz, METHOD_NEW_BUILDER)
      val setTypeMethod = getMethod(builderClazz, METHOD_SET_TYPE, String::class.java)
      val setSkusListMethod = getMethod(builderClazz, METHOD_SET_SKU_LIST, MutableList::class.java)
      val buildMethod = getMethod(builderClazz, METHOD_BUILD)
      if (newBuilderMethod == null ||
          setTypeMethod == null ||
          setSkusListMethod == null ||
          buildMethod == null) {
        return
      }
      instance =
          InAppPurchaseSkuDetailsWrapper(
              skuDetailsParamsClazz,
              builderClazz,
              newBuilderMethod,
              setTypeMethod,
              setSkusListMethod,
              buildMethod)
    }
  }
}


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

import android.content.Context
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method

@AutoHandleExceptions
object InAppPurchaseUtils {
  /** Returns the Class object associated with the class or interface with the given string name */
  @JvmStatic
  fun getClass(className: String): Class<*>? {
    return try {
      Class.forName(className)
    } catch (e: ClassNotFoundException) {
      null
    }
  }

  /**
   * Returns a Method object that reflects the specified public member method of the class or
   * interface represented by this Class object.
   */
  @JvmStatic
  fun getMethod(clazz: Class<*>, methodName: String, vararg args: Class<*>?): Method? {
    return try {
      clazz.getMethod(methodName, *args)
    } catch (e: NoSuchMethodException) {
      null
    }
  }

  /**
   * Gets the declared method from class provided and returns the method to be use for invocation
   */
  @JvmStatic
  internal fun getDeclaredMethod(
      clazz: Class<*>,
      methodName: String,
      vararg args: Class<*>?
  ): Method? {
    return try {
      clazz.getDeclaredMethod(methodName, *args)
    } catch (e: NoSuchMethodException) {
      null
    }
  }

  /**
   * Invokes the underlying method represented by this Method object, on the specified object with
   * the specified parameters.
   */
  @JvmStatic
  fun invokeMethod(clazz: Class<*>, method: Method, obj: Any?, vararg args: Any?): Any? {
    var obj = obj
    if (obj != null) {
      obj = clazz.cast(obj)
    }
    try {
      return method.invoke(obj, *args)
    } catch (e: IllegalAccessException) {
      /* swallow */
    } catch (e: InvocationTargetException) {
      /* swallow */
    }
    return null
  }

  /** Gets class from the context class loader and returns null if class is not found. */
  @JvmStatic
  internal fun getClassFromContext(context: Context, className: String): Class<*>? {
    try {
      return context.classLoader.loadClass(className)
    } catch (e: ClassNotFoundException) {
      return null
    }
  }
}


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

import com.facebook.FacebookSdk
import com.facebook.appevents.ml.ModelManager
import com.facebook.appevents.ml.ModelManager.predict
import com.facebook.internal.FetchedAppGateKeepersManager.getGateKeeperForKey
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import org.json.JSONObject

@AutoHandleExceptions
object IntegrityManager {
  const val INTEGRITY_TYPE_NONE = "none"
  const val INTEGRITY_TYPE_ADDRESS = "address"
  const val INTEGRITY_TYPE_HEALTH = "health"
  private const val RESTRICTIVE_ON_DEVICE_PARAMS_KEY = "_onDeviceParams"
  private var enabled = false
  private var isSampleEnabled = false

  @JvmStatic
  fun enable() {
    enabled = true
    isSampleEnabled =
        getGateKeeperForKey("FBSDKFeatureIntegritySample", FacebookSdk.getApplicationId(), false)
  }

  /** Process integrity parameters */
  @JvmStatic
  fun processParameters(parameters: MutableMap<String, String>) {
    if (!enabled || parameters.isEmpty()) {
      return
    }
    try {
      val keys = parameters.keys.toList()
      val restrictiveParamJson = JSONObject()
      for (key in keys) {
        val value = checkNotNull(parameters[key])
        if (shouldFilter(key) || shouldFilter(value)) {
          parameters.remove(key)
          restrictiveParamJson.put(key, if (isSampleEnabled) value else "")
        }
      }
      if (restrictiveParamJson.length() != 0) {
        parameters[RESTRICTIVE_ON_DEVICE_PARAMS_KEY] = restrictiveParamJson.toString()
      }
    } catch (e: Exception) {
      /* swallow */
    }
  }

  private fun shouldFilter(input: String): Boolean {
    val predictResult = getIntegrityPredictionResult(input)
    return INTEGRITY_TYPE_NONE != predictResult
  }

  private fun getIntegrityPredictionResult(textFeature: String): String {
    val dense = FloatArray(30) { 0f }
    val res = predict(ModelManager.Task.MTML_INTEGRITY_DETECT, arrayOf(dense), arrayOf(textFeature))
    return res?.get(0) ?: INTEGRITY_TYPE_NONE
  }
}


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

import android.os.Build
import android.os.Bundle
import com.facebook.FacebookSdk
import com.facebook.internal.FetchedAppSettingsManager
import com.facebook.internal.Utility
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import org.json.JSONArray
import org.json.JSONObject

@AutoHandleExceptions
object MACARuleMatchingManager {
  private var enabled = false

  private var MACARules: JSONArray? = null

  private var keys = arrayOf(
    "event",
    "_locale",
    "_appVersion",
    "_deviceOS",
    "_platform",
    "_deviceModel",
    "_nativeAppID",
    "_nativeAppShortVersion",
    "_timezone",
    "_carrier",
    "_deviceOSTypeName",
    "_deviceOSVersion",
    "_remainingDiskGB",
  )

  @JvmStatic
  fun enable() {
    loadMACARules()
    if (MACARules != null) {
      enabled = true
    }
  }

  private fun loadMACARules() {
    val settings = FetchedAppSettingsManager.queryAppSettings(FacebookSdk.getApplicationId(), false)
      ?: return
    MACARules = settings.MACARuleMatchingSetting
  }

  @JvmStatic
  fun getKey(logic: JSONObject): String? {
    val its = logic.keys()
    while (its.hasNext()) {
      return its.next()
    }
    return null
  }

  @JvmStatic
  fun stringComparison(
    variable: String,
    values: JSONObject,
    data: Bundle?
  ): Boolean {
    val op = getKey(values) ?: return false
    val ruleValue = values.get(op).toString()
    val dataValue = (data?.get(variable.lowercase()) ?: data?.get(variable)) ?: return false

    return when (op) {
      "contains" -> {
        dataValue.toString().contains(ruleValue)
      }
      "i_contains" -> {
        dataValue.toString().lowercase().contains(ruleValue.lowercase())
      }
      "i_not_contains" -> {
        !dataValue.toString().lowercase().contains(ruleValue.lowercase())
      }
      "regex_match" -> {
        ruleValue.toRegex().matches(dataValue.toString())
      }
      "eq" -> {
        dataValue.toString() == ruleValue
      }
      "neq" -> {
        dataValue.toString() != ruleValue
      }
      "lt" -> {
        dataValue.toString().toDouble() < ruleValue.toDouble()
      }
      "lte" -> {
        dataValue.toString().toDouble() <= ruleValue.toDouble()
      }
      "gt" -> {
        dataValue.toString().toDouble() > ruleValue.toDouble()
      }
      "gte" -> {
        dataValue.toString().toDouble() >= ruleValue.toDouble()
      }
      else -> false
    }
  }

  @JvmStatic
  fun isMatchCCRule(
    ruleString: String?,
    data: Bundle?
  ): Boolean {
    if (ruleString == null || data == null) {
      return false
    }

    val ruleJson = JSONObject(ruleString)
    val op = getKey(ruleJson) ?: return false
    val values = ruleJson.get(op)

    return when (op) {
      "and" -> {
        val v = (values as JSONArray?) ?: return false
        for (i in 0 until v.length()) {
          val thisRes = isMatchCCRule(v.get(i).toString(), data)
          if (!thisRes) {
            return false
          }
        }
        return true
      }
      "or" -> {
        val v = (values as JSONArray?) ?: return false
        for (i in 0 until v.length()) {
          val thisRes = isMatchCCRule(v.get(i).toString(), data)
          if (thisRes) {
            return true
          }
        }
        return false
      }
      "not" -> !isMatchCCRule(values.toString(), data)
      else -> {
        val v = (values as JSONObject?) ?: return false
        return stringComparison(op, v, data)
      }
    }
  }

  @JvmStatic
  fun getMatchPropertyIDs(params: Bundle?): String {
    if (MACARules == null || MACARules?.length() == 0) {
      return "[]"
    }
    val rules = MACARules as JSONArray
    val res = mutableListOf<Long>()
    for (i in 0 until rules.length()) {
      val entry = rules.optString(i) ?: continue
      val json = JSONObject(entry)
      val pid = json.optLong("id")
      if (pid == 0L) continue
      val rule = json.optString("rule") ?: continue
      if (isMatchCCRule(rule, params)) {
        res.add(pid)
      }
    }
    return JSONArray(res).toString()
  }

  @JvmStatic
  fun processParameters(params: Bundle?, event: String) {
    if (!enabled || params == null) {
      return
    }

    try {
      generateInfo(params, event)
      params.putString("_audiencePropertyIds", getMatchPropertyIDs(params))
      params.putString("cs_maca", "1")
      removeGeneratedInfo(params)
    } catch (_: Exception) {}
  }

  @JvmStatic
  fun generateInfo(params: Bundle, event: String) {
    params.putString("event", event)
    params.putString("_locale",
      (Utility.locale?.language ?: "")+ '_' + (Utility.locale?.country ?: ""))
    params.putString("_appVersion", Utility.versionName ?: "")
    params.putString("_deviceOS", "ANDROID")
    params.putString("_platform", "mobile")
    params.putString("_deviceModel", Build.MODEL ?: "")
    params.putString("_nativeAppID", FacebookSdk.getApplicationId())
    params.putString("_nativeAppShortVersion", Utility.versionName ?: "")
    params.putString("_timezone", Utility.deviceTimeZoneName)
    params.putString("_carrier", Utility.carrierName)
    params.putString("_deviceOSTypeName", "ANDROID")
    params.putString("_deviceOSVersion", Build.VERSION.RELEASE)
    params.putLong("_remainingDiskGB", Utility.availableExternalStorageGB)
  }

  @JvmStatic
  fun removeGeneratedInfo(params: Bundle) {
    for (k in keys) {
      params.remove(k)
    }
  }
}


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

import android.os.Bundle
import com.facebook.FacebookSdk
import com.facebook.internal.FetchedAppSettingsManager
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import org.json.JSONArray

@AutoHandleExceptions
object ProtectedModeManager {
  private var enabled = false

  val defaultStandardParameterNames: HashSet<String> by lazy {
    hashSetOf(
      "_currency",
      "_valueToSum",
      "fb_availability",
      "fb_body_style",
      "fb_checkin_date",
      "fb_checkout_date",
      "fb_city",
      "fb_condition_of_vehicle",
      "fb_content_category",
      "fb_content_ids",
      "fb_content_name",
      "fb_content_type",
      "fb_contents",
      "fb_country",
      "fb_currency",
      "fb_delivery_category",
      "fb_departing_arrival_date",
      "fb_departing_departure_date",
      "fb_destination_airport",
      "fb_destination_ids",
      "fb_dma_code",
      "fb_drivetrain",
      "fb_exterior_color",
      "fb_fuel_type",
      "fb_hotel_score",
      "fb_interior_color",
      "fb_lease_end_date",
      "fb_lease_start_date",
      "fb_listing_type",
      "fb_make",
      "fb_mileage.unit",
      "fb_mileage.value",
      "fb_model",
      "fb_neighborhood",
      "fb_num_adults",
      "fb_num_children",
      "fb_num_infants",
      "fb_num_items",
      "fb_order_id",
      "fb_origin_airport",
      "fb_postal_code",
      "fb_predicted_ltv",
      "fb_preferred_baths_range",
      "fb_preferred_beds_range",
      "fb_preferred_neighborhoods",
      "fb_preferred_num_stops",
      "fb_preferred_price_range",
      "fb_preferred_star_ratings",
      "fb_price",
      "fb_property_type",
      "fb_region",
      "fb_returning_arrival_date",
      "fb_returning_departure_date",
      "fb_search_string",
      "fb_state_of_vehicle",
      "fb_status",
      "fb_suggested_destinations",
      "fb_suggested_home_listings",
      "fb_suggested_hotels",
      "fb_suggested_jobs",
      "fb_suggested_local_service_businesses",
      "fb_suggested_location_based_items",
      "fb_suggested_vehicles",
      "fb_transmission",
      "fb_travel_class",
      "fb_travel_end",
      "fb_travel_start",
      "fb_trim",
      "fb_user_bucket",
      "fb_value",
      "fb_vin",
      "fb_year",
      "lead_event_source",
      "predicted_ltv",
      "product_catalog_id",

      // AppCustomEventField list
      "app_user_id",
      "appVersion",
      "_eventName",
      "_eventName_md5",
      /* duplicated _currency */
      "_implicitlyLogged",
      "_inBackground",
      "_isTimedEvent",
      "_logTime",
      /* duplicated fb_order_id */
      "_session_id",
      "_ui",
      /* duplicated _valueToSum */
      "_valueToUpdate",
      "_is_fb_codeless",
      "_is_suggested_event",
      "_fb_pixel_referral_id",
      "fb_pixel_id",
      "trace_id",
      "subscription_id",
      /* duplicated predicted_ltv */
      "event_id",
      "_restrictedParams",
      "_onDeviceParams",
      "purchase_valid_result_type",
      "core_lib_included",
      "login_lib_included",
      "share_lib_included",
      "place_lib_included",
      "messenger_lib_included",
      "applinks_lib_included",
      "marketing_lib_included",
      "_codeless_action",
      "sdk_initialized",
      "billing_client_lib_included",
      "billing_service_lib_included",
      "user_data_keys",
      "device_push_token",
      "fb_mobile_pckg_fp",
      "fb_mobile_app_cert_hash",
      "aggregate_id",
      "anonymous_id",
      "campaign_ids",

      // AppEventsIgnoredParams List
      "fb_post_attachment",
      "receipt_data",

      // List from dev doc
      "ad_type",
      "fb_content",
      "fb_content_id",
      /* duplicated fb_content_type */
      /* duplicated fb_currency */
      "fb_description",
      "fb_level",
      "fb_max_rating_value",
      /* duplicated fb_num_items */
      /* duplicated fb_order_id */
      "fb_payment_info_available",
      "fb_registration_method",
      /* duplicated fb_search_string */
      "fb_success",
      "pm",
      "_audiencePropertyIds",
      "cs_maca"
    )
  }

  private var standardParams: HashSet<String>? = null;

  @JvmStatic
  fun enable() {
    enabled = true
    loadStandardParams()
  }

  @JvmStatic
  fun disable() {
    enabled = false
  }

  private fun loadStandardParams() {
    val settings = FetchedAppSettingsManager.queryAppSettings(FacebookSdk.getApplicationId(), false)
      ?: return
    // Load the standard params from app settings first, if it doesn't exist, then use the default
    // one inside the class
    standardParams = convertJSONArrayToHashSet(settings.protectedModeStandardParamsSetting) ?: defaultStandardParameterNames
  }

  private fun convertJSONArrayToHashSet(jsonArray: JSONArray?): HashSet<String>? {
    if (jsonArray == null || jsonArray!!.length() == 0) {
      return null
    }
    val hashSet: HashSet<String> = HashSet()
    for (i in 0 until jsonArray.length()) {
      val element: String = jsonArray.getString(i)
      hashSet.add(element)
    }
    return hashSet
  }

  /** Process parameters for protected mode */
  @JvmStatic
  fun processParametersForProtectedMode(
    parameters: Bundle?
  ) {
    if (!enabled || parameters == null || parameters.isEmpty || standardParams == null) {
      return
    }

    val paramsToRemove = mutableListOf<String>()

    parameters.keySet().forEach { param ->
      if (param !in standardParams!!) {
        paramsToRemove.add(param)
      }
    }

    paramsToRemove.forEach { paramToRemove ->
      parameters.remove(paramToRemove)
    }

    parameters.putString("pm", "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.appevents.internal

import android.app.Activity
import android.app.Application
import android.os.Bundle
import android.util.Log
import androidx.annotation.RestrictTo
import com.facebook.FacebookSdk
import com.facebook.LoggingBehavior
import com.facebook.appevents.AppEventsLogger
import com.facebook.appevents.aam.MetadataIndexer
import com.facebook.appevents.codeless.CodelessManager
import com.facebook.appevents.iap.InAppPurchaseManager
import com.facebook.appevents.internal.AppEventUtility.assertIsMainThread
import com.facebook.appevents.internal.Constants.getDefaultAppEventsSessionTimeoutInSeconds
import com.facebook.appevents.internal.SessionInfo.Companion.clearSavedSessionFromDisk
import com.facebook.appevents.internal.SessionInfo.Companion.getStoredSessionInfo
import com.facebook.appevents.internal.SessionLogger.logActivateApp
import com.facebook.appevents.internal.SessionLogger.logDeactivateApp
import com.facebook.appevents.suggestedevents.SuggestedEventsManager
import com.facebook.internal.FeatureManager
import com.facebook.internal.FeatureManager.checkFeature
import com.facebook.internal.FetchedAppSettingsManager.getAppSettingsWithoutQuery
import com.facebook.internal.Logger.Companion.log
import com.facebook.internal.Utility.getActivityName
import java.lang.ref.WeakReference
import java.util.UUID
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger

object ActivityLifecycleTracker {
  private val TAG: String =
      ActivityLifecycleTracker::class.java.canonicalName
          ?: "com.facebook.appevents.internal.ActivityLifecycleTracker"
  private const val INCORRECT_IMPL_WARNING =
      "Unexpected activity pause without a " +
          "matching activity resume. Logging data may be incorrect. Make sure you call " +
          "activateApp from your Application's onCreate method"
  private const val INTERRUPTION_THRESHOLD_MILLISECONDS: Long = 1000
  private val singleThreadExecutor = Executors.newSingleThreadScheduledExecutor()

  @Volatile private var currentFuture: ScheduledFuture<*>? = null
  private val currentFutureLock = Any()
  private val foregroundActivityCount = AtomicInteger(0)

  // This member should only be changed or updated when executing on the singleThreadExecutor.
  @Volatile private var currentSession: SessionInfo? = null
  private val tracking = AtomicBoolean(false)
  private var appId: String? = null
  private var currentActivityAppearTime: Long = 0
  private var activityReferences = 0
  private var currActivity: WeakReference<Activity>? = null

  @JvmStatic
  fun startTracking(application: Application, appId: String?) {
    if (!tracking.compareAndSet(false, true)) {
      return
    }
    checkFeature(FeatureManager.Feature.CodelessEvents) { enabled ->
      if (enabled) {
        CodelessManager.enable()
      } else {
        CodelessManager.disable()
      }
    }
    ActivityLifecycleTracker.appId = appId
    application.registerActivityLifecycleCallbacks(
        object : Application.ActivityLifecycleCallbacks {
          override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
            log(LoggingBehavior.APP_EVENTS, TAG, "onActivityCreated")
            assertIsMainThread()
            onActivityCreated(activity)
          }

          override fun onActivityStarted(activity: Activity) {
            activityReferences++
            log(LoggingBehavior.APP_EVENTS, TAG, "onActivityStarted")
          }

          override fun onActivityResumed(activity: Activity) {
            log(LoggingBehavior.APP_EVENTS, TAG, "onActivityResumed")
            assertIsMainThread()
            ActivityLifecycleTracker.onActivityResumed(activity)
          }

          override fun onActivityPaused(activity: Activity) {
            log(LoggingBehavior.APP_EVENTS, TAG, "onActivityPaused")
            assertIsMainThread()
            ActivityLifecycleTracker.onActivityPaused(activity)
          }

          override fun onActivityStopped(activity: Activity) {
            log(LoggingBehavior.APP_EVENTS, TAG, "onActivityStopped")
            AppEventsLogger.onContextStop()
            activityReferences--
          }

          override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
            log(LoggingBehavior.APP_EVENTS, TAG, "onActivitySaveInstanceState")
          }

          override fun onActivityDestroyed(activity: Activity) {
            log(LoggingBehavior.APP_EVENTS, TAG, "onActivityDestroyed")
            ActivityLifecycleTracker.onActivityDestroyed(activity)
          }
        })
  }

  @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
  @JvmStatic
  fun isInBackground(): Boolean {
    return 0 == activityReferences
  }

  @JvmStatic
  fun isTracking(): Boolean {
    return tracking.get()
  }

  @JvmStatic
  fun getCurrentSessionGuid(): UUID? {
    return if (currentSession != null) currentSession?.sessionId else null
  }

  // Public in order to allow unity sdk to correctly log app events
  @JvmStatic
  fun onActivityCreated(activity: Activity?) {
    singleThreadExecutor.execute {
      if (currentSession == null) {
        currentSession = getStoredSessionInfo()
      }
    }
  }

  // Public in order to allow unity sdk to correctly log app events
  @JvmStatic
  fun onActivityResumed(activity: Activity) {
    currActivity = WeakReference(activity)
    foregroundActivityCount.incrementAndGet()
    cancelCurrentTask()
    val currentTime = System.currentTimeMillis()
    currentActivityAppearTime = currentTime
    val activityName = getActivityName(activity)
    CodelessManager.onActivityResumed(activity)
    MetadataIndexer.onActivityResumed(activity)
    SuggestedEventsManager.trackActivity(activity)
    InAppPurchaseManager.startTracking()
    val appContext = activity.applicationContext
    val handleActivityResume = Runnable {
      val lastEventTime = currentSession?.sessionLastEventTime
      if (currentSession == null) {
        currentSession = SessionInfo(currentTime, null)
        logActivateApp(activityName, null, appId, appContext)
      } else if (lastEventTime != null) {
        val suspendTime = currentTime - lastEventTime
        if (suspendTime > sessionTimeoutInSeconds * 1000) {
          // We were suspended for a significant amount of time.
          // Count this as a new session and log the old session
          logDeactivateApp(activityName, currentSession, appId)
          logActivateApp(activityName, null, appId, appContext)
          currentSession = SessionInfo(currentTime, null)
        } else if (suspendTime > INTERRUPTION_THRESHOLD_MILLISECONDS) {
          currentSession?.incrementInterruptionCount()
        }
      }
      currentSession?.sessionLastEventTime = currentTime
      currentSession?.writeSessionToDisk()
    }
    singleThreadExecutor.execute(handleActivityResume)
  }

  private fun onActivityPaused(activity: Activity) {
    val count = foregroundActivityCount.decrementAndGet()
    if (count < 0) {
      // Our ref count can be off if a developer doesn't call activate
      // app from the Application's onCreate method.
      foregroundActivityCount.set(0)
      Log.w(TAG, INCORRECT_IMPL_WARNING)
    }
    cancelCurrentTask()
    val currentTime = System.currentTimeMillis()
    val activityName = getActivityName(activity)
    CodelessManager.onActivityPaused(activity)
    val handleActivityPaused = Runnable {
      if (currentSession == null) {
        // This can happen if a developer doesn't call activate
        // app from the Application's onCreate method
        currentSession = SessionInfo(currentTime, null)
      }
      currentSession?.sessionLastEventTime = currentTime
      if (foregroundActivityCount.get() <= 0) {
        // Schedule check to see if we still have 0 foreground
        // activities in our set time. This indicates that the app has
        // been backgrounded
        val task = Runnable {
          if (currentSession == null) {
            currentSession = SessionInfo(currentTime, null)
          }
          if (foregroundActivityCount.get() <= 0) {
            logDeactivateApp(activityName, currentSession, appId)
            clearSavedSessionFromDisk()
            currentSession = null
          }
          synchronized(currentFutureLock) { currentFuture = null }
        }
        synchronized(currentFutureLock) {
          currentFuture =
              singleThreadExecutor.schedule(
                  task, sessionTimeoutInSeconds.toLong(), TimeUnit.SECONDS)
        }
      }
      val appearTime = currentActivityAppearTime
      val timeSpentOnActivityInSeconds =
          if (appearTime > 0) (currentTime - appearTime) / 1000 else 0
      AutomaticAnalyticsLogger.logActivityTimeSpentEvent(activityName, timeSpentOnActivityInSeconds)
      currentSession?.writeSessionToDisk()
    }
    singleThreadExecutor.execute(handleActivityPaused)
  }

  private fun onActivityDestroyed(activity: Activity) {
    CodelessManager.onActivityDestroyed(activity)
  }

  private val sessionTimeoutInSeconds: Int
    private get() {
      val settings =
          getAppSettingsWithoutQuery(FacebookSdk.getApplicationId())
              ?: return getDefaultAppEventsSessionTimeoutInSeconds()
      return settings.sessionTimeoutInSeconds
    }

  private fun cancelCurrentTask() {
    synchronized(currentFutureLock) {
      if (currentFuture != null) {
        currentFuture?.cancel(false)
      }
      currentFuture = null
    }
  }

  @JvmStatic
  fun getCurrentActivity(): Activity? {
    return if (currActivity != null) currActivity?.get() else null
  }
}


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

import android.content.Context
import com.facebook.LoggingBehavior
import com.facebook.appevents.AppEventsLogger
import com.facebook.internal.AttributionIdentifiers
import com.facebook.internal.Logger.Companion.log
import com.facebook.internal.Utility.dataProcessingOptions
import com.facebook.internal.Utility.setAppEventAttributionParameters
import com.facebook.internal.Utility.setAppEventExtendedDeviceInfoParameters
import org.json.JSONException
import org.json.JSONObject

/**
 * com.facebook.appevents.internal is solely for the use of other packages within the Facebook SDK
 * for Android. Use of any of the classes in this package is unsupported, and they may be modified
 * or removed without warning at any time.
 */
object AppEventsLoggerUtility {
  private val API_ACTIVITY_TYPE_TO_STRING: Map<GraphAPIActivityType, String> =
      hashMapOf(
          GraphAPIActivityType.MOBILE_INSTALL_EVENT to "MOBILE_APP_INSTALL",
          GraphAPIActivityType.CUSTOM_APP_EVENTS to "CUSTOM_APP_EVENTS")

  @Throws(JSONException::class)
  @JvmStatic
  fun getJSONObjectForGraphAPICall(
      activityType: GraphAPIActivityType,
      attributionIdentifiers: AttributionIdentifiers?,
      anonymousAppDeviceGUID: String?,
      limitEventUsage: Boolean,
      context: Context
  ): JSONObject {
    val publishParams = JSONObject()
    publishParams.put("event", API_ACTIVITY_TYPE_TO_STRING[activityType])
    val externalAnalyticsUserId = AppEventsLogger.getUserID()
    if (externalAnalyticsUserId != null) {
      publishParams.put("app_user_id", externalAnalyticsUserId)
    }
    setAppEventAttributionParameters(
        publishParams, attributionIdentifiers, anonymousAppDeviceGUID, limitEventUsage, context)

    // The code to get all the Extended info is safe but just in case we can wrap the
    // whole call in its own try/catch block since some of the things it does might
    // cause unexpected exceptions on rooted/funky devices:
    try {
      setAppEventExtendedDeviceInfoParameters(publishParams, context)
    } catch (e: Exception) {
      // Swallow but log
      log(
          LoggingBehavior.APP_EVENTS,
          "AppEvents",
          "Fetching extended device info parameters failed: '%s'",
          e.toString())
    }
    val dataProcessingOptions = dataProcessingOptions
    if (dataProcessingOptions != null) {
      val it = dataProcessingOptions.keys()
      while (it.hasNext()) {
        val key = it.next()
        publishParams.put(key, dataProcessingOptions[key])
      }
    }
    publishParams.put("application_package_name", context.packageName)
    return publishParams
  }

  enum class GraphAPIActivityType {
    MOBILE_INSTALL_EVENT,
    CUSTOM_APP_EVENTS
  }
}