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

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
import android.util.AttributeSet
import android.util.Log
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import com.facebook.AccessToken
import com.facebook.FacebookException
import com.facebook.LoggingBehavior
import com.facebook.Profile
import com.facebook.ProfileTracker
import com.facebook.internal.ImageDownloader
import com.facebook.internal.ImageRequest
import com.facebook.internal.ImageResponse
import com.facebook.internal.Logger
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import com.facebook.login.R

/**
 * View that displays the profile photo of a supplied profile ID, while conforming to user specified
 * dimensions.
 */
class ProfilePictureView : FrameLayout {
  /**
   * Callback interface that will be called when a network or other error is encountered while
   * retrieving profile pictures.
   */
  interface OnErrorListener {
    /**
     * Called when a network or other error is encountered.
     *
     * @param error a FacebookException representing the error that was encountered.
     */
    fun onError(error: FacebookException)
  }

  private val image = ImageView(context)

  private var queryHeight = ImageRequest.UNSPECIFIED_DIMENSION
  private var queryWidth = ImageRequest.UNSPECIFIED_DIMENSION
  private var imageContents: Bitmap? = null
  private var lastRequest: ImageRequest? = null
  private var customizedDefaultProfilePicture: Bitmap? = null
  private var profileTracker: ProfileTracker? = null

  /** Profile Id for the current profile photo */
  var profileId: String? = null
    set(value) {
      var force = false
      if (field.isNullOrEmpty() || !field.equals(value, ignoreCase = true)) {
        // Clear out the old profilePicture before requesting for the new one.
        setBlankProfilePicture()
        force = true
      }
      field = value
      refreshImage(force)
    }

  /** Indicates whether the cropped version of the profile photo has been chosen */
  var isCropped = IS_CROPPED_DEFAULT_VALUE
    set(value) {
      field = value
      // No need to force the refresh since we will catch the change in required dimensions
      refreshImage(false)
    }

  /**
   * OnErrorListener for this instance of ProfilePictureView to call into when certain
   * errors occur.
   */
  var onErrorListener: OnErrorListener? = null

  /** Preset size of this profile photo (SMALL, NORMAL or LARGE) */
  var presetSize = CUSTOM
    set(value) {
      when (value) {
        SMALL, NORMAL, LARGE, CUSTOM -> field = value
        else -> throw IllegalArgumentException("Must use a predefined preset size")
      }
      requestLayout()
    }

  /** Indicates whether the ProfilePictureView should subscribe to Profile updates */
  var shouldUpdateOnProfileChange: Boolean
    get() = profileTracker?.isTracking ?: false
    set(value) {
      if (value) {
        profileTracker?.startTracking()
      } else {
        profileTracker?.stopTracking()
      }
    }

  /**
   * Constructor
   *
   * @param context Context for this View
   */
  constructor(context: Context) : super(context) {
    initialize()
  }

  /**
   * Constructor
   *
   * @param context Context for this View
   * @param attrs AttributeSet for this View. The attribute 'preset_size' is processed here
   */
  constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
    initialize()
    parseAttributes(attrs)
  }

  /**
   * Constructor
   *
   * @param context Context for this View
   * @param attrs AttributeSet for this View. The attribute 'preset_size' is processed here
   * @param defStyle Default style for this View
   */
  constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
    initialize()
    parseAttributes(attrs)
  }

  @AutoHandleExceptions
  private fun initialize() {
    // We only want our ImageView in here. Nothing else is permitted
    removeAllViews()
    val imageLayout = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
    image.layoutParams = imageLayout

    // We want to prevent up-scaling the image, but still have it fit within
    // the layout bounds as best as possible.
    image.scaleType = ImageView.ScaleType.CENTER_INSIDE
    addView(image)
    profileTracker = object : ProfileTracker() {
      override fun onCurrentProfileChanged(oldProfile: Profile?, currentProfile: Profile?) {
        profileId = currentProfile?.id
        refreshImage(true)
      }
    }
  }

  /**
   * The ProfilePictureView will display the provided image while the specified profile is being
   * loaded, or if the specified profile is not available.
   *
   * @param inputBitmap The bitmap to render until the actual profile is loaded.
   */
  fun setDefaultProfilePicture(inputBitmap: Bitmap?) {
    customizedDefaultProfilePicture = inputBitmap
  }

  /**
   * Overriding onMeasure to handle the case where WRAP_CONTENT might be specified in the layout.
   * Since we don't know the dimensions of the profile photo, we need to handle this case
   * specifically.
   *
   * The approach is to default to a NORMAL sized amount of space in the case that a preset size
   * is not specified. This logic is applied to both width and height
   */
  override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    var widthMeasure = widthMeasureSpec
    var heightMeasure = heightMeasureSpec
    val params = layoutParams
    var customMeasure = false
    var newHeight = MeasureSpec.getSize(heightMeasure)
    var newWidth = MeasureSpec.getSize(widthMeasure)
    if (MeasureSpec.getMode(heightMeasure) != MeasureSpec.EXACTLY
        && params.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
      newHeight = getPresetSizeInPixels(true) // Default to a preset size
      heightMeasure = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY)
      customMeasure = true
    }
    if (MeasureSpec.getMode(widthMeasure) != MeasureSpec.EXACTLY
        && params.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
      newWidth = getPresetSizeInPixels(true) // Default to a preset size
      widthMeasure = MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY)
      customMeasure = true
    }
    if (customMeasure) {
      // Since we are providing custom dimensions, we need to handle the measure
      // phase from here
      setMeasuredDimension(newWidth, newHeight)
      measureChildren(widthMeasure, heightMeasure)
    } else {
      // Rely on FrameLayout to do the right thing
      super.onMeasure(widthMeasure, heightMeasure)
    }
  }

  /**
   * In addition to calling super.Layout(), we also attempt to get a new image that is properly
   * sized for the layout dimensions
   */
  override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
    super.onLayout(changed, left, top, right, bottom)

    // See if the image needs redrawing
    refreshImage(false)
  }

  /**
   * Some of the current state is returned as a Bundle to allow quick restoration of the
   * ProfilePictureView object in scenarios like orientation changes.
   *
   * @return a Parcelable containing the current state
   */
  override fun onSaveInstanceState(): Parcelable {
    val superState = super.onSaveInstanceState()
    val instanceState = Bundle()
    instanceState.putParcelable(SUPER_STATE_KEY, superState)
    instanceState.putString(PROFILE_ID_KEY, profileId)
    instanceState.putInt(PRESET_SIZE_KEY, this.presetSize)
    instanceState.putBoolean(IS_CROPPED_KEY, isCropped)
    instanceState.putInt(BITMAP_WIDTH_KEY, queryWidth)
    instanceState.putInt(BITMAP_HEIGHT_KEY, queryHeight)
    instanceState.putBoolean(PENDING_REFRESH_KEY, lastRequest != null)
    return instanceState
  }

  /**
   * If the passed in state is a Bundle, an attempt is made to restore from it.
   *
   * @param state a Parcelable containing the current state
   */
  override fun onRestoreInstanceState(state: Parcelable) {
    if (state.javaClass != Bundle::class.java) {
      super.onRestoreInstanceState(state)
    } else {
      val instanceState = state as Bundle
      super.onRestoreInstanceState(instanceState.getParcelable(SUPER_STATE_KEY))
      profileId = instanceState.getString(PROFILE_ID_KEY)
      this.presetSize = instanceState.getInt(PRESET_SIZE_KEY)
      isCropped = instanceState.getBoolean(IS_CROPPED_KEY)
      queryWidth = instanceState.getInt(BITMAP_WIDTH_KEY)
      queryHeight = instanceState.getInt(BITMAP_HEIGHT_KEY)
      refreshImage(true)
    }
  }

  override fun onDetachedFromWindow() {
    super.onDetachedFromWindow()
    // Null out lastRequest. This way, when the response is returned, we can ascertain
    // that the view is detached and hence should not attempt to update its contents.
    lastRequest = null
  }

  @AutoHandleExceptions
  private fun parseAttributes(attrs: AttributeSet) {
    val a = context.obtainStyledAttributes(attrs, R.styleable.com_facebook_profile_picture_view)
    this.presetSize = a.getInt(R.styleable.com_facebook_profile_picture_view_com_facebook_preset_size, CUSTOM)
    isCropped = a.getBoolean(
        R.styleable.com_facebook_profile_picture_view_com_facebook_is_cropped,
        IS_CROPPED_DEFAULT_VALUE
    )
    a.recycle()
  }

  @AutoHandleExceptions
  private fun refreshImage(force: Boolean) {
    val changed = updateImageQueryParameters()
    // Note: do not use Utility.isNullOrEmpty here as this will cause the Eclipse
    // Graphical Layout editor to fail in some cases
    val profileId = this.profileId
    if (profileId == null || profileId.isEmpty() || isUnspecifiedDimensions()) {
      setBlankProfilePicture()
    } else if (changed || force) {
      sendImageRequest(true)
    }
  }

  private fun isUnspecifiedDimensions(): Boolean {
    return queryWidth == ImageRequest.UNSPECIFIED_DIMENSION
        && queryHeight == ImageRequest.UNSPECIFIED_DIMENSION
  }

  @AutoHandleExceptions
  private fun setBlankProfilePicture() {
    // If we have a pending image download request cancel it
    lastRequest?.let { ImageDownloader.cancelRequest(it) }
    val customizedPicture = customizedDefaultProfilePicture
    if (customizedPicture == null) {
      val blankImageResource = if (isCropped)
        R.drawable.com_facebook_profile_picture_blank_square
      else
        R.drawable.com_facebook_profile_picture_blank_portrait
      setImageBitmap(BitmapFactory.decodeResource(resources, blankImageResource))
    } else {
      // Update profile image dimensions.
      updateImageQueryParameters()
      // Resize inputBitmap to new dimensions of queryWidth and queryHeight.
      val scaledBitmap = Bitmap.createScaledBitmap(customizedPicture, queryWidth, queryHeight, false)
      setImageBitmap(scaledBitmap)
    }
  }

  @AutoHandleExceptions
  private fun setImageBitmap(imageBitmap: Bitmap?) {
    imageBitmap?.let {
      imageContents = it // Hold for save-restore cycles
      image.setImageBitmap(it)
    }
  }

  @AutoHandleExceptions
  private fun sendImageRequest(allowCachedResponse: Boolean) {
    val accessToken = if (AccessToken.isCurrentAccessTokenActive())
      AccessToken.getCurrentAccessToken()?.token ?: ""
    else
      ""
    val profilePictureUri = getProfilePictureUri(accessToken)
    val request = ImageRequest.Builder(context, profilePictureUri)
        .setAllowCachedRedirects(allowCachedResponse)
        .setCallerTag(this)
        .setCallback { response -> processResponse(response) }
        .build()

    // Make sure to cancel the old request before sending the new one to prevent
    // accidental cancellation of the new request. This could happen if the URL and
    // caller tag stayed the same.
    lastRequest?.let { ImageDownloader.cancelRequest(it) }
    lastRequest = request
    ImageDownloader.downloadAsync(request)
  }

  private fun getProfilePictureUri(accessToken: String): Uri {
    val currentProfile = Profile.getCurrentProfile()
    return if (currentProfile != null && AccessToken.isLoggedInWithInstagram())
      currentProfile.getProfilePictureUri(queryWidth, queryHeight)
    else
      ImageRequest.getProfilePictureUri(profileId, queryWidth, queryHeight, accessToken)
  }

  @AutoHandleExceptions
  private fun processResponse(response: ImageResponse?) {
    // First check if the response is for the right request. We may have:
    // 1. Sent a new request, thus super-ceding this one.
    // 2. Detached this view, in which case the response should be discarded.
    if (response == null || response.request != lastRequest) {
      return
    }

    lastRequest = null
    val responseImage = response.bitmap
    val error = response.error
    if (error != null) {
      val listener = onErrorListener
      if (listener != null) {
        listener.onError(
            FacebookException("Error in downloading profile picture for profileId: $profileId", error)
        )
      } else {
        Logger.log(LoggingBehavior.REQUESTS, Log.ERROR, TAG, error.toString())
      }
    } else {
      responseImage?.let {
        setImageBitmap(it)
        if (response.isCachedRedirect) {
          sendImageRequest(false)
        }
      }
    }
  }

  @AutoHandleExceptions
  private fun updateImageQueryParameters(): Boolean {
    var newHeightPx = height
    var newWidthPx = width
    if (newWidthPx < MIN_SIZE || newHeightPx < MIN_SIZE) {
      // Not enough space laid out for this View yet. Or something else is awry.
      return false
    }
    val presetSize = getPresetSizeInPixels(false)
    if (presetSize != ImageRequest.UNSPECIFIED_DIMENSION) {
      newWidthPx = presetSize
      newHeightPx = presetSize
    }

    // The cropped version is square
    // If full version is desired, then only one dimension is required.
    if (newWidthPx <= newHeightPx) {
      newHeightPx = if (isCropped) newWidthPx else ImageRequest.UNSPECIFIED_DIMENSION
    } else {
      newWidthPx = if (isCropped) newHeightPx else ImageRequest.UNSPECIFIED_DIMENSION
    }
    val changed = newWidthPx != queryWidth || newHeightPx != queryHeight
    queryWidth = newWidthPx
    queryHeight = newHeightPx
    return changed
  }

  @AutoHandleExceptions
  private fun getPresetSizeInPixels(forcePreset: Boolean): Int {
    if (this.presetSize == CUSTOM && !forcePreset) {
      return ImageRequest.UNSPECIFIED_DIMENSION
    }
    val dimensionId: Int = when (this.presetSize) {
      SMALL -> R.dimen.com_facebook_profilepictureview_preset_size_small
      NORMAL -> R.dimen.com_facebook_profilepictureview_preset_size_normal
      LARGE -> R.dimen.com_facebook_profilepictureview_preset_size_large
      CUSTOM -> R.dimen.com_facebook_profilepictureview_preset_size_normal
      else -> return ImageRequest.UNSPECIFIED_DIMENSION
    }
    return resources.getDimensionPixelSize(dimensionId)
  }

  companion object {
    /** Tag used when logging calls are made by ProfilePictureView  */
    val TAG: String = ProfilePictureView::class.java.simpleName

    /**
     * Indicates that the specific size of the View will be set via layout params. ProfilePictureView
     * will default to NORMAL X NORMAL, if the layout params set on this instance do not have a fixed
     * size. Used in calls to setPresetSize() and getPresetSize(). Corresponds with the preset_size
     * Xml attribute that can be set on ProfilePictureView.
     */
    const val CUSTOM = -1

    /**
     * Indicates that the profile image should fit in a SMALL X SMALL space, regardless of whether the
     * cropped or un-cropped version is chosen. Used in calls to setPresetSize() and getPresetSize().
     * Corresponds with the preset_size Xml attribute that can be set on ProfilePictureView.
     */
    const val SMALL = -2

    /**
     * Indicates that the profile image should fit in a NORMAL X NORMAL space, regardless of whether
     * the cropped or un-cropped version is chosen. Used in calls to setPresetSize() and
     * getPresetSize(). Corresponds with the preset_size Xml attribute that can be set on
     * ProfilePictureView.
     */
    const val NORMAL = -3

    /**
     * Indicates that the profile image should fit in a LARGE X LARGE space, regardless of whether the
     * cropped or un-cropped version is chosen. Used in calls to setPresetSize() and getPresetSize().
     * Corresponds with the preset_size Xml attribute that can be set on ProfilePictureView.
     */
    const val LARGE = -4
    private const val MIN_SIZE = 1
    private const val IS_CROPPED_DEFAULT_VALUE = true
    private const val SUPER_STATE_KEY = "ProfilePictureView_superState"
    private const val PROFILE_ID_KEY = "ProfilePictureView_profileId"
    private const val PRESET_SIZE_KEY = "ProfilePictureView_presetSize"
    private const val IS_CROPPED_KEY = "ProfilePictureView_isCropped"
    private const val BITMAP_KEY = "ProfilePictureView_bitmap"
    private const val BITMAP_WIDTH_KEY = "ProfilePictureView_width"
    private const val BITMAP_HEIGHT_KEY = "ProfilePictureView_height"
    private const val PENDING_REFRESH_KEY = "ProfilePictureView_refresh"
  }
}


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

