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

/**
 * Predefined event and parameter names for logging events common to many apps. Logging occurs
 * through the [AppEventsLogger.logEvent] family of methods.
 */
object AppEventsConstants {
  // Event names
  // General purpose
  /** Log this event when an app is being activated. */
  const val EVENT_NAME_ACTIVATED_APP = "fb_mobile_activate_app"
  const val EVENT_NAME_DEACTIVATED_APP = "fb_mobile_deactivate_app"
  const val EVENT_NAME_SESSION_INTERRUPTIONS = "fb_mobile_app_interruptions"
  const val EVENT_NAME_TIME_BETWEEN_SESSIONS = "fb_mobile_time_between_sessions"

  /** Log this event when the user has completed registration with the app. */
  const val EVENT_NAME_COMPLETED_REGISTRATION = "fb_mobile_complete_registration"

  /** Log this event when the user has viewed a form of content in the app. */
  const val EVENT_NAME_VIEWED_CONTENT = "fb_mobile_content_view"

  /** Log this event when the user has performed a search within the app. */
  const val EVENT_NAME_SEARCHED = "fb_mobile_search"

  /**
   * Log this event when the user has rated an item in the app. The valueToSum passed to logEvent
   * should be the numeric rating.
   */
  const val EVENT_NAME_RATED = "fb_mobile_rate"

  /** Log this event when the user has completed a tutorial in the app. */
  const val EVENT_NAME_COMPLETED_TUTORIAL = "fb_mobile_tutorial_completion"

  /** Log this event when the app obtained a push registration token from FCM. */
  const val EVENT_NAME_PUSH_TOKEN_OBTAINED = "fb_mobile_obtain_push_token"
  // Ecommerce related
  /**
   * Log this event when the user has added an item to their cart. The valueToSum passed to logEvent
   * should be the item's price.
   */
  const val EVENT_NAME_ADDED_TO_CART = "fb_mobile_add_to_cart"

  /**
   * Log this event when the user has added an item to their wishlist. The valueToSum passed to
   * logEvent should be the item's price.
   */
  const val EVENT_NAME_ADDED_TO_WISHLIST = "fb_mobile_add_to_wishlist"

  /**
   * Log this event when the user has entered the checkout process. The valueToSum passed to
   * logEvent should be the total price in the cart.
   */
  const val EVENT_NAME_INITIATED_CHECKOUT = "fb_mobile_initiated_checkout"

  /** Log this event when the user has entered their payment info. */
  const val EVENT_NAME_ADDED_PAYMENT_INFO = "fb_mobile_add_payment_info"

  @Deprecated(
      """Use {@link AppEventsLogger#logPurchase(java.math.BigDecimal, java.util.Currency)}
        instead. Log this event when the user has completed a purchase. The {@link
   *     AppEventsLogger#logPurchase(java.math.BigDecimal, java.util.Currency)} method is a shortcut
        for logging this event.""")
  const val EVENT_NAME_PURCHASED = "fb_mobile_purchase"
  // Gaming related
  /** Log this event when the user has achieved a level in the app. */
  const val EVENT_NAME_ACHIEVED_LEVEL = "fb_mobile_level_achieved"

  /** Log this event when the user has unlocked an achievement in the app. */
  const val EVENT_NAME_UNLOCKED_ACHIEVEMENT = "fb_mobile_achievement_unlocked"

  /**
   * Log this event when the user has spent app credits. The valueToSum passed to logEvent should be
   * the number of credits spent.
   */
  const val EVENT_NAME_SPENT_CREDITS = "fb_mobile_spent_credits"

  /** A telephone/SMS, email, chat or other type of contact between a customer and your business. */
  const val EVENT_NAME_CONTACT = "Contact"

  /**
   * The customization of products through a configuration tool or other application your business
   * owns.
   */
  const val EVENT_NAME_CUSTOMIZE_PRODUCT = "CustomizeProduct"

  /** The donation of funds to your organization or cause. */
  const val EVENT_NAME_DONATE = "Donate"

  /**
   * When a person finds one of your locations via web or application, with an intention to visit
   * (example: find product at a local store).
   */
  const val EVENT_NAME_FIND_LOCATION = "FindLocation"

  /** The booking of an appointment to visit one of your locations. */
  const val EVENT_NAME_SCHEDULE = "Schedule"

  /** The start of a free trial of a product or service you offer (example: trial subscription). */
  const val EVENT_NAME_START_TRIAL = "StartTrial"

  /**
   * The submission of an application for a product, service or program you offer (example: credit
   * card, educational program or job).
   */
  const val EVENT_NAME_SUBMIT_APPLICATION = "SubmitApplication"

  /** The start of a paid subscription for a product or service you offer. */
  const val EVENT_NAME_SUBSCRIBE = "Subscribe"

  /** Log this event when the user views an ad. */
  const val EVENT_NAME_AD_IMPRESSION = "AdImpression"

  /** Log this event when the user clicks an ad. */
  const val EVENT_NAME_AD_CLICK = "AdClick"

  /** Log the live streaming events from sdk */
  const val EVENT_NAME_LIVE_STREAMING_START = "fb_sdk_live_streaming_start"
  const val EVENT_NAME_LIVE_STREAMING_STOP = "fb_sdk_live_streaming_stop"
  const val EVENT_NAME_LIVE_STREAMING_PAUSE = "fb_sdk_live_streaming_pause"
  const val EVENT_NAME_LIVE_STREAMING_RESUME = "fb_sdk_live_streaming_resume"
  const val EVENT_NAME_LIVE_STREAMING_ERROR = "fb_sdk_live_streaming_error"
  const val EVENT_NAME_LIVE_STREAMING_UPDATE_STATUS = "fb_sdk_live_streaming_update_status"

  /** Product Catalog related events */
  const val EVENT_NAME_PRODUCT_CATALOG_UPDATE = "fb_mobile_catalog_update"
  // Event parameters
  /** Paramete keys for live streaming events */
  const val EVENT_PARAM_LIVE_STREAMING_PREV_STATUS = "live_streaming_prev_status"
  const val EVENT_PARAM_LIVE_STREAMING_STATUS = "live_streaming_status"
  const val EVENT_PARAM_LIVE_STREAMING_ERROR = "live_streaming_error"

  /**
   * Parameter key used to specify currency used with logged event. E.g. "USD", "EUR", "GBP". See
   * [ISO-4217](http://en.wikipedia.org/wiki/ISO_4217) for specific values.
   */
  const val EVENT_PARAM_CURRENCY = "fb_currency"

  /**
   * Parameter key used to specify the method the user has used to register for the app, e.g.,
   * "Facebook", "email", "Twitter", etc.
   */
  const val EVENT_PARAM_REGISTRATION_METHOD = "fb_registration_method"

  /**
   * Parameter key used to specify a generic content type/family for the logged event, e.g. "music",
   * "photo", "video". Options to use will vary depending on the nature of the app.
   */
  const val EVENT_PARAM_CONTENT_TYPE = "fb_content_type"

  /**
   * Parameter key used to specify data for the one or more pieces of content being logged about.
   * Data should be a JSON encoded string. Example: "[{\"id\": \"1234\", \"quantity\": 2,
   * \"item_price\": 5.99}, {\"id\": \"5678\", \"quantity\": 1, \"item_price\": 9.99}]"
   */
  const val EVENT_PARAM_CONTENT = "fb_content"

  /**
   * Parameter key used to specify an ID for the specific piece of content being logged about. This
   * could be an EAN, article identifier, etc., depending on the nature of the app.
   */
  const val EVENT_PARAM_CONTENT_ID = "fb_content_id"

  /** Parameter key used to specify the string provided by the user for a search operation. */
  const val EVENT_PARAM_SEARCH_STRING = "fb_search_string"

  /**
   * Parameter key used to specify whether the activity being logged about was successful or not.
   * EVENT_PARAM_VALUE_YES and EVENT_PARAM_VALUE_NO are good canonical values to use for this
   * parameter.
   */
  const val EVENT_PARAM_SUCCESS = "fb_success"

  /**
   * Parameter key used to specify the maximum rating available for the EVENT_NAME_RATE event. E.g.,
   * "5" or "10".
   */
  const val EVENT_PARAM_MAX_RATING_VALUE = "fb_max_rating_value"

  /**
   * Parameter key used to specify whether payment info is available for the
   * EVENT_NAME_INITIATED_CHECKOUT event. EVENT_PARAM_VALUE_YES and EVENT_PARAM_VALUE_NO are good
   * canonical values to use for this parameter.
   */
  const val EVENT_PARAM_PAYMENT_INFO_AVAILABLE = "fb_payment_info_available"

  /**
   * Parameter key used to specify how many items are being processed for an
   * EVENT_NAME_INITIATED_CHECKOUT or EVENT_NAME_PURCHASE event.
   */
  const val EVENT_PARAM_NUM_ITEMS = "fb_num_items"

  /** Parameter key used to specify the level achieved in an EVENT_NAME_LEVEL_ACHIEVED event. */
  const val EVENT_PARAM_LEVEL = "fb_level"

  /**
   * Parameter key used to specify a description appropriate to the event being logged. E.g., the
   * name of the achievement unlocked in the EVENT_NAME_ACHIEVEMENT_UNLOCKED event.
   */
  const val EVENT_PARAM_DESCRIPTION = "fb_description"

  /** Parameter key used to specify source application package. */
  const val EVENT_PARAM_SOURCE_APPLICATION = "fb_mobile_launch_source"

  /** Parameter key used to specify package fingerprint. */
  const val EVENT_PARAM_PACKAGE_FP = "fb_mobile_pckg_fp"

  /** Parameter key used to specify hashed cert for signing the apk. */
  const val EVENT_PARAM_APP_CERT_HASH = "fb_mobile_app_cert_hash"
  // Parameter values
  /** Yes-valued parameter value to be used with parameter keys that need a Yes/No value */
  const val EVENT_PARAM_VALUE_YES = "1"

  /** No-valued parameter value to be used with parameter keys that need a Yes/No value */
  const val EVENT_PARAM_VALUE_NO = "0"

  /**
   * Parameter key used to specify the type of ad in an EVENT_NAME_AD_IMPRESSION or
   * EVENT_NAME_AD_CLICK event. E.g. "banner", "interstitial", "rewarded_video", "native"
   */
  const val EVENT_PARAM_AD_TYPE = "ad_type"

  /**
   * Parameter key used to specify the unique ID for all events within a subscription in an
   * EVENT_NAME_SUBSCRIBE or EVENT_NAME_START_TRIAL event.
   */
  const val EVENT_PARAM_ORDER_ID = "fb_order_id"
  const val EVENT_PARAM_VALUE_TO_SUM = "_valueToSum"

  /**
   * Parameter keys used to specify additional information about item for
   * EVENT_NAME_PRODUCT_CATALOG_UPDATE event.
   */
  const val EVENT_PARAM_PRODUCT_CUSTOM_LABEL_0 = "fb_product_custom_label_0"
  const val EVENT_PARAM_PRODUCT_CUSTOM_LABEL_1 = "fb_product_custom_label_1"
  const val EVENT_PARAM_PRODUCT_CUSTOM_LABEL_2 = "fb_product_custom_label_2"
  const val EVENT_PARAM_PRODUCT_CUSTOM_LABEL_3 = "fb_product_custom_label_3"
  const val EVENT_PARAM_PRODUCT_CUSTOM_LABEL_4 = "fb_product_custom_label_4"

  // Optional field "google_product_category"
  const val EVENT_PARAM_PRODUCT_CATEGORY = "fb_product_category"

