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

package com.facebook.internal

import android.content.Context
import android.content.Intent
import android.content.pm.ProviderInfo
import android.database.Cursor
import android.net.Uri
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import com.facebook.FacebookException
import com.facebook.FacebookOperationCanceledException
import com.facebook.FacebookSdk
import com.facebook.internal.FacebookSignatureValidator.validateSignature
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import com.facebook.login.DefaultAudience
import com.facebook.login.LoginTargetApp
import java.util.TreeSet
import java.util.UUID
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.math.max
import kotlin.math.min

/**
 * com.facebook.internal is solely for the use of other packages within the Facebook SDK for
 * Android. Use of any of the classes in this package is unsupported, and they may be modified or
 * removed without warning at any time.
 */
@AutoHandleExceptions
object NativeProtocol {
  const val NO_PROTOCOL_AVAILABLE = -1
  private val TAG = NativeProtocol::class.java.name
  private const val FACEBOOK_PROXY_AUTH_ACTIVITY = "com.facebook.katana.ProxyAuth"
  private const val FACEBOOK_TOKEN_REFRESH_ACTIVITY =
      "com.facebook.katana.platform.TokenRefreshService"
  const val FACEBOOK_PROXY_AUTH_PERMISSIONS_KEY = "scope"
  const val FACEBOOK_PROXY_AUTH_APP_ID_KEY = "client_id"
  const val FACEBOOK_PROXY_AUTH_E2E_KEY = "e2e"
  const val FACEBOOK_SDK_VERSION_KEY = "facebook_sdk_version"

  // ---------------------------------------------------------------------------------------------
  // Native Protocol updated 2012-11
  const val INTENT_ACTION_PLATFORM_ACTIVITY = "com.facebook.platform.PLATFORM_ACTIVITY"
  const val INTENT_ACTION_PLATFORM_SERVICE = "com.facebook.platform.PLATFORM_SERVICE"
  const val PROTOCOL_VERSION_20121101 = 2012_11_01
  const val PROTOCOL_VERSION_20130502 = 2013_05_02
  const val PROTOCOL_VERSION_20130618 = 2013_06_18
  const val PROTOCOL_VERSION_20131024 = 2013_10_24
  const val PROTOCOL_VERSION_20131107 = 2013_11_07
  const val PROTOCOL_VERSION_20140204 = 2014_02_04
  const val PROTOCOL_VERSION_20140313 = 2014_03_13
  const val PROTOCOL_VERSION_20140324 = 2014_03_24
  const val PROTOCOL_VERSION_20140701 = 2014_07_01
  const val PROTOCOL_VERSION_20141001 = 2014_10_01
  const val PROTOCOL_VERSION_20141028 = 2014_10_28
  const val PROTOCOL_VERSION_20141107 = 2014_11_07 // Bucketed Result Intents
  const val PROTOCOL_VERSION_20141218 = 2014_12_18
  const val PROTOCOL_VERSION_20150401 = 2015_04_01
  const val PROTOCOL_VERSION_20150702 = 2015_07_02
  const val PROTOCOL_VERSION_20160327 = 2016_03_27
  const val PROTOCOL_VERSION_20161017 = 2016_10_17
  const val PROTOCOL_VERSION_20170213 = 2017_02_13
  const val PROTOCOL_VERSION_20170411 = 2017_04_11 // express login
  const val PROTOCOL_VERSION_20170417 = 2017_04_17
  const val PROTOCOL_VERSION_20171115 = 2017_11_15
  const val PROTOCOL_VERSION_20210906 = 2021_09_06
  const val EXTRA_PROTOCOL_VERSION = "com.facebook.platform.protocol.PROTOCOL_VERSION"
  const val EXTRA_PROTOCOL_ACTION = "com.facebook.platform.protocol.PROTOCOL_ACTION"
  const val EXTRA_PROTOCOL_CALL_ID = "com.facebook.platform.protocol.CALL_ID"
  const val EXTRA_GET_INSTALL_DATA_PACKAGE = "com.facebook.platform.extra.INSTALLDATA_PACKAGE"
  const val EXTRA_PROTOCOL_BRIDGE_ARGS = "com.facebook.platform.protocol.BRIDGE_ARGS"
  const val EXTRA_PROTOCOL_METHOD_ARGS = "com.facebook.platform.protocol.METHOD_ARGS"
  const val EXTRA_PROTOCOL_METHOD_RESULTS = "com.facebook.platform.protocol.RESULT_ARGS"
  const val BRIDGE_ARG_APP_NAME_STRING = "app_name"
  const val BRIDGE_ARG_ACTION_ID_STRING = "action_id"
  const val BRIDGE_ARG_ERROR_BUNDLE = "error"
  const val EXTRA_DIALOG_COMPLETE_KEY = "com.facebook.platform.extra.DID_COMPLETE"
  const val EXTRA_DIALOG_COMPLETION_GESTURE_KEY = "com.facebook.platform.extra.COMPLETION_GESTURE"
  const val RESULT_ARGS_DIALOG_COMPLETE_KEY = "didComplete"
  const val RESULT_ARGS_DIALOG_COMPLETION_GESTURE_KEY = "completionGesture"

  // Messages supported by PlatformService:
  const val MESSAGE_GET_ACCESS_TOKEN_REQUEST = 0x10000
  const val MESSAGE_GET_ACCESS_TOKEN_REPLY = 0x10001
  const val MESSAGE_GET_PROTOCOL_VERSIONS_REQUEST = 0x10002
  const val MESSAGE_GET_PROTOCOL_VERSIONS_REPLY = 0x10003
  const val MESSAGE_GET_INSTALL_DATA_REQUEST = 0x10004
  const val MESSAGE_GET_INSTALL_DATA_REPLY = 0x10005
  const val MESSAGE_GET_LIKE_STATUS_REQUEST = 0x10006
  const val MESSAGE_GET_LIKE_STATUS_REPLY = 0x10007
  const val MESSAGE_GET_AK_SEAMLESS_TOKEN_REQUEST = 0x10008
  const val MESSAGE_GET_AK_SEAMLESS_TOKEN_REPLY = 0x10009
  const val MESSAGE_GET_LOGIN_STATUS_REQUEST = 0x1000A
  const val MESSAGE_GET_LOGIN_STATUS_REPLY = 0x1000B

  // MESSAGE_ERROR_REPLY data keys:
  // See STATUS_*
  // MESSAGE_GET_ACCESS_TOKEN_REQUEST data keys:
  // EXTRA_APPLICATION_ID
  // MESSAGE_GET_ACCESS_TOKEN_REPLY data keys:
  // EXTRA_ACCESS_TOKEN
  // EXTRA_EXPIRES_SECONDS_SINCE_EPOCH
  // EXTRA_PERMISSIONS
  // EXTRA_DATA_ACCESS_EXPIRATION_TIME
  // MESSAGE_GET_LIKE_STATUS_REQUEST data keys:
  // EXTRA_APPLICATION_ID
  // EXTRA_OBJECT_ID
  // MESSAGE_GET_LIKE_STATUS_REPLY data keys:
  // EXTRA_OBJECT_IS_LIKED
  // EXTRA_LIKE_COUNT_STRING_WITH_LIKE
  // EXTRA_LIKE_COUNT_STRING_WITHOUT_LIKE
  // EXTRA_SOCIAL_SENTENCE_WITH_LIKE
  // EXTRA_SOCIAL_SENTENCE_WITHOUT_LIKE
  // EXTRA_UNLIKE_TOKEN
  // MESSAGE_GET_PROTOCOL_VERSIONS_REPLY data keys:
  const val EXTRA_PROTOCOL_VERSIONS = "com.facebook.platform.extra.PROTOCOL_VERSIONS"

  // Values of EXTRA_PROTOCOL_ACTION supported by PlatformActivity:
  const val ACTION_FEED_DIALOG = "com.facebook.platform.action.request.FEED_DIALOG"
  const val ACTION_MESSAGE_DIALOG = "com.facebook.platform.action.request.MESSAGE_DIALOG"
  const val ACTION_OGACTIONPUBLISH_DIALOG =
      "com.facebook.platform.action.request.OGACTIONPUBLISH_DIALOG"
  const val ACTION_OGMESSAGEPUBLISH_DIALOG =
      "com.facebook.platform.action.request.OGMESSAGEPUBLISH_DIALOG"
  const val ACTION_LIKE_DIALOG = "com.facebook.platform.action.request.LIKE_DIALOG"

  // The value of ACTION_APPINVITE_DIALOG is different since that is what is on the server.
  const val ACTION_APPINVITE_DIALOG = "com.facebook.platform.action.request.APPINVITES_DIALOG"
  const val ACTION_CAMERA_EFFECT = "com.facebook.platform.action.request.CAMERA_EFFECT"
  const val ACTION_SHARE_STORY = "com.facebook.platform.action.request.SHARE_STORY"

  // Extras supported for ACTION_LOGIN_DIALOG:
  const val EXTRA_PERMISSIONS = "com.facebook.platform.extra.PERMISSIONS"
  const val EXTRA_APPLICATION_ID = "com.facebook.platform.extra.APPLICATION_ID"
  const val EXTRA_APPLICATION_NAME = "com.facebook.platform.extra.APPLICATION_NAME"
  const val EXTRA_USER_ID = "com.facebook.platform.extra.USER_ID"
  const val EXTRA_LOGGER_REF = "com.facebook.platform.extra.LOGGER_REF"
  const val EXTRA_TOAST_DURATION_MS = "com.facebook.platform.extra.EXTRA_TOAST_DURATION_MS"
  const val EXTRA_GRAPH_API_VERSION = "com.facebook.platform.extra.GRAPH_API_VERSION"
  const val EXTRA_NONCE = "com.facebook.platform.extra.NONCE"

  // Extras returned by setResult() for ACTION_LOGIN_DIALOG
  const val EXTRA_ACCESS_TOKEN = "com.facebook.platform.extra.ACCESS_TOKEN"
  const val EXTRA_EXPIRES_SECONDS_SINCE_EPOCH =
      "com.facebook.platform.extra.EXPIRES_SECONDS_SINCE_EPOCH"
  const val EXTRA_DATA_ACCESS_EXPIRATION_TIME =
      "com.facebook.platform.extra.EXTRA_DATA_ACCESS_EXPIRATION_TIME"
  const val EXTRA_AUTHENTICATION_TOKEN = "com.facebook.platform.extra.ID_TOKEN"

  // EXTRA_PERMISSIONS
  const val RESULT_ARGS_ACCESS_TOKEN = "access_token"
  const val RESULT_ARGS_GRAPH_DOMAIN = "graph_domain"
  const val RESULT_ARGS_SIGNED_REQUEST = "signed request"
  const val RESULT_ARGS_EXPIRES_SECONDS_SINCE_EPOCH = "expires_seconds_since_epoch"
  const val RESULT_ARGS_PERMISSIONS = "permissions"

  // OG objects will have this key to set to true if they should be created as part of OG Action
  // publish
  const val OPEN_GRAPH_CREATE_OBJECT_KEY = "fbsdk:create_object"

  // Determines whether an image is user generated
  const val IMAGE_USER_GENERATED_KEY = "user_generated"

  // url key for images
  const val IMAGE_URL_KEY = "url"

  // Keys for status data in MESSAGE_ERROR_REPLY from PlatformService and for error
  // extras returned by PlatformActivity's setResult() in case of errors:
  const val STATUS_ERROR_TYPE = "com.facebook.platform.status.ERROR_TYPE"
  const val STATUS_ERROR_DESCRIPTION = "com.facebook.platform.status.ERROR_DESCRIPTION"
  const val STATUS_ERROR_CODE = "com.facebook.platform.status.ERROR_CODE"
  const val STATUS_ERROR_SUBCODE = "com.facebook.platform.status.ERROR_SUBCODE"
  const val STATUS_ERROR_JSON = "com.facebook.platform.status.ERROR_JSON"
  const val BRIDGE_ARG_ERROR_TYPE = "error_type"
  const val BRIDGE_ARG_ERROR_DESCRIPTION = "error_description"
  const val BRIDGE_ARG_ERROR_CODE = "error_code"
  const val BRIDGE_ARG_ERROR_SUBCODE = "error_subcode"
  const val BRIDGE_ARG_ERROR_JSON = "error_json"

  // Expected values for ERROR_KEY_TYPE.  Clients should tolerate other values:
  const val ERROR_UNKNOWN_ERROR = "UnknownError"
  const val ERROR_PROTOCOL_ERROR = "ProtocolError"
  const val ERROR_USER_CANCELED = "UserCanceled"
  const val ERROR_APPLICATION_ERROR = "ApplicationError"
  const val ERROR_NETWORK_ERROR = "NetworkError"
  const val ERROR_PERMISSION_DENIED = "PermissionDenied"
  const val ERROR_SERVICE_DISABLED = "ServiceDisabled"
  const val WEB_DIALOG_URL = "url"
  const val WEB_DIALOG_ACTION = "action"
  const val WEB_DIALOG_PARAMS = "params"
  const val WEB_DIALOG_IS_FALLBACK = "is_fallback"
  const val AUDIENCE_ME = "only_me"
  const val AUDIENCE_FRIENDS = "friends"
  const val AUDIENCE_EVERYONE = "everyone"
  private const val CONTENT_SCHEME = "content://"
  private const val PLATFORM_PROVIDER = ".provider.PlatformProvider"
  private const val PLATFORM_PROVIDER_VERSIONS = "$PLATFORM_PROVIDER/versions"