import android.app.Activity
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewTreeObserver
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.PopupWindow
import android.widget.TextView
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import com.facebook.login.R
import java.lang.ref.WeakReference

/**
 * This displays a popup tool tip for a specified view.
 * @param text The text to be displayed in the tool tip
 * @param anchor The view to anchor this tool tip to.
 */
@AutoHandleExceptions
class ToolTipPopup(private val text: String, anchor: View) {
  /** The values here describe the styles available for the tool tip class.  */
  enum class Style {
    /**
     * The tool tip will be shown with a blue style; including a blue background and blue arrows.
     */
    BLUE,

    /**
     * The tool tip will be shown with a black style; including a black background and black arrows.
     */
    BLACK
  }

  private val anchorViewRef: WeakReference<View> = WeakReference(anchor)
  private val context: Context = anchor.context
  private var popupContent: PopupContentView? = null
  private var popupWindow: PopupWindow? = null
  private var style = Style.BLUE
  private var nuxDisplayTime = DEFAULT_POPUP_DISPLAY_TIME
  private val scrollListener = ViewTreeObserver.OnScrollChangedListener {
    if (anchorViewRef.get() != null) {
      popupWindow?.let {
        if (it.isShowing) {
          if (it.isAboveAnchor) {
            popupContent?.showBottomArrow()
          } else {
            popupContent?.showTopArrow()
          }
        }
      }
    }
  }

  /**
   * Sets the [Style] of this tool tip.
   *
   * @param style the style for the tool tip
   */
  fun setStyle(style: Style) {
    this.style = style
  }

  /** Display this tool tip to the user  */
  fun show() {
    if (anchorViewRef.get() != null) {
      val popupContent = PopupContentView(context)
      this.popupContent = popupContent
      val body = popupContent.findViewById<View>(R.id.com_facebook_tooltip_bubble_view_text_body) as TextView
      body.text = text
      if (style == Style.BLUE) {
        popupContent.bodyFrame.setBackgroundResource(R.drawable.com_facebook_tooltip_blue_background)
        popupContent.bottomArrow.setImageResource(R.drawable.com_facebook_tooltip_blue_bottomnub)
        popupContent.topArrow.setImageResource(R.drawable.com_facebook_tooltip_blue_topnub)
        popupContent.xOut.setImageResource(R.drawable.com_facebook_tooltip_blue_xout)
      } else {
        popupContent.bodyFrame.setBackgroundResource(R.drawable.com_facebook_tooltip_black_background)
        popupContent.bottomArrow.setImageResource(R.drawable.com_facebook_tooltip_black_bottomnub)
        popupContent.topArrow.setImageResource(R.drawable.com_facebook_tooltip_black_topnub)
        popupContent.xOut.setImageResource(R.drawable.com_facebook_tooltip_black_xout)
      }
      val window = (context as Activity).window
      val decorView = window.decorView
      val decorWidth = decorView.width
      val decorHeight = decorView.height
      registerObserver()
      popupContent.measure(
          View.MeasureSpec.makeMeasureSpec(decorWidth, View.MeasureSpec.AT_MOST),
          View.MeasureSpec.makeMeasureSpec(decorHeight, View.MeasureSpec.AT_MOST),
      )
      val popupWindow = PopupWindow(
          popupContent,
          popupContent.measuredWidth,
          popupContent.measuredHeight,
      )
      this.popupWindow = popupWindow
      popupWindow.showAsDropDown(anchorViewRef.get())
      updateArrows()
      if (nuxDisplayTime > 0) {
        popupContent.postDelayed({ dismiss() }, nuxDisplayTime)
      }
      popupWindow.isTouchable = true
      popupContent.setOnClickListener { dismiss() }
    }
  }

  /**
   * Set the time (in milliseconds) the tool tip will be displayed. Any number less than or equal to
   * 0 will cause the tool tip to be displayed indefinitely
   *
   * @param displayTime The amount of time (in milliseconds) to display the tool tip
   */
  fun setNuxDisplayTime(displayTime: Long) {
    nuxDisplayTime = displayTime
  }

  private fun updateArrows() {
    popupWindow?.let {
      if (it.isShowing) {
        if (it.isAboveAnchor) {
          popupContent?.showBottomArrow()
        } else {
          popupContent?.showTopArrow()
        }
      }
    }
  }

  /** Dismiss the tool tip  */
  fun dismiss() {
    unregisterObserver()
    popupWindow?.dismiss()
  }

  private fun registerObserver() {
    unregisterObserver()
    anchorViewRef.get()?.viewTreeObserver?.addOnScrollChangedListener(scrollListener)
  }

  private fun unregisterObserver() {
    anchorViewRef.get()?.viewTreeObserver?.removeOnScrollChangedListener(scrollListener)
  }