  /**
   * Parameter keys used to specify the product deep links for EVENT_NAME_PRODUCT_CATALOG_UPDATE
   * event.
   */
  const val EVENT_PARAM_PRODUCT_APPLINK_IOS_URL = "fb_product_applink_ios_url"
  const val EVENT_PARAM_PRODUCT_APPLINK_IOS_APP_STORE_ID = "fb_product_applink_ios_app_store_id"
  const val EVENT_PARAM_PRODUCT_APPLINK_IOS_APP_NAME = "fb_product_applink_ios_app_name"
  const val EVENT_PARAM_PRODUCT_APPLINK_IPHONE_URL = "fb_product_applink_iphone_url"
  const val EVENT_PARAM_PRODUCT_APPLINK_IPHONE_APP_STORE_ID =
      "fb_product_applink_iphone_app_store_id"
  const val EVENT_PARAM_PRODUCT_APPLINK_IPHONE_APP_NAME = "fb_product_applink_iphone_app_name"
  const val EVENT_PARAM_PRODUCT_APPLINK_IPAD_URL = "fb_product_applink_ipad_url"
  const val EVENT_PARAM_PRODUCT_APPLINK_IPAD_APP_STORE_ID = "fb_product_applink_ipad_app_store_id"
  const val EVENT_PARAM_PRODUCT_APPLINK_IPAD_APP_NAME = "fb_product_applink_ipad_app_name"
  const val EVENT_PARAM_PRODUCT_APPLINK_ANDROID_URL = "fb_product_applink_android_url"
  const val EVENT_PARAM_PRODUCT_APPLINK_ANDROID_PACKAGE = "fb_product_applink_android_package"
  const val EVENT_PARAM_PRODUCT_APPLINK_ANDROID_APP_NAME = "fb_product_applink_android_app_name"
  const val EVENT_PARAM_PRODUCT_APPLINK_WINDOWS_PHONE_URL = "fb_product_applink_windows_phone_url"
  const val EVENT_PARAM_PRODUCT_APPLINK_WINDOWS_PHONE_APP_ID =
      "fb_product_applink_windows_phone_app_id"
  const val EVENT_PARAM_PRODUCT_APPLINK_WINDOWS_PHONE_APP_NAME =
      "fb_product_applink_windows_phone_app_name"
}


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

package com.facebook.appevents

import android.app.Application
import android.content.Context
import android.os.Bundle
import android.webkit.WebView
import androidx.annotation.RestrictTo
import com.facebook.AccessToken
import java.math.BigDecimal
import java.util.Currency

/**
 * The AppEventsLogger class allows the developer to log various types of events back to Facebook.
 * In order to log events, the app must create an instance of this class via a [
 * newLogger][.newLogger] method, and then call the various "log" methods off of that.
 *
 * This client-side event logging is then available through Facebook App Insights and for use with
 * Facebook Ads conversion tracking and optimization.
 *
 * The AppEventsLogger class has a few related roles:
 *
 * * Logging predefined and application-defined events to Facebook App Insights with a numeric value
 * to sum across a large number of events, and an optional set of key/value parameters that define
 * "segments" for this event (e.g., 'purchaserStatus' : 'frequent', or 'gamerLevel' :
 * 'intermediate'). These events may also be used for ads conversion tracking, optimization, and
 * other ads related targeting in the future.
 * * Methods that control the way in which events are flushed out to the Facebook servers.
 *
 * Here are some important characteristics of the logging mechanism provided by AppEventsLogger:
 *
 * * Events are not sent immediately when logged. They're cached and flushed out to the Facebook
 * servers in a number of situations:
 *
 * * when an event count threshold is passed (currently 100 logged events).
 * * when a time threshold is passed (currently 15 seconds).
 * * when an app has gone to background and is then brought back to the foreground.
 *
 * * Events will be accumulated when the app is in a disconnected state, and sent when the
 * connection is restored and one of the above 'flush' conditions are met.
 * * The AppEventsLogger class is intended to be used from the thread it was created on. Multiple
 * AppEventsLoggers may be created on other threads if desired.
 * * The developer can call the setFlushBehavior method to force the flushing of events to only
 * occur on an explicit call to the `flush` method.
 * * The developer can turn on console debug output for event logging and flushing to the server by
 * calling FacebookSdk.addLoggingBehavior(LoggingBehavior.APP_EVENTS);
 *
 * Some things to note when logging events:
 *
 * * There is a limit on the number of unique event names an app can use, on the order of 1000.
 * * There is a limit to the number of unique parameter names in the provided parameters that can be
 * used per event, on the order of 25. This is not just for an individual call, but for all
 * invocations for that eventName.
 * * Event names and parameter names must be between 2 and 40 characters, and must consist of
 * alphanumeric characters, _, -, or spaces.
 * * The length of each parameter value can be no more than on the order of 100 characters.
 */