  // Columns returned by PlatformProvider
  private const val PLATFORM_PROVIDER_VERSION_COLUMN = "version"
  private val facebookAppInfoList = buildFacebookAppList()
  private val effectCameraAppInfoList = buildEffectCameraAppInfoList()
  private val actionToAppInfoMap = buildActionToAppInfoMap()
  private val protocolVersionsAsyncUpdating = AtomicBoolean(false)
  private fun buildFacebookAppList(): List<NativeAppInfo> {
    // Katana needs to be the first thing in the list since it will get selected as the default
    // FACEBOOK_APP_INFO
    return arrayListOf(KatanaAppInfo(), WakizashiAppInfo())
  }

  private fun buildEffectCameraAppInfoList(): List<NativeAppInfo> {
    // Add the effect test app in first position to make it the default choice.
    val list: ArrayList<NativeAppInfo> = arrayListOf(EffectTestAppInfo())
    list.addAll(buildFacebookAppList())
    return list
  }

  private fun buildActionToAppInfoMap(): Map<String, List<NativeAppInfo>> {
    val map: MutableMap<String, List<NativeAppInfo>> = HashMap()
    val messengerAppInfoList = ArrayList<NativeAppInfo>()
    messengerAppInfoList.add(MessengerAppInfo())

    // Add individual actions and the list they should try
    map[ACTION_OGACTIONPUBLISH_DIALOG] = facebookAppInfoList
    map[ACTION_FEED_DIALOG] = facebookAppInfoList
    map[ACTION_LIKE_DIALOG] = facebookAppInfoList
    map[ACTION_APPINVITE_DIALOG] = facebookAppInfoList
    map[ACTION_MESSAGE_DIALOG] = messengerAppInfoList
    map[ACTION_OGMESSAGEPUBLISH_DIALOG] = messengerAppInfoList
    map[ACTION_CAMERA_EFFECT] = effectCameraAppInfoList
    map[ACTION_SHARE_STORY] = facebookAppInfoList
    return map
  }

  @JvmStatic
  fun validateActivityIntent(context: Context, intent: Intent?, appInfo: NativeAppInfo?): Intent? {
    if (intent == null) {
      return null
    }
    val resolveInfo = context.packageManager.resolveActivity(intent, 0) ?: return null
    return if (!validateSignature(context, resolveInfo.activityInfo.packageName)) {
      null
    } else intent
  }

  @JvmStatic
  fun validateServiceIntent(context: Context, intent: Intent?, appInfo: NativeAppInfo?): Intent? {
    if (intent == null) {
      return null
    }
    val resolveInfo = context.packageManager.resolveService(intent, 0) ?: return null
    return if (!validateSignature(context, resolveInfo.serviceInfo.packageName)) {
      null
    } else intent
  }

  @JvmStatic
  fun createInstagramIntent(
      context: Context,
      applicationId: String,
      permissions: Collection<String?>,
      e2e: String,
      isRerequest: Boolean,
      isForPublish: Boolean,
      defaultAudience: DefaultAudience,
      clientState: String,
      authType: String,
      messengerPageId: String?,
      resetMessengerState: Boolean,
      isFamilyLogin: Boolean,
      shouldSkipAccountDedupe: Boolean
  ): Intent? {
    val appInfo: NativeAppInfo = InstagramAppInfo()
    var intent =
        createNativeAppIntent(
            appInfo,
            applicationId,
            permissions,
            e2e,
            isForPublish,
            defaultAudience,
            clientState,
            authType,
            false,
            messengerPageId,
            resetMessengerState,
            LoginTargetApp.INSTAGRAM,
            isFamilyLogin,
            shouldSkipAccountDedupe,
            "",
            null,
            null)
    intent = validateActivityIntent(context, intent, appInfo)
    return intent
  }

  private fun createNativeAppIntent(
      appInfo: NativeAppInfo,
      applicationId: String,
      permissions: Collection<String?>,
      e2e: String,
      isForPublish: Boolean,
      defaultAudience: DefaultAudience,
      clientState: String,
      authType: String,
      ignoreAppSwitchToLoggedOut: Boolean,
      messengerPageId: String?,
      resetMessengerState: Boolean,
      targetApp: LoginTargetApp,
      isFamilyLogin: Boolean,
      shouldSkipAccountDedupe: Boolean,
      nonce: String?,
      codeChallenge: String?,
      codeChallengeMethod: String?
  ): Intent? {
    val activityName = appInfo.getLoginActivity() ?: return null
    // the NativeApp doesn't have a login activity
    val intent =
        Intent()
            .setClassName(appInfo.getPackage(), activityName)
            .putExtra(FACEBOOK_PROXY_AUTH_APP_ID_KEY, applicationId)
    intent.putExtra(FACEBOOK_SDK_VERSION_KEY, FacebookSdk.getSdkVersion())
    if (!Utility.isNullOrEmpty(permissions)) {
      intent.putExtra(FACEBOOK_PROXY_AUTH_PERMISSIONS_KEY, TextUtils.join(",", permissions))
    }
    if (!Utility.isNullOrEmpty(e2e)) {
      intent.putExtra(FACEBOOK_PROXY_AUTH_E2E_KEY, e2e)
    }
    intent.putExtra(ServerProtocol.DIALOG_PARAM_STATE, clientState)
    intent.putExtra(ServerProtocol.DIALOG_PARAM_RESPONSE_TYPE, appInfo.getResponseType())
    intent.putExtra(ServerProtocol.DIALOG_PARAM_NONCE, nonce)

    // TODO T111412069
    //    if (!Utility.isNullOrEmpty(codeChallenge) && !Utility.isNullOrEmpty(codeChallengeMethod))
    // {
    //      intent.putExtra(ServerProtocol.DIALOG_PARAM_CODE_CHALLENGE, codeChallenge)
    //      intent.putExtra(ServerProtocol.DIALOG_PARAM_CODE_CHALLENGE_METHOD, codeChallengeMethod)
    //      intent.putExtra(
    //          ServerProtocol.DIALOG_PARAM_RESPONSE_TYPE, ServerProtocol.DIALOG_RESPONSE_TYPE_CODE)
    //    }
    intent.putExtra(
        ServerProtocol.DIALOG_PARAM_RETURN_SCOPES, ServerProtocol.DIALOG_RETURN_SCOPES_TRUE)
    if (isForPublish) {
      intent.putExtra(
          ServerProtocol.DIALOG_PARAM_DEFAULT_AUDIENCE, defaultAudience.nativeProtocolAudience)
    }

    // Override the API Version for Auth
    intent.putExtra(ServerProtocol.DIALOG_PARAM_LEGACY_OVERRIDE, FacebookSdk.getGraphApiVersion())
    intent.putExtra(ServerProtocol.DIALOG_PARAM_AUTH_TYPE, authType)
    if (ignoreAppSwitchToLoggedOut) {
      intent.putExtra(ServerProtocol.DIALOG_PARAM_FAIL_ON_LOGGED_OUT, true)
    }

    intent.putExtra(ServerProtocol.DIALOG_PARAM_MESSENGER_PAGE_ID, messengerPageId)
    intent.putExtra(ServerProtocol.DIALOG_PARAM_RESET_MESSENGER_STATE, resetMessengerState)
    if (isFamilyLogin) {
      intent.putExtra(ServerProtocol.DIALOG_PARAM_FX_APP, targetApp.toString())
    }
    if (shouldSkipAccountDedupe) {
      intent.putExtra(ServerProtocol.DIALOG_PARAM_SKIP_DEDUPE, true)
    }
    return intent
  }

  @JvmStatic
  fun createProxyAuthIntents(
      context: Context?,
      applicationId: String,
      permissions: Collection<String?>,
      e2e: String,
      isRerequest: Boolean,
      isForPublish: Boolean,
      defaultAudience: DefaultAudience,
      clientState: String,
      authType: String,
      ignoreAppSwitchToLoggedOut: Boolean,
      messengerPageId: String?,
      resetMessengerState: Boolean,
      isFamilyLogin: Boolean,
      shouldSkipAccountDedupe: Boolean,
      nonce: String?,
      codeChallenge: String?,
      codeChallengeMethod: String? = "S256"
  ): List<Intent> {
    return facebookAppInfoList.mapNotNull {
      createNativeAppIntent(
          it,
          applicationId,
          permissions,
          e2e,
          isForPublish,
          defaultAudience,
          clientState,
          authType,
          ignoreAppSwitchToLoggedOut,
          messengerPageId,
          resetMessengerState,
          LoginTargetApp.FACEBOOK,
          isFamilyLogin,
          shouldSkipAccountDedupe,
          nonce,
          codeChallenge,
          codeChallengeMethod)
    }
  }

  @JvmStatic fun getLatestKnownVersion(): Int = KNOWN_PROTOCOL_VERSIONS[0]

  // Note: be sure this stays sorted in descending order; add new versions at the beginning
  private val KNOWN_PROTOCOL_VERSIONS =
      arrayOf(
          PROTOCOL_VERSION_20210906,
          PROTOCOL_VERSION_20171115,
          PROTOCOL_VERSION_20170417,
          PROTOCOL_VERSION_20170411,
          PROTOCOL_VERSION_20170213,
          PROTOCOL_VERSION_20161017,
          PROTOCOL_VERSION_20160327,
          PROTOCOL_VERSION_20150702,
          PROTOCOL_VERSION_20150401,
          PROTOCOL_VERSION_20141218,
          PROTOCOL_VERSION_20141107,
          PROTOCOL_VERSION_20141028,
          PROTOCOL_VERSION_20141001,
          PROTOCOL_VERSION_20140701,
          PROTOCOL_VERSION_20140324,
          PROTOCOL_VERSION_20140313,
          PROTOCOL_VERSION_20140204,
          PROTOCOL_VERSION_20131107,
          PROTOCOL_VERSION_20131024,
          PROTOCOL_VERSION_20130618,
          PROTOCOL_VERSION_20130502,
          PROTOCOL_VERSION_20121101)

  @JvmStatic
  fun isVersionCompatibleWithBucketedIntent(version: Int): Boolean {
    return KNOWN_PROTOCOL_VERSIONS.contains(version) && version >= PROTOCOL_VERSION_20140701
  }

  /**
   * Will create an Intent that can be used to invoke an action in a Facebook app via the Native
   * Protocol
   */
  @JvmStatic
  fun createPlatformActivityIntent(
      context: Context,
      callId: String?,
      action: String?,
      versionResult: ProtocolVersionQueryResult?,
      extras: Bundle?
  ): Intent? {
    if (versionResult == null) {
      return null
    }
    val appInfo = versionResult.appInfo ?: return null
    var intent: Intent? =
        Intent()
            .setAction(INTENT_ACTION_PLATFORM_ACTIVITY)
            .setPackage(appInfo.getPackage())
            .addCategory(Intent.CATEGORY_DEFAULT)
    intent = validateActivityIntent(context, intent, appInfo)
    if (intent == null) {
      return null
    }
    setupProtocolRequestIntent(intent, callId, action, versionResult.protocolVersion, extras)
    return intent
  }

  /** Will setup the passed in Intent in the shape of a Native Protocol request Intent. */
  @JvmStatic
  fun setupProtocolRequestIntent(
      intent: Intent,
      callId: String?,
      action: String?,
      version: Int,
      params: Bundle?
  ) {
    val applicationId = FacebookSdk.getApplicationId()
    val applicationName = FacebookSdk.getApplicationName()
    intent
        .putExtra(EXTRA_PROTOCOL_VERSION, version)
        .putExtra(EXTRA_PROTOCOL_ACTION, action)
        .putExtra(EXTRA_APPLICATION_ID, applicationId)
    if (isVersionCompatibleWithBucketedIntent(version)) {
      // This is a bucketed intent
      val bridgeArguments = Bundle()
      bridgeArguments.putString(BRIDGE_ARG_ACTION_ID_STRING, callId)
      Utility.putNonEmptyString(bridgeArguments, BRIDGE_ARG_APP_NAME_STRING, applicationName)
      intent.putExtra(EXTRA_PROTOCOL_BRIDGE_ARGS, bridgeArguments)
      val methodArguments = params ?: Bundle()
      intent.putExtra(EXTRA_PROTOCOL_METHOD_ARGS, methodArguments)
    } else {
      // This is the older flat intent
      intent.putExtra(EXTRA_PROTOCOL_CALL_ID, callId)
      if (!Utility.isNullOrEmpty(applicationName)) {
        intent.putExtra(EXTRA_APPLICATION_NAME, applicationName)
      }
      if (params != null) {
        intent.putExtras(params)
      }
    }
  }