  private inner class PopupContentView(context: Context) : FrameLayout(context) {
    val topArrow: ImageView
    val bottomArrow: ImageView
    val bodyFrame: View
    val xOut: ImageView

    init {
      val inflater = LayoutInflater.from(context)
      inflater.inflate(R.layout.com_facebook_tooltip_bubble, this)
      topArrow = findViewById<View>(R.id.com_facebook_tooltip_bubble_view_top_pointer) as ImageView
      bottomArrow = findViewById<View>(R.id.com_facebook_tooltip_bubble_view_bottom_pointer) as ImageView
      bodyFrame = findViewById(R.id.com_facebook_body_frame)
      xOut = findViewById<View>(R.id.com_facebook_button_xout) as ImageView
    }

    fun showTopArrow() {
      topArrow.visibility = VISIBLE
      bottomArrow.visibility = INVISIBLE
    }

    fun showBottomArrow() {
      topArrow.visibility = INVISIBLE
      bottomArrow.visibility = VISIBLE
    }
  }

  companion object {
    /** The default time that the tool tip will be displayed  */
    const val DEFAULT_POPUP_DISPLAY_TIME: Long = 6000
  }
}


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

class Messenger


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

/**
 * Parameters describing the Intent that Messenger sent to the app.
 * Returned by [MessengerUtils.getMessengerThreadParamsForIntent].
 */
class MessengerThreadParams(
    /** The origin of the flow that the user originated from.  */
    val origin: Origin,
    /**
     * A token representing the thread the user originated from. This is an opaque value that is not
     * meant for the app to consume. It exists to complete the flow back to Messenger.
     */
    val threadToken: String,
    /**
     * Metadata that originated from content the app originally set when it sent the request to
     * Messenger.
     */
    val metadata: String,
    /**
     * The list of participants in the thread represented as App-scoped User IDs. This may not always
     * be set and will only ever be set for apps that include Facebook login. When set, it will only
     * include the participants in the thread that have logged into the app.
     * See [docs](https://developers.facebook.com/docs/apps/upgrading) for more info.
     */
    val participants: List<String>) {

  /** The origin of the flow that user originated from.  */
  enum class Origin {
    /** The user clicked on a reply link in Messenger to a particular message.  */
    REPLY_FLOW,

    /** The user clicked an app shortcut in Messenger.  */
    COMPOSE_FLOW,

    /** The user came from a flow that was not known at the time this code was written.  */
    UNKNOWN
  }
}


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

import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import com.facebook.FacebookSdk.getApplicationId
import com.facebook.bolts.AppLinks.getAppLinkExtras
import com.facebook.internal.FacebookSignatureValidator.validateSignature
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import java.lang.RuntimeException
import java.util.HashSet

/**
 * **Utilities for Messenger Content Platform.**
 *
 * Applications should specify the app id in their manifest or call
 * [com.facebook.FacebookSdk.setApplicationId] in their application startup path. For specifying in
 * the manifest, add a meta tag in your <application> tag.
 *
 * ```xml
 * <meta-data android:name="com.facebook.sdk.ApplicationId" android:value="YOUR_APP_ID"/>
 * ```
 *
 * When sharing to Messenger, apps should call the [shareToMessenger] method. For example,
 *
 * ```kotlin
 * val params = ShareToMessengerParams.newBuilder(uri, "image/ *")
 *   .setMetaData(metaData)
 *   .build();
 * MessengerUtils.shareToMessenger(this, REQUEST_CODE_SHARE_TO_MESSENGER, params);
 * ```
 *
 * To handle receiving a composer shortcut or reply intent from Messenger, apps should put the
 * following intent filter in their manifest for the activity that receives the intent:
 *
 * ```xml
 * <intent-filter>
 *   <action android:name="android.intent.action.PICK" />
 *   <category android:name="android.intent.category.DEFAULT"/>
 *   <category android:name="com.facebook.orca.category.PLATFORM_THREAD_20150311"/>
 * </intent-filter>
 * ```
 *
 * When handling the intent, then call [getMessengerThreadParamsForIntent] to receive the parameters
 * for messenger. When the user has clicked the Send button to send the content to Messenger, then
 * call [finishShareToMessenger] to return the data back to Messenger.
 */
object MessengerUtils {
  private const val TAG = "MessengerUtils"
  const val PACKAGE_NAME = "com.facebook.orca"
  const val EXTRA_PROTOCOL_VERSION = "com.facebook.orca.extra.PROTOCOL_VERSION"
  const val EXTRA_APP_ID = "com.facebook.orca.extra.APPLICATION_ID"
  const val EXTRA_REPLY_TOKEN_KEY = "com.facebook.orca.extra.REPLY_TOKEN"
  const val EXTRA_THREAD_TOKEN_KEY = "com.facebook.orca.extra.THREAD_TOKEN"
  const val EXTRA_METADATA = "com.facebook.orca.extra.METADATA"
  const val EXTRA_EXTERNAL_URI = "com.facebook.orca.extra.EXTERNAL_URI"
  const val EXTRA_PARTICIPANTS = "com.facebook.orca.extra.PARTICIPANTS"
  const val EXTRA_IS_REPLY = "com.facebook.orca.extra.IS_REPLY"
  const val EXTRA_IS_COMPOSE = "com.facebook.orca.extra.IS_COMPOSE"
  const val PROTOCOL_VERSION_20150314 = 20150314
  const val ORCA_THREAD_CATEGORY_20150314 = "com.facebook.orca.category.PLATFORM_THREAD_20150314"

  /**
   * Starts an intent to share a piece of media on Messenger using the messenger content platform.
   *
   * @param activity the activity sharing the content
   * @param requestCode a unique request code for [Activity.startActivityForResult]
   * @param shareToMessengerParams parameters for what to share
   */
  @AutoHandleExceptions
  fun shareToMessenger(
      activity: Activity,
      requestCode: Int,
      shareToMessengerParams: ShareToMessengerParams
  ) {
    if (!hasMessengerInstalled(activity)) {
      openMessengerInPlayStore(activity)
      return
    }
    val allAvailableVersions = getAllAvailableProtocolVersions(activity)
    if (allAvailableVersions.contains(PROTOCOL_VERSION_20150314)) {
      shareToMessenger20150314(activity, requestCode, shareToMessengerParams)
    } else {
      // TODO -- should we show a upgrade dialog?
      openMessengerInPlayStore(activity)
    }
  }

  private fun shareToMessenger20150314(
      activity: Activity,
      requestCode: Int,
      shareToMessengerParams: ShareToMessengerParams
  ) {
    try {
      val shareIntent = Intent(Intent.ACTION_SEND)
      shareIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
      shareIntent.setPackage(PACKAGE_NAME)
      shareIntent.putExtra(Intent.EXTRA_STREAM, shareToMessengerParams.uri)
      shareIntent.type = shareToMessengerParams.mimeType
      shareIntent.putExtra(EXTRA_PROTOCOL_VERSION, PROTOCOL_VERSION_20150314)
      shareIntent.putExtra(EXTRA_APP_ID, getApplicationId())
      shareIntent.putExtra(EXTRA_METADATA, shareToMessengerParams.metaData)
      shareIntent.putExtra(EXTRA_EXTERNAL_URI, shareToMessengerParams.externalUri)
      activity.startActivityForResult(shareIntent, requestCode)
    } catch (e: ActivityNotFoundException) {
      val openMessenger = activity.packageManager.getLaunchIntentForPackage(PACKAGE_NAME)
      activity.startActivity(openMessenger)
    }
  }

  /**
   * When handling an `Intent` from Messenger, call this to parse the parameters of the intent.
   *
   * @param intent the intent of the activity
   * @return a [MessengerThreadParams] or null if this intent wasn't recognized as a request from
   * Messenger to share.
   */
  @AutoHandleExceptions
  fun getMessengerThreadParamsForIntent(intent: Intent): MessengerThreadParams? {
    val categories = intent.categories ?: return null
    if (categories.contains(ORCA_THREAD_CATEGORY_20150314)) {
      val appLinkExtras = getAppLinkExtras(intent)
      val threadToken = appLinkExtras?.getString(EXTRA_THREAD_TOKEN_KEY)
      val metadata = appLinkExtras?.getString(EXTRA_METADATA)
      val participants = appLinkExtras?.getString(EXTRA_PARTICIPANTS)
      val isReply = appLinkExtras?.getBoolean(EXTRA_IS_REPLY)
      val isCompose = appLinkExtras?.getBoolean(EXTRA_IS_COMPOSE)
      val origin =
          if (isReply == true) {
            MessengerThreadParams.Origin.REPLY_FLOW
          } else if (isCompose == true) {
            MessengerThreadParams.Origin.COMPOSE_FLOW
          } else {
            MessengerThreadParams.Origin.UNKNOWN
          }
      if (threadToken != null && metadata != null) {
        return MessengerThreadParams(origin, threadToken, metadata, parseParticipants(participants))
      }
    }
    return null
  }

  /**
   * Finishes the activity and returns the media item the user picked to Messenger.
   *
   * @param activity the activity that received the original intent from Messenger
   * @param shareToMessengerParams parameters for what to share
   */
  fun finishShareToMessenger(activity: Activity, shareToMessengerParams: ShareToMessengerParams) {
    val originalIntent = activity.intent
    val categories = originalIntent.categories
    if (categories == null) {
      // This shouldn't happen.
      activity.setResult(Activity.RESULT_CANCELED, null)
      activity.finish()
      return
    }
    if (categories.contains(ORCA_THREAD_CATEGORY_20150314)) {
      val appLinkExtras = getAppLinkExtras(originalIntent)
      val resultIntent = Intent()
      if (appLinkExtras != null && categories.contains(ORCA_THREAD_CATEGORY_20150314)) {
        resultIntent.putExtra(EXTRA_PROTOCOL_VERSION, PROTOCOL_VERSION_20150314)
        val threadToken = appLinkExtras.getString(EXTRA_THREAD_TOKEN_KEY)
        resultIntent.putExtra(EXTRA_THREAD_TOKEN_KEY, threadToken)
      } else {
        throw RuntimeException() // Can't happen.
      }
      resultIntent.setDataAndType(shareToMessengerParams.uri, shareToMessengerParams.mimeType)
      resultIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
      resultIntent.putExtra(EXTRA_APP_ID, getApplicationId())
      resultIntent.putExtra(EXTRA_METADATA, shareToMessengerParams.metaData)
      resultIntent.putExtra(EXTRA_EXTERNAL_URI, shareToMessengerParams.externalUri)
      activity.setResult(Activity.RESULT_OK, resultIntent)
      activity.finish()
    } else {
      // This shouldn't happen.
      activity.setResult(Activity.RESULT_CANCELED, null)
      activity.finish()
    }
  }