class AppEventsLogger
private constructor(context: Context, applicationId: String?, accessToken: AccessToken?) {
  // Enums
  /** Controls when an AppEventsLogger sends log events to the server */
  enum class FlushBehavior {
    /**
     * Flush automatically: periodically (every 15 seconds or after every 100 events), and always at
     * app reactivation. This is the default value.
     */
    AUTO,

    /** Only flush when AppEventsLogger.flush() is explicitly invoked. */
    EXPLICIT_ONLY
  }

  /** Product availability for Product Catalog product item update */
  enum class ProductAvailability {
    /** Item ships immediately */
    IN_STOCK,

    /** No plan to restock */
    OUT_OF_STOCK,

    /** Available in future */
    PREORDER,

    /** Ships in 1-2 weeks */
    AVALIABLE_FOR_ORDER,

    /** Discontinued */
    DISCONTINUED
  }

  /** Product condition for Product Catalog product item update */
  enum class ProductCondition {
    NEW,
    REFURBISHED,
    USED
  }

  private val loggerImpl: AppEventsLoggerImpl =
      AppEventsLoggerImpl(context, applicationId, accessToken)

  /**
   * Log an app event with the specified name.
   *
   * @param eventName eventName used to denote the event. Choose amongst the EVENT_NAME_* constants
   * in [AppEventsConstants] when possible. Or create your own if none of the EVENT_NAME_* constants
   * are applicable. Event names should be 40 characters or less, alphanumeric, and can include
   * spaces, underscores or hyphens, but must not have a space or hyphen as the first character. Any
   * given app should have no more than 1000 distinct event names.
   */
  fun logEvent(eventName: String?) {
    loggerImpl.logEvent(eventName)
  }

  /**
   * Log an app event with the specified name and the supplied value.
   *
   * @param eventName eventName used to denote the event. Choose amongst the EVENT_NAME_* constants
   * in [AppEventsConstants] when possible. Or create your own if none of the EVENT_NAME_* constants
   * are applicable. Event names should be 40 characters or less, alphanumeric, and can include
   * spaces, underscores or hyphens, but must not have a space or hyphen as the first character. Any
   * given app should have no more than 1000 distinct event names. * @param eventName
   * @param valueToSum a value to associate with the event which will be summed up in Insights for
   * across all instances of the event, so that average values can be determined, etc.
   */
  fun logEvent(eventName: String?, valueToSum: Double) {
    loggerImpl.logEvent(eventName, valueToSum)
  }

  /**
   * Log an app event with the specified name and set of parameters.
   *
   * @param eventName eventName used to denote the event. Choose amongst the EVENT_NAME_* constants
   * in [AppEventsConstants] when possible. Or create your own if none of the EVENT_NAME_* constants
   * are applicable. Event names should be 40 characters or less, alphanumeric, and can include
   * spaces, underscores or hyphens, but must not have a space or hyphen as the first character. Any
   * given app should have no more than 1000 distinct event names.
   * @param parameters A Bundle of parameters to log with the event. Insights will allow looking at
   * the logs of these events via different parameter values. You can log on the order of 25
   * parameters with each distinct eventName. It's advisable to limit the number of unique values
   * provided for each parameter in the thousands. As an example, don't attempt to provide a unique
   * parameter value for each unique user in your app. You won't get meaningful aggregate reporting
   * on so many parameter values. The values in the bundles should be Strings or numeric values.
   */
  fun logEvent(eventName: String?, parameters: Bundle?) {
    loggerImpl.logEvent(eventName, parameters)
  }

  /**
   * Log an app event with the specified name, supplied value, and set of parameters.
   *
   * @param eventName eventName used to denote the event. Choose amongst the EVENT_NAME_* constants
   * in [AppEventsConstants] when possible. Or create your own if none of the EVENT_NAME_* constants
   * are applicable. Event names should be 40 characters or less, alphanumeric, and can include
   * spaces, underscores or hyphens, but must not have a space or hyphen as the first character. Any
   * given app should have no more than 1000 distinct event names.
   * @param valueToSum a value to associate with the event which will be summed up in Insights for
   * across all instances of the event, so that average values can be determined, etc.
   * @param parameters A Bundle of parameters to log with the event. Insights will allow looking at
   * the logs of these events via different parameter values. You can log on the order of 25
   * parameters with each distinct eventName. It's advisable to limit the number of unique values
   * provided for each parameter in the thousands. As an example, don't attempt to provide a unique
   * parameter value for each unique user in your app. You won't get meaningful aggregate reporting
   * on so many parameter values. The values in the bundles should be Strings or numeric values.
   */
  fun logEvent(eventName: String?, valueToSum: Double, parameters: Bundle?) {
    loggerImpl.logEvent(eventName, valueToSum, parameters)
  }

  /**
   * Logs a purchase event with Facebook, in the specified amount and with the specified currency.
   *
   * @param purchaseAmount Amount of purchase, in the currency specified by the 'currency'
   * parameter. This value will be rounded to the thousandths place (e.g., 12.34567 becomes 12.346).
   * @param currency Currency used to specify the amount.
   */
  fun logPurchase(purchaseAmount: BigDecimal?, currency: Currency?) {
    loggerImpl.logPurchase(purchaseAmount, currency)
  }

  /**
   * Logs a purchase event with Facebook explicitly, in the specified amount and with the specified
   * currency. Additional detail about the purchase can be passed in through the parameters bundle.
   *
   * @param purchaseAmount Amount of purchase, in the currency specified by the 'currency'
   * parameter. This value will be rounded to the thousandths place (e.g., 12.34567 becomes 12.346).
   * @param currency Currency used to specify the amount.
   * @param parameters Arbitrary additional information for describing this event. This should have
   * no more than 24 entries, and keys should be mostly consistent from one purchase event to the
   * next.
   */
  fun logPurchase(purchaseAmount: BigDecimal?, currency: Currency?, parameters: Bundle?) {
    loggerImpl.logPurchase(purchaseAmount, currency, parameters)
  }

  /**
   * Logs an app event that tracks that the application was open via Push Notification.
   *
   * @param payload Notification payload received.
   */
  fun logPushNotificationOpen(payload: Bundle) {
    loggerImpl.logPushNotificationOpen(payload, null)
  }

  /**
   * Logs an app event that tracks that the application was open via Push Notification.
   *
   * @param payload Notification payload received.
   */
  fun logPushNotificationOpen(payload: Bundle, action: String?) {
    loggerImpl.logPushNotificationOpen(payload, action)
  }

  /**
   * Uploads product catalog product item as an app event.
   *
   * @param itemID Unique ID for the item. Can be a variant for a product. Max size is 100.
   * @param availability If item is in stock. Accepted values are: in stock - Item ships immediately
   * out of stock - No plan to restock preorder - Available in future available for order - Ships in
   * 1-2 weeks discontinued - Discontinued
   * @param condition Product condition: new, refurbished or used.
   * @param description Short text describing product. Max size is 5000.
   * @param imageLink Link to item image used in ad.
   * @param link Link to merchant's site where someone can buy the item.
   * @param title Title of item.
   * @param priceAmount Amount of purchase, in the currency specified by the 'currency' parameter.
   * This value will be rounded to the thousandths place (e.g., 12.34567 becomes 12.346).
   * @param currency Currency used to specify the amount.
   * @param gtin Global Trade Item Number including UPC, EAN, JAN and ISBN
   * @param mpn Unique manufacture ID for product
   * @param brand Name of the brand Note: Either gtin, mpn or brand is required.
   * @param parameters Optional fields for deep link specification.
   */
  fun logProductItem(
      itemID: String?,
      availability: ProductAvailability?,
      condition: ProductCondition?,
      description: String?,
      imageLink: String?,
      link: String?,
      title: String?,
      priceAmount: BigDecimal?,
      currency: Currency?,
      gtin: String?,
      mpn: String?,
      brand: String?,
      parameters: Bundle?
  ) {
    loggerImpl.logProductItem(
        itemID,
        availability,
        condition,
        description,
        imageLink,
        link,
        title,
        priceAmount,
        currency,
        gtin,
        mpn,
        brand,
        parameters)
  }

  /**
   * Explicitly flush any stored events to the server. Implicit flushes may happen depending on the
   * value of getFlushBehavior. This method allows for explicit, app invoked flushing.
   */
  fun flush() {
    loggerImpl.flush()
  }

  /**
   * Determines if the logger is valid for the given access token.
   *
   * @param accessToken The access token to check.
   * @return True if the access token is valid for this logger.
   */
  fun isValidForAccessToken(accessToken: AccessToken): Boolean {
    return loggerImpl.isValidForAccessToken(accessToken)
  }

  /**
   * Returns the app ID this logger was configured to log to.
   *
   * @return the Facebook app ID
   */
  val applicationId: String
    get() = loggerImpl.applicationId

  companion object {
    // Constants
    private val TAG = AppEventsLogger::class.java.canonicalName

    /**
     * Notifies the events system that the app has launched and activate and deactivate events
     * should start being logged automatically. By default this function is called automatically
     * from sdkInitialize() flow. In case 'com.facebook.sdk.AutoLogAppEventsEnabled' manifest
     * setting is set to false, it should typically be called from the OnCreate method of you
     * application.
     *
     * @param application The running application
     */
    @JvmStatic
    fun activateApp(application: Application) {
      AppEventsLoggerImpl.activateApp(application, null)
    }

    /**
     * Notifies the events system that the app has launched and activate and deactivate events
     * should start being logged automatically. By default this function is called automatically
     * from sdkInitialize() flow. In case 'com.facebook.sdk.AutoLogAppEventsEnabled' manifest
     * setting is set to false, it should typically be called from the OnCreate method of you
     * application.
     *
     * Call this if you wish to use a different Application ID then the one specified in the
     * Facebook SDK.
     *
     * @param application The running application
     * @param applicationId The application id used to log activate/deactivate events.
     */
    @JvmStatic
    fun activateApp(application: Application, applicationId: String?) {
      AppEventsLoggerImpl.activateApp(application, applicationId)
    }

    /**
     * Notifies the events system which internal SDK Libraries, and some specific external Libraries
     * that the app is utilizing. This is called internally and does NOT need to be called
     * externally.
     *
     * @param context The Context
     * @param applicationId The String applicationId
     */
    @JvmStatic
    fun initializeLib(context: Context, applicationId: String?) {
      AppEventsLoggerImpl.initializeLib(context, applicationId)
    }

    /**
     * Build an AppEventsLogger instance to log events through. The Facebook app that these events
     * are targeted at comes from this application's metadata. The application ID used to log events
     * will be determined from the app ID specified in the package metadata.
     *
     * @param context Used to access the applicationId and the attributionId for non-authenticated
     * users.
     * @return AppEventsLogger instance to invoke log* methods on.
     */
    @JvmStatic
    fun newLogger(context: Context): AppEventsLogger {
      return AppEventsLogger(context, null, null)
    }

    /**
     * Build an AppEventsLogger instance to log events through.
     *
     * @param context Used to access the attributionId for non-authenticated users.
     * @param accessToken Access token to use for logging events. If null, the active access token
     * will be used, if any; if not the logging will happen against the default app ID specified in
     * the package metadata.
     */
    @JvmStatic
    fun newLogger(context: Context, accessToken: AccessToken?): AppEventsLogger {
      return AppEventsLogger(context, null, accessToken)
    }

    /**
     * Build an AppEventsLogger instance to log events through.
     *
     * @param context Used to access the attributionId for non-authenticated users.
     * @param applicationId Explicitly specified Facebook applicationId to log events against. If
     * null, the default app ID specified in the package metadata will be used.
     * @param accessToken Access token to use for logging events. If null, the active access token
     * will be used, if any; if not the logging will happen against the default app ID specified in
     * the package metadata.
     * @return AppEventsLogger instance to invoke log* methods on.
     */
    @JvmStatic
    fun newLogger(
        context: Context,
        applicationId: String?,
        accessToken: AccessToken?
    ): AppEventsLogger {
      return AppEventsLogger(context, applicationId, accessToken)
    }

    /**
     * Build an AppEventsLogger instance to log events that are attributed to the application but
     * not to any particular Session.
     *
     * @param context Used to access the attributionId for non-authenticated users.
     * @param applicationId Explicitly specified Facebook applicationId to log events against. If
     * null, the default app ID specified in the package metadata will be used.
     * @return AppEventsLogger instance to invoke log* methods on.
     */
    @JvmStatic
    fun newLogger(context: Context, applicationId: String?): AppEventsLogger {
      return AppEventsLogger(context, applicationId, null)
    }

    /**
     * The action used to indicate that a flush of app events has occurred. This should be used as
     * an action in an IntentFilter and BroadcastReceiver registered with the [ ].
     */
    const val ACTION_APP_EVENTS_FLUSHED = "com.facebook.sdk.APP_EVENTS_FLUSHED"
    const val APP_EVENTS_EXTRA_NUM_EVENTS_FLUSHED = "com.facebook.sdk.APP_EVENTS_NUM_EVENTS_FLUSHED"
    const val APP_EVENTS_EXTRA_FLUSH_RESULT = "com.facebook.sdk.APP_EVENTS_FLUSH_RESULT"
    /**
     * Access the behavior that AppEventsLogger uses to determine when to flush logged events to the
     * server. This setting applies to all instances of AppEventsLogger.
     *
     * @return Specified flush behavior.
     */
    @JvmStatic
    fun getFlushBehavior(): FlushBehavior? {
      return AppEventsLoggerImpl.getFlushBehavior()
    }
    /**
     * Set the behavior that this AppEventsLogger uses to determine when to flush logged events to
     * the server. This setting applies to all instances of AppEventsLogger.
     *
     * @param flushBehavior the desired behavior.
     */
    @JvmStatic
    fun setFlushBehavior(flushBehavior: FlushBehavior) {
      AppEventsLoggerImpl.setFlushBehavior(flushBehavior)
    }

    /**
     * Call this when the consuming Activity/Fragment receives an onStop() callback in order to
     * persist any outstanding events to disk so they may be flushed at a later time. The next flush
     * (explicit or not) will check for any outstanding events and if present, include them in that
     * flush. Note that this call may trigger an I/O operation on the calling thread. Explicit use
     * of this method is necessary.
     */
    @JvmStatic
    fun onContextStop() {
      AppEventsLoggerImpl.onContextStop()
    }

    /**
     * Sets and sends registration id to register the current app for push notifications.
     *
     * @param registrationId RegistrationId received from FCM.
     */
    @JvmStatic
    fun setPushNotificationsRegistrationId(registrationId: String?) {
      AppEventsLoggerImpl.setPushNotificationsRegistrationId(registrationId)
    }

    /**
     * Intended to be used as part of a hybrid webapp. If you call this method, the FB SDK will add
     * a new JavaScript interface into your webview. If the FB Pixel is used within the webview, and
     * references the app ID of this app, then it will detect the presence of this injected
     * JavaScript object and pass Pixel events back to the FB SDK for logging using the AppEvents
     * framework.
     *
     * @param webView The webview to augment with the additional JavaScript behaviour
     * @param context Used to access the applicationId and the attributionId for non-authenticated
     * users.
     */
    @JvmStatic
    fun augmentWebView(webView: WebView, context: Context?) {
      AppEventsLoggerImpl.augmentWebView(webView, context)
    }
    /** Returns the set user id else null. */
    @JvmStatic
    fun getUserID(): String? {
      return AnalyticsUserIDStore.getUserID()
    }
    /**
     * Sets a user id to associate with all app events. This can be used to associate your own user
     * id with the app events logged from this instance of an application.
     *
     * The user ID will be persisted between application instances.
     *
     * @param userID A User ID, or null to clear the User ID
     */
    @JvmStatic
    fun setUserID(userID: String?) {
      AnalyticsUserIDStore.setUserID(userID)
    }

    /** Clears the currently set user id. */
    @JvmStatic
    fun clearUserID() {
      AnalyticsUserIDStore.setUserID(null)
    }

    /**
     * Sets user data to associate with all app events. All user data are hashed and used to match
     * Facebook user from this instance of an application.
     *
     * The user data will be persisted between application instances.
     *
     * @param email user's email
     * @param firstName user's first name
     * @param lastName user's last name
     * @param phone user's phone
     * @param dateOfBirth user's date of birth
     * @param gender user's gender
     * @param city user's city
     * @param state user's state
     * @param zip user's zip
     * @param country user's country
     */
    @JvmStatic
    fun setUserData(
        email: String?,
        firstName: String?,
        lastName: String?,
        phone: String?,
        dateOfBirth: String?,
        gender: String?,
        city: String?,
        state: String?,
        zip: String?,
        country: String?
    ) {
      UserDataStore.setUserDataAndHash(
          email, firstName, lastName, phone, dateOfBirth, gender, city, state, zip, country)
    }

    /** Returns the set user data else null. */
    @JvmStatic
    fun getUserData(): String {
      return UserDataStore.getHashedUserData()
    }

    /** Clears the current user data */
    @JvmStatic
    fun clearUserData() {
      UserDataStore.clear()
    }

    /**
     * Each app/device pair gets an GUID that is sent back with App Events and persisted with this
     * app/device pair.
     *
     * @param context The application context.
     * @return The GUID for this app/device pair.
     */
    @JvmStatic
    fun getAnonymousAppDeviceGUID(context: Context): String {
      return AppEventsLoggerImpl.getAnonymousAppDeviceGUID(context)
    }

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    @JvmStatic
    fun setInstallReferrer(referrer: String?) {
      AppEventsLoggerImpl.setInstallReferrer(referrer)
    }
  }
}


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

package com.facebook.appevents

import android.app.Application
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.webkit.WebView
import com.facebook.AccessToken
import com.facebook.AccessToken.Companion.getCurrentAccessToken
import com.facebook.FacebookException
import com.facebook.FacebookSdk
import com.facebook.LoggingBehavior
import com.facebook.appevents.AppEventQueue.add
import com.facebook.appevents.AppEventQueue.flush
import com.facebook.appevents.AppEventQueue.getKeySet
import com.facebook.appevents.AppEventQueue.persistToDisk
import com.facebook.appevents.integrity.MACARuleMatchingManager
import com.facebook.appevents.integrity.ProtectedModeManager.processParametersForProtectedMode
import com.facebook.appevents.internal.ActivityLifecycleTracker.getCurrentSessionGuid
import com.facebook.appevents.internal.ActivityLifecycleTracker.isInBackground
import com.facebook.appevents.internal.ActivityLifecycleTracker.startTracking
import com.facebook.appevents.internal.AutomaticAnalyticsLogger.isImplicitPurchaseLoggingEnabled
import com.facebook.appevents.internal.Constants
import com.facebook.appevents.ondeviceprocessing.OnDeviceProcessingManager.isOnDeviceProcessingEnabled
import com.facebook.appevents.ondeviceprocessing.OnDeviceProcessingManager.sendCustomEventAsync
import com.facebook.internal.AnalyticsEvents
import com.facebook.internal.FeatureManager
import com.facebook.internal.FeatureManager.isEnabled
import com.facebook.internal.FetchedAppGateKeepersManager.getGateKeeperForKey
import com.facebook.internal.FetchedAppSettingsManager.queryAppSettings
import com.facebook.internal.InstallReferrerUtil
import com.facebook.internal.InstallReferrerUtil.tryUpdateReferrerInfo
import com.facebook.internal.Logger.Companion.log
import com.facebook.internal.Utility.getActivityName
import com.facebook.internal.Utility.getMetadataApplicationId
import com.facebook.internal.Utility.isNullOrEmpty
import com.facebook.internal.Utility.logd
import com.facebook.internal.Utility.stringsEqualOrEmpty
import com.facebook.internal.Validate.sdkInitialized
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import java.math.BigDecimal
import java.util.Currency
import java.util.UUID
import java.util.concurrent.Executor
import java.util.concurrent.ScheduledThreadPoolExecutor
import java.util.concurrent.TimeUnit
import kotlin.collections.HashSet
import kotlin.collections.MutableSet
import kotlin.collections.indices
import kotlin.collections.isNotEmpty
import kotlin.collections.toTypedArray
import org.json.JSONException
import org.json.JSONObject