  /**
   * Use this method to set a result on an Activity, where the result needs to be in the shape of
   * the native protocol used for native dialogs.
   */
  @JvmStatic
  fun createProtocolResultIntent(
      requestIntent: Intent,
      results: Bundle?,
      error: FacebookException?
  ): Intent? {
    val callId = getCallIdFromIntent(requestIntent) ?: return null
    val resultIntent = Intent()
    resultIntent.putExtra(EXTRA_PROTOCOL_VERSION, getProtocolVersionFromIntent(requestIntent))
    val bridgeArguments = Bundle()
    bridgeArguments.putString(BRIDGE_ARG_ACTION_ID_STRING, callId.toString())
    if (error != null) {
      bridgeArguments.putBundle(BRIDGE_ARG_ERROR_BUNDLE, createBundleForException(error))
    }
    resultIntent.putExtra(EXTRA_PROTOCOL_BRIDGE_ARGS, bridgeArguments)
    if (results != null) {
      resultIntent.putExtra(EXTRA_PROTOCOL_METHOD_RESULTS, results)
    }
    return resultIntent
  }

  @JvmStatic
  fun createPlatformServiceIntent(context: Context): Intent? {
    for (appInfo in facebookAppInfoList) {
      var intent: Intent? =
          Intent(INTENT_ACTION_PLATFORM_SERVICE)
              .setPackage(appInfo.getPackage())
              .addCategory(Intent.CATEGORY_DEFAULT)
      intent = validateServiceIntent(context, intent, appInfo)
      if (intent != null) {
        return intent
      }
    }
    return null
  }

  @JvmStatic
  fun getProtocolVersionFromIntent(intent: Intent): Int {
    return intent.getIntExtra(EXTRA_PROTOCOL_VERSION, 0)
  }

  @JvmStatic
  fun getCallIdFromIntent(intent: Intent?): UUID? {
    if (intent == null) {
      return null
    }
    val version = getProtocolVersionFromIntent(intent)
    var callIdString: String? = null
    if (isVersionCompatibleWithBucketedIntent(version)) {
      val bridgeArgs = intent.getBundleExtra(EXTRA_PROTOCOL_BRIDGE_ARGS)
      if (bridgeArgs != null) {
        callIdString = bridgeArgs.getString(BRIDGE_ARG_ACTION_ID_STRING)
      }
    } else {
      callIdString = intent.getStringExtra(EXTRA_PROTOCOL_CALL_ID)
    }
    var callId: UUID? = null
    if (callIdString != null) {
      try {
        callId = UUID.fromString(callIdString)
      } catch (exception: IllegalArgumentException) {}
    }
    return callId
  }

  @JvmStatic
  fun getBridgeArgumentsFromIntent(intent: Intent): Bundle? {
    val version = getProtocolVersionFromIntent(intent)
    return if (!isVersionCompatibleWithBucketedIntent(version)) {
      null
    } else intent.getBundleExtra(EXTRA_PROTOCOL_BRIDGE_ARGS)
  }

  @JvmStatic
  fun getMethodArgumentsFromIntent(intent: Intent): Bundle? {
    val version = getProtocolVersionFromIntent(intent)
    return if (!isVersionCompatibleWithBucketedIntent(version)) {
      intent.extras
    } else intent.getBundleExtra(EXTRA_PROTOCOL_METHOD_ARGS)
  }

  @JvmStatic
  fun getSuccessResultsFromIntent(resultIntent: Intent): Bundle? {
    val version = getProtocolVersionFromIntent(resultIntent)
    val extras = resultIntent.extras
    return if (!isVersionCompatibleWithBucketedIntent(version) || extras == null) {
      extras
    } else extras.getBundle(EXTRA_PROTOCOL_METHOD_RESULTS)
  }

  @JvmStatic
  fun isErrorResult(resultIntent: Intent): Boolean {
    val bridgeArgs = getBridgeArgumentsFromIntent(resultIntent)
    return bridgeArgs?.containsKey(BRIDGE_ARG_ERROR_BUNDLE)
        ?: resultIntent.hasExtra(STATUS_ERROR_TYPE)
  }

  @JvmStatic
  fun getErrorDataFromResultIntent(resultIntent: Intent): Bundle? {
    if (!isErrorResult(resultIntent)) {
      return null
    }
    val bridgeArgs = getBridgeArgumentsFromIntent(resultIntent)
    return if (bridgeArgs != null) {
      bridgeArgs.getBundle(BRIDGE_ARG_ERROR_BUNDLE)
    } else resultIntent.extras
  }

  @JvmStatic
  fun getExceptionFromErrorData(errorData: Bundle?): FacebookException? {
    if (errorData == null) {
      return null
    }
    var type = errorData.getString(BRIDGE_ARG_ERROR_TYPE)
    if (type == null) {
      type = errorData.getString(STATUS_ERROR_TYPE)
    }
    var description = errorData.getString(BRIDGE_ARG_ERROR_DESCRIPTION)
    if (description == null) {
      description = errorData.getString(STATUS_ERROR_DESCRIPTION)
    }
    return if (type != null && type.equals(ERROR_USER_CANCELED, ignoreCase = true)) {
      FacebookOperationCanceledException(description)
    } else FacebookException(description)

    /* TODO parse error values and create appropriate exception class */
  }

  @JvmStatic
  fun createBundleForException(e: FacebookException?): Bundle? {
    if (e == null) {
      return null
    }
    val errorBundle = Bundle()
    errorBundle.putString(BRIDGE_ARG_ERROR_DESCRIPTION, e.toString())
    if (e is FacebookOperationCanceledException) {
      errorBundle.putString(BRIDGE_ARG_ERROR_TYPE, ERROR_USER_CANCELED)
    }
    return errorBundle
  }

  @JvmStatic
  fun getLatestAvailableProtocolVersionForService(minimumVersion: Int): Int {
    // Services are currently always against the Facebook App
    return getLatestAvailableProtocolVersionForAppInfoList(
            facebookAppInfoList, intArrayOf(minimumVersion))
        .protocolVersion
  }

  @JvmStatic
  fun getLatestAvailableProtocolVersionForAction(
      action: String,
      versionSpec: IntArray
  ): ProtocolVersionQueryResult {
    val appInfoList = actionToAppInfoMap[action] ?: emptyList()
    return getLatestAvailableProtocolVersionForAppInfoList(appInfoList, versionSpec)
  }

  private fun getLatestAvailableProtocolVersionForAppInfoList(
      appInfoList: List<NativeAppInfo>?,
      versionSpec: IntArray
  ): ProtocolVersionQueryResult {
    // Kick off an update
    updateAllAvailableProtocolVersionsAsync()
    if (appInfoList == null) {
      return ProtocolVersionQueryResult.createEmpty()
    }

    // Could potentially cache the NativeAppInfo to latestProtocolVersion
    for (appInfo in appInfoList) {
      val protocolVersion =
          computeLatestAvailableVersionFromVersionSpec(
              appInfo.getAvailableVersions(), getLatestKnownVersion(), versionSpec)
      if (protocolVersion != NO_PROTOCOL_AVAILABLE) {
        return ProtocolVersionQueryResult.create(appInfo, protocolVersion)
      }
    }
    return ProtocolVersionQueryResult.createEmpty()
  }

  @JvmStatic
  fun updateAllAvailableProtocolVersionsAsync() {
    if (!protocolVersionsAsyncUpdating.compareAndSet(false, true)) {
      return
    }
    FacebookSdk.getExecutor().execute {
      try {
        for (appInfo in facebookAppInfoList) {
          appInfo.fetchAvailableVersions(true)
        }
      } finally {
        protocolVersionsAsyncUpdating.set(false)
      }
    }
  }

  private fun fetchAllAvailableProtocolVersionsForAppInfo(appInfo: NativeAppInfo): TreeSet<Int> {
    val allAvailableVersions = TreeSet<Int>()
    val appContext = FacebookSdk.getApplicationContext()
    val contentResolver = appContext.contentResolver
    val projection = arrayOf(PLATFORM_PROVIDER_VERSION_COLUMN)
    val uri = buildPlatformProviderVersionURI(appInfo)
    var c: Cursor? = null
    try {
      // First see if the base provider exists as a check for whether the native app is
      // installed. We do this prior to querying, to prevent errors from being output to
      // logcat saying that the provider was not found.
      val pm = FacebookSdk.getApplicationContext().packageManager
      val contentProviderName = appInfo.getPackage() + PLATFORM_PROVIDER
      var providerInfo: ProviderInfo? = null
      try {
        providerInfo = pm.resolveContentProvider(contentProviderName, 0)
      } catch (e: RuntimeException) {
        // Accessing a dead provider will cause an DeadObjectException in the
        // package manager. It will be thrown as a Runtime Exception.
        // This will cause a incorrect indication of if the FB app installed but
        // it is better then crashing.
        Log.e(TAG, "Failed to query content resolver.", e)
      }
      if (providerInfo != null) {
        c =
            try {
              contentResolver.query(uri, projection, null, null, null)
            } catch (ex: NullPointerException) {
              Log.e(TAG, "Failed to query content resolver.")
              // Meizu devices running Android 5.0+ have a bug where they can throw a
              // NullPointerException when trying resolve a ContentProvider. Additionally,
              // rarely some 5.0+ devices have a bug which can rarely cause a
              // SecurityException to be thrown. Also, on some Samsung 4.4 / 7.0 / 7.1 devices
              // an IllegalArgumentException may be thrown with message "attempt to launch
              // content provider before system ready".  This will cause a incorrect indication
              // of if the FB app installed but it is better then crashing.
              null
            } catch (ex: SecurityException) {
              Log.e(TAG, "Failed to query content resolver.")
              null
            } catch (ex: IllegalArgumentException) {
              Log.e(TAG, "Failed to query content resolver.")
              null
            }
        if (c != null) {
          while (c.moveToNext()) {
            val version = c.getInt(c.getColumnIndex(PLATFORM_PROVIDER_VERSION_COLUMN))
            allAvailableVersions.add(version)
          }
        }
      }
    } finally {
      c?.close()
    }
    return allAvailableVersions
  }

  @JvmStatic
  fun computeLatestAvailableVersionFromVersionSpec(
      allAvailableFacebookAppVersions: TreeSet<Int>?,
      latestSdkVersion: Int,
      versionSpec: IntArray
  ): Int {
    if (allAvailableFacebookAppVersions == null) {
      return NO_PROTOCOL_AVAILABLE
    }
    // Remember that these ranges are sorted in ascending order and can be unbounded. So we are
    // starting from the end of the version-spec array and working backwards, to try get the
    // newest possible version
    var versionSpecIndex = versionSpec.size - 1
    val fbAppVersionsIterator = allAvailableFacebookAppVersions.descendingIterator()
    var latestFacebookAppVersion = -1
    while (fbAppVersionsIterator.hasNext()) {
      val fbAppVersion = fbAppVersionsIterator.next()

      // We're holding on to the greatest fb-app version available.
      latestFacebookAppVersion = max(latestFacebookAppVersion, fbAppVersion)

      // If there is a newer version in the versionSpec, throw it away, we don't have it
      while (versionSpecIndex >= 0 && versionSpec[versionSpecIndex] > fbAppVersion) {
        versionSpecIndex--
      }
      if (versionSpecIndex < 0) {
        // There was no fb app version that fell into any range in the versionSpec - or -
        // the versionSpec was empty, which means that this action is not supported.
        return NO_PROTOCOL_AVAILABLE
      }

      // If we are here, we know we are within a range specified in the versionSpec. We should
      // see if it is a disabled or enabled range.
      if (versionSpec[versionSpecIndex] == fbAppVersion) {
        // if the versionSpecIndex is even, it is enabled; if odd, disabled
        return if (versionSpecIndex % 2 == 0) min(latestFacebookAppVersion, latestSdkVersion)
        else NO_PROTOCOL_AVAILABLE
      }
    }
    return NO_PROTOCOL_AVAILABLE
  }

  private fun buildPlatformProviderVersionURI(appInfo: NativeAppInfo): Uri {
    return Uri.parse(CONTENT_SCHEME + appInfo.getPackage() + PLATFORM_PROVIDER_VERSIONS)
  }

  abstract class NativeAppInfo {
    abstract fun getPackage(): String
    abstract fun getLoginActivity(): String?
    private var availableVersions: TreeSet<Int>? = null
    open fun getResponseType(): String =
        ServerProtocol.DIALOG_RESPONSE_TYPE_ID_TOKEN_AND_SIGNED_REQUEST
    open fun onAvailableVersionsNullOrEmpty() = Unit