  /**
   * Checks whether any version of messenger is installed.
   *
   * @param context an android context
   * @return whether any version of messenger is installed
   */
  fun hasMessengerInstalled(context: Context): Boolean {
    return validateSignature(context, PACKAGE_NAME)
  }

  /**
   * Opens the play store to install Messenger.
   *
   * @param context an android context.
   */
  @AutoHandleExceptions
  fun openMessengerInPlayStore(context: Context) {
    try {
      startViewUri(context, "market://details?id=$PACKAGE_NAME")
    } catch (anfe: ActivityNotFoundException) {
      startViewUri(context, "http://play.google.com/store/apps/details?id=$PACKAGE_NAME")
    }
  }

  private fun getAllAvailableProtocolVersions(context: Context): Set<Int> {
    val contentResolver = context.contentResolver
    val allAvailableVersions: MutableSet<Int> = HashSet()
    val uri = Uri.parse("content://com.facebook.orca.provider.MessengerPlatformProvider/versions")
    val projection = arrayOf("version")
    val cursor = contentResolver.query(uri, projection, null, null, null)
    cursor?.use { c ->
      val versionColumnIndex = c.getColumnIndex("version")
      while (c.moveToNext()) {
        allAvailableVersions.add(c.getInt(versionColumnIndex))
      }
    }
    return allAvailableVersions
  }

  private fun startViewUri(context: Context, uri: String) =
      context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(uri)))

  private fun parseParticipants(s: String?): List<String> {
    if (s.isNullOrEmpty()) {
      return listOf()
    }
    val parts = s.split(",").toTypedArray()
    return parts.map { it.trim { c -> c <= ' ' } }
  }
}


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

import android.net.Uri

/**
 * Parameters used by [MessengerUtils] for sending the media to Messenger to share. See [developer docs](https://developers.facebook.com/docs/messenger/android) for more info.
 */
class ShareToMessengerParams internal constructor(builder: ShareToMessengerParamsBuilder) {
    companion object {
        val VALID_URI_SCHEMES: Set<String>
        val VALID_MIME_TYPES: Set<String>
        val VALID_EXTERNAL_URI_SCHEMES: Set<String>

        /**
         * Creates a new builder for creating a [ShareToMessengerParams] instance
         *
         * @param uri the uri of the local content. Must be a file://, content://, or res:// URI.
         * @param mimeType the mime-type of the content. See [.VALID_MIME_TYPES] for what mime types
         * are supported.
         * @return the builder instance
         */
        @JvmStatic
        fun newBuilder(uri: Uri, mimeType: String): ShareToMessengerParamsBuilder {
            return ShareToMessengerParamsBuilder(uri, mimeType)
        }

        init {
            val validMimeTypes = HashSet<String>()
            validMimeTypes.add("image/*")
            validMimeTypes.add("image/jpeg")
            validMimeTypes.add("image/png")
            validMimeTypes.add("image/gif")
            validMimeTypes.add("image/webp")
            validMimeTypes.add("video/*")
            validMimeTypes.add("video/mp4")
            validMimeTypes.add("audio/*")
            validMimeTypes.add("audio/mpeg")
            VALID_MIME_TYPES = validMimeTypes.toSet()

            val validUriSchemes = HashSet<String>()
            validUriSchemes.add("content")
            validUriSchemes.add("android.resource")
            validUriSchemes.add("file")
            VALID_URI_SCHEMES = validUriSchemes.toSet()

            val validExternalUriSchemes = HashSet<String>()
            validExternalUriSchemes.add("http")
            validExternalUriSchemes.add("https")
            VALID_EXTERNAL_URI_SCHEMES = validExternalUriSchemes.toSet()
        }
    }

    /**
     * The URI of the local image, video, or audio clip to send to messenger. Must be a file://,
     * content://, or res:// URI.
     */
    val uri: Uri

    /**
     * The mime type of the content. See [.VALID_MIME_TYPES] for what mime types are supported.
     */
    val mimeType: String

    /**
     * The metadata to attach to the content to shared. See [developer docs](https://developers.facebook.com/docs/messenger/android) for more info.
     */
    val metaData: String?

    /**
     * An external URI that Messenger can use to download the content on Facebook's servers instead of
     * requiring the Messenger application to upload the content. The content returned by the this URI
     * must be exactly the same as the content specified by [.uri]. If the content is different,
     * Messenger may fail to send the content. See [developer docs](https://developers.facebook.com/docs/messenger/android) for more info.
     */
    val externalUri: Uri?

    init {
        uri = checkNotNull(builder.uri) { "Must provide non-null uri" }
        mimeType = checkNotNull(builder.mimeType) { "Must provide mimeType" }
        metaData = builder.metaData
        externalUri = builder.externalUri

        require(VALID_URI_SCHEMES.contains(uri.scheme)) { "Unsupported URI scheme: ${uri.scheme}" }
        require(VALID_MIME_TYPES.contains(mimeType)) { "Unsupported mime-type: $mimeType" }
        if (externalUri != null) {
            require(VALID_EXTERNAL_URI_SCHEMES.contains(externalUri.scheme)) { "Unsupported external uri scheme: ${externalUri.scheme}" }
        }
    }
}


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

class Share


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

package com.facebook

import android.annotation.SuppressLint
import java.util.concurrent.Executor
import java.util.concurrent.ScheduledThreadPoolExecutor
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.runner.RunWith
import org.mockito.MockitoAnnotations
import org.powermock.core.classloader.annotations.PowerMockIgnore
import org.powermock.modules.junit4.rule.PowerMockRule
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.shadows.ShadowLog

// ShadowLog is used to redirect the android.util.Log calls to System.out
@SuppressLint("RunWithRobolectricTestRunner")
@Config(shadows = [ShadowLog::class], manifest = Config.NONE, sdk = [21])
@RunWith(RobolectricTestRunner::class)
@PowerMockIgnore(
    "org.mockito.*",
    "org.robolectric.*",
    "androidx.*",
    "android.*",
    "org.json.*",
    "javax.xml.*",
    "org.xml.sax.*",
    "org.w3c.dom.*",
    "org.springframework.context.*",
    "org.apache.log4j.*",
    "kotlin.*")
/**
 * Base class for PowerMock tests. Important: the classes that derive from this should end with Test
 * (i.e. not Tests) otherwise the gradle task "test" doesn't pick them up.
 */
abstract class FacebookPowerMockTestCase {
  @Rule @JvmField var rule = PowerMockRule()
  @Before
  open fun setup() {
    ShadowLog.stream = System.out
    MockitoAnnotations.initMocks(this)
  }

  @After
  fun clearMocks() {
    org.mockito.Mockito.framework().clearInlineMocks()
  }

  class FacebookSerialExecutor : Executor {
    override fun execute(command: Runnable) {
      command.run()
    }
  }

  class FacebookSerialThreadPoolExecutor(corePoolSize: Int) :
      ScheduledThreadPoolExecutor(corePoolSize) {
    override fun execute(command: Runnable) {
      command.run()
    }
  }

  class FacebookSerialThreadPoolMockExecutor(corePoolSize: Int) :
      ScheduledThreadPoolExecutor(corePoolSize) {
    override fun execute(command: Runnable) {
      // do nothing
    }
  }
}


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

package com.facebook

import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.MockitoAnnotations
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.shadows.ShadowLog

// ShadowLog is used to redirect the android.util.Log calls to System.out
@Config(shadows = [ShadowLog::class], sdk = [21])
@RunWith(RobolectricTestRunner::class)
/**
 * Base class for Robolectric tests. Important: the classes that derive from this should end with
 * Test (i.e. not Tests) otherwise the gradle task "test" doesn't pick them up.
 */
abstract class FacebookTestCase {
  @Before
  open fun setUp() {
    ShadowLog.stream = System.out
    MockitoAnnotations.initMocks(this)
  }
}


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

package com.facebook

import android.os.Bundle
import android.os.Parcel
import android.os.Parcelable
import java.util.Date
import kotlin.collections.HashSet
import org.assertj.core.api.Assertions.assertThat
import org.json.JSONArray
import org.json.JSONObject

object FacebookTestUtility {
  const val DOUBLE_EQUALS_DELTA = 0.00001

  fun nowPlusSeconds(offset: Long): Date {
    return Date(Date().time + offset * 1000L)
  }

  fun <T> assertSameCollectionContents(expected: Collection<T?>?, actual: Collection<T?>?) {
    if (expected == null || expected.isEmpty()) {
      assertThat(actual).isNullOrEmpty()
    } else {
      checkNotNull(actual)
      assertThat(actual).isNotEmpty
      for (item in expected) {
        assertThat(actual).contains(item)
      }
      for (item in actual) {
        assertThat(expected).contains(item)
      }
    }
  }

  private fun assertEqualContents(a: Bundle, b: Bundle, collectionOrderMatters: Boolean) {
    for (key in a.keySet()) {
      assertThat(b.containsKey(key)).isTrue.withFailMessage("bundle does not include key $key")
      val aValue = a[key]
      val bValue = b[key]
      if (!collectionOrderMatters && aValue is Collection<*> && bValue is Collection<*>) {
        this.assertSameCollectionContents(aValue as Collection<*>?, bValue as Collection<*>?)
      } else {
        assertThat(a[key]).isEqualTo(b[key])
      }
    }
    for (key in b.keySet()) {
      assertThat(a.containsKey(key)).isTrue.withFailMessage("bundle does not include key $key")
    }
  }

  @JvmStatic
  fun assertEqualContentsWithoutOrder(a: Bundle, b: Bundle) {
    assertEqualContents(a, b, false)
  }

  fun assertEqualContents(a: Bundle, b: Bundle) {
    assertEqualContents(a, b, true)
  }

  @JvmStatic
  fun assertEquals(expected: JSONObject?, actual: JSONObject?) {
    // JSONObject.equals does not do an order-independent comparison, so let's roll our own  :(
    if (areEqual(expected, actual)) {
      return
    }

    assertThat(true).isFalse.withFailMessage("JSONObject is not equal")
  }

  @JvmStatic
  fun assertEquals(expected: JSONArray?, actual: JSONArray?) {
    // JSONObject.equals does not do an order-independent comparison, so let's roll our own  :(
    if (areEqual(expected, actual)) {
      return
    }
    assertThat(true).isFalse.withFailMessage("JSONArray is not equal")
  }