@AutoHandleExceptions
internal class AppEventsLoggerImpl
internal constructor(activityName: String, applicationId: String?, accessToken: AccessToken?) {

  internal constructor(
      context: Context?,
      applicationId: String?,
      accessToken: AccessToken?
  ) : this(getActivityName(context), applicationId, accessToken)

  // Instance member variables
  private val contextName: String
  private var accessTokenAppId: AccessTokenAppIdPair

  fun logEvent(eventName: String?) {
    logEvent(eventName, null)
  }

  fun logEvent(eventName: String?, parameters: Bundle? = null) {
    logEvent(eventName, null, parameters, false, getCurrentSessionGuid())
  }

  fun logEvent(eventName: String?, valueToSum: Double) {
    logEvent(eventName, valueToSum, null)
  }

  fun logEvent(eventName: String?, valueToSum: Double, parameters: Bundle?) {
    logEvent(eventName, valueToSum, parameters, false, getCurrentSessionGuid())
  }

  fun logEventFromSE(eventName: String?, buttonText: String?) {
    val parameters = Bundle()
    parameters.putString("_is_suggested_event", "1")
    parameters.putString("_button_text", buttonText)
    logEvent(eventName, parameters)
  }

  fun logPurchase(purchaseAmount: BigDecimal?, currency: Currency?) {
    logPurchase(purchaseAmount, currency, null)
  }

  fun logPurchase(purchaseAmount: BigDecimal?, currency: Currency?, parameters: Bundle? = null) {
    if (isImplicitPurchaseLoggingEnabled()) {
      Log.w(
          TAG,
          "You are logging purchase events while auto-logging of in-app purchase is " +
              "enabled in the SDK. Make sure you don't log duplicate events")
    }
    logPurchase(purchaseAmount, currency, parameters, false)
  }

  fun logPurchaseImplicitly(purchaseAmount: BigDecimal?, currency: Currency?, parameters: Bundle?) {
    logPurchase(purchaseAmount, currency, parameters, true)
  }

  fun logPurchase(
      purchaseAmount: BigDecimal?,
      currency: Currency?,
      parameters: Bundle?,
      isImplicitlyLogged: Boolean
  ) {
    var parameters = parameters
    if (purchaseAmount == null) {
      notifyDeveloperError("purchaseAmount cannot be null")
      return
    } else if (currency == null) {
      notifyDeveloperError("currency cannot be null")
      return
    }
    if (parameters == null) {
      parameters = Bundle()
    }
    parameters.putString(AppEventsConstants.EVENT_PARAM_CURRENCY, currency.currencyCode)
    logEvent(
        AppEventsConstants.EVENT_NAME_PURCHASED,
        purchaseAmount.toDouble(),
        parameters,
        isImplicitlyLogged,
        getCurrentSessionGuid())
    eagerFlush()
  }

  fun logPushNotificationOpen(payload: Bundle, action: String?) {
    var campaignId: String? = null
    try {
      val payloadString = payload.getString(PUSH_PAYLOAD_KEY)
      if (isNullOrEmpty(payloadString)) {
        return // Ignore the payload if no fb push payload is present.
      }
      val facebookPayload = JSONObject(payloadString)
      campaignId = facebookPayload.getString(PUSH_PAYLOAD_CAMPAIGN_KEY)
    } catch (je: JSONException) {
      // ignore
    }
    if (campaignId == null) {
      log(
          LoggingBehavior.DEVELOPER_ERRORS,
          TAG,
          "Malformed payload specified for logging a push notification open.")
      return
    }
    val parameters = Bundle()
    parameters.putString(APP_EVENT_PUSH_PARAMETER_CAMPAIGN, campaignId)
    if (action != null) {
      parameters.putString(APP_EVENT_PUSH_PARAMETER_ACTION, action)
    }
    logEvent(APP_EVENT_NAME_PUSH_OPENED, parameters)
  }

  fun logProductItem(
      itemID: String?,
      availability: AppEventsLogger.ProductAvailability?,
      condition: AppEventsLogger.ProductCondition?,
      description: String?,
      imageLink: String?,
      link: String?,
      title: String?,
      priceAmount: BigDecimal?,
      currency: Currency?,
      gtin: String?,
      mpn: String?,
      brand: String?,
      parameters: Bundle?
  ) {
    var parameters = parameters
    if (itemID == null) {
      notifyDeveloperError("itemID cannot be null")
      return
    } else if (availability == null) {
      notifyDeveloperError("availability cannot be null")
      return
    } else if (condition == null) {
      notifyDeveloperError("condition cannot be null")
      return
    } else if (description == null) {
      notifyDeveloperError("description cannot be null")
      return
    } else if (imageLink == null) {
      notifyDeveloperError("imageLink cannot be null")
      return
    } else if (link == null) {
      notifyDeveloperError("link cannot be null")
      return
    } else if (title == null) {
      notifyDeveloperError("title cannot be null")
      return
    } else if (priceAmount == null) {
      notifyDeveloperError("priceAmount cannot be null")
      return
    } else if (currency == null) {
      notifyDeveloperError("currency cannot be null")
      return
    } else if (gtin == null && mpn == null && brand == null) {
      notifyDeveloperError("Either gtin, mpn or brand is required")
      return
    }
    if (parameters == null) {
      parameters = Bundle()
    }
    parameters.putString(Constants.EVENT_PARAM_PRODUCT_ITEM_ID, itemID)
    parameters.putString(Constants.EVENT_PARAM_PRODUCT_AVAILABILITY, availability.name)
    parameters.putString(Constants.EVENT_PARAM_PRODUCT_CONDITION, condition.name)
    parameters.putString(Constants.EVENT_PARAM_PRODUCT_DESCRIPTION, description)
    parameters.putString(Constants.EVENT_PARAM_PRODUCT_IMAGE_LINK, imageLink)
    parameters.putString(Constants.EVENT_PARAM_PRODUCT_LINK, link)
    parameters.putString(Constants.EVENT_PARAM_PRODUCT_TITLE, title)
    parameters.putString(
        Constants.EVENT_PARAM_PRODUCT_PRICE_AMOUNT,
        priceAmount.setScale(3, BigDecimal.ROUND_HALF_UP).toString())
    parameters.putString(Constants.EVENT_PARAM_PRODUCT_PRICE_CURRENCY, currency.currencyCode)
    if (gtin != null) {
      parameters.putString(Constants.EVENT_PARAM_PRODUCT_GTIN, gtin)
    }
    if (mpn != null) {
      parameters.putString(Constants.EVENT_PARAM_PRODUCT_MPN, mpn)
    }
    if (brand != null) {
      parameters.putString(Constants.EVENT_PARAM_PRODUCT_BRAND, brand)
    }
    logEvent(AppEventsConstants.EVENT_NAME_PRODUCT_CATALOG_UPDATE, parameters)
    eagerFlush()
  }

  fun flush() {
    flush(FlushReason.EXPLICIT)
  }

  fun isValidForAccessToken(accessToken: AccessToken): Boolean {
    val other = AccessTokenAppIdPair(accessToken)
    return accessTokenAppId == other
  }

  fun logSdkEvent(eventName: String, valueToSum: Double?, parameters: Bundle?) {
    if (!eventName.startsWith(ACCOUNT_KIT_EVENT_NAME_PREFIX)) {
      Log.e(
          TAG,
          "logSdkEvent is deprecated and only supports account kit for legacy, " +
              "please use logEvent instead")
      return
    }
    if (FacebookSdk.getAutoLogAppEventsEnabled()) {
      logEvent(eventName, valueToSum, parameters, true, getCurrentSessionGuid())
    }
  }

  /**
   * Returns the app ID this logger was configured to log to.
   *
   * @return the Facebook app ID
   */
  val applicationId: String
    get() = accessTokenAppId.applicationId

  fun logEventImplicitly(eventName: String?, valueToSum: Double?, parameters: Bundle?) {
    logEvent(eventName, valueToSum, parameters, true, getCurrentSessionGuid())
  }

  fun logEventImplicitly(
      eventName: String?,
      purchaseAmount: BigDecimal?,
      currency: Currency?,
      parameters: Bundle?
  ) {
    var parameters = parameters
    if (purchaseAmount == null || currency == null) {
      logd(TAG, "purchaseAmount and currency cannot be null")
      return
    }
    if (parameters == null) {
      parameters = Bundle()
    }
    parameters.putString(AppEventsConstants.EVENT_PARAM_CURRENCY, currency.currencyCode)
    logEvent(eventName, purchaseAmount.toDouble(), parameters, true, getCurrentSessionGuid())
  }

  fun logEvent(
      eventName: String?,
      valueToSum: Double?,
      parameters: Bundle?,
      isImplicitlyLogged: Boolean,
      currentSessionId: UUID?
  ) {
    if (eventName == null || eventName.isEmpty()) {
      return
    }

    // Kill events if kill-switch is enabled
    if (getGateKeeperForKey(APP_EVENTS_KILLSWITCH, FacebookSdk.getApplicationId(), false)) {
      log(
          LoggingBehavior.APP_EVENTS,
          "AppEvents",
          "KillSwitch is enabled and fail to log app event: %s",
          eventName)
      return
    }
    try {
      MACARuleMatchingManager.processParameters(parameters, eventName)
      processParametersForProtectedMode(parameters)
      val event =
          AppEvent(
              contextName,
              eventName,
              valueToSum,
              parameters,
              isImplicitlyLogged,
              isInBackground(),
              currentSessionId)
      logEvent(event, accessTokenAppId)
    } catch (jsonException: JSONException) {
      // If any of the above failed, just consider this an illegal event.
      log(
          LoggingBehavior.APP_EVENTS,
          "AppEvents",
          "JSON encoding for app event failed: '%s'",
          jsonException.toString())
    } catch (e: FacebookException) {
      // If any of the above failed, just consider this an illegal event.
      log(LoggingBehavior.APP_EVENTS, "AppEvents", "Invalid app event: %s", e.toString())
    }
  }

  companion object {
    // Constants
    private val TAG =
        AppEventsLoggerImpl::class.java.canonicalName
            ?: "com.facebook.appevents.AppEventsLoggerImpl"
    private const val APP_SUPPORTS_ATTRIBUTION_ID_RECHECK_PERIOD_IN_SECONDS = 60 * 60 * 24
    private const val PUSH_PAYLOAD_KEY = "fb_push_payload"
    private const val PUSH_PAYLOAD_CAMPAIGN_KEY = "campaign"
    private const val APP_EVENT_NAME_PUSH_OPENED = "fb_mobile_push_opened"
    private const val APP_EVENT_PUSH_PARAMETER_CAMPAIGN = "fb_push_campaign"
    private const val APP_EVENT_PUSH_PARAMETER_ACTION = "fb_push_action"
    private const val ACCOUNT_KIT_EVENT_NAME_PREFIX = "fb_ak"
    private var backgroundExecutor: ScheduledThreadPoolExecutor? = null
    private var flushBehaviorField = AppEventsLogger.FlushBehavior.AUTO
    @JvmStatic
    fun getFlushBehavior(): AppEventsLogger.FlushBehavior {
      synchronized(staticLock) {
        return flushBehaviorField
      }
    }

    @JvmStatic
    fun setFlushBehavior(flushBehavior: AppEventsLogger.FlushBehavior) {
      synchronized(staticLock) { flushBehaviorField = flushBehavior }
    }
    private val staticLock = Any()
    private var anonymousAppDeviceGUID: String? = null
    private var isActivateAppEventRequested = false

    // Log implicit push token event and flush logger immediately
    private var pushNotificationsRegistrationIdField: String? = null
    @JvmStatic
    fun getPushNotificationsRegistrationId(): String? {
      synchronized(staticLock) {
        return pushNotificationsRegistrationIdField
      }
    }
    @JvmStatic
    fun setPushNotificationsRegistrationId(registrationId: String?) {
      synchronized(staticLock) {
        if (!stringsEqualOrEmpty(pushNotificationsRegistrationIdField, registrationId)) {
          pushNotificationsRegistrationIdField = registrationId
          val logger = AppEventsLoggerImpl(FacebookSdk.getApplicationContext(), null, null)
          // Log implicit push token event and flush logger immediately
          logger.logEvent(AppEventsConstants.EVENT_NAME_PUSH_TOKEN_OBTAINED)
          if (getFlushBehavior() != AppEventsLogger.FlushBehavior.EXPLICIT_ONLY) {
            logger.flush()
          }
        }
      }
    }
    private const val APP_EVENT_PREFERENCES = "com.facebook.sdk.appEventPreferences"
    private const val APP_EVENTS_KILLSWITCH = "app_events_killswitch"
    @JvmStatic
    fun activateApp(application: Application, applicationId: String?) {
      var applicationId = applicationId
      if (!FacebookSdk.isInitialized()) {
        throw FacebookException(
            "The Facebook sdk must be initialized before calling " + "activateApp")
      }
      AnalyticsUserIDStore.initStore()
      UserDataStore.initStore()
      if (applicationId == null) {
        applicationId = FacebookSdk.getApplicationId()
      }

      // activateApp supersedes publishInstall in the public API, so we need to explicitly invoke
      // it, since the server can't reliably infer install state for all conditions of an app
      // activate.
      FacebookSdk.publishInstallAsync(application, applicationId)

      // Will do nothing in case AutoLogAppEventsEnabled is true, as we already started the
      // tracking as part of sdkInitialize() flow
      startTracking(application, applicationId)
    }

    @JvmStatic
    fun functionDEPRECATED(extraMsg: String) {
      Log.w(TAG, "This function is deprecated. $extraMsg")
    }

    @JvmStatic
    fun initializeLib(context: Context, applicationId: String?) {
      if (!FacebookSdk.getAutoLogAppEventsEnabled()) {
        return
      }
      val logger = AppEventsLoggerImpl(context, applicationId, null)
      checkNotNull(backgroundExecutor).execute {
        val params = Bundle()
        val classes =
            arrayOf( // internal SDK Libraries
                "com.facebook.core.Core",
                "com.facebook.login.Login",
                "com.facebook.share.Share",
                "com.facebook.places.Places",
                "com.facebook.messenger.Messenger",
                "com.facebook.applinks.AppLinks",
                "com.facebook.marketing.Marketing",
                "com.facebook.gamingservices.GamingServices",
                "com.facebook.all.All", // external SDK Libraries
                "com.android.billingclient.api.BillingClient",
                "com.android.vending.billing.IInAppBillingService")
        val keys =
            arrayOf( // internal SDK Libraries
                "core_lib_included",
                "login_lib_included",
                "share_lib_included",
                "places_lib_included",
                "messenger_lib_included",
                "applinks_lib_included",
                "marketing_lib_included",
                "gamingservices_lib_included",
                "all_lib_included", // external SDK Libraries
                "billing_client_lib_included",
                "billing_service_lib_included")
        if (classes.size != keys.size) {
          throw FacebookException("Number of class names and key names should match")
        }
        var bitmask = 0
        for (i in classes.indices) {
          val className = classes[i]
          val keyName = keys[i]
          try {
            Class.forName(className)
            params.putInt(keyName, 1)
            bitmask = bitmask or (1 shl i)
          } catch (ignored: ClassNotFoundException) {
            /* no op */
          }
        }
        val preferences = context.getSharedPreferences(APP_EVENT_PREFERENCES, Context.MODE_PRIVATE)
        val previousBitmask = preferences.getInt("kitsBitmask", 0)
        if (previousBitmask != bitmask) {
          preferences.edit().putInt("kitsBitmask", bitmask).apply()
          logger.logEventImplicitly(AnalyticsEvents.EVENT_SDK_INITIALIZE, null, params)
        }
      }
    }
    @JvmStatic
    fun onContextStop() {
      // TODO: (v4) add onContextStop() to samples that use the logger.
      persistToDisk()
    }

    // will make async connection to try retrieve data. First time we might return null
    // instead of actual result.
    // Should not be a problem as subsequent calls will have the data if it exists.
    @JvmStatic
    fun getInstallReferrer(): String? {
      tryUpdateReferrerInfo(
          object : InstallReferrerUtil.Callback {
            // will make async connection to try retrieve data. First time we might return null
            // instead of actual result.
            // Should not be a problem as subsequent calls will have the data if it exists.
            override fun onReceiveReferrerUrl(s: String?) {
              setInstallReferrer(s)
            }
          })
      val ctx = FacebookSdk.getApplicationContext()
      val preferences = ctx.getSharedPreferences(APP_EVENT_PREFERENCES, Context.MODE_PRIVATE)
      return preferences.getString("install_referrer", null)
    }
    @JvmStatic
    fun setInstallReferrer(referrer: String?) {
      val ctx = FacebookSdk.getApplicationContext()
      val preferences = ctx.getSharedPreferences(APP_EVENT_PREFERENCES, Context.MODE_PRIVATE)
      if (referrer != null) {
        preferences.edit().putString("install_referrer", referrer).apply()
      }
    }

    @JvmStatic
    fun augmentWebView(webView: WebView, context: Context?) {
      val parts = Build.VERSION.RELEASE.split(".").toTypedArray()
      val majorRelease = if (parts.isNotEmpty()) parts[0].toInt() else 0
      val minorRelease = if (parts.size > 1) parts[1].toInt() else 0
      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 ||
          majorRelease < 4 ||
          majorRelease == 4 && minorRelease <= 1) {
        log(
            LoggingBehavior.DEVELOPER_ERRORS,
            TAG,
            "augmentWebView is only available for Android SDK version >= 17 on devices " +
                "running Android >= 4.2")
        return
      }
      webView.addJavascriptInterface(
          FacebookSDKJSInterface(context), "fbmq_" + FacebookSdk.getApplicationId())
    }

    private fun initializeTimersIfNeeded() {
      synchronized(staticLock) {
        if (backgroundExecutor != null) {
          return
        }
        // Having single runner thread enforces ordered execution of tasks,
        // which matters in some cases e.g. making sure user id is set before
        // trying to update user properties for a given id
        backgroundExecutor = ScheduledThreadPoolExecutor(1)
      }
      val attributionRecheckRunnable = Runnable {
        val applicationIds: MutableSet<String> = HashSet()
        for (accessTokenAppId in getKeySet()) {
          applicationIds.add(accessTokenAppId.applicationId)
        }
        for (applicationId in applicationIds) {
          queryAppSettings(applicationId, true)
        }
      }
      checkNotNull(backgroundExecutor)
          .scheduleAtFixedRate(
              attributionRecheckRunnable,
              0,
              APP_SUPPORTS_ATTRIBUTION_ID_RECHECK_PERIOD_IN_SECONDS.toLong(),
              TimeUnit.SECONDS)
    }

    private fun logEvent(event: AppEvent, accessTokenAppId: AccessTokenAppIdPair) {
      add(accessTokenAppId, event)
      if (isEnabled(FeatureManager.Feature.OnDevicePostInstallEventProcessing) &&
          isOnDeviceProcessingEnabled()) {
        sendCustomEventAsync(accessTokenAppId.applicationId, event)
      }

      // Make sure Activated_App is always before other app events
      if (!event.getIsImplicit() && !isActivateAppEventRequested) {
        if (event.name == AppEventsConstants.EVENT_NAME_ACTIVATED_APP) {
          isActivateAppEventRequested = true
        } else {
          log(
              LoggingBehavior.APP_EVENTS,
              "AppEvents",
              "Warning: Please call AppEventsLogger.activateApp(...)" +
                  "from the long-lived activity's onResume() method" +
                  "before logging other app events.")
        }
      }
    }

    fun eagerFlush() {
      if (getFlushBehavior() != AppEventsLogger.FlushBehavior.EXPLICIT_ONLY) {
        flush(FlushReason.EAGER_FLUSHING_EVENT)
      }
    }

    /**
     * Invoke this method, rather than throwing an Exception, for situations where user/server input
     * might reasonably cause this to occur, and thus don't want an exception thrown at production
     * time, but do want logging notification.
     */
    private fun notifyDeveloperError(message: String) {
      log(LoggingBehavior.DEVELOPER_ERRORS, "AppEvents", message)
    }

    @JvmStatic
    fun getAnalyticsExecutor(): Executor {
      if (backgroundExecutor == null) {
        initializeTimersIfNeeded()
      }
      return checkNotNull(backgroundExecutor)
    }

    @JvmStatic
    fun getAnonymousAppDeviceGUID(context: Context): String {
      if (anonymousAppDeviceGUID == null) {
        synchronized(staticLock) {
          if (anonymousAppDeviceGUID == null) {
            val preferences =
                context.getSharedPreferences(APP_EVENT_PREFERENCES, Context.MODE_PRIVATE)
            anonymousAppDeviceGUID = preferences.getString("anonymousAppDeviceGUID", null)
            if (anonymousAppDeviceGUID == null) {
              // Arbitrarily prepend XZ to distinguish from device supplied identifiers.
              anonymousAppDeviceGUID = "XZ" + UUID.randomUUID().toString()
              context
                  .getSharedPreferences(APP_EVENT_PREFERENCES, Context.MODE_PRIVATE)
                  .edit()
                  .putString("anonymousAppDeviceGUID", anonymousAppDeviceGUID)
                  .apply()
            }
          }
        }
      }
      return checkNotNull(anonymousAppDeviceGUID)
    }
  }

  init {
    var applicationId = applicationId
    var accessToken = accessToken
    sdkInitialized()
    contextName = activityName
    if (accessToken == null) {
      accessToken = getCurrentAccessToken()
    }

    // If we have a session and the appId passed is null or matches the session's app ID:
    if (accessToken != null &&
        !accessToken.isExpired &&
        (applicationId == null || applicationId == accessToken.applicationId)) {
      accessTokenAppId = AccessTokenAppIdPair(accessToken)
    } else {
      // If no app ID passed, get it from the manifest:
      if (applicationId == null) {
        applicationId = getMetadataApplicationId(FacebookSdk.getApplicationContext())
      }
      accessTokenAppId = AccessTokenAppIdPair(null, checkNotNull(applicationId))
    }
    initializeTimersIfNeeded()
  }
}


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