    fun getAvailableVersions(): TreeSet<Int>? {
      if (availableVersions == null || availableVersions?.isEmpty() != false) {
        fetchAvailableVersions(false)
      }
      return availableVersions
    }

    @Synchronized
    fun fetchAvailableVersions(force: Boolean) {
      if (force || availableVersions == null || availableVersions?.isEmpty() != false) {
        availableVersions = fetchAllAvailableProtocolVersionsForAppInfo(this)
      }
      if (availableVersions.isNullOrEmpty()) {
        onAvailableVersionsNullOrEmpty()
      }
    }
  }

  private class KatanaAppInfo : NativeAppInfo() {
    override fun getLoginActivity() = FACEBOOK_PROXY_AUTH_ACTIVITY
    override fun getPackage() = "com.facebook.katana"

    override fun onAvailableVersionsNullOrEmpty() {
      if (isAndroidAPIVersionNotLessThan30()) {
        Log.w(
            TAG,
            "Apps that target Android API 30+ (Android 11+) cannot call Facebook native apps unless the package visibility needs are declared. Please follow https://developers.facebook.com/docs/android/troubleshooting/#faq_267321845055988 to make the declaration.")
      }
    }

    private fun isAndroidAPIVersionNotLessThan30(): Boolean {
      val context = FacebookSdk.getApplicationContext()
      val applicationInfo = context.applicationInfo
      return applicationInfo.targetSdkVersion >= 30
    }
  }

  private class MessengerAppInfo : NativeAppInfo() {
    override fun getLoginActivity() = null
    override fun getPackage() = "com.facebook.orca"
  }

  private class WakizashiAppInfo : NativeAppInfo() {
    override fun getLoginActivity() = FACEBOOK_PROXY_AUTH_ACTIVITY
    override fun getPackage() = "com.facebook.wakizashi"
  }

  private class InstagramAppInfo : NativeAppInfo() {
    override fun getLoginActivity(): String = "com.instagram.platform.AppAuthorizeActivity"
    override fun getPackage(): String = "com.instagram.android"
    override fun getResponseType(): String = ServerProtocol.DIALOG_RESPONSE_TYPE_TOKEN_AND_SCOPES
  }

  private class EffectTestAppInfo : NativeAppInfo() {
    override fun getLoginActivity() = null
    override fun getPackage() = "com.facebook.arstudio.player"
  }

  class ProtocolVersionQueryResult private constructor() {
    var appInfo: NativeAppInfo? = null
      private set
    var protocolVersion = 0
      private set

    companion object {
      @JvmStatic
      fun create(nativeAppInfo: NativeAppInfo?, protocolVersion: Int): ProtocolVersionQueryResult {
        val result = ProtocolVersionQueryResult()
        result.appInfo = nativeAppInfo
        result.protocolVersion = protocolVersion
        return result
      }

      @JvmStatic
      fun createEmpty(): ProtocolVersionQueryResult {
        val result = ProtocolVersionQueryResult()
        result.protocolVersion = NO_PROTOCOL_AVAILABLE
        return result
      }
    }
  }
}


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

package com.facebook.internal

import java.util.concurrent.ConcurrentHashMap
import org.json.JSONObject

/**
 * com.facebook.internal is solely for the use of other packages within the Facebook SDK for
 * Android. Use of any of the classes in this package is unsupported, and they may be modified or
 * removed without warning at any time.
 *
 * Caches the profile information associated to a specific access token. This minimizes the number
 * of request we need to make to the server.
 */
internal object ProfileInformationCache {
  private val infoCache = ConcurrentHashMap<String, JSONObject>()

  @JvmStatic
  fun getProfileInformation(accessToken: String): JSONObject? {
    return infoCache[accessToken]
  }

  @JvmStatic
  fun putProfileInformation(key: String, value: JSONObject) {
    infoCache[key] = value
  }
}


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

package com.facebook.internal

import android.os.Bundle
import android.util.Log
import com.facebook.FacebookSdk
import com.facebook.LoggingBehavior
import com.facebook.internal.Logger.Companion.log
import org.json.JSONException

/**
 * com.facebook.internal is solely for the use of other packages within the Facebook SDK for
 * Android. Use of any of the classes in this package is unsupported, and they may be modified or
 * removed without warning at any time.
 */
object ServerProtocol {
  private val TAG = ServerProtocol::class.java.name
  private const val DIALOG_AUTHORITY_FORMAT = "m.%s"
  private const val GAMING_DIALOG_AUTHORITY_FORMAT = "%s"
  const val DIALOG_PATH = "dialog/"
  const val DIALOG_PARAM_ACCESS_TOKEN = "access_token"
  const val DIALOG_PARAM_APP_ID = "app_id"
  const val DIALOG_PARAM_AUTH_TYPE = "auth_type"
  const val DIALOG_PARAM_CBT = "cbt"
  const val DIALOG_PARAM_CLIENT_ID = "client_id"
  const val DIALOG_PARAM_CODE_CHALLENGE = "code_challenge"
  const val DIALOG_PARAM_CODE_CHALLENGE_METHOD = "code_challenge_method"
  const val DIALOG_PARAM_CODE_REDIRECT_URI = "code_redirect_uri"
  const val DIALOG_PARAM_CUSTOM_TABS_PREFETCHING = "cct_prefetching"
  const val DIALOG_PARAM_DISPLAY = "display"
  const val DIALOG_PARAM_DISPLAY_TOUCH = "touch"
  const val DIALOG_PARAM_E2E = "e2e"
  const val DIALOG_PARAM_AUTHENTICATION_TOKEN = "id_token"
  const val DIALOG_PARAM_IES = "ies"
  const val DIALOG_PARAM_LEGACY_OVERRIDE = "legacy_override"
  const val DIALOG_PARAM_LOGIN_BEHAVIOR = "login_behavior"
  const val DIALOG_PARAM_NONCE = "nonce"
  const val DIALOG_PARAM_REDIRECT_URI = "redirect_uri"
  const val DIALOG_PARAM_RESPONSE_TYPE = "response_type"
  const val DIALOG_PARAM_RETURN_SCOPES = "return_scopes"
  const val DIALOG_PARAM_SCOPE = "scope"
  const val DIALOG_PARAM_SSO_DEVICE = "sso"
  const val DIALOG_PARAM_DEFAULT_AUDIENCE = "default_audience"
  const val DIALOG_PARAM_SDK_VERSION = "sdk"
  const val DIALOG_PARAM_STATE = "state"
  const val DIALOG_PARAM_FAIL_ON_LOGGED_OUT = "fail_on_logged_out"
  const val DIALOG_PARAM_CCT_OVER_LOGGED_OUT_APP_SWITCH = "cct_over_app_switch"
  const val DIALOG_PARAM_MESSENGER_PAGE_ID = "messenger_page_id"
  const val DIALOG_PARAM_RESET_MESSENGER_STATE = "reset_messenger_state"
  const val DIALOG_REREQUEST_AUTH_TYPE = "rerequest"
  const val DIALOG_PARAM_FX_APP = "fx_app"
  const val DIALOG_PARAM_SKIP_DEDUPE = "skip_dedupe"
  const val DIALOG_RESPONSE_TYPE_CODE = "code,signed_request,graph_domain"
  const val DIALOG_RESPONSE_TYPE_TOKEN_AND_SCOPES =
      "token,signed_request,graph_domain,granted_scopes"
  const val DIALOG_RESPONSE_TYPE_TOKEN_AND_SIGNED_REQUEST = "token,signed_request,graph_domain"
  const val DIALOG_RESPONSE_TYPE_ID_TOKEN_AND_SIGNED_REQUEST =
      "id_token,token,signed_request,graph_domain"
  const val DIALOG_RETURN_SCOPES_TRUE = "true"
  const val DIALOG_REDIRECT_URI = "fbconnect://success"
  const val DIALOG_REDIRECT_CHROME_OS_URI = "fbconnect://chrome_os_success"
  const val DIALOG_CANCEL_URI = "fbconnect://cancel"
  const val FALLBACK_DIALOG_PARAM_APP_ID = "app_id"
  const val FALLBACK_DIALOG_PARAM_BRIDGE_ARGS = "bridge_args"
  const val FALLBACK_DIALOG_PARAM_KEY_HASH = "android_key_hash"
  const val FALLBACK_DIALOG_PARAM_METHOD_ARGS = "method_args"
  const val FALLBACK_DIALOG_PARAM_METHOD_RESULTS = "method_results"
  const val FALLBACK_DIALOG_PARAM_VERSION = "version"
  const val FALLBACK_DIALOG_DISPLAY_VALUE_TOUCH = "touch"

  const val INSTAGRAM_OAUTH_PATH = "oauth/authorize"

  // URL components
  private const val GRAPH_VIDEO_URL_FORMAT = "https://graph-video.%s"
  private const val GRAPH_URL_FORMAT = "https://graph.%s"
  @JvmStatic fun getDefaultAPIVersion() = "v16.0"

  @JvmStatic
  fun getErrorsProxyAuthDisabled(): Collection<String> =
      listOf("service_disabled", "AndroidAuthKillSwitchException")

  @JvmStatic
  fun getErrorsUserCanceled(): Collection<String> =
      listOf("access_denied", "OAuthAccessDeniedException")

  @JvmStatic fun getErrorConnectionFailure(): String = "CONNECTION_FAILURE"

  @JvmStatic
  fun getDialogAuthority(): String =
      String.format(DIALOG_AUTHORITY_FORMAT, FacebookSdk.getFacebookDomain())
  @JvmStatic
  fun getGamingDialogAuthority(): String =
      String.format(GAMING_DIALOG_AUTHORITY_FORMAT, FacebookSdk.getFacebookGamingDomain())
  @JvmStatic
  fun getInstagramDialogAuthority(): String =
      String.format(DIALOG_AUTHORITY_FORMAT, FacebookSdk.getInstagramDomain())
  @JvmStatic
  fun getGraphUrlBase(): String = String.format(GRAPH_URL_FORMAT, FacebookSdk.getGraphDomain())
  @JvmStatic
  fun getGraphVideoUrlBase(): String =
      String.format(GRAPH_VIDEO_URL_FORMAT, FacebookSdk.getGraphDomain())
  @JvmStatic
  fun getFacebookGraphUrlBase(): String =
      String.format(GRAPH_URL_FORMAT, FacebookSdk.getFacebookDomain())
  @JvmStatic
  fun getGraphUrlBaseForSubdomain(subdomain: String): String =
      String.format(GRAPH_URL_FORMAT, subdomain)

  @JvmStatic
  fun getQueryParamsForPlatformActivityIntentWebFallback(
      callId: String,
      version: Int,
      methodArgs: Bundle?
  ): Bundle? {
    val context = FacebookSdk.getApplicationContext()
    val keyHash = FacebookSdk.getApplicationSignature(context)
    if (Utility.isNullOrEmpty(keyHash)) {
      return null
    }
    val webParams = Bundle()
    webParams.putString(FALLBACK_DIALOG_PARAM_KEY_HASH, keyHash)
    webParams.putString(FALLBACK_DIALOG_PARAM_APP_ID, FacebookSdk.getApplicationId())
    webParams.putInt(FALLBACK_DIALOG_PARAM_VERSION, version)
    webParams.putString(DIALOG_PARAM_DISPLAY, FALLBACK_DIALOG_DISPLAY_VALUE_TOUCH)
    val bridgeArguments = Bundle()
    bridgeArguments.putString(NativeProtocol.BRIDGE_ARG_ACTION_ID_STRING, callId)
    try {
      val bridgeArgsJSON = BundleJSONConverter.convertToJSON(bridgeArguments)
      val methodArgsJSON = BundleJSONConverter.convertToJSON(methodArgs ?: Bundle())
      if (bridgeArgsJSON == null || methodArgsJSON == null) {
        return null
      }
      webParams.putString(FALLBACK_DIALOG_PARAM_BRIDGE_ARGS, bridgeArgsJSON.toString())
      webParams.putString(FALLBACK_DIALOG_PARAM_METHOD_ARGS, methodArgsJSON.toString())
    } catch (je: JSONException) {
      log(LoggingBehavior.DEVELOPER_ERRORS, Log.ERROR, TAG, "Error creating Url -- $je")
      return null
    } catch (je: IllegalArgumentException) {
      log(LoggingBehavior.DEVELOPER_ERRORS, Log.ERROR, TAG, "Error creating Url -- $je")
      return null
    }
    return webParams
  }
}


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

package com.facebook.internal

import java.util.EnumSet

enum class SmartLoginOption(val value: Long) {
  None(0),
  Enabled(1),
  RequireConfirm(2);

  companion object {
    private val ALL: EnumSet<SmartLoginOption> = EnumSet.allOf(SmartLoginOption::class.java)

    @JvmStatic
    fun parseOptions(bitmask: Long): EnumSet<SmartLoginOption> {
      val result = EnumSet.noneOf(SmartLoginOption::class.java)
      for (opt in ALL) {
        if (bitmask and opt.value != 0L) {
          result.add(opt)
        }
      }
      return result
    }
  }
}


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