  fun <T : Any> assertNotNull(actual: T?): T {
    assertThat(actual).isNotNull
    return actual as T
  }

  private fun areEqual(expected: JSONObject?, actual: JSONObject?): Boolean {
    // JSONObject.equals does not do an order-independent comparison, so let's roll our own  :(
    if (expected === actual) {
      return true
    }
    if (expected == null || actual == null) {
      return false
    }
    val expectedKeysIterator = expected.keys()
    val expectedKeys = HashSet<String>()
    while (expectedKeysIterator.hasNext()) {
      expectedKeys.add(expectedKeysIterator.next())
    }
    val actualKeysIterator = actual.keys()
    while (actualKeysIterator.hasNext()) {
      val key = actualKeysIterator.next()
      if (!areEqual(expected.opt(key), actual.opt(key))) {
        return false
      }
      expectedKeys.remove(key)
    }
    return expectedKeys.isEmpty()
  }

  private fun areEqual(expected: JSONArray?, actual: JSONArray?): Boolean {
    // JSONObject.equals does not do an order-independent comparison, so we need to check values
    // that are JSONObject
    // manually
    if (expected === actual) {
      return true
    }
    if (expected == null || actual == null) {
      return false
    }
    if (expected.length() != actual.length()) {
      return false
    }
    val length = expected.length()
    for (i in 0 until length) {
      if (!areEqual(expected.opt(i), actual.opt(i))) {
        return false
      }
    }
    return true
  }

  private fun areEqual(expected: Any?, actual: Any?): Boolean {
    if (expected === actual) {
      return true
    }
    if (expected == null || actual == null) {
      return false
    }
    if (expected is JSONObject && actual is JSONObject) {
      return areEqual(expected as JSONObject?, actual as JSONObject?)
    }
    return if (expected is JSONArray && actual is JSONArray) {
      areEqual(expected as JSONArray?, actual as JSONArray?)
    } else expected == actual
  }

  inline fun <reified E : Parcelable?> parcelAndUnparcel(obj: E): E? {
    val writeParcel = Parcel.obtain()
    val readParcel = Parcel.obtain()
    return try {
      writeParcel.writeParcelable(obj, 0)
      val bytes = writeParcel.marshall()
      readParcel.unmarshall(bytes, 0, bytes.size)
      readParcel.setDataPosition(0)
      val classLoader = E::class.java.classLoader
      readParcel.readParcelable(classLoader)
    } finally {
      writeParcel.recycle()
      readParcel.recycle()
    }
  }
}


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

package com.facebook

import android.annotation.TargetApi
import android.content.SharedPreferences
import android.os.Build

@TargetApi(Build.VERSION_CODES.N)
class MockSharedPreference : SharedPreferences {
  private val preferenceMap: HashMap<String, Any?> = HashMap()
  private val editor: MockEditor = MockEditor(preferenceMap)
  override fun getAll(): Map<String, *> = preferenceMap

  override fun getString(key: String, defValue: String?): String? {
    return preferenceMap.getOrDefault(key, defValue) as String?
  }

  override fun getStringSet(key: String, defValues: Set<String>?): Set<String>? {
    return preferenceMap.getOrDefault(key, defValues) as Set<String>?
  }

  override fun getInt(key: String, defValue: Int): Int {
    return preferenceMap.getOrDefault(key, defValue) as Int
  }

  override fun getLong(key: String, defValue: Long): Long {
    return preferenceMap.getOrDefault(key, defValue) as Long
  }

  override fun getFloat(key: String, defValue: Float): Float {
    return preferenceMap.getOrDefault(key, defValue) as Float
  }

  override fun getBoolean(key: String, defValue: Boolean): Boolean {
    return preferenceMap.getOrDefault(key, defValue) as Boolean
  }

  override fun contains(key: String): Boolean {
    return preferenceMap.containsKey(key)
  }

  override fun edit(): SharedPreferences.Editor = editor

  override fun registerOnSharedPreferenceChangeListener(
      listener: SharedPreferences.OnSharedPreferenceChangeListener
  ) = Unit
  override fun unregisterOnSharedPreferenceChangeListener(
      listener: SharedPreferences.OnSharedPreferenceChangeListener
  ) = Unit

  class MockEditor internal constructor(private val preferenceMap: MutableMap<String, Any?>) :
      SharedPreferences.Editor {
    override fun putString(key: String, value: String?): SharedPreferences.Editor {
      preferenceMap[key] = value
      return this
    }

    override fun putStringSet(key: String, values: Set<String>?): SharedPreferences.Editor {
      preferenceMap[key] = values
      return this
    }

    override fun putInt(key: String, value: Int): SharedPreferences.Editor {
      preferenceMap[key] = value
      return this
    }

    override fun putLong(key: String, value: Long): SharedPreferences.Editor {
      preferenceMap[key] = value
      return this
    }

    override fun putFloat(key: String, value: Float): SharedPreferences.Editor {
      preferenceMap[key] = value
      return this
    }

    override fun putBoolean(key: String, value: Boolean): SharedPreferences.Editor {
      preferenceMap[key] = value
      return this
    }

    override fun remove(key: String): SharedPreferences.Editor {
      preferenceMap.remove(key)
      return this
    }

    override fun clear(): SharedPreferences.Editor {
      preferenceMap.clear()
      return this
    }

    override fun commit(): Boolean = true

    override fun apply() = Unit
  }
}


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

package com.facebook.util.common

import java.util.concurrent.Executor

/**
 * CaptureExecutor will capture the runnable for execution. Then the tests can run the runnable
 * object by themselves.
 */
class CaptureExecutor : Executor {
  var capturedRunnable: Runnable? = null
  override fun execute(task: Runnable?) {
    capturedRunnable = task
  }
}


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

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import org.junit.Assert
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.mock

inline fun <reified T : Exception> assertThrows(runnable: () -> Any?) {
  try {
    runnable.invoke()
  } catch (e: Throwable) {
    if (e is T) {
      return
    }
    Assert.fail(
        "expected ${T::class.qualifiedName} but caught " + "${e::class.qualifiedName} instead")
  }
  Assert.fail("expected ${T::class.qualifiedName}")
}

inline fun <reified T : Any> anyObject(): T = anyOrNull<T>()

fun mockLocalBroadcastManager(applicationContext: Context): LocalBroadcastManager {
  val localBroadcastManager = mock<LocalBroadcastManager>()
  val registeredReceiver = ArrayList<BroadcastReceiver>()
  doAnswer { invocation ->
        val receiver = invocation.getArgument<BroadcastReceiver>(0)
        registeredReceiver.add(receiver)
        null
      }
      .`when`(localBroadcastManager)
      .registerReceiver(anyObject(), anyObject())
  doAnswer { invocation ->
        val receiver = invocation.getArgument<BroadcastReceiver>(0)
        registeredReceiver.remove(receiver)
        null
      }
      .`when`(localBroadcastManager)
      .unregisterReceiver(anyObject())
  doAnswer { invocation ->
        val intent = invocation.getArgument<Intent>(0)
        for (receiver in registeredReceiver) {
          receiver.onReceive(applicationContext, intent)
        }
        null
      }
      .`when`(localBroadcastManager)
      .sendBroadcast(anyObject())
  return localBroadcastManager
}


/*
 * 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 org.mockito.configuration

class MockitoConfiguration : DefaultMockitoConfiguration() {
  override fun enableClassCache(): Boolean {
    return false
  }
}


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

package com.facebook.fbloginsample

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith

/**
 * Instrumented test, which will execute on an Android device.
 *
 * See [testing documentation](http://d.android.com/tools/testing).
 */
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
  @Test
  fun useAppContext() {
    // Context of the app under test.
    val appContext = InstrumentationRegistry.getInstrumentation().targetContext
    Assert.assertEquals("com.facebook.fbloginsample", appContext.packageName)
  }
}


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

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import com.facebook.CallbackManager.Factory.create
import com.facebook.FacebookCallback
import com.facebook.FacebookException
import com.facebook.login.LoginResult
import com.facebook.login.widget.LoginButton

class FacebookLoginActivity : Activity() {
  private var callbackManager = create()

  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
    super.onActivityResult(requestCode, resultCode, data)
    callbackManager.onActivityResult(requestCode, resultCode, data)
  }

  public override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContentView(R.layout.activity_facebook_login)
    val loginButton: LoginButton = findViewById(R.id.login_button)

    // Set the initial permissions to request from the user while logging in
    loginButton.permissions = listOf(EMAIL, USER_POSTS)
    loginButton.authType = AUTH_TYPE

    // Register a callback to respond to the user
    loginButton.registerCallback(
        callbackManager,
        object : FacebookCallback<LoginResult> {
          override fun onSuccess(result: LoginResult) {
            setResult(RESULT_OK)
            finish()
          }

          override fun onCancel() {
            setResult(RESULT_CANCELED)
            finish()
          }

          override fun onError(error: FacebookException) {
            // Handle exception
          }
        })
  }

  companion object {
    private const val EMAIL = "email"
    private const val USER_POSTS = "user_posts"
    private const val AUTH_TYPE = "rerequest"
  }
}


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

import android.app.Application
import com.facebook.drawee.backends.pipeline.Fresco

class FBLoginSampleApp : Application() {
  override fun onCreate() {
    super.onCreate()
    Fresco.initialize(this)
  }
}


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

import android.app.Activity
import android.app.AlertDialog
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.Button
import com.facebook.AccessToken
import com.facebook.AccessToken.Companion.getCurrentAccessToken
import com.facebook.LoginStatusCallback
import com.facebook.login.LoginManager.Companion.getInstance
import com.google.android.material.snackbar.Snackbar
import java.lang.Exception