package com.facebook.appevents

import androidx.annotation.RestrictTo
import com.facebook.appevents.aam.MetadataIndexer
import com.facebook.appevents.cloudbridge.AppEventsCAPIManager
import com.facebook.appevents.eventdeactivation.EventDeactivationManager
import com.facebook.appevents.iap.InAppPurchaseManager
import com.facebook.appevents.integrity.MACARuleMatchingManager
import com.facebook.appevents.ml.ModelManager
import com.facebook.appevents.integrity.ProtectedModeManager
import com.facebook.appevents.restrictivedatafilter.RestrictiveDataManager
import com.facebook.internal.FeatureManager
import com.facebook.internal.FeatureManager.checkFeature
import com.facebook.internal.FetchedAppSettings
import com.facebook.internal.FetchedAppSettingsManager
import com.facebook.internal.FetchedAppSettingsManager.getAppSettingsAsync
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@AutoHandleExceptions
object AppEventsManager {
  /**
   * Start AppEvents functionality.
   *
   * Note that the function should be called after FacebookSdk is initialized.
   */
  @JvmStatic
  fun start() {
    getAppSettingsAsync(
        object : FetchedAppSettingsManager.FetchedAppSettingsCallback {
          override fun onSuccess(fetchedAppSettings: FetchedAppSettings?) {
            checkFeature(FeatureManager.Feature.AAM) { enabled ->
              if (enabled) {
                MetadataIndexer.enable()
              }
            }
            checkFeature(FeatureManager.Feature.RestrictiveDataFiltering) { enabled ->
              if (enabled) {
                RestrictiveDataManager.enable()
              }
            }
            checkFeature(FeatureManager.Feature.PrivacyProtection) { enabled ->
              if (enabled) {
                ModelManager.enable()
              }
            }
            checkFeature(FeatureManager.Feature.EventDeactivation) { enabled ->
              if (enabled) {
                EventDeactivationManager.enable()
              }
            }
            checkFeature(FeatureManager.Feature.IapLogging) { enabled ->
              if (enabled) {
                InAppPurchaseManager.enableAutoLogging()
              }
            }
            checkFeature(FeatureManager.Feature.ProtectedMode) { enabled ->
              if (enabled) {
                ProtectedModeManager.enable()
              }
            }
            checkFeature(FeatureManager.Feature.MACARuleMatching) { enabled ->
              if (enabled) {
                MACARuleMatchingManager.enable()
              }
            }
            checkFeature(FeatureManager.Feature.CloudBridge) { enabled ->
              if (enabled) {
                AppEventsCAPIManager.enable()
              }
            }
          }

          override fun onError() = Unit
        })
  }
}


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