package com.facebook.internal

import android.net.Uri
import android.util.Log
import com.facebook.LoggingBehavior
import java.io.IOException
import java.io.InputStreamReader
import java.io.OutputStream
import java.util.HashSet

/**
 * com.facebook.internal is solely for the use of other packages within the Facebook SDK for
 * Android. Use of any of the classes in this package is unsupported, and they may be modified or
 * removed without warning at any time.
 */
object UrlRedirectCache {
  private val tag = UrlRedirectCache::class.simpleName ?: "UrlRedirectCache"
  private val redirectContentTag = tag + "_Redirect"
  private var urlRedirectFileLruCache: FileLruCache? = null

  @Synchronized
  @JvmStatic
  @Throws(IOException::class)
  fun getCache(): FileLruCache {
    val nonNullCache = urlRedirectFileLruCache ?: FileLruCache(tag, FileLruCache.Limits())
    urlRedirectFileLruCache = nonNullCache
    return nonNullCache
  }

  @JvmStatic
  fun getRedirectedUri(uri: Uri?): Uri? {
    if (uri == null) {
      return null
    }

    var uriString = uri.toString()
    var reader: InputStreamReader? = null
    val redirectChain = HashSet<String>()
    redirectChain.add(uriString)
    try {
      val cache = getCache()
      var redirectExists = false
      var stream = cache.get(uriString, this.redirectContentTag)

      while (stream != null) {
        redirectExists = true
        // Get the redirected url
        reader = InputStreamReader(stream)
        val buffer = CharArray(128)
        val urlBuilder = StringBuilder()
        var bufferLength = reader.read(buffer, 0, buffer.size)
        while (bufferLength > 0) {
          urlBuilder.append(buffer, 0, bufferLength)
          bufferLength = reader.read(buffer, 0, buffer.size)
        }
        Utility.closeQuietly(reader)

        // Iterate to the next url in the redirection
        val redirectToUriString = urlBuilder.toString()
        if (redirectChain.contains(redirectToUriString)) {
          if (redirectToUriString == uriString) {
            // uriString redirect to itself. Stop the loop
            break
          } else {
            // A loop with more than 1 address is detected. It's unexpected.
            // In this case, return null so that the caller can directly use the original address.
            Logger.log(
                LoggingBehavior.CACHE, Log.ERROR, this.tag, "A loop detected in UrlRedirectCache")
            return null
          }
        }
        uriString = redirectToUriString
        redirectChain.add(uriString)
        stream = cache.get(uriString, this.redirectContentTag)
      }

      if (redirectExists) {
        return Uri.parse(uriString)
      }
    } catch (e: IOException) {
      Logger.log(
          LoggingBehavior.CACHE,
          Log.INFO,
          this.tag,
          "IOException when accessing cache: " + e.message)
    } finally {
      Utility.closeQuietly(reader)
    }

    return null
  }

  @JvmStatic
  fun cacheUriRedirect(fromUri: Uri?, toUri: Uri?) {
    if (fromUri == null || toUri == null) {
      return
    }

    var redirectStream: OutputStream? = null
    try {
      val cache = getCache()
      redirectStream = cache.openPutStream(fromUri.toString(), this.redirectContentTag)
      redirectStream.write(toUri.toString().toByteArray())
    } catch (e: IOException) {
      // Caching is best effort
      Logger.log(
          LoggingBehavior.CACHE,
          Log.INFO,
          this.tag,
          "IOException when accessing cache: " + e.message)
    } finally {
      Utility.closeQuietly(redirectStream)
    }
  }

  @JvmStatic
  fun clearCache() {
    try {
      getCache().clearCache()
    } catch (e: IOException) {
      Logger.log(LoggingBehavior.CACHE, Log.WARN, tag, "clearCache failed " + e.message)
    }
  }
}


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

package com.facebook.internal

import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.database.Cursor
import android.hardware.display.DisplayManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.os.Parcel
import android.os.StatFs
import android.provider.OpenableColumns
import android.telephony.TelephonyManager
import android.text.TextUtils
import android.util.DisplayMetrics
import android.util.Log
import android.view.Display
import android.view.WindowManager
import android.view.autofill.AutofillManager
import android.webkit.CookieManager
import android.webkit.CookieSyncManager
import com.facebook.AccessToken
import com.facebook.FacebookException
import com.facebook.FacebookSdk
import com.facebook.GraphRequest
import com.facebook.HttpMethod
import com.facebook.appevents.UserDataStore
import com.facebook.internal.ProfileInformationCache.getProfileInformation
import com.facebook.internal.ProfileInformationCache.putProfileInformation
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import java.io.BufferedInputStream
import java.io.Closeable
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
import java.io.OutputStream
import java.io.UnsupportedEncodingException
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method
import java.math.BigInteger
import java.net.HttpURLConnection
import java.net.URLConnection
import java.net.URLDecoder
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.text.DecimalFormat
import java.util.Date
import java.util.Locale
import java.util.Random
import java.util.TimeZone
import java.util.regex.Pattern
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.collections.HashSet
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import org.json.JSONTokener

/**
 * com.facebook.internal is solely for the use of other packages within the Facebook SDK for
 * Android. Use of any of the classes in this package is unsupported, and they may be modified or
 * removed without warning at any time.
 */
object Utility {
  const val LOG_TAG = "FacebookSDK"
  private const val HASH_ALGORITHM_MD5 = "MD5"
  private const val HASH_ALGORITHM_SHA1 = "SHA-1"
  private const val HASH_ALGORITHM_SHA256 = "SHA-256"
  private const val URL_SCHEME = "https"
  private const val EXTRA_APP_EVENTS_INFO_FORMAT_VERSION = "a2"
  private const val UTF8 = "UTF-8"

  // This is the default used by the buffer streams, but they trace a warning if you do not
  // specify.
  const val DEFAULT_STREAM_BUFFER_SIZE = 8192

  // Refresh extended device info every 30 minutes
  private const val REFRESH_TIME_FOR_EXTENDED_DEVICE_INFO_MILLIS = 30 * 60 * 1000
  private const val NO_CARRIER = "NoCarrier"
  private var numCPUCores = 0
  private var timestampOfLastCheck: Long = -1
  private var totalExternalStorageGB: Long = -1
  var availableExternalStorageGB: Long = -1
  private var deviceTimezoneAbbreviation = ""
  var deviceTimeZoneName = ""
  var carrierName = NO_CARRIER
  var versionName: String? = ""
  var locale: Locale? = null

  // https://stackoverflow.com/questions/39784415/how-to-detect-programmatically-if-android-app-is-running-in-chrome-book-or-in
  private const val ARC_DEVICE_PATTERN = ".+_cheets|cheets_.+"

  private const val FACEBOOK_PROFILE_FIELDS = "id,name,first_name,middle_name,last_name"
  private const val INSTAGRAM_PROFILE_FIELDS = "id,name,profile_picture"

  @JvmStatic
  fun isNullOrEmpty(c: Collection<*>?): Boolean {
    return c == null || c.isEmpty()
  }

  @JvmStatic
  fun isNullOrEmpty(s: String?): Boolean {
    return s == null || s.isEmpty()
  }

  /**
   * Use this when you want to normalize empty and null strings This way, Utility.areObjectsEqual
   * can used for comparison, where a null string is to be treated the same as an empty string.
   *
   * @param s The string to coerce
   * @param valueIfNullOrEmpty The value if s is null or empty.
   * @return The original string s if it's not null or empty, otherwise the valueIfNullOrEmpty
   */
  @JvmStatic
  fun coerceValueIfNullOrEmpty(s: String?, valueIfNullOrEmpty: String?): String? {
    return if (isNullOrEmpty(s)) {
      valueIfNullOrEmpty
    } else s
  }

  @JvmStatic
  fun md5hash(key: String): String? {
    return hashWithAlgorithm(HASH_ALGORITHM_MD5, key)
  }

  @JvmStatic
  fun sha1hash(key: String): String? {
    return hashWithAlgorithm(HASH_ALGORITHM_SHA1, key)
  }

  @JvmStatic
  fun sha1hash(bytes: ByteArray): String? {
    return hashWithAlgorithm(HASH_ALGORITHM_SHA1, bytes)
  }

  @JvmStatic
  fun sha256hash(key: String?): String? {
    return if (key == null) {
      null
    } else hashWithAlgorithm(HASH_ALGORITHM_SHA256, key)
  }

  @JvmStatic
  fun sha256hash(bytes: ByteArray?): String? {
    return if (bytes == null) {
      null
    } else hashWithAlgorithm(HASH_ALGORITHM_SHA256, bytes)
  }

  private fun hashWithAlgorithm(algorithm: String, key: String): String? {
    return hashWithAlgorithm(algorithm, key.toByteArray())
  }

  private fun hashWithAlgorithm(algorithm: String, bytes: ByteArray): String? {
    val hash =
        try {
          MessageDigest.getInstance(algorithm)
        } catch (e: NoSuchAlgorithmException) {
          return null
        }
    return hashBytes(hash, bytes)
  }

  private fun hashBytes(hash: MessageDigest, bytes: ByteArray): String {
    hash.update(bytes)
    val digest = hash.digest()
    val builder = StringBuilder()
    for (b in digest) {
      builder.append(Integer.toHexString(b.toInt() shr 4 and 0xf))
      builder.append(Integer.toHexString(b.toInt() shr 0 and 0xf))
    }
    return builder.toString()
  }

  @JvmStatic
  fun buildUri(authority: String?, path: String?, parameters: Bundle?): Uri {
    val builder = Uri.Builder()
    builder.scheme(URL_SCHEME)
    builder.authority(authority)
    builder.path(path)
    if (parameters != null) {
      for (key in parameters.keySet()) {
        val parameter = parameters[key]
        if (parameter is String) {
          builder.appendQueryParameter(key, parameter as String?)
        }
      }
    }
    return builder.build()
  }

  @JvmStatic
  fun parseUrlQueryString(queryString: String?): Bundle {
    val params = Bundle()
    if (!isNullOrEmpty(queryString)) {
      val array = checkNotNull(queryString).split("&").toTypedArray()
      for (parameter in array) {
        val keyValuePair = parameter.split("=").toTypedArray()
        try {
          if (keyValuePair.size == 2) {
            params.putString(
                URLDecoder.decode(keyValuePair[0], UTF8), URLDecoder.decode(keyValuePair[1], UTF8))
          } else if (keyValuePair.size == 1) {
            params.putString(URLDecoder.decode(keyValuePair[0], UTF8), "")
          }
        } catch (e: UnsupportedEncodingException) {
          // shouldn't happen
          logd(LOG_TAG, e)
        }
      }
    }
    return params
  }

  @JvmStatic
  fun putNonEmptyString(b: Bundle, key: String?, value: String?) {
    if (!isNullOrEmpty(value)) {
      b.putString(key, value)
    }
  }

  @JvmStatic
  fun putCommaSeparatedStringList(b: Bundle, key: String?, list: List<String?>?) {
    if (list != null) {
      val commaSeparated = TextUtils.join(",", list)
      b.putString(key, commaSeparated)
    }
  }

  @JvmStatic
  fun putUri(b: Bundle, key: String?, uri: Uri?) {
    if (uri != null) {
      putNonEmptyString(b, key, uri.toString())
    }
  }

  @JvmStatic
  fun putJSONValueInBundle(bundle: Bundle, key: String?, value: Any?): Boolean {
    if (value == null) {
      bundle.remove(key)
    } else if (value is Boolean) {
      bundle.putBoolean(key, value)
    } else if (value is BooleanArray) {
      bundle.putBooleanArray(key, value as BooleanArray?)
    } else if (value is Double) {
      bundle.putDouble(key, value)
    } else if (value is DoubleArray) {
      bundle.putDoubleArray(key, value as DoubleArray?)
    } else if (value is Int) {
      bundle.putInt(key, value)
    } else if (value is IntArray) {
      bundle.putIntArray(key, value as IntArray?)
    } else if (value is Long) {
      bundle.putLong(key, value)
    } else if (value is LongArray) {
      bundle.putLongArray(key, value as LongArray?)
    } else if (value is String) {
      bundle.putString(key, value as String?)
    } else if (value is JSONArray) {
      bundle.putString(key, value.toString())
    } else if (value is JSONObject) {
      bundle.putString(key, value.toString())
    } else {
      return false
    }
    return true
  }

  @JvmStatic
  fun closeQuietly(closeable: Closeable?) {
    try {
      closeable?.close()
    } catch (ioe: IOException) {
      // ignore
    }
  }

  @JvmStatic
  fun disconnectQuietly(connection: URLConnection?) {
    if (connection != null && connection is HttpURLConnection) {
      connection.disconnect()
    }
  }