class MainActivity : Activity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContentView(R.layout.activity_main)
    if (resources.getString(R.string.facebook_app_id) == DEFAULT_FB_APP_ID) {
      showAlertNoFacebookAppId()
      return
    }
    if (resources.getString(R.string.facebook_client_token) == DEFAULT_FB_CLIENT_COKEN) {
      showAlertNoFacebookClientToken()
      return
    }
    val view = findViewById<View>(R.id.activity_main)
    // User was previously logged in, can log them in directly here.
    // If this callback is called, a popup notification appears
    getInstance()
        .retrieveLoginStatus(
            this,
            object : LoginStatusCallback {
              override fun onCompleted(accessToken: AccessToken) {
                val snackbar = Snackbar.make(view, "User Logged in", 2)
                snackbar.show()
              }

              override fun onFailure() {
                // If MainActivity is reached without the user being logged in,
                // redirect to the Login Activity
                if (getCurrentAccessToken() == null) {
                  val loginIntent = Intent(this@MainActivity, FacebookLoginActivity::class.java)
                  startActivity(loginIntent)
                }
              }

              override fun onError(exception: Exception) {
                // Handle exception
              }
            })

    // Make a button which leads to profile information of the user
    val gotoProfileButton = findViewById<Button>(R.id.btn_profile)
    gotoProfileButton.setOnClickListener {
      if (getCurrentAccessToken() == null) {
        val profileIntent = Intent(this@MainActivity, FacebookLoginActivity::class.java)
        startActivityForResult(profileIntent, RESULT_PROFILE_ACTIVITY)
      } else {
        val profileIntent = Intent(this@MainActivity, ProfileActivity::class.java)
        startActivity(profileIntent)
      }
    }

    // Make a button which leads to posts made by the user
    val gotoPostsFeedButton = findViewById<Button>(R.id.btn_posts)
    gotoPostsFeedButton.setOnClickListener {
      if (getCurrentAccessToken() == null) {
        val loginIntent = Intent(this@MainActivity, FacebookLoginActivity::class.java)
        startActivityForResult(loginIntent, RESULT_POSTS_ACTIVITY)
      } else {
        val postsFeedIntent = Intent(this@MainActivity, PostFeedActivity::class.java)
        startActivity(postsFeedIntent)
      }
    }

    // Make a button which leads to request or revoke permissions
    val gotoPermissionsButton = findViewById<Button>(R.id.btn_permissions)
    gotoPermissionsButton.setOnClickListener {
      if (getCurrentAccessToken() == null) {
        val loginIntent = Intent(this@MainActivity, FacebookLoginActivity::class.java)
        startActivityForResult(loginIntent, RESULT_PERMISSIONS_ACTIVITY)
      } else {
        val permissionsIntent = Intent(this@MainActivity, PermissionsActivity::class.java)
        startActivity(permissionsIntent)
      }
    }

    // Make a logout button
    val fbLogoutButton = findViewById<Button>(R.id.btn_fb_logout)
    fbLogoutButton.setOnClickListener {
      getInstance().logOut()
      val loginIntent = Intent(this@MainActivity, FacebookLoginActivity::class.java)
      startActivity(loginIntent)
    }
  }

  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
    when (requestCode) {
      RESULT_PROFILE_ACTIVITY ->
          if (resultCode == RESULT_OK) {
            val profileIntent = Intent(this@MainActivity, ProfileActivity::class.java)
            startActivity(profileIntent)
          }
      RESULT_POSTS_ACTIVITY ->
          if (resultCode == RESULT_OK) {
            val postFeedIntent = Intent(this@MainActivity, PostFeedActivity::class.java)
            startActivity(postFeedIntent)
          }
      RESULT_PERMISSIONS_ACTIVITY ->
          if (resultCode == RESULT_OK) {
            val permissionsIntent = Intent(this@MainActivity, PermissionsActivity::class.java)
            startActivity(permissionsIntent)
          }
      else -> super.onActivityResult(requestCode, resultCode, data)
    }
  }

  private fun showAlertNoFacebookAppId() {
    val alert = AlertDialog.Builder(this@MainActivity).create()
    alert.setTitle("Use your facebook app id in strings.xml")
    alert.setMessage(
        "This sample app can not properly function without your app id. " +
            "Use your facebook app id in strings.xml. Check out https://developers.facebook.com/docs/android/getting-started/ for more info. " +
            "Restart the app after that")
    alert.show()
  }

  private fun showAlertNoFacebookClientToken() {
    val alert = AlertDialog.Builder(this@MainActivity).create()
    alert.setTitle("Use your facebook client token in strings.xml")
    alert.setMessage(
        "On and above Facebook SDK v13.0, all Graph API calls must have a valid client token." +
            "Add your client token to the facebook_client_token string in strings.xml. Check out https://developers.facebook.com/docs/android/getting-started#app_id for more info. " +
            "Restart the app after that")
    alert.show()
  }

  companion object {
    private const val RESULT_PROFILE_ACTIVITY = 1
    private const val RESULT_POSTS_ACTIVITY = 2
    private const val RESULT_PERMISSIONS_ACTIVITY = 3
    private const val DEFAULT_FB_APP_ID = "ENTER_YOUR_FB_APP_ID_HERE"
    private const val DEFAULT_FB_CLIENT_COKEN = "ENTER_YOUR_FB_CLIENT_TOKEN_HERE"
  }
}


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

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.Switch
import android.widget.Toast
import com.facebook.AccessToken
import com.facebook.AccessToken.AccessTokenRefreshCallback
import com.facebook.CallbackManager
import com.facebook.CallbackManager.Factory.create
import com.facebook.FacebookCallback
import com.facebook.FacebookException
import com.facebook.GraphResponse
import com.facebook.fbloginsample.callbacks.PermissionCallback
import com.facebook.fbloginsample.callbacks.PermissionCallback.IPermissionResponse
import com.facebook.fbloginsample.requests.PermissionRequest.makeRevokePermRequest
import com.facebook.login.LoginManager
import com.facebook.login.LoginResult

class PermissionsActivity :
    Activity(), IPermissionResponse, AccessTokenRefreshCallback, FacebookCallback<LoginResult> {
  private lateinit var emailSwitch: Switch
  private lateinit var userPostsSwitch: Switch
  private lateinit var publishPostSwitch: Switch
  private lateinit var entireAppSwitch: Switch
  private lateinit var callbackManager: CallbackManager

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContentView(R.layout.activity_permissions)
    callbackManager = create()
    LoginManager.getInstance().registerCallback(callbackManager, this)

    // Make a switch for the email permission
    emailSwitch = findViewById(R.id.switch_email_permission)
    emailSwitch.setOnCheckedChangeListener { _, isChecked ->
      val permissions = AccessToken.getCurrentAccessToken()?.permissions
      if (isChecked && permissions?.contains(EMAIL) == false) {
        // Make request to user to grant email permission
        LoginManager.getInstance().logInWithReadPermissions(this@PermissionsActivity, listOf(EMAIL))
      } else if (!isChecked && permissions?.contains(EMAIL) == true) {
        // Make revoke email permission request
        makeRevokePermRequest(EMAIL, PermissionCallback(this@PermissionsActivity).callback)
      }
    }

    // Make a switch for the user posts permission
    userPostsSwitch = findViewById(R.id.switch_user_posts_permission)
    userPostsSwitch.setOnCheckedChangeListener { _, isChecked ->
      val permissions = AccessToken.getCurrentAccessToken()?.permissions
      if (isChecked && permissions?.contains(USER_POSTS) == false) {
        // Make request to user to grant user_posts permission
        LoginManager.getInstance()
            .logInWithReadPermissions(this@PermissionsActivity, listOf(USER_POSTS))
      } else if (!isChecked && permissions?.contains(USER_POSTS) == true) {
        // Make revoke user_posts permission request
        makeRevokePermRequest(USER_POSTS, PermissionCallback(this@PermissionsActivity).callback)
      }
    }

    // Make a switch for the publish posts permission
    publishPostSwitch = findViewById(R.id.switch_publish_post_permission)
    publishPostSwitch.setOnCheckedChangeListener { _, isChecked ->
      val permissions = AccessToken.getCurrentAccessToken()?.permissions
      if (isChecked && permissions?.contains(PUBLISH_ACTIONS) == false) {
        // Make request to user to grant publish_actions permission
        LoginManager.getInstance()
            .logInWithPublishPermissions(this@PermissionsActivity, listOf(PUBLISH_ACTIONS))
      } else if (!isChecked && permissions?.contains(PUBLISH_ACTIONS) == true) {
        // Make revoke publish_actions permission request
        makeRevokePermRequest(
            PUBLISH_ACTIONS, PermissionCallback(this@PermissionsActivity).callback)
      }
    }

    // Make a switch for the app login permission
    entireAppSwitch = findViewById(R.id.switch_app_permission)
    entireAppSwitch.setOnCheckedChangeListener { _, isChecked ->
      if (isChecked && AccessToken.getCurrentAccessToken() == null) {
        // Make request to user to login
        LoginManager.getInstance()
            .logInWithReadPermissions(this@PermissionsActivity, listOf(PUBLIC_PROFILE))
      } else if (!isChecked && AccessToken.getCurrentAccessToken() != null) {
        makeRevokePermRequest(APP, PermissionCallback(this@PermissionsActivity).callback)
      }
    }
  }

  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
    super.onActivityResult(requestCode, resultCode, data)
    callbackManager.onActivityResult(requestCode, resultCode, data)
  }

  override fun onResume() {
    super.onResume()
    setPermissionsSwitches()
  }

  // PermissionCallback.IPermissionResponse Callbacks
  override fun onCompleted(response: GraphResponse?) {
    response?.error?.let {
      Toast.makeText(this, "Error with permissions request: " + it.errorMessage, Toast.LENGTH_LONG)
          .show()
    }
    AccessToken.refreshCurrentAccessTokenAsync(this@PermissionsActivity)
  }

  // Facebook Login Callbacks
  override fun onSuccess(result: LoginResult) {
    // Refresh token cached on device after login succeeds
    AccessToken.refreshCurrentAccessTokenAsync(this)
  }

  override fun onCancel() {
    // Handle user cancel ...
  }

  override fun onError(error: FacebookException) {
    // Handle exception ...
  }

  // Access Token Refresh Callbacks
  override fun OnTokenRefreshed(accessToken: AccessToken?) {
    if (accessToken == null) {
      LoginManager.getInstance().logOut()
      val loginIntent = Intent(this@PermissionsActivity, FacebookLoginActivity::class.java)
      startActivity(loginIntent)
    } else {
      setPermissionsSwitches()
    }
  }

  override fun OnTokenRefreshFailed(exception: FacebookException?) {
    // Handle exception ...
  }

  // Set switch on/off according to current user-granted permissions
  private fun setPermissionsSwitches() {
    val permissions = AccessToken.getCurrentAccessToken()?.permissions
    emailSwitch.isChecked = permissions?.contains(EMAIL) == true
    userPostsSwitch.isChecked = permissions?.contains(USER_POSTS) == true
    publishPostSwitch.isChecked = permissions?.contains(PUBLISH_ACTIONS) == true
    entireAppSwitch.isChecked = AccessToken.getCurrentAccessToken() != null
  }

  companion object {
    private const val EMAIL = "email"
    private const val USER_POSTS = "user_posts"
    private const val PUBLISH_ACTIONS = "publish_actions"
    private const val PUBLIC_PROFILE = "public_profile"
    private const val APP = "app"
  }
}


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