package com.facebook.appevents

import com.facebook.appevents.AppEventDiskStore.readAndClearStore
import com.facebook.appevents.AppEventDiskStore.saveEventsToDisk
import com.facebook.appevents.internal.AppEventUtility.assertIsNotMainThread
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions

@AutoHandleExceptions
internal object AppEventStore {
  private val TAG = AppEventStore::class.java.name

  @Synchronized
  @JvmStatic
  fun persistEvents(accessTokenAppIdPair: AccessTokenAppIdPair, appEvents: SessionEventsState) {
    assertIsNotMainThread()
    val persistedEvents = readAndClearStore()
    persistedEvents.addEvents(accessTokenAppIdPair, appEvents.eventsToPersist)
    saveEventsToDisk(persistedEvents)
  }

  @Synchronized
  @JvmStatic
  fun persistEvents(eventsToPersist: AppEventCollection) {
    assertIsNotMainThread()
    val persistedEvents = readAndClearStore()
    for (accessTokenAppIdPair in eventsToPersist.keySet()) {
      val sessionEventsState = checkNotNull(eventsToPersist[accessTokenAppIdPair])
      persistedEvents.addEvents(accessTokenAppIdPair, sessionEventsState.eventsToPersist)
    }
    saveEventsToDisk(persistedEvents)
  }
}


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

package com.facebook.appevents

import android.content.Context
import android.os.Bundle
import android.webkit.JavascriptInterface
import com.facebook.LoggingBehavior
import com.facebook.internal.Logger.Companion.log
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import org.json.JSONException
import org.json.JSONObject

@AutoHandleExceptions
internal class FacebookSDKJSInterface(private val context: Context?) {
  @JavascriptInterface
  fun sendEvent(pixelId: String?, eventName: String?, jsonString: String?) {
    if (pixelId == null) {
      log(
          LoggingBehavior.DEVELOPER_ERRORS,
          TAG,
          "Can't bridge an event without a referral Pixel ID. " +
              "Check your webview Pixel configuration")
      return
    }
    val logger = InternalAppEventsLogger.createInstance(context)
    val parameters = jsonStringToBundle(jsonString)
    parameters.putString(PARAMETER_FBSDK_PIXEL_REFERRAL, pixelId)
    logger.logEvent(eventName, parameters)
  }

  @get:JavascriptInterface val protocol = "fbmq-0.1"

  companion object {
    val TAG = FacebookSDKJSInterface::class.java.simpleName
    private const val PARAMETER_FBSDK_PIXEL_REFERRAL = "_fb_pixel_referral_id"

    @Throws(JSONException::class)
    private fun jsonToBundle(jsonObject: JSONObject): Bundle {
      val bundle = Bundle()
      val iter: Iterator<*> = jsonObject.keys()
      while (iter.hasNext()) {
        val key = iter.next() as String
        val value = jsonObject.getString(key)
        bundle.putString(key, value)
      }
      return bundle
    }

    private fun jsonStringToBundle(jsonString: String?): Bundle {
      try {
        val jsonObject = JSONObject(jsonString)
        return jsonToBundle(jsonObject)
      } catch (ignored: JSONException) {
        // ignore
      }
      return Bundle()
    }
  }
}


/*
 * 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 enum class FlushReason {
  EXPLICIT,
  TIMER,
  SESSION_CHANGE,
  PERSISTED_EVENTS,
  EVENT_THRESHOLD,
  EAGER_FLUSHING_EVENT
}


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

enum class FlushResult {
  SUCCESS,
  SERVER_ERROR,
  NO_CONNECTIVITY,
  UNKNOWN_ERROR
}


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

package com.facebook.appevents

internal class FlushStatistics {
  var numEvents = 0
  var result = FlushResult.SUCCESS
}


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

package com.facebook.appevents

import android.content.Context
import android.os.Bundle
import androidx.annotation.RestrictTo
import com.facebook.AccessToken
import com.facebook.FacebookSdk
import java.math.BigDecimal
import java.util.Currency
import java.util.concurrent.Executor

/**
 * com.facebook.appevents.InternalAppEventsLogger 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.
 */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
class InternalAppEventsLogger internal constructor(private val loggerImpl: AppEventsLoggerImpl) {
  constructor(context: Context?) : this(AppEventsLoggerImpl(context, null, null))
  constructor(
      context: Context?,
      applicationId: String?
  ) : this(AppEventsLoggerImpl(context, applicationId, null))
  constructor(
      activityName: String,
      applicationId: String?,
      accessToken: AccessToken?
  ) : this(AppEventsLoggerImpl(activityName, applicationId, accessToken))

  fun logEvent(eventName: String?, parameters: Bundle?) {
    if (FacebookSdk.getAutoLogAppEventsEnabled()) {
      loggerImpl.logEvent(eventName, parameters)
    }
  }

  fun logEvent(eventName: String?, valueToSum: Double, parameters: Bundle?) {
    if (FacebookSdk.getAutoLogAppEventsEnabled()) {
      loggerImpl.logEvent(eventName, valueToSum, parameters)
    }
  }

  fun logPurchaseImplicitly(purchaseAmount: BigDecimal?, currency: Currency?, parameters: Bundle?) {
    if (FacebookSdk.getAutoLogAppEventsEnabled()) {
      loggerImpl.logPurchaseImplicitly(purchaseAmount, currency, parameters)
    }
  }

  fun logEventFromSE(eventName: String?, buttonText: String?) {
    loggerImpl.logEventFromSE(eventName, buttonText)
  }

  fun logEventImplicitly(
      eventName: String?,
      purchaseAmount: BigDecimal?,
      currency: Currency?,
      parameters: Bundle?
  ) {
    if (FacebookSdk.getAutoLogAppEventsEnabled()) {
      loggerImpl.logEventImplicitly(eventName, purchaseAmount, currency, parameters)
    }
  }

  fun logEventImplicitly(eventName: String?) {
    if (FacebookSdk.getAutoLogAppEventsEnabled()) {
      loggerImpl.logEventImplicitly(eventName, null, null)
    }
  }

  fun logEventImplicitly(eventName: String?, valueToSum: Double?, parameters: Bundle?) {
    if (FacebookSdk.getAutoLogAppEventsEnabled()) {
      loggerImpl.logEventImplicitly(eventName, valueToSum, parameters)
    }
  }

  fun logEventImplicitly(eventName: String?, parameters: Bundle?) {
    if (FacebookSdk.getAutoLogAppEventsEnabled()) {
      loggerImpl.logEventImplicitly(eventName, null, parameters)
    }
  }

  fun logChangedSettingsEvent(parameters: Bundle) {
    val previousAutoLog = parameters.getInt("previous") and 0x02 != 0
    val eventName = "fb_sdk_settings_changed"
    if (previousAutoLog || FacebookSdk.getAutoLogAppEventsEnabled()) {
      loggerImpl.logEventImplicitly(eventName, null, parameters)
    }
  }

  fun flush() {
    loggerImpl.flush()
  }

  companion object {
    @JvmStatic
    fun getFlushBehavior(): AppEventsLogger.FlushBehavior {
      return AppEventsLoggerImpl.getFlushBehavior()
    }

    @JvmStatic
    fun getAnalyticsExecutor(): Executor {
      return AppEventsLoggerImpl.getAnalyticsExecutor()
    }

    @JvmStatic
    fun getPushNotificationsRegistrationId(): String? {
      return AppEventsLoggerImpl.getPushNotificationsRegistrationId()
    }

    @JvmStatic
    fun setUserData(userData: Bundle?) {
      UserDataStore.setUserDataAndHash(userData)
    }

    @RestrictTo(RestrictTo.Scope.GROUP_ID)
    @JvmStatic
    fun setInternalUserData(ud: Map<String, String>) {
      UserDataStore.setInternalUd(ud)
    }

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
    @JvmStatic
    @JvmOverloads
    fun createInstance(context: Context?, applicationId: String? = null): InternalAppEventsLogger =
        InternalAppEventsLogger(context, applicationId)

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
    @JvmStatic
    fun createInstance(
        activityName: String,
        applicationId: String?,
        accessToken: AccessToken?
    ): InternalAppEventsLogger = InternalAppEventsLogger(activityName, applicationId, accessToken)
  }
}


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

package com.facebook.appevents

import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import java.io.ObjectStreamException
import java.io.Serializable

@AutoHandleExceptions
internal class PersistedEvents : Serializable {
  private val events = hashMapOf<AccessTokenAppIdPair, MutableList<AppEvent>>()

  constructor()
  constructor(appEventMap: HashMap<AccessTokenAppIdPair, MutableList<AppEvent>>) {
    events.putAll(appEventMap)
  }

  fun keySet(): Set<AccessTokenAppIdPair> = events.keys

  fun entrySet(): Set<Map.Entry<AccessTokenAppIdPair, MutableList<AppEvent>>> = events.entries

  operator fun get(accessTokenAppIdPair: AccessTokenAppIdPair): List<AppEvent>? {
    return events[accessTokenAppIdPair]
  }

  fun containsKey(accessTokenAppIdPair: AccessTokenAppIdPair): Boolean {
    return events.containsKey(accessTokenAppIdPair)
  }

  fun addEvents(accessTokenAppIdPair: AccessTokenAppIdPair, appEvents: List<AppEvent>) {
    if (!events.containsKey(accessTokenAppIdPair)) {
      events[accessTokenAppIdPair] = appEvents.toMutableList()
      return
    }
    events[accessTokenAppIdPair]?.addAll(appEvents)
  }