  @JvmStatic
  fun getMetadataApplicationId(context: Context?): String {
    Validate.notNull(context, "context")
    return FacebookSdk.getApplicationId()
  }

  @JvmStatic
  fun convertJSONObjectToHashMap(jsonObject: JSONObject): Map<String, Any> {
    val map = HashMap<String, Any>()
    val keys = jsonObject.names() ?: return map
    for (i in 0 until keys.length()) {
      var key: String
      try {
        key = keys.getString(i)
        var value = jsonObject[key]
        if (value is JSONObject) {
          value = convertJSONObjectToHashMap(value)
        }
        map[key] = value
      } catch (e: JSONException) {}
    }
    return map
  }

  @JvmStatic
  fun convertJSONObjectToStringMap(jsonObject: JSONObject): Map<String, String> {
    val map = HashMap<String, String>()
    val keys = jsonObject.keys()
    while (keys.hasNext()) {
      val key = keys.next()
      val value = jsonObject.optString(key)
      if (value != null) {
        map[key] = value
      }
    }
    return map
  }

  @JvmStatic
  fun convertJSONArrayToList(jsonArray: JSONArray): List<String> {
    return try {
      val result: MutableList<String> = ArrayList()
      for (i in 0 until jsonArray.length()) {
        result.add(jsonArray.getString(i))
      }
      result
    } catch (je: JSONException) {
      ArrayList()
    }
  }

  // Returns either a JSONObject or JSONArray representation of the 'key' property of
  // 'jsonObject'.
  @Throws(JSONException::class)
  @JvmStatic
  fun getStringPropertyAsJSON(
      jsonObject: JSONObject,
      key: String?,
      nonJSONPropertyKey: String?
  ): Any? {
    var jsonObject = jsonObject
    var value = jsonObject.opt(key)
    if (value != null && value is String) {
      val tokener = JSONTokener(value)
      value = tokener.nextValue()
    }
    return if (value != null && !(value is JSONObject || value is JSONArray)) {
      if (nonJSONPropertyKey != null) {
        // Facebook sometimes gives us back a non-JSON value such as
        // literal "true" or "false" as a result.
        // If we got something like that, we present it to the caller as a JSONObject
        // with a single property. We only do this if the caller wants that behavior.
        jsonObject = JSONObject()
        jsonObject.putOpt(nonJSONPropertyKey, value)
        jsonObject
      } else {
        throw FacebookException("Got an unexpected non-JSON object.")
      }
    } else value
  }

  @Throws(IOException::class)
  @JvmStatic
  fun readStreamToString(inputStream: InputStream?): String {
    var bufferedInputStream: BufferedInputStream? = null
    var reader: InputStreamReader? = null
    return try {
      bufferedInputStream = BufferedInputStream(inputStream)
      reader = InputStreamReader(bufferedInputStream)
      val stringBuilder = StringBuilder()
      val bufferSize = 1024 * 2
      val buffer = CharArray(bufferSize)
      var n = 0
      while (reader.read(buffer).also { n = it } != -1) {
        stringBuilder.append(buffer, 0, n)
      }
      stringBuilder.toString()
    } finally {
      closeQuietly(bufferedInputStream)
      closeQuietly(reader)
    }
  }

  @Throws(IOException::class)
  @JvmStatic
  fun copyAndCloseInputStream(inputStream: InputStream?, outputStream: OutputStream): Int {
    var bufferedInputStream: BufferedInputStream? = null
    var totalBytes = 0
    try {
      bufferedInputStream = BufferedInputStream(inputStream)
      val buffer = ByteArray(8192)
      var bytesRead: Int
      while (bufferedInputStream.read(buffer).also { bytesRead = it } != -1) {
        outputStream.write(buffer, 0, bytesRead)
        totalBytes += bytesRead
      }
    } finally {
      bufferedInputStream?.close()
      inputStream?.close()
    }
    return totalBytes
  }

  @JvmStatic
  fun stringsEqualOrEmpty(a: String?, b: String?): Boolean {
    val aEmpty = a.isNullOrEmpty()
    val bEmpty = b.isNullOrEmpty()
    if (aEmpty && bEmpty) {
      // Both null or empty, they match.
      return true
    }
    return if (!aEmpty && !bEmpty) {
      // Both non-empty, check equality.
      a == b
    } else false
    // One empty, one non-empty, can't match.
  }

  private fun clearCookiesForDomain(context: Context, domain: String) {
    // This is to work around a bug where CookieManager may fail to instantiate if
    // CookieSyncManager has never been created.
    val syncManager = CookieSyncManager.createInstance(context)
    syncManager.sync()
    val cookieManager = CookieManager.getInstance()
    val cookies = cookieManager.getCookie(domain) ?: return
    val splitCookies = cookies.split(";").toTypedArray()
    for (cookie in splitCookies) {
      val cookieParts = cookie.split("=").toTypedArray()
      if (cookieParts.size > 0) {
        val newCookie =
            cookieParts[0].trim { it <= ' ' } + "=;expires=Sat, 1 Jan 2000 00:00:01 UTC;"
        cookieManager.setCookie(domain, newCookie)
      }
    }
    cookieManager.removeExpiredCookie()
  }

  @JvmStatic
  fun clearFacebookCookies(context: Context) {
    // setCookie acts differently when trying to expire cookies between builds of Android that
    // are using Chromium HTTP stack and those that are not. Using both of these domains to
    // ensure it works on both.
    try {
      clearCookiesForDomain(context, "facebook.com")
      clearCookiesForDomain(context, ".facebook.com")
      clearCookiesForDomain(context, "https://facebook.com")
      clearCookiesForDomain(context, "https://.facebook.com")
    } catch (e: Exception) {
      // An exception is thrown during cookie clearance. It's likely that webview is not available.
    }
  }

  @JvmStatic
  fun logd(tag: String?, e: Exception?) {
    if (FacebookSdk.isDebugEnabled() && tag != null && e != null) {
      Log.d(tag, e.javaClass.simpleName + ": " + e.message)
    }
  }

  @JvmStatic
  fun logd(tag: String?, msg: String?) {
    if (FacebookSdk.isDebugEnabled() && tag != null && msg != null) {
      Log.d(tag, msg)
    }
  }

  @JvmStatic
  fun logd(tag: String?, msg: String?, t: Throwable?) {
    if (FacebookSdk.isDebugEnabled() && !isNullOrEmpty(tag)) {
      Log.d(tag, msg, t)
    }
  }

  @JvmStatic
  fun <T> areObjectsEqual(a: T?, b: T?): Boolean {
    return if (a == null) {
      b == null
    } else a == b
  }

  @JvmStatic
  fun safeGetStringFromResponse(response: JSONObject?, propertyName: String?): String {
    return if (response != null) response.optString(propertyName, "") else ""
  }

  @JvmStatic
  fun tryGetJSONObjectFromResponse(response: JSONObject?, propertyKey: String?): JSONObject? {
    return response?.optJSONObject(propertyKey)
  }

  @JvmStatic
  fun tryGetJSONArrayFromResponse(response: JSONObject?, propertyKey: String?): JSONArray? {
    return response?.optJSONArray(propertyKey)
  }

  @Throws(JSONException::class)
  @JvmStatic
  fun jsonArrayToStringList(jsonArray: JSONArray): List<String> {
    val result = ArrayList<String>()
    for (i in 0 until jsonArray.length()) {
      result.add(jsonArray.getString(i))
    }
    return result
  }

  @Throws(JSONException::class)
  @JvmStatic
  fun jsonArrayToSet(jsonArray: JSONArray): Set<String> {
    val result: MutableSet<String> = HashSet()
    for (i in 0 until jsonArray.length()) {
      result.add(jsonArray.getString(i))
    }
    return result
  }

  @JvmStatic
  fun mapToJsonStr(map: Map<String?, String?>): String {
    return if (map.isEmpty()) {
      ""
    } else {
      try {
        val jsonObject = JSONObject()
        for ((key, value) in map) {
          jsonObject.put(key, value)
        }
        jsonObject.toString()
      } catch (_e: JSONException) {
        ""
      }
    }
  }

  @JvmStatic
  fun jsonStrToMap(str: String): Map<String, String> {
    return if (str.isEmpty()) {
      HashMap()
    } else {
      try {
        val map: MutableMap<String, String> = HashMap()
        val jsonObject = JSONObject(str)
        val keys = jsonObject.keys()
        while (keys.hasNext()) {
          val key = keys.next()
          map[key] = jsonObject.getString(key)
        }
        map
      } catch (_e: JSONException) {
        HashMap()
      }
    }
  }

  @Throws(JSONException::class)
  @JvmStatic
  fun setAppEventAttributionParameters(
      params: JSONObject,
      attributionIdentifiers: AttributionIdentifiers?,
      anonymousAppDeviceGUID: String?,
      limitEventUsage: Boolean,
      context: Context
  ) {
    if (!FeatureManager.isEnabled(FeatureManager.Feature.ServiceUpdateCompliance)) {
      params.put("anon_id", anonymousAppDeviceGUID)
    }
    params.put("application_tracking_enabled", !limitEventUsage)
    params.put("advertiser_id_collection_enabled", FacebookSdk.getAdvertiserIDCollectionEnabled())
    if (attributionIdentifiers != null) {
      if (FeatureManager.isEnabled(FeatureManager.Feature.ServiceUpdateCompliance)) {
        appendAnonIdUnderCompliance(params, attributionIdentifiers, anonymousAppDeviceGUID, context)
      }
      if (attributionIdentifiers.attributionId != null) {
        if (FeatureManager.isEnabled(FeatureManager.Feature.ServiceUpdateCompliance)) {
          appendAttributionIdUnderCompliance(params, attributionIdentifiers, context)
        } else {
          params.put("attribution", attributionIdentifiers.attributionId)
        }
      }
      if (attributionIdentifiers.androidAdvertiserId != null) {
        params.put("advertiser_id", attributionIdentifiers.androidAdvertiserId)
        params.put("advertiser_tracking_enabled", !attributionIdentifiers.isTrackingLimited)
      }
      if (!attributionIdentifiers.isTrackingLimited) {
        val userData = UserDataStore.getAllHashedUserData()
        if (!userData.isEmpty()) {
          params.put("ud", userData)
        }
      }
      if (attributionIdentifiers.androidInstallerPackage != null) {
        params.put("installer_package", attributionIdentifiers.androidInstallerPackage)
      }
    }
  } /* no op */

  /**
   * Get the app version of the app, as specified by the manifest.
   *
   * Note that the function should be called after FacebookSdk is initialized. Otherwise, exception
   * FacebookSdkNotInitializedException will be thrown.
   *
   * @return The version name of this app
   */
  @JvmStatic
  fun getAppVersion(): String? {
    val context = FacebookSdk.getApplicationContext() ?: return null
    val pkgName = context.packageName
    try {
      val pi = context.packageManager.getPackageInfo(pkgName, 0) ?: return null
      return pi.versionName
    } catch (e: PackageManager.NameNotFoundException) {
      /* no op */
    }
    return null
  }

  @Throws(JSONException::class)
  @JvmStatic
  fun setAppEventExtendedDeviceInfoParameters(params: JSONObject, appContext: Context) {
    val extraInfoArray = JSONArray()
    extraInfoArray.put(EXTRA_APP_EVENTS_INFO_FORMAT_VERSION)
    refreshPeriodicExtendedDeviceInfo(appContext)

    // Application Manifest info:
    val pkgName = appContext.packageName
    var versionCode = -1
    try {
      val pi = appContext.packageManager.getPackageInfo(pkgName, 0) ?: return
      versionCode = pi.versionCode
      versionName = pi.versionName
    } catch (e: PackageManager.NameNotFoundException) {
      // Swallow
    }

    // Application Manifest info:
    extraInfoArray.put(pkgName)
    extraInfoArray.put(versionCode)
    extraInfoArray.put(versionName)

    // OS/Device info
    extraInfoArray.put(Build.VERSION.RELEASE)
    extraInfoArray.put(Build.MODEL)

    // Locale
    locale =
        try {
          appContext.resources.configuration.locale
        } catch (e: Exception) {
          Locale.getDefault()
        }
    extraInfoArray.put((locale?.language ?: "") + "_" + (locale?.country ?: ""))

    // Time zone
    extraInfoArray.put(deviceTimezoneAbbreviation)

    // Carrier
    extraInfoArray.put(carrierName)

    // Screen dimensions
    var width = 0
    var height = 0
    var density = 0.0
    try {
      val display =
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            val displayManager =
                appContext.getSystemService(Context.DISPLAY_SERVICE) as? DisplayManager
            displayManager?.getDisplay(Display.DEFAULT_DISPLAY)
          } else {
            (appContext.getSystemService(Context.WINDOW_SERVICE) as? WindowManager)?.defaultDisplay
          }
      if (display != null) {
        val displayMetrics = DisplayMetrics()
        display.getMetrics(displayMetrics)
        width = displayMetrics.widthPixels
        height = displayMetrics.heightPixels
        density = displayMetrics.density.toDouble()
      }
    } catch (e: Exception) {
      // Swallow
    }
    extraInfoArray.put(width)
    extraInfoArray.put(height)
    val df = DecimalFormat("#.##")
    extraInfoArray.put(df.format(density))