import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.widget.Button
import android.widget.ImageButton
import android.widget.ListView
import android.widget.TextView
import com.facebook.AccessToken
import com.facebook.CallbackManager
import com.facebook.FacebookCallback
import com.facebook.FacebookException
import com.facebook.fbloginsample.adapters.PostAdapter
import com.facebook.fbloginsample.callbacks.GetPostsCallback
import com.facebook.fbloginsample.callbacks.PublishPostCallback
import com.facebook.fbloginsample.entities.Post
import com.facebook.fbloginsample.requests.PostsRequest
import com.facebook.login.LoginManager
import com.facebook.login.LoginResult
import java.util.ArrayList

class PostFeedActivity :
    Activity(),
    GetPostsCallback.IGetPostsResponse,
    PublishPostCallback.IPublishPostResponse,
    AccessToken.AccessTokenRefreshCallback,
    FacebookCallback<LoginResult> {

  private lateinit var postsListView: ListView
  private lateinit var composeTextView: TextView
  private lateinit var callbackManager: CallbackManager
  private var attachmentUri: Uri? = null

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContentView(R.layout.activity_post_feed)
    callbackManager = CallbackManager.Factory.create()
    LoginManager.getInstance().registerCallback(callbackManager, this)

    val postAdapter = PostAdapter(this, mutableListOf())
    postsListView = findViewById(R.id.post_list_view)
    postsListView.adapter = postAdapter

    // Attach image
    val attachImgBtn = findViewById<ImageButton>(R.id.btn_attach)
    attachImgBtn.setOnClickListener {
      val intent = Intent()
      intent.type = TYPE_IMAGE
      intent.action = Intent.ACTION_GET_CONTENT
      startActivityForResult(Intent.createChooser(intent, SELECT_PICTURE), PICK_IMAGE)
    }

    // Compose post
    composeTextView = findViewById(R.id.compose_text)
    val composePostBtn = findViewById<Button>(R.id.btn_send)
    composePostBtn.setOnClickListener {
      if (AccessToken.getCurrentAccessToken()?.permissions?.contains(PUBLISH_ACTIONS) == true) {
        makePost()
      } else {
        // Get Publish Permissions
        LoginManager.getInstance()
            .logInWithPublishPermissions(this@PostFeedActivity, listOf(PUBLISH_ACTIONS))
      }
    }
  }

  override fun onResume() {
    super.onResume()
    posts
  }

  public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
    super.onActivityResult(requestCode, resultCode, data)

    callbackManager.onActivityResult(requestCode, resultCode, data)
    if (requestCode == PICK_IMAGE) {
      attachmentUri = data.data
    }
  }

  // From GetPostsCallback
  override fun onGetPostsCompleted(posts: List<Post>) {
    if (posts.isNotEmpty()) {
      postsListView.adapter = PostAdapter(this@PostFeedActivity, posts)
    }
  }

  // From PublishPostCallback
  override fun onPublishCompleted() {
    posts
  }

  // From AccessTokenRefreshCallback
  override fun OnTokenRefreshed(accessToken: AccessToken?) {
    makePost()
  }

  override fun OnTokenRefreshFailed(exception: FacebookException?) {
    // Handle exception ...
  }

  // From FacebookLogin
  override fun onSuccess(result: LoginResult) {
    // Refresh token cached on device after login succeeds
    AccessToken.refreshCurrentAccessTokenAsync(this)
  }

  override fun onCancel() {
    // Handle user cancel ...
  }

  override fun onError(error: FacebookException) {
    // Handle exception ...
  }

  // Private methods
  private val posts: Unit
    get() {
      PostsRequest.makeGetPostsRequest(GetPostsCallback(this).callback)
    }

  private fun makePost() {
    if (attachmentUri == null) {
      PostsRequest.makePublishPostRequest(
          composeTextView.text.toString(), PublishPostCallback(this).graphRequestCallback)
    } else {
      PostsRequest.makePublishPostRequest(attachmentUri, PublishPostCallback(this).shareCallback)
    }
    composeTextView.text = ""
    attachmentUri = null
  }

  companion object {
    private const val PICK_IMAGE = 1
    private const val PUBLISH_ACTIONS = "publish_actions"
    private const val SELECT_PICTURE = "Select Picture"
    private const val TYPE_IMAGE = "image/*"
  }
}


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

import android.app.Activity
import android.graphics.Color
import android.os.Bundle
import android.widget.TextView
import com.facebook.drawee.view.SimpleDraweeView
import com.facebook.fbloginsample.callbacks.GetUserCallback
import com.facebook.fbloginsample.callbacks.GetUserCallback.IGetUserResponse
import com.facebook.fbloginsample.entities.User
import com.facebook.fbloginsample.requests.UserRequest

class ProfileActivity : Activity(), IGetUserResponse {
  private lateinit var mProfilePhotoView: SimpleDraweeView
  private lateinit var mName: TextView
  private lateinit var mId: TextView
  private lateinit var mEmail: TextView
  private lateinit var mPermissions: TextView

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContentView(R.layout.activity_profile)
    mProfilePhotoView = findViewById(R.id.profile_photo)
    mName = findViewById(R.id.name)
    mId = findViewById(R.id.id)
    mEmail = findViewById(R.id.email)
    mPermissions = findViewById(R.id.permissions)
  }

  override fun onResume() {
    super.onResume()
    UserRequest.makeUserRequest(GetUserCallback(this@ProfileActivity).callback)
  }

  override fun onCompleted(user: User) {
    mProfilePhotoView.setImageURI(user.picture)
    mName.text = user.name
    mId.text = user.id
    if (user.email == null) {
      mEmail.setText(R.string.no_email_perm)
      mEmail.setTextColor(Color.RED)
    } else {
      mEmail.text = user.email
      mEmail.setTextColor(Color.BLACK)
    }
    mPermissions.text = user.permissions
  }
}


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

import java.text.ParsePosition
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

object Util {
  fun makePrettyDate(getCreatedTime: String): String {
    val formatter = SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ssZ", Locale.US)
    val pos = ParsePosition(0)
    val then = formatter.parse(getCreatedTime, pos).time
    val now = Date().time
    val seconds = (now - then) / 1000
    val minutes = seconds / 60
    val hours = minutes / 60
    val days = hours / 24
    var friendly: String?
    val num: Long
    when {
      days > 0 -> {
        num = days
        friendly = "$days day"
      }
      hours > 0 -> {
        num = hours
        friendly = "$hours hour"
      }
      minutes > 0 -> {
        num = minutes
        friendly = "$minutes minute"
      }
      else -> {
        num = seconds
        friendly = "$seconds second"
      }
    }
    if (num > 1) {
      friendly += "s"
    }
    return "$friendly ago"
  }
}


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

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.TextView
import com.facebook.drawee.view.SimpleDraweeView
import com.facebook.fbloginsample.R
import com.facebook.fbloginsample.Util
import com.facebook.fbloginsample.entities.Post
import com.facebook.login.widget.ProfilePictureView

class PostAdapter(private val _context: Context, private val posts: List<Post>) :
    ArrayAdapter<Post>(_context, R.layout.news_feed_item, posts) {

  override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
    val post = posts[position]
    var rowView = convertView
    if (rowView == null) {
      val inflater = _context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
      rowView = inflater.inflate(R.layout.news_feed_item, null)
      val viewHolder = ViewHolder()
      viewHolder.profilePhoto = rowView.findViewById(R.id.profile_photo)
      viewHolder.postDate = rowView.findViewById(R.id.post_date)
      viewHolder.username = rowView.findViewById(R.id.username)
      viewHolder.message = rowView.findViewById(R.id.message)
      viewHolder.postPicture = rowView.findViewById(R.id.post_picture)
      rowView.tag = viewHolder
    }

    checkNotNull(rowView)

    val viewHolder = rowView.tag as ViewHolder
    viewHolder.profilePhoto.profileId = post.fromId
    viewHolder.postDate.text = Util.makePrettyDate(post.createdTime)
    viewHolder.username.text = post.fromName
    viewHolder.message.text = post.message
    viewHolder.postPicture.setImageURI(post.picture)
    return rowView
  }

  private class ViewHolder {
    lateinit var profilePhoto: ProfilePictureView
    lateinit var postDate: TextView
    lateinit var username: TextView
    lateinit var message: TextView
    lateinit var postPicture: SimpleDraweeView
  }
}


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

import com.facebook.GraphRequest
import com.facebook.fbloginsample.entities.Post
import java.util.ArrayList
import org.json.JSONException
import org.json.JSONObject

class GetPostsCallback(private val getPostsResponse: IGetPostsResponse) {
  interface IGetPostsResponse {
    fun onGetPostsCompleted(posts: List<Post>)
  }

  private val posts = mutableListOf<Post>()

  val callback =
      GraphRequest.Callback { response ->
        try {
          val postsObj = response.getJSONObject() ?: return@Callback
          val jsonPostsArray = postsObj.getJSONArray("data")
          for (i in 0 until jsonPostsArray.length()) {
            val jPost = jsonPostsArray.getJSONObject(i)
            val post = jsonToPost(jPost)
            posts.add(post)
          }
        } catch (e: JSONException) {
          // Handle exception ...
        }

        // Handled by PostFeedActivity
        getPostsResponse.onGetPostsCompleted(posts)
      }

  @Throws(JSONException::class)
  private fun jsonToPost(post: JSONObject): Post {
    val message = if (post.has("message")) post.getString("message") else null
    var picture = if (post.has("picture")) post.getString("picture") else null
    val createdTime = post.getString("created_time")
    val id = post.getString("id")
    val from = post.getJSONObject("from")
    val fromName = from.getString("name")
    val fromId = from.getString("id")
    return Post(message, createdTime, id, picture, fromName, fromId)
  }
}


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

import android.net.Uri
import com.facebook.GraphRequest
import com.facebook.fbloginsample.entities.User
import java.lang.StringBuilder
import org.json.JSONException
import org.json.JSONObject

class GetUserCallback(private val getUserResponse: IGetUserResponse) {
  interface IGetUserResponse {
    fun onCompleted(user: User)
  }