  internal class SerializationProxyV1(
      private val proxyEvents: HashMap<AccessTokenAppIdPair, MutableList<AppEvent>>
  ) : Serializable {
    @Throws(ObjectStreamException::class)
    private fun readResolve(): Any {
      return PersistedEvents(proxyEvents)
    }

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

  @Throws(ObjectStreamException::class)
  private fun writeReplace(): Any {
    return SerializationProxyV1(events)
  }

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


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

package com.facebook.appevents

import android.content.Context
import com.facebook.GraphRequest
import com.facebook.appevents.eventdeactivation.EventDeactivationManager.processEvents
import com.facebook.appevents.internal.AppEventsLoggerUtility
import com.facebook.appevents.internal.AppEventsLoggerUtility.getJSONObjectForGraphAPICall
import com.facebook.internal.AttributionIdentifiers
import com.facebook.internal.Utility.logd
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject

@AutoHandleExceptions
internal class SessionEventsState(
    private val attributionIdentifiers: AttributionIdentifiers,
    private val anonymousAppDeviceGUID: String
) {
  private var accumulatedEvents: MutableList<AppEvent> = mutableListOf()
  private val inFlightEvents: MutableList<AppEvent> = mutableListOf()
  private var numSkippedEventsDueToFullBuffer = 0

  // Synchronize here and in other methods on this class, because could be coming in from
  // different AppEventsLoggers on different threads pointing at the same session.
  @Synchronized
  fun addEvent(event: AppEvent) {
    if (accumulatedEvents.size + inFlightEvents.size >= MAX_ACCUMULATED_LOG_EVENTS) {
      numSkippedEventsDueToFullBuffer++
    } else {
      accumulatedEvents.add(event)
    }
  }

  @get:Synchronized
  val accumulatedEventCount: Int
    get() = accumulatedEvents.size

  @Synchronized
  fun clearInFlightAndStats(moveToAccumulated: Boolean) {
    if (moveToAccumulated) {
      accumulatedEvents.addAll(inFlightEvents)
    }
    inFlightEvents.clear()
    numSkippedEventsDueToFullBuffer = 0
  }

  fun populateRequest(
      request: GraphRequest,
      applicationContext: Context,
      includeImplicitEvents: Boolean,
      limitEventUsage: Boolean
  ): Int {
    var numSkipped: Int
    var jsonArray: JSONArray
    synchronized(this) {
      numSkipped = numSkippedEventsDueToFullBuffer

      // drop deprecated events
      processEvents(accumulatedEvents)

      // move all accumulated events to inFlight.
      inFlightEvents.addAll(accumulatedEvents)
      accumulatedEvents.clear()
      jsonArray = JSONArray()
      for (event in inFlightEvents) {
        if (event.isChecksumValid) {
          if (includeImplicitEvents || !event.isImplicit) {
            jsonArray.put(event.jsonObject)
          }
        } else {
          logd(TAG, "Event with invalid checksum: $event")
        }
      }
      if (jsonArray.length() == 0) {
        return 0
      }
    }
    populateRequest(request, applicationContext, numSkipped, jsonArray, limitEventUsage)
    return jsonArray.length()
  }

  // We will only persist accumulated events, not ones currently in-flight. This means if
  // an in-flight request fails, those requests will not be persisted and thus might be
  // lost if the process terminates while the flush is in progress.
  @get:Synchronized
  val eventsToPersist: List<AppEvent>
    get() {
      // We will only persist accumulated events, not ones currently in-flight. This means if
      // an in-flight request fails, those requests will not be persisted and thus might be
      // lost if the process terminates while the flush is in progress.
      val result: List<AppEvent> = accumulatedEvents
      accumulatedEvents = mutableListOf()
      return result
    }

  @Synchronized
  fun accumulatePersistedEvents(events: List<AppEvent>) {
    // We won't skip events due to a full buffer, since we already accumulated them once and
    // persisted them. But they will count against the buffer size when further events are
    // accumulated.
    accumulatedEvents.addAll(events)
  }

  private fun populateRequest(
      request: GraphRequest,
      applicationContext: Context,
      numSkipped: Int,
      events: JSONArray,
      limitEventUsage: Boolean
  ) {
    var publishParams: JSONObject?
    try {
      publishParams =
          getJSONObjectForGraphAPICall(
              AppEventsLoggerUtility.GraphAPIActivityType.CUSTOM_APP_EVENTS,
              attributionIdentifiers,
              anonymousAppDeviceGUID,
              limitEventUsage,
              applicationContext)
      if (numSkippedEventsDueToFullBuffer > 0) {
        publishParams.put("num_skipped_events", numSkipped)
      }
    } catch (e: JSONException) {
      // Swallow
      publishParams = JSONObject()
    }
    request.graphObject = publishParams
    val requestParameters = request.parameters
    val jsonString = events.toString()
    requestParameters.putString("custom_events", jsonString)
    request.tag = jsonString
    request.parameters = requestParameters
  }

  companion object {
    private val TAG = SessionEventsState::class.java.simpleName
    private val MAX_ACCUMULATED_LOG_EVENTS = 1000
  }
}


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

package com.facebook.appevents

import android.content.SharedPreferences
import android.os.Bundle
import android.preference.PreferenceManager
import android.util.Log
import android.util.Patterns
import androidx.annotation.RestrictTo
import com.facebook.FacebookSdk
import com.facebook.appevents.InternalAppEventsLogger.Companion.getAnalyticsExecutor
import com.facebook.appevents.aam.MetadataRule.Companion.getEnabledRuleNames
import com.facebook.internal.Utility.jsonStrToMap
import com.facebook.internal.Utility.mapToJsonStr
import com.facebook.internal.Utility.sha256hash
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import java.lang.StringBuilder
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicBoolean

@AutoHandleExceptions
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
object UserDataStore {
  private val TAG = UserDataStore::class.java.simpleName
  private const val USER_DATA_KEY = "com.facebook.appevents.UserDataStore.userData"
  private const val INTERNAL_USER_DATA_KEY = "com.facebook.appevents.UserDataStore.internalUserData"
  private lateinit var sharedPreferences: SharedPreferences
  private val initialized = AtomicBoolean(false)
  private const val MAX_NUM = 5
  private const val DATA_SEPARATOR = ","
  private val externalHashedUserData = ConcurrentHashMap<String?, String?>()
  private val internalHashedUserData = ConcurrentHashMap<String?, String?>()

  /** User data types */
  const val EMAIL = "em"
  const val FIRST_NAME = "fn"
  const val LAST_NAME = "ln"
  const val PHONE = "ph"
  const val DATE_OF_BIRTH = "db"
  const val GENDER = "ge"
  const val CITY = "ct"
  const val STATE = "st"
  const val ZIP = "zp"
  const val COUNTRY = "country"
  @JvmStatic
  fun initStore() {
    if (initialized.get()) {
      return
    }
    initAndWait()
  }

  private fun writeDataIntoCache(key: String, value: String) {
    FacebookSdk.getExecutor().execute {
      if (!initialized.get()) {
        initAndWait()
      }
      sharedPreferences.edit().putString(key, value).apply()
    }
  }

  @JvmStatic
  fun setUserDataAndHash(ud: Bundle?) {
    getAnalyticsExecutor().execute {
      if (!initialized.get()) {
        Log.w(TAG, "initStore should have been called before calling setUserData")
        initAndWait()
      }
      updateHashUserData(ud)
      writeDataIntoCache(USER_DATA_KEY, mapToJsonStr(externalHashedUserData))
      writeDataIntoCache(INTERNAL_USER_DATA_KEY, mapToJsonStr(internalHashedUserData))
    }
  }

  @JvmStatic
  fun setUserDataAndHash(
      email: String?,
      firstName: String?,
      lastName: String?,
      phone: String?,
      dateOfBirth: String?,
      gender: String?,
      city: String?,
      state: String?,
      zip: String?,
      country: String?
  ) {
    val ud = Bundle()
    if (email != null) {
      ud.putString(EMAIL, email)
    }
    if (firstName != null) {
      ud.putString(FIRST_NAME, firstName)
    }
    if (lastName != null) {
      ud.putString(LAST_NAME, lastName)
    }
    if (phone != null) {
      ud.putString(PHONE, phone)
    }
    if (dateOfBirth != null) {
      ud.putString(DATE_OF_BIRTH, dateOfBirth)
    }
    if (gender != null) {
      ud.putString(GENDER, gender)
    }
    if (city != null) {
      ud.putString(CITY, city)
    }
    if (state != null) {
      ud.putString(STATE, state)
    }
    if (zip != null) {
      ud.putString(ZIP, zip)
    }
    if (country != null) {
      ud.putString(COUNTRY, country)
    }
    setUserDataAndHash(ud)
  }

  @JvmStatic
  fun clear() {
    getAnalyticsExecutor().execute {
      if (!initialized.get()) {
        Log.w(TAG, "initStore should have been called before calling setUserData")
        initAndWait()
      }
      externalHashedUserData.clear()
      sharedPreferences.edit().putString(USER_DATA_KEY, null).apply()
    }
  }

  @JvmStatic
  internal fun getHashedUserData(): String {
    if (!initialized.get()) {
      Log.w(TAG, "initStore should have been called before calling setUserID")
      initAndWait()
    }
    return mapToJsonStr(externalHashedUserData)
  }

  @JvmStatic
  fun getAllHashedUserData(): String {
    if (!initialized.get()) {
      initAndWait()
    }
    val allHashedUserData: MutableMap<String?, String?> = HashMap()
    allHashedUserData.putAll(externalHashedUserData)
    allHashedUserData.putAll(enabledInternalUserData)
    return mapToJsonStr(allHashedUserData)
  }

  private val enabledInternalUserData: Map<String?, String?>
    private get() {
      val enabledInternalUD: MutableMap<String?, String?> = HashMap()
      val ruleNames: Set<String?> = getEnabledRuleNames()
      for (ruleKey in internalHashedUserData.keys) {
        if (ruleNames.contains(ruleKey)) {
          enabledInternalUD[ruleKey] = internalHashedUserData[ruleKey]
        }
      }
      return enabledInternalUD
    }

  @Synchronized
  private fun initAndWait() {
    if (initialized.get()) {
      return
    }
    sharedPreferences =
        PreferenceManager.getDefaultSharedPreferences(FacebookSdk.getApplicationContext())
    val externalUdRaw = sharedPreferences.getString(USER_DATA_KEY, "") ?: ""
    val internalUdRaw = sharedPreferences.getString(INTERNAL_USER_DATA_KEY, "") ?: ""
    externalHashedUserData.putAll(jsonStrToMap(externalUdRaw))
    internalHashedUserData.putAll(jsonStrToMap(internalUdRaw))
    initialized.set(true)
  }

  private fun updateHashUserData(ud: Bundle?) {
    if (ud == null) {
      return
    }
    for (key in ud.keySet()) {
      val rawVal = ud[key] ?: continue
      val value = rawVal.toString()
      if (maybeSHA256Hashed(value)) {
        externalHashedUserData[key] = value.toLowerCase()
      } else {
        val encryptedValue = sha256hash(normalizeData(key, value))
        if (encryptedValue != null) {
          externalHashedUserData[key] = encryptedValue
        }
      }
    }
  }

  @JvmStatic
  fun setInternalUd(ud: Map<String, String>) {
    if (!initialized.get()) {
      initAndWait()
    }
    for ((key, rawVal) in ud) {
      val value = sha256hash(normalizeData(key, rawVal.trim { it <= ' ' }))
      if (internalHashedUserData.containsKey(key)) {
        val originalVal = internalHashedUserData[key]
        val previousData = originalVal?.split(DATA_SEPARATOR.toRegex())?.toTypedArray() ?: arrayOf()
        val set: MutableSet<String?> = mutableSetOf(*previousData)
        if (set.contains(value)) {
          return
        }
        val sb = StringBuilder()
        if (previousData.isEmpty()) {
          sb.append(value)
        } else if (previousData.size < MAX_NUM) {
          sb.append(originalVal).append(DATA_SEPARATOR).append(value)
        } else {
          for (i in 1 until MAX_NUM) {
            sb.append(previousData[i]).append(DATA_SEPARATOR)
          }
          sb.append(value)
          set.remove(previousData[0])
        }
        // Update new added value into hashed User Data and save to cache
        internalHashedUserData[key] = sb.toString()
      } else {
        internalHashedUserData[key] = value
      }
    }
    writeDataIntoCache(INTERNAL_USER_DATA_KEY, mapToJsonStr(internalHashedUserData))
  }

  private fun normalizeData(type: String, data: String): String {
    var data = data
    data = data.trim { it <= ' ' }.toLowerCase()
    if (EMAIL == type) {
      return if (Patterns.EMAIL_ADDRESS.matcher(data).matches()) {
        data
      } else {
        Log.e(TAG, "Setting email failure: this is not a valid email address")
        ""
      }
    }
    if (PHONE == type) {
      return data.replace("[^0-9]".toRegex(), "")
    }
    if (GENDER == type) {
      data = if (data.isNotEmpty()) data.substring(0, 1) else ""
      return if ("f" == data || "m" == data) {
        data
      } else {
        Log.e(TAG, "Setting gender failure: the supported value for gender is f or m")
        ""
      }
    }
    return data
  }

  private fun maybeSHA256Hashed(data: String): Boolean {
    return data.matches(Regex("[A-Fa-f0-9]{64}"))
  }
}


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

import android.app.Activity
import androidx.annotation.RestrictTo
import androidx.annotation.UiThread
import com.facebook.FacebookSdk
import com.facebook.appevents.aam.MetadataRule.Companion.getRules
import com.facebook.appevents.aam.MetadataRule.Companion.updateRules
import com.facebook.internal.AttributionIdentifiers.Companion.isTrackingLimited
import com.facebook.internal.FetchedAppSettingsManager.queryAppSettings
import com.facebook.internal.Utility.logd
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import java.lang.Exception

@AutoHandleExceptions
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
object MetadataIndexer {
  private val TAG = MetadataIndexer::class.java.canonicalName
  private var enabled = false
  @UiThread
  @JvmStatic
  fun onActivityResumed(activity: Activity) {
    try {
      if (!enabled || getRules().isEmpty()) {
        return
      }
      MetadataViewObserver.startTrackingActivity(activity)
    } catch (e: Exception) {}
  }

  private fun updateRules() {
    val settings = queryAppSettings(FacebookSdk.getApplicationId(), false) ?: return
    val rawRule = settings.rawAamRules ?: return
    updateRules(rawRule)
  }

  @JvmStatic
  fun enable() {
    try {
      FacebookSdk.getExecutor().execute {
        val context = FacebookSdk.getApplicationContext()
        if (!isTrackingLimited(context)) {
          updateRules()
          enabled = true
        }
      }
    } catch (e: Exception) {
      logd(TAG, e)
    }
  }
}


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

import android.content.res.Resources
import android.view.View
import android.widget.EditText
import android.widget.TextView
import com.facebook.appevents.codeless.internal.ViewHierarchy
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions

@AutoHandleExceptions
internal object MetadataMatcher {
  private const val MAX_INDICATOR_LENGTH = 100

  @JvmStatic
  fun getCurrentViewIndicators(view: View): List<String> {
    val indicators: MutableList<String> = mutableListOf()
    // Hint
    indicators.add(ViewHierarchy.getHintOfView(view))
    // tag
    val tag = view.tag
    if (tag != null) {
      indicators.add(tag.toString())
    }
    // description
    val description = view.contentDescription
    if (description != null) {
      indicators.add(description.toString())
    }
    // resource id name
    try {
      if (view.id != -1) {
        // resource name format: {package_name}:id/{id_name}
        val resourceName = view.resources.getResourceName(view.id)
        val splitted = resourceName.split("/".toRegex()).toTypedArray()
        if (splitted.size == 2) {
          indicators.add(splitted[1])
        }
      }
    } catch (_e: Resources.NotFoundException) {
      /*no op*/
    }
    val validIndicators: MutableList<String> = mutableListOf()
    for (indicator in indicators) {
      if (indicator.isNotEmpty() && indicator.length <= MAX_INDICATOR_LENGTH) {
        validIndicators.add(indicator.toLowerCase())
      }
    }
    return validIndicators
  }

  @JvmStatic
  fun getAroundViewIndicators(view: View): List<String> {
    val aroundTextIndicators: MutableList<String> = mutableListOf()
    val parentView: View? = ViewHierarchy.getParentOfView(view)
    if (parentView != null) {
      for (child in ViewHierarchy.getChildrenOfView(parentView)) {
        if (view !== child) {
          aroundTextIndicators.addAll(getTextIndicators(child))
        }
      }
    }
    return aroundTextIndicators
  }

  @JvmStatic
  fun matchIndicator(indicators: List<String>, keys: List<String>): Boolean {
    for (indicator in indicators) {
      if (matchIndicator(indicator, keys)) {
        return true
      }
    }
    return false
  }

  private fun matchIndicator(indicator: String, keys: List<String>): Boolean {
    for (key in keys) {
      if (indicator.contains(key)) {
        return true
      }
    }
    return false
  }

  @JvmStatic
  fun matchValue(text: String, rule: String): Boolean {
    return text.matches(Regex(rule))
  }

  private fun getTextIndicators(view: View): List<String> {
    val indicators: MutableList<String> = mutableListOf()
    if (view is EditText) {
      return indicators
    }
    if (view is TextView) {
      val text = view.text.toString()
      if (text.isNotEmpty() && text.length < MAX_INDICATOR_LENGTH) {
        indicators.add(text.toLowerCase())
      }
      return indicators
    }
    val children = ViewHierarchy.getChildrenOfView(view)
    for (child in children) {
      indicators.addAll(getTextIndicators(child))
    }
    return indicators
  }
}


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

import androidx.annotation.RestrictTo
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import java.util.concurrent.CopyOnWriteArraySet
import org.json.JSONException
import org.json.JSONObject

@AutoHandleExceptions
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
class MetadataRule
private constructor(val name: String, keyRules: List<String>, val valRule: String) {
  val keyRules = keyRules
    get() = ArrayList(field)
  companion object {
    private val rules: MutableSet<MetadataRule> = CopyOnWriteArraySet()
    private const val FIELD_K = "k"
    private const val FIELD_V = "v"
    private const val FIELD_K_DELIMITER = ","
    @JvmStatic
    fun getRules(): Set<MetadataRule> {
      return HashSet(rules)
    }

    @JvmStatic
    fun updateRules(rulesFromServer: String) {
      try {
        rules.clear()
        val jsonObject = JSONObject(rulesFromServer)
        constructRules(jsonObject)
      } catch (e: JSONException) {}
    }

    @JvmStatic
    fun getEnabledRuleNames(): Set<String> {
      val ruleNames: MutableSet<String> = HashSet()
      for (r in rules) {
        ruleNames.add(r.name)
      }
      return ruleNames
    }

    private fun constructRules(jsonObject: JSONObject) {
      val keys = jsonObject.keys()
      while (keys.hasNext()) {
        val key = keys.next()
        val rule = jsonObject.optJSONObject(key) ?: continue
        val k = rule.optString(FIELD_K)
        val v = rule.optString(FIELD_V)
        if (k.isEmpty()) {
          continue
        }
        rules.add(MetadataRule(key, k.split(FIELD_K_DELIMITER), v))
      }
    }
  }
}


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

import android.app.Activity
import android.os.Handler
import android.os.Looper
import android.view.View
import android.view.ViewTreeObserver
import android.widget.EditText
import androidx.annotation.UiThread
import com.facebook.appevents.InternalAppEventsLogger.Companion.setInternalUserData
import com.facebook.appevents.aam.MetadataMatcher.getAroundViewIndicators
import com.facebook.appevents.aam.MetadataMatcher.getCurrentViewIndicators
import com.facebook.appevents.aam.MetadataMatcher.matchIndicator
import com.facebook.appevents.aam.MetadataMatcher.matchValue
import com.facebook.appevents.aam.MetadataRule.Companion.getRules
import com.facebook.appevents.internal.AppEventUtility.getRootView
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import java.lang.ref.WeakReference
import java.util.concurrent.atomic.AtomicBoolean

@AutoHandleExceptions
internal class MetadataViewObserver private constructor(activity: Activity) :
    ViewTreeObserver.OnGlobalFocusChangeListener {
  private val processedText: MutableSet<String> = mutableSetOf()
  private val uiThreadHandler: Handler = Handler(Looper.getMainLooper())
  private val activityWeakReference: WeakReference<Activity> = WeakReference(activity)
  private val isTracking: AtomicBoolean = AtomicBoolean(false)

  private fun startTracking() {
    if (isTracking.getAndSet(true)) {
      return
    }
    val rootView = getRootView(activityWeakReference.get()) ?: return
    val observer = rootView.viewTreeObserver
    if (observer.isAlive) {
      observer.addOnGlobalFocusChangeListener(this)
    }
  }

  private fun stopTracking() {
    if (!isTracking.getAndSet(false)) {
      return
    }
    val rootView = getRootView(activityWeakReference.get()) ?: return
    val observer = rootView.viewTreeObserver
    if (!observer.isAlive) {
      return
    }
    observer.removeOnGlobalFocusChangeListener(this)
  }

  override fun onGlobalFocusChanged(oldView: View?, newView: View?) {
    if (oldView != null) {
      process(oldView)
    }
    if (newView != null) {
      process(newView)
    }
  }

  private fun process(view: View) {
    val runnable = Runnable {
      if (view !is EditText) {
        return@Runnable
      }
      processEditText(view)
    }
    runOnUIThread(runnable)
  }

  private fun processEditText(view: View) {
    val text = (view as EditText).text.toString().trim().toLowerCase()
    if (text.isEmpty() || processedText.contains(text) || text.length > MAX_TEXT_LENGTH) {
      return
    }
    processedText.add(text)
    val userData: MutableMap<String, String> = HashMap()
    val currentViewIndicators = getCurrentViewIndicators(view)
    var aroundTextIndicators: List<String>? = null
    for (rule in getRules()) {
      val normalizedText = preNormalize(rule.name, text)
      // 1. match value if value rule is not empty
      if (rule.valRule.isNotEmpty() && !matchValue(normalizedText, rule.valRule)) {
        continue
      }

      // 2. match indicator
      if (matchIndicator(currentViewIndicators, rule.keyRules)) {
        putUserData(userData, rule.name, normalizedText)
        continue
      }
      // only fetch once
      if (aroundTextIndicators == null) {
        aroundTextIndicators = getAroundViewIndicators(view)
      }
      if (matchIndicator(aroundTextIndicators, rule.keyRules)) {
        putUserData(userData, rule.name, normalizedText)
      }
    }
    setInternalUserData(userData)
  }

  private fun runOnUIThread(runnable: Runnable) {
    if (Thread.currentThread() === Looper.getMainLooper().thread) {
      runnable.run()
    } else {
      uiThreadHandler.post(runnable)
    }
  }

  companion object {
    private const val MAX_TEXT_LENGTH = 100
    private val observers: MutableMap<Int, MetadataViewObserver> = hashMapOf()
    @UiThread
    @JvmStatic
    fun startTrackingActivity(activity: Activity) {
      val key = activity.hashCode()
      val observer = observers.getOrPut(key) { MetadataViewObserver(activity) }
      observer.startTracking()
    }

    @UiThread
    @JvmStatic
    fun stopTrackingActivity(activity: Activity) {
      val key = activity.hashCode()
      observers.remove(key)?.stopTracking()
    }

    private fun preNormalize(key: String, value: String): String {
      return if ("r2" == key) {
        value.replace("[^\\d.]".toRegex(), "")
      } else value
    }

    private fun putUserData(userData: MutableMap<String, String>, key: String, value: String) {
      var value = value
      when (key) {
        "r3" ->
            value =
                if (value.startsWith("m") || value.startsWith("b") || value.startsWith("ge")) {
                  "m"
                } else {
                  "f"
                }
        "r4",
        "r5" -> value = value.replace("[^a-z]+".toRegex(), "") // lowercase already
        "r6" ->
            if (value.contains("-")) {
              val splitArray = value.split("-".toRegex()).toTypedArray()
              value = splitArray[0]
            }
      }
      userData[key] = value
    }
  }
}