    // CPU Cores
    extraInfoArray.put(refreshBestGuessNumberOfCPUCores())

    // External Storage
    extraInfoArray.put(totalExternalStorageGB)
    extraInfoArray.put(availableExternalStorageGB)
    extraInfoArray.put(deviceTimeZoneName)
    params.put("extinfo", extraInfoArray.toString())
  }

  @JvmStatic
  fun getMethodQuietly(
      clazz: Class<*>,
      methodName: String,
      vararg parameterTypes: Class<*>?
  ): Method? {
    return try {
      clazz.getMethod(methodName, *parameterTypes)
    } catch (ex: NoSuchMethodException) {
      null
    }
  }

  @JvmStatic
  fun getMethodQuietly(
      className: String,
      methodName: String,
      vararg parameterTypes: Class<*>?
  ): Method? {
    return try {
      val clazz = Class.forName(className)
      getMethodQuietly(clazz, methodName, *parameterTypes)
    } catch (ex: ClassNotFoundException) {
      null
    }
  }

  @JvmStatic
  fun invokeMethodQuietly(receiver: Any?, method: Method, vararg args: Any?): Any? {
    return try {
      method.invoke(receiver, *args)
    } catch (ex: IllegalAccessException) {
      null
    } catch (ex: InvocationTargetException) {
      null
    }
  }

  /**
   * Returns the name of the current activity if the context is an activity, otherwise return
   * "unknown"
   */
  @JvmStatic
  fun getActivityName(context: Context?): String {
    return if (context == null) {
      "null"
    } else if (context === context.applicationContext) {
      "unknown"
    } else {
      context.javaClass.simpleName
    }
  }

  @JvmStatic
  fun getUriString(uri: Uri?): String? {
    return uri?.toString()
  }

  @JvmStatic
  fun isWebUri(uri: Uri?): Boolean {
    return uri != null &&
        ("http".equals(uri.scheme, ignoreCase = true) ||
            "https".equals(uri.scheme, ignoreCase = true) ||
            "fbstaging".equals(uri.scheme, ignoreCase = true))
  }

  @JvmStatic
  fun isContentUri(uri: Uri?): Boolean {
    return uri != null && "content".equals(uri.scheme, ignoreCase = true)
  }

  @JvmStatic
  fun isFileUri(uri: Uri?): Boolean {
    return uri != null && "file".equals(uri.scheme, ignoreCase = true)
  }

  @JvmStatic
  fun getContentSize(contentUri: Uri): Long {
    var cursor: Cursor? = null
    return try {
      cursor =
          FacebookSdk.getApplicationContext()
              .contentResolver
              .query(contentUri, null, null, null, null)
      if (cursor === null) {
        return 0L
      }
      val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
      cursor.moveToFirst()
      cursor.getLong(sizeIndex)
    } finally {
      cursor?.close()
    }
  }

  @JvmStatic
  fun getBundleLongAsDate(bundle: Bundle?, key: String?, dateBase: Date): Date? {
    if (bundle == null) {
      return null
    }
    val secondsFromBase: Long
    val secondsObject = bundle[key]
    if (secondsObject is Long) {
      secondsFromBase = secondsObject
    } else if (secondsObject is String) {
      try {
        secondsFromBase = secondsObject.toLong()
      } catch (e: NumberFormatException) {
        return null
      }
    } else {
      return null
    }
    return if (secondsFromBase == 0L) {
      Date(Long.MAX_VALUE)
    } else {
      Date(dateBase.time + secondsFromBase * 1000L)
    }
  }

  @JvmStatic
  fun writeStringMapToParcel(parcel: Parcel, map: Map<String?, String?>?) {
    if (map == null) {
      // 0 is for empty map, -1 to indicate null
      parcel.writeInt(-1)
    } else {
      parcel.writeInt(map.size)
      for ((key, value) in map) {
        parcel.writeString(key)
        parcel.writeString(value)
      }
    }
  }

  @JvmStatic
  fun readStringMapFromParcel(parcel: Parcel): Map<String?, String?>? {
    val size = parcel.readInt()
    if (size < 0) {
      return null
    }
    val map: MutableMap<String?, String?> = HashMap()
    for (i in 0 until size) {
      map[parcel.readString()] = parcel.readString()
    }
    return map
  }

  @JvmStatic
  fun writeNonnullStringMapToParcel(parcel: Parcel, map: Map<String, String>?) {
    if (map == null) {
      // 0 is for empty map, -1 to indicate null
      parcel.writeInt(-1)
    } else {
      parcel.writeInt(map.size)
      for ((key, value) in map) {
        parcel.writeString(key)
        parcel.writeString(value)
      }
    }
  }

  @JvmStatic
  fun readNonnullStringMapFromParcel(parcel: Parcel): Map<String, String>? {
    val size = parcel.readInt()
    if (size < 0) {
      return null
    }
    val map: MutableMap<String, String> = HashMap()
    for (i in 0 until size) {
      val key = parcel.readString()
      val value = parcel.readString()
      if (key != null && value != null) {
        map[key] = value
      }
    }
    return map
  }

  @JvmStatic
  fun isCurrentAccessToken(token: AccessToken?): Boolean {
    return token != null && token == AccessToken.getCurrentAccessToken()
  }

  @JvmStatic
  fun getGraphDomainFromTokenDomain(tokenGraphDomain: String?): String {
    val facebookDomain = FacebookSdk.getFacebookDomain()
    if (tokenGraphDomain == null) {
      return facebookDomain
    }
    return when (tokenGraphDomain) {
      FacebookSdk.GAMING -> facebookDomain.replace(FacebookSdk.FACEBOOK_COM, FacebookSdk.FB_GG)
      FacebookSdk.INSTAGRAM ->
          facebookDomain.replace(FacebookSdk.FACEBOOK_COM, FacebookSdk.INSTAGRAM_COM)
      else -> facebookDomain
    }
  }

  @JvmStatic
  fun getGraphMeRequestWithCacheAsync(
      accessToken: String,
      callback: GraphMeRequestWithCacheCallback
  ) {
    val cachedValue = getProfileInformation(accessToken)
    if (cachedValue != null) {
      callback.onSuccess(cachedValue)
      return
    }
    val graphCallback =
        GraphRequest.Callback { response ->
          if (response.error != null) {
            callback.onFailure(response.error.exception)
          } else {
            putProfileInformation(accessToken, checkNotNull(response.jsonObject))
            callback.onSuccess(response.jsonObject)
          }
        }
    val graphRequest = getGraphMeRequestWithCache(accessToken)
    graphRequest.callback = graphCallback
    graphRequest.executeAsync()
  }

  @JvmStatic
  fun awaitGetGraphMeRequestWithCache(accessToken: String): JSONObject? {
    val cachedValue = getProfileInformation(accessToken)
    if (cachedValue != null) {
      return cachedValue
    }
    val graphRequest = getGraphMeRequestWithCache(accessToken)
    val response = graphRequest.executeAndWait()
    return if (response.error != null) {
      null
    } else response.jsonObject
  }

  private fun getGraphMeRequestWithCache(accessToken: String): GraphRequest {
    val parameters = Bundle()
    parameters.putString(
        GraphRequest.FIELDS_PARAM,
        getProfileFieldsForGraphDomain(getCurrentTokenDomainWithDefault()))
    parameters.putString("access_token", accessToken)
    val request = GraphRequest.newMeRequest(null, null)
    request.parameters = parameters
    request.httpMethod = HttpMethod.GET
    return request
  }

  private fun getProfileFieldsForGraphDomain(graphDomain: String?): String {
    return if (graphDomain == FacebookSdk.INSTAGRAM) {
      INSTAGRAM_PROFILE_FIELDS
    } else FACEBOOK_PROFILE_FIELDS
  }

  private fun getCurrentTokenDomainWithDefault(): String? {
    val accessToken = AccessToken.getCurrentAccessToken()
    return if (accessToken != null && accessToken.graphDomain != null) {
      accessToken.graphDomain
    } else AccessToken.DEFAULT_GRAPH_DOMAIN
  }

  /**
   * Return our best guess at the available number of cores. Will always return at least 1.
   *
   * @return The minimum number of CPU cores
   */
  private fun refreshBestGuessNumberOfCPUCores(): Int {
    // If we have calculated this before, return that value
    if (numCPUCores > 0) {
      return numCPUCores
    }

    // Enumerate all available CPU files and try to count the number of CPU cores.
    try {
      val cpuDir = File("/sys/devices/system/cpu/")
      val cpuFiles = cpuDir.listFiles { dir, fileName -> Pattern.matches("cpu[0-9]+", fileName) }
      if (cpuFiles != null) {
        numCPUCores = cpuFiles.size
      }
    } catch (e: Exception) {}

    // If enumerating and counting the CPU cores fails, use the runtime. Fallback to 1 if
    // that returns bogus values.
    if (numCPUCores <= 0) {
      numCPUCores = Math.max(Runtime.getRuntime().availableProcessors(), 1)
    }
    return numCPUCores
  }

  private fun refreshPeriodicExtendedDeviceInfo(appContext: Context) {
    if (timestampOfLastCheck == -1L ||
        System.currentTimeMillis() - timestampOfLastCheck >=
            REFRESH_TIME_FOR_EXTENDED_DEVICE_INFO_MILLIS) {
      timestampOfLastCheck = System.currentTimeMillis()
      refreshTimezone()
      refreshCarrierName(appContext)
      refreshTotalExternalStorage()
      refreshAvailableExternalStorage()
    }
  }

  private fun refreshTimezone() {
    try {
      val tz = TimeZone.getDefault()
      deviceTimezoneAbbreviation = tz.getDisplayName(tz.inDaylightTime(Date()), TimeZone.SHORT)
      deviceTimeZoneName = tz.id
    } catch (e: AssertionError) {
      // Workaround for a bug in Android that can cause crashes on Android 8.0 and 8.1
    } catch (e: Exception) {}
  }

  /**
   * Get and cache the carrier name since this won't change during the lifetime of the app.
   *
   * @return The carrier name
   */
  private fun refreshCarrierName(appContext: Context) {
    if (carrierName == NO_CARRIER) {
      try {
        val telephonyManager =
            appContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        carrierName = telephonyManager.networkOperatorName
      } catch (e: Exception) {}
    }
  }

  /** @return whether there is external storage: */
  private fun externalStorageExists(): Boolean {
    return Environment.MEDIA_MOUNTED == Environment.getExternalStorageState()
  }

  // getAvailableBlocks/getBlockSize deprecated but required pre-API v18
  private fun refreshAvailableExternalStorage() {
    try {
      if (externalStorageExists()) {
        val path = Environment.getExternalStorageDirectory()
        val stat = StatFs(path.path)
        availableExternalStorageGB = stat.availableBlocks.toLong() * stat.blockSize.toLong()
      }
      availableExternalStorageGB = convertBytesToGB(availableExternalStorageGB.toDouble())
    } catch (e: Exception) {
      // Swallow
    }
  }

  // getAvailableBlocks/getBlockSize deprecated but required pre-API v18
  private fun refreshTotalExternalStorage() {
    try {
      if (externalStorageExists()) {
        val path = Environment.getExternalStorageDirectory()
        val stat = StatFs(path.path)
        totalExternalStorageGB = stat.blockCount.toLong() * stat.blockSize.toLong()
      }
      totalExternalStorageGB = convertBytesToGB(totalExternalStorageGB.toDouble())
    } catch (e: Exception) {
      // Swallow
    }
  }

  private fun convertBytesToGB(bytes: Double): Long {
    return Math.round(bytes / (1024.0 * 1024.0 * 1024.0))
  }

  private fun appendAnonIdUnderCompliance(
      params: JSONObject,
      attributionIdentifiers: AttributionIdentifiers,
      anonymousAppDeviceGUID: String?,
      context: Context
  ) {
    // TODO: change to Build.VERSION_CODES.S after we start building with API 31
    if (Build.VERSION.SDK_INT >= 31 && isGooglePlayServicesAvailable(context)) {
      if (!attributionIdentifiers.isTrackingLimited) {
        params.put("anon_id", anonymousAppDeviceGUID)
      }
    } else {
      params.put("anon_id", anonymousAppDeviceGUID)
    }
  }

  private fun appendAttributionIdUnderCompliance(
      params: JSONObject,
      attributionIdentifiers: AttributionIdentifiers,
      context: Context
  ) {
    // TODO: change to Build.VERSION_CODES.S after we start building with API 31
    if (Build.VERSION.SDK_INT >= 31 && isGooglePlayServicesAvailable(context)) {
      if (!attributionIdentifiers.isTrackingLimited) {
        params.put("attribution", attributionIdentifiers.attributionId)
      }
    } else {
      params.put("attribution", attributionIdentifiers.attributionId)
    }
  }

  private fun isGooglePlayServicesAvailable(context: Context): Boolean {
    val method =
        getMethodQuietly(
            "com.google.android.gms.common.GooglePlayServicesUtil",
            "isGooglePlayServicesAvailable",
            Context::class.java)
            ?: return false
    val connectionResult = invokeMethodQuietly(null, method, context)
    return !(connectionResult !is Int || connectionResult != 0)
  }

  @JvmStatic
  fun generateRandomString(length: Int): String {
    val r = Random()
    return BigInteger(length * 5, r).toString(32)
  }

  /*
   * There is a bug on Android O that excludes the dialog's view hierarchy from the
   * ViewStructure used by Autofill because the window token is lost when the dialog
   * is resized, hence the token needs to be saved dialog is attached to a window and restored
   * when the dialog attributes change after it is resized.
   */
  @JvmStatic
  fun mustFixWindowParamsForAutofill(context: Context): Boolean {
    // TODO: once this bug is fixed on Android P, checks for version here as well
    return isAutofillAvailable(context)
  }

  @JvmStatic
  fun isAutofillAvailable(context: Context): Boolean {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
      // Autofill Framework is only available on Android O and higher
      return false
    }
    val afm = context.getSystemService(AutofillManager::class.java)
    // Returns whether autofill is supported by device or and enabled for current user.
    return afm != null && afm.isAutofillSupported && afm.isEnabled
  }

  /**
   * Determines whether the application is running on Chrome OS or not
   *
   * @param context the [Context]
   * @return true if the application is running on Chrome OS; false otherwise.
   */
  @JvmStatic
  fun isChromeOS(context: Context): Boolean {
    // TODO: (T29986208) android.os.Build.VERSION_CODES.O_MR1 and PackageManager.FEATURE_PC
    val isChromeOS: Boolean
    isChromeOS =
        if (Build.VERSION.SDK_INT >= 27) {
          context.packageManager.hasSystemFeature("android.hardware.type.pc")
        } else {
          Build.DEVICE != null && Build.DEVICE.matches(Regex(ARC_DEVICE_PATTERN))
        }
    return isChromeOS
  }

  val resourceLocale: Locale?
    @JvmStatic
    get() =
        try {
          FacebookSdk.getApplicationContext().resources.configuration.locale
        } catch (e: Exception) {
          null
        }
  val currentLocale: Locale
    @JvmStatic
    get() {
      val locale = resourceLocale
      return locale ?: Locale.getDefault()
    }

  @JvmStatic
  fun runOnNonUiThread(runnable: Runnable?) {
    try {
      FacebookSdk.getExecutor().execute(runnable)
    } catch (e: Exception) {
      /*no op*/
    }
  }

  @JvmStatic
  fun getAppName(context: Context): String {
    return try {
      val applicationName = FacebookSdk.getApplicationName()
      if (applicationName != null) {
        return applicationName
      }
      val applicationInfo = context.applicationInfo
      val stringId = applicationInfo.labelRes
      if (stringId == 0) applicationInfo.nonLocalizedLabel.toString()
      else context.getString(stringId)
    } catch (e: Exception) {
      ""
    }
  }

  /* no op */
  @JvmStatic
  val isAutoAppLinkSetup: Boolean
    get() {
      try {
        val intent = Intent(Intent.ACTION_VIEW)
        intent.data = Uri.parse(String.format("fb%s://applinks", FacebookSdk.getApplicationId()))
        val ctx = FacebookSdk.getApplicationContext()
        val packageManager = ctx.packageManager
        val packageName = ctx.packageName
        val activities =
            packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
        for (info in activities) {
          if (packageName == info.activityInfo.packageName) {
            return true
          }
        }
      } catch (e: Exception) {
        /* no op */
      }
      return false
    }

  val dataProcessingOptions: JSONObject?
    @JvmStatic
    @AutoHandleExceptions
    get() {
      val context = FacebookSdk.getApplicationContext()
      val data =
          context
              .getSharedPreferences(
                  FacebookSdk.DATA_PROCESSING_OPTIONS_PREFERENCES, Context.MODE_PRIVATE)
              .getString(FacebookSdk.DATA_PROCESSION_OPTIONS, null)
      if (data != null) {
        try {
          return JSONObject(data)
        } catch (e: JSONException) {}
      }
      return null
    }

  val isDataProcessingRestricted: Boolean
    @JvmStatic
    @AutoHandleExceptions
    get() {
      val dataProcessingOptions = dataProcessingOptions ?: return false
      try {
        val options = dataProcessingOptions.getJSONArray("data_processing_options")
        for (i in 0 until options.length()) {
          val option = options.getString(i).toLowerCase()
          if (option == "ldu") {
            return true
          }
        }
      } catch (e: Exception) {}
      return false
    }

  interface GraphMeRequestWithCacheCallback {
    fun onSuccess(userInfo: JSONObject?)
    fun onFailure(error: FacebookException?)
  }
}


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