  val callback: GraphRequest.Callback
  @Throws(JSONException::class)
  private fun jsonToUser(user: JSONObject): User {
    val picture = Uri.parse(user.getJSONObject("picture").getJSONObject("data").getString("url"))
    val name = user.getString("name")
    val id = user.getString("id")
    var email: String? = null
    if (user.has("email")) {
      email = user.getString("email")
    }

    // Build permissions display string
    val builder = StringBuilder()
    val perms = user.getJSONObject("permissions").getJSONArray("data")
    builder.append("Permissions:\n")
    for (i in 0 until perms.length()) {
      val jsonObject = perms.getJSONObject(i)
      val permissionString = "${jsonObject["permission"]}: ${jsonObject["status"]}\n"
      builder.append(permissionString)
    }
    val permissions = builder.toString()
    return User(picture, name, id, email, permissions)
  }

  init {
    callback =
        GraphRequest.Callback { response ->
          try {
            val userObj = response.getJSONObject() ?: return@Callback
            val user = jsonToUser(userObj)
            // Handled by ProfileActivity
            getUserResponse.onCompleted(user)
          } catch (e: JSONException) {
            // Handle exception ...
          }
        }
  }
}


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

import com.facebook.GraphRequest
import com.facebook.GraphResponse

class PermissionCallback(caller: IPermissionResponse) {
  interface IPermissionResponse {
    fun onCompleted(response: GraphResponse?)
  }

  val callback = GraphRequest.Callback { response: GraphResponse? -> caller.onCompleted(response) }
}


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

import com.facebook.FacebookCallback
import com.facebook.FacebookException
import com.facebook.GraphRequest
import com.facebook.share.Sharer

class PublishPostCallback(caller: IPublishPostResponse) {
  interface IPublishPostResponse {
    fun onPublishCompleted()
  }

  val graphRequestCallback =
      GraphRequest.Callback { // Handled by PostFeedActivity
        caller.onPublishCompleted()
      }

  val shareCallback =
      object : FacebookCallback<Sharer.Result> {
        override fun onSuccess(result: Sharer.Result) {
          // Handled by PostFeedActivity
          caller.onPublishCompleted()
        }

        override fun onCancel() {
          // Handle user cancel ...
        }

        override fun onError(error: FacebookException) {
          // Handle exception ...
        }
      }
}


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

class Post(
    val message: String?,
    val createdTime: String,
    val id: String,
    val picture: String?,
    val fromName: String,
    val fromId: String
)


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

package com.facebook.fbloginsample.entities

import android.net.Uri

class User(
    val picture: Uri,
    val name: String,
    val id: String,
    val email: String?,
    val permissions: String
)


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

package com.facebook.fbloginsample.requests

import com.facebook.AccessToken.Companion.getCurrentAccessToken
import com.facebook.GraphRequest
import com.facebook.GraphRequest.Companion.newGraphPathRequest
import com.facebook.HttpMethod

object PermissionRequest {
  private const val PERMISSIONS_ENDPOINT = "/me/permissions"
  private const val APP = "app"

  @JvmStatic
  fun makeRevokePermRequest(permission: String, callback: GraphRequest.Callback?) {
    val graphPath =
        if (permission == APP) {
          PERMISSIONS_ENDPOINT
        } else {
          "$PERMISSIONS_ENDPOINT/$permission"
        }
    val request = newGraphPathRequest(getCurrentAccessToken(), graphPath, callback)
    request.httpMethod = HttpMethod.DELETE
    request.executeAsync()
  }
}


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

import android.net.Uri
import android.os.Bundle
import com.facebook.AccessToken
import com.facebook.FacebookCallback
import com.facebook.GraphRequest
import com.facebook.HttpMethod.*
import com.facebook.share.ShareApi
import com.facebook.share.Sharer
import com.facebook.share.model.SharePhoto
import com.facebook.share.model.SharePhotoContent

object PostsRequest {
  private const val FEED_ENDPOINT = "/me/feed"

  fun makeGetPostsRequest(callback: GraphRequest.Callback) {
    val params = Bundle()
    params.putString("fields", "message,created_time,id,picture,story,from")
    val request =
        GraphRequest(AccessToken.getCurrentAccessToken(), FEED_ENDPOINT, params, GET, callback)
    request.executeAsync()
  }

  fun makePublishPostRequest(message: String, callback: GraphRequest.Callback) {
    val params = Bundle()
    params.putString("message", message)
    val request =
        GraphRequest(AccessToken.getCurrentAccessToken(), FEED_ENDPOINT, params, POST, callback)
    request.executeAsync()
  }

  fun makePublishPostRequest(attachmentUri: Uri?, callback: FacebookCallback<Sharer.Result>) {
    val photo = SharePhoto.Builder().setImageUrl(attachmentUri).build()
    val content = SharePhotoContent.Builder().addPhoto(photo).build()
    ShareApi.share(content, callback)
  }
}


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

import android.os.Bundle
import com.facebook.AccessToken.Companion.getCurrentAccessToken
import com.facebook.GraphRequest
import com.facebook.HttpMethod.GET

object UserRequest {
  private const val ME_ENDPOINT = "/me"

  fun makeUserRequest(callback: GraphRequest.Callback?) {
    val params = Bundle()
    params.putString("fields", "picture,name,id,email,permissions")
    val request = GraphRequest(getCurrentAccessToken(), ME_ENDPOINT, params, GET, callback)
    request.executeAsync()
  }
}


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

import org.junit.Assert.assertEquals
import org.junit.Test

/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * See [testing documentation](http://d.android.com/tools/testing).
 */
class ExampleUnitTest {
  @Test
  fun addition_isCorrect() {
    assertEquals(4, 2 + 2)
  }
}


/*
 * 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.example.hellofacebook;

import android.os.Bundle
import android.util.Log
import com.facebook.FacebookBroadcastReceiver

/**
 * This is a simple example to demonstrate how an app could extend FacebookBroadcastReceiver to
 * handle notifications that long-running operations such as photo uploads have finished.
 */
class HelloFacebookBroadcastReceiver: FacebookBroadcastReceiver() {
    override fun onSuccessfulAppCall(appCallId: String, action: String, extras: Bundle) {
        // A real app could update UI or notify the user that their photo was uploaded.
        Log.d("HelloFacebook", "Photo uploaded by call $appCallId succeeded.")
    }

    override fun onFailedAppCall(appCallId: String, action: String, extras: Bundle) {
        // A real app could update UI or notify the user that their photo was not uploaded.
        Log.d("HelloFacebook", "Photo uploaded by call $appCallId failed.")
    }
}


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

import androidx.compose.foundation.layout.padding
import androidx.compose.material.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.facebook.samples.kotlinsampleapp.appevents.AppEventScreen
import com.facebook.samples.kotlinsampleapp.home.HomeScreen
import com.facebook.samples.kotlinsampleapp.home.HomeViewModel
import com.facebook.samples.kotlinsampleapp.login.LoginMenuScreen
import com.facebook.samples.kotlinsampleapp.sharing.SharingScreen

enum class Screen {
  MainScreen,
  LoginScreen,
  SharingScreen,
  AppEventsScreen
}

@Composable
fun App(homeViewModel: HomeViewModel) {
  val navController = rememberNavController()
  Scaffold() { innerPadding -> NavHost(navController, modifier = Modifier.padding(innerPadding)) }
}

@Composable
fun NavHost(navController: NavHostController, modifier: Modifier = Modifier) {
  NavHost(
      navController = navController,
      startDestination = Screen.MainScreen.name,
      modifier = modifier) {
        composable(Screen.MainScreen.name) {
          val homeViewModel: HomeViewModel = viewModel()
          HomeScreen(
              homeViewModel,
              { navController.navigate(Screen.LoginScreen.name) },
              { navController.navigate(Screen.SharingScreen.name) },
              { navController.navigate(Screen.AppEventsScreen.name) })
        }
        composable(Screen.LoginScreen.name) { LoginMenuScreen() }
        composable(Screen.SharingScreen.name) { SharingScreen() }
        composable(Screen.AppEventsScreen.name) { AppEventScreen() }
      }
}


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

import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.facebook.samples.kotlinsampleapp.home.HomeViewModel

class MainActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val homeViewModel: HomeViewModel by viewModels()
    setContent { App(homeViewModel) }
  }
}


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

import android.app.Application
import com.facebook.appevents.AppEventsLogger

class SampleApplication : Application() {
  override fun onCreate() {
    super.onCreate()
    AppEventsLogger.activateApp(this)
  }
}


/*
 * 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.samples.kotlinsampleapp.appevents

import android.content.Context
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.facebook.appevents.AppEventsLogger
import com.facebook.samples.kotlinsampleapp.common.MenuItem

@Composable
fun AppEventScreen() {
  val context = LocalContext.current
  val logEvent = { ctx: Context, eventName: String ->
    AppEventsLogger.newLogger(ctx).logEvent(eventName)
  }

  Column(verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.padding(16.dp)) {
    Text("This screen shows how to emit custom events using FB SDK")
    MenuItem("Log event", onClick = { logEvent(context, "EMIT_EVENT_CLICK") })
  }
}


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

import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Divider
import androidx.compose.material.Text
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

@Composable
fun MenuItem(text: String, onClick: () -> Unit = {}) {
  val interactionSource = remember { MutableInteractionSource() }
  Column {
    Row(
        modifier =
            Modifier.clickable(
                    interactionSource = interactionSource,
                    indication = rememberRipple(),
                    role = Role.Button,
                    onClick = onClick)
                .fillMaxWidth(1.0f)) {
          Text(text, fontSize = 24.sp)
        }
    Divider(modifier = Modifier.padding(vertical = 4.dp))
  }
}


/*
 * 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.samples.kotlinsampleapp.home

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.facebook.samples.kotlinsampleapp.common.MenuItem

@Composable
fun HomeScreen(
    homeViewModel: HomeViewModel,
    navigateToLogin: () -> Unit,
    navigateToSharing: () -> Unit,
    navigateToAppEvents: () -> Unit
) {
  val homeViewState by homeViewModel.homeViewState.observeAsState(HomeViewState())

  Column {
    ProfileView(
        homeViewState.profile,
        homeViewState.loginBehavior,
        homeViewState.availableLoginBehaviors,
        homeViewModel::logout,
        homeViewModel::selectLoginBehavior)
    HomeMenu(navigateToLogin, navigateToSharing, navigateToAppEvents)
  }
}

@Composable
fun HomeMenu(
    navigateToLogin: () -> Unit,
    navigateToSharing: () -> Unit,
    navigateToAppEvents: () -> Unit
) {
  Column(verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.padding(16.dp)) {
    MenuItem("Login", onClick = navigateToLogin)
    MenuItem("AppEvents", onClick = navigateToAppEvents)
    MenuItem("Sharing", onClick = navigateToSharing)
  }
}