package com.facebook.internal

import android.Manifest
import android.annotation.SuppressLint
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.net.Uri
import android.os.Looper
import android.util.Log
import com.facebook.FacebookException
import com.facebook.FacebookSdk
import com.facebook.FacebookSdkNotInitializedException

/**
 * com.facebook.internal is solely for the use of other packages within the Facebook SDK for
 * Android. Use of any of the classes in this package is unsupported, and they may be modified or
 * removed without warning at any time.
 */
object Validate {
  private val TAG = Validate::class.java.name
  private const val NO_INTERNET_PERMISSION_REASON =
      "No internet permissions granted for the app, please add " +
          "<uses-permission android:name=\"android.permission.INTERNET\" /> " +
          "to your AndroidManifest.xml."
  private const val FACEBOOK_ACTIVITY_NOT_FOUND_REASON =
      "FacebookActivity is not declared in the AndroidManifest.xml. If you are using the " +
          "facebook-common module or dependent modules please add " +
          "com.facebook.FacebookActivity to your AndroidManifest.xml file. See " +
          "https://developers.facebook.com/docs/android/getting-started for more info."
  private const val CONTENT_PROVIDER_NOT_FOUND_REASON =
      "A ContentProvider for this app was not set up in the AndroidManifest.xml, please " +
          "add %s as a provider to your AndroidManifest.xml file. See " +
          "https://developers.facebook.com/docs/sharing/android for more info."
  private const val CONTENT_PROVIDER_BASE = "com.facebook.app.FacebookContentProvider"
  const val CUSTOM_TAB_REDIRECT_URI_PREFIX = "fbconnect://cct."

  @JvmStatic
  fun notNull(arg: Any?, name: String) {
    if (arg == null) {
      throw NullPointerException("Argument '$name' cannot be null")
    }
  }

  @JvmStatic
  fun <T> notEmpty(container: Collection<T>, name: String) {
    require(!container.isEmpty()) { "Container '$name' cannot be empty" }
  }

  @JvmStatic
  fun <T> containsNoNulls(container: Collection<T>, name: String) {
    for (item in container) {
      if (item == null) {
        throw NullPointerException("Container '$name' cannot contain null values")
      }
    }
  }

  @JvmStatic
  fun containsNoNullOrEmpty(container: Collection<String?>, name: String) {
    for (item in container) {
      if (item == null) {
        throw NullPointerException("Container '$name' cannot contain null values")
      }
      require(item.isNotEmpty()) { "Container '$name' cannot contain empty values" }
    }
  }

  @JvmStatic
  fun <T> notEmptyAndContainsNoNulls(container: Collection<T>, name: String) {
    containsNoNulls(container, name)
    notEmpty(container, name)
  }

  @JvmStatic
  fun runningOnUiThread() {
    if (Looper.getMainLooper() != Looper.myLooper()) {
      throw FacebookException("This method should be called from the UI thread")
    }
  }

  @JvmStatic
  fun notNullOrEmpty(arg: String?, name: String): String {
    require(arg != null && arg.isNotEmpty()) { "Argument '$name' cannot be null or empty" }
    return arg
  }

  @JvmStatic
  fun notEmpty(arg: String, name: String) {
    require(arg.isNotEmpty()) { "Argument '$name' cannot be empty" }
  }

  @JvmStatic
  fun oneOf(arg: Any?, name: String, vararg values: Any?) {
    for (value in values) {
      if (value == arg) {
        return
      }
    }
    throw IllegalArgumentException("Argument '$name' was not one of the allowed values")
  }

  @JvmStatic
  fun sdkInitialized() {
    if (!FacebookSdk.isInitialized()) {
      throw FacebookSdkNotInitializedException(
          "The SDK has not been initialized, make sure to call " +
              "FacebookSdk.sdkInitialize() first.")
    }
  }

  @JvmStatic
  fun hasAppID(): String {
    return checkNotNull(FacebookSdk.getApplicationId()) {
      "No App ID found, please set the App ID."
    }
  }

  @JvmStatic
  fun hasClientToken(): String {
    return checkNotNull(FacebookSdk.getClientToken()) {
      "No Client Token found, please set the Client Token. Please follow " +
          "https://developers.facebook.com/docs/android/getting-started/#client-access-token " +
          "to get the token and fill it in AndroidManifest.xml"
    }
  }

  @JvmStatic
  fun hasInternetPermissions(context: Context) {
    hasInternetPermissions(context, true)
  }

  @JvmStatic
  fun hasInternetPermissions(context: Context, shouldThrow: Boolean) {
    if (context.checkCallingOrSelfPermission(Manifest.permission.INTERNET) ==
        PackageManager.PERMISSION_DENIED) {
      check(!shouldThrow) { NO_INTERNET_PERMISSION_REASON }
      Log.w(TAG, NO_INTERNET_PERMISSION_REASON)
    }
  }

  @JvmStatic
  fun hasWiFiPermission(context: Context): Boolean {
    return hasPermission(context, Manifest.permission.ACCESS_WIFI_STATE)
  }

  @JvmStatic
  fun hasChangeWifiStatePermission(context: Context): Boolean {
    return hasPermission(context, Manifest.permission.CHANGE_WIFI_STATE)
  }

  @JvmStatic
  fun hasLocationPermission(context: Context): Boolean {
    return hasPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) ||
        hasPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
  }

  @JvmStatic
  fun hasBluetoothPermission(context: Context): Boolean {
    return hasPermission(context, Manifest.permission.BLUETOOTH) &&
        hasPermission(context, Manifest.permission.BLUETOOTH_ADMIN)
  }

  @JvmStatic
  fun hasPermission(context: Context, permission: String): Boolean {
    return context.checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED
  }

  @JvmStatic
  fun hasFacebookActivity(context: Context) {
    hasFacebookActivity(context, true)
  }

  @SuppressLint("WrongConstant")
  @JvmStatic
  fun hasFacebookActivity(context: Context, shouldThrow: Boolean) {
    val pm = context.packageManager
    var activityInfo: ActivityInfo? = null
    if (pm != null) {
      val componentName = ComponentName(context, "com.facebook.FacebookActivity")
      try {
        activityInfo = pm.getActivityInfo(componentName, PackageManager.GET_ACTIVITIES)
      } catch (e: PackageManager.NameNotFoundException) {
        // ignore
      }
    }
    if (activityInfo == null) {
      check(!shouldThrow) { FACEBOOK_ACTIVITY_NOT_FOUND_REASON }
      Log.w(TAG, FACEBOOK_ACTIVITY_NOT_FOUND_REASON)
    }
  }

  @JvmStatic
  fun hasCustomTabRedirectActivity(context: Context, redirectURI: String): Boolean {
    val pm = context.packageManager
    var infos: List<ResolveInfo>? = null
    if (pm != null) {
      val intent = Intent()
      intent.action = Intent.ACTION_VIEW
      intent.addCategory(Intent.CATEGORY_DEFAULT)
      intent.addCategory(Intent.CATEGORY_BROWSABLE)
      intent.data = Uri.parse(redirectURI)
      infos = pm.queryIntentActivities(intent, PackageManager.GET_RESOLVED_FILTER)
    }
    var hasActivity = false
    if (infos != null) {
      for (info in infos) {
        val activityInfo = info.activityInfo
        if (activityInfo.name == "com.facebook.CustomTabActivity" &&
            activityInfo.packageName == context.packageName) {
          hasActivity = true
        } else {
          // another application is listening for this url scheme, don't open
          // Custom Tab for security reasons
          return false
        }
      }
    }
    return hasActivity
  }

  @JvmStatic
  fun hasContentProvider(context: Context) {
    val appId = hasAppID()
    val pm = context.packageManager
    if (pm != null) {
      val providerName = CONTENT_PROVIDER_BASE + appId
      checkNotNull(pm.resolveContentProvider(providerName, 0)) {
        String.format(CONTENT_PROVIDER_NOT_FOUND_REASON, providerName)
      }
    }
  }
}