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

import com.facebook.FacebookPowerMockTestCase
import java.util.concurrent.ExecutorService
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.powermock.reflect.Whitebox

class BoltsExecutorsTest : FacebookPowerMockTestCase() {
  companion object {
    private const val IMMEDIATE_MAX_DEPTH = 15
  }
  private lateinit var mockBackgroundExecutor: ExecutorService
  override fun setup() {
    mockBackgroundExecutor = mock()

    val mockBoltsExecutorCompanion = mock<BoltsExecutors.Companion>()
    whenever(mockBoltsExecutorCompanion.background()).thenReturn(mockBackgroundExecutor)
    whenever(mockBoltsExecutorCompanion.immediate()).thenCallRealMethod()
    whenever(mockBoltsExecutorCompanion.scheduled()).thenCallRealMethod()
    Whitebox.setInternalState(BoltsExecutors::class.java, "Companion", mockBoltsExecutorCompanion)
  }

  @Test
  fun `test immediate executor`() {
    val mockCommand = mock<Runnable>()
    BoltsExecutors.immediate().execute(mockCommand)
    verify(mockCommand).run()
  }

  @Test
  fun `test execute more than MAX_DEPTH times on the immediate executor will forward to background`() {
    var counter = 0
    var commandToRepeat: Runnable? = null
    val testCommand = Runnable {
      counter += 1
      // run a fixed amount of times to avoid crash in testing
      if (counter < IMMEDIATE_MAX_DEPTH * 2) {
        commandToRepeat?.let { BoltsExecutors.immediate().execute(it) }
      }
    }
    commandToRepeat = testCommand
    BoltsExecutors.immediate().execute(testCommand)
    assertThat(counter).isEqualTo(IMMEDIATE_MAX_DEPTH)
    verify(mockBackgroundExecutor).execute(testCommand)
  }
}


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

import java.util.concurrent.CancellationException
import org.assertj.core.api.Assertions.assertThat
import org.junit.Assert
import org.junit.Test

class CancellationTest {
  @Test
  fun testTokenIsCancelled() {
    val cts = CancellationTokenSource()
    val token = cts.token
    assertThat(token.isCancellationRequested).isFalse
    assertThat(cts.isCancellationRequested).isFalse
    cts.cancel()
    assertThat(token.isCancellationRequested).isTrue
    assertThat(cts.isCancellationRequested).isTrue
  }

  @Test
  fun testTokenIsCancelledAfterNoDelay() {
    val cts = CancellationTokenSource()
    val token = cts.token
    assertThat(token.isCancellationRequested).isFalse
    cts.cancelAfter(0)
    assertThat(token.isCancellationRequested).isTrue
    assertThat(cts.isCancellationRequested).isTrue
  }

  @Test
  fun testTokenIsCancelledAfterDelay() {
    val cts = CancellationTokenSource()
    val token = cts.token
    assertThat(token.isCancellationRequested).isFalse
    cts.cancelAfter(100)
    assertThat(token.isCancellationRequested).isFalse
    assertThat(cts.isCancellationRequested).isFalse
    Thread.sleep(150)
    assertThat(token.isCancellationRequested).isTrue
    assertThat(cts.isCancellationRequested).isTrue
  }

  @Test
  fun testTokenCancelAfterDelayCancellation() {
    val cts = CancellationTokenSource()
    val token = cts.token
    assertThat(token.isCancellationRequested).isFalse
    cts.cancelAfter(100)
    assertThat(token.isCancellationRequested).isFalse
    assertThat(cts.isCancellationRequested).isFalse
    cts.cancelAfter(-1)
    Thread.sleep(150)
    assertThat(token.isCancellationRequested).isFalse
    assertThat(cts.isCancellationRequested).isFalse
  }

  @Test
  fun testTokenThrowsWhenCancelled() {
    val cts = CancellationTokenSource()
    val token = cts.token
    try {
      token.throwIfCancellationRequested()
    } catch (e: CancellationException) {
      Assert.fail(
          "Token has not been cancelled yet, " +
              CancellationException::class.java.simpleName +
              " should not be thrown")
    }
    cts.cancel()
    try {
      token.throwIfCancellationRequested()
      Assert.fail(CancellationException::class.java.simpleName + " should be thrown")
    } catch (e: CancellationException) {
      // Do nothing
    }
  }

  @Test
  fun testTokenCallsRegisteredActionWhenCancelled() {
    val cts = CancellationTokenSource()
    val token = cts.token
    var result: Any? = null
    token.register { result = "Run" }
    Assert.assertNull(result)
    cts.cancel()
    Assert.assertNotNull(result)
  }

  @Test
  fun testCancelledTokenCallsRegisteredActionImmediately() {
    val cts = CancellationTokenSource()
    val token = cts.token
    var result: Any? = null
    cts.cancel()
    token.register { result = "Run" }
    Assert.assertNotNull(result)
  }

  @Test
  fun testTokenDoesNotCallUnregisteredAction() {
    val cts = CancellationTokenSource()
    val token = cts.token
    var result1: Any? = null
    var result2: Any? = null
    val registration1 = token.register { result1 = "Run!" }
    token.register { result2 = "Run!" }
    registration1.close()
    cts.cancel()
    Assert.assertNull(result1)
    Assert.assertNotNull(result2)
  }
}


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

import com.facebook.FacebookPowerMockTestCase
import org.junit.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify

class CancellationTokenRegistrationTest : FacebookPowerMockTestCase() {
  private lateinit var mockTokenSource: CancellationTokenSource
  private lateinit var mockAction: Runnable

  override fun setup() {
    mockTokenSource = mock()
    mockAction = mock()
  }

  @Test
  fun `test closing registration unregister from token source only once`() {
    val registration = CancellationTokenRegistration(mockTokenSource, mockAction)
    registration.close()
    registration.close()
    verify(mockTokenSource, times(1)).unregister(registration)
  }

  @Test
  fun `test run action will also unregister`() {
    val registration = CancellationTokenRegistration(mockTokenSource, mockAction)
    registration.runAction()
    verify(mockAction).run()
    verify(mockTokenSource).unregister(registration)
  }

  @Test(expected = IllegalStateException::class)
  fun `test run action can only be executed once`() {
    val registration = CancellationTokenRegistration(mockTokenSource, mockAction)
    registration.runAction()
    registration.runAction()
  }
}


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

import com.facebook.FacebookPowerMockTestCase
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.ScheduledFuture
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.powermock.reflect.Whitebox

class CancellationTokenSourceTest : FacebookPowerMockTestCase() {
  @Test(expected = IllegalStateException::class)
  fun `test get token after close`() {
    val tokenSource = CancellationTokenSource()
    tokenSource.close()
    tokenSource.token
  }

  @Test
  fun `test cancel twice`() {
    val mockListener = mock<Runnable>()
    val tokenSource = CancellationTokenSource()
    tokenSource.register(mockListener)
    tokenSource.cancel()
    tokenSource.cancel()
    verify(mockListener, times(1)).run()
    assertThat(tokenSource.isCancellationRequested).isTrue
  }

  @Test
  fun `test unregister a listener`() {
    val mockListener = mock<Runnable>()
    val tokenSource = CancellationTokenSource()
    val registration = tokenSource.register(mockListener)
    tokenSource.unregister(registration)
    tokenSource.cancel()
    verify(mockListener, never()).run()
  }

  @Test
  fun `test cancelAfter`() {
    val mockExecutor = mock<ScheduledExecutorService>()
    var capturedRunnable: Runnable? = null
    whenever(mockExecutor.schedule(any<Runnable>(), any(), any())).then {
      capturedRunnable = it.arguments[0] as Runnable
      mock<ScheduledFuture<Any>>()
    }
    val tokenSource = CancellationTokenSource()
    Whitebox.setInternalState(tokenSource, "executor", mockExecutor)
    val mockListener = mock<Runnable>()

    tokenSource.register(mockListener)
    tokenSource.cancelAfter(1)
    assertThat(capturedRunnable).isNotNull
    capturedRunnable?.run()
    verify(mockListener, times(1)).run()
  }
}


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

import com.facebook.FacebookPowerMockTestCase
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

class CancellationTokenTest : FacebookPowerMockTestCase() {
  private lateinit var mockCancellationTokenSource: CancellationTokenSource
  override fun setup() {
    mockCancellationTokenSource = mock()
  }

  @Test
  fun `test register action on cancellation token`() {
    val token = CancellationToken(mockCancellationTokenSource)
    val mockAction = mock<Runnable>()
    token.register(mockAction)
    verify(mockCancellationTokenSource).register(mockAction)
  }

  @Test
  fun `test checking cancel status`() {
    val token = CancellationToken(mockCancellationTokenSource)
    whenever(mockCancellationTokenSource.isCancellationRequested).thenReturn(false)
    assertThat(token.isCancellationRequested).isFalse
    whenever(mockCancellationTokenSource.isCancellationRequested).thenReturn(true)
    assertThat(token.isCancellationRequested).isTrue
  }
}


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

import com.facebook.FacebookPowerMockTestCase
import java.lang.IllegalStateException
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.powermock.api.mockito.PowerMockito
import org.powermock.core.classloader.annotations.PrepareForTest

@PrepareForTest(TaskCompletionSource::class)
class TaskCompletionSourceTest : FacebookPowerMockTestCase() {
  private lateinit var mockTask: Task<Void>

  override fun setup() {
    mockTask = mock()
    PowerMockito.whenNew(Task::class.java).withNoArguments().thenReturn(mockTask)
  }

  @Test
  fun `test get task`() {
    val source = TaskCompletionSource<Void>()
    assertThat(source.task).isEqualTo(mockTask)
  }

  @Test
  fun `test set result`() {
    whenever(mockTask.trySetResult(any())).thenReturn(true)
    val source = TaskCompletionSource<Void>()
    source.setResult(mock())
    verify(mockTask).trySetResult(any())
  }

  @Test(expected = IllegalStateException::class)
  fun `test set result if task completed`() {
    whenever(mockTask.trySetResult(any())).thenReturn(false)
    val source = TaskCompletionSource<Void>()
    source.setResult(mock())
  }

  @Test
  fun `test set cancelled`() {
    whenever(mockTask.trySetCancelled()).thenReturn(true)
    val source = TaskCompletionSource<Void>()
    source.setCancelled()
    verify(mockTask).trySetCancelled()
  }

  @Test(expected = IllegalStateException::class)
  fun `test set cancelled if task completed`() {
    whenever(mockTask.trySetCancelled()).thenReturn(false)
    val source = TaskCompletionSource<Void>()
    source.setCancelled()
  }

  @Test
  fun `test set error`() {
    whenever(mockTask.trySetError(any())).thenReturn(true)
    val source = TaskCompletionSource<Void>()
    source.setError(mock())
    verify(mockTask).trySetError(any())
  }

  @Test(expected = IllegalStateException::class)
  fun `test set error if task completed`() {
    whenever(mockTask.trySetError(any())).thenReturn(false)
    val source = TaskCompletionSource<Void>()
    source.setError(mock())
  }
}


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

import java.util.concurrent.Callable
import java.util.concurrent.CancellationException
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantLock
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test

class TaskTest {
  private fun runTaskTest(callable: Callable<Task<*>>) {
    try {
      val task = callable.call()
      task.waitForCompletion()
      if (task.isFaulted) {
        val error = task.error
        if (error is RuntimeException) {
          throw error
        }
        throw RuntimeException(error)
      } else if (task.isCancelled) {
        throw RuntimeException(CancellationException())
      }
    } catch (e: Exception) {
      throw RuntimeException(e)
    }
  }

  // runs in a separate method to ensure it is out of scope.
  private fun startFailedTask() {
    Task.call<Any> { throw RuntimeException() }.waitForCompletion()
  }

  /**
   * Launches a given number of tasks (of Integer) that will complete either in a completed,
   * cancelled or faulted state (random distribution). Each task will reach completion after a
   * somehow random delay (between 500 and 600 ms). Each task reaching a success completion state
   * will have its result set to a random Integer (between 0 to 1000).
   *
   * @param numberOfTasksToLaunch The number of tasks to launch
   * @return A collection containing all the tasks that have been launched
   */
  private fun launchTasksWithRandomCompletions(numberOfTasksToLaunch: Int): Collection<Task<Int>> {
    val tasks = ArrayList<Task<Int>>()
    for (i in 0 until numberOfTasksToLaunch) {
      val task =
          Task.callInBackground {
            Thread.sleep((500 + Math.random() * 100).toLong())
            val rand = Math.random()
            if (rand >= 0.7) {
              throw RuntimeException("This task failed.")
            } else if (rand >= 0.4) {
              throw CancellationException()
            }
            (Math.random() * 1_000).toInt()
          }
      tasks.add(task)
    }
    return tasks
  }

  @Test
  fun testCache() {
    assertThat(Task.forResult<Any>(null)).isSameAs(Task.forResult<Any>(null))
    val trueTask = Task.forResult(true)
    assertThat(trueTask.result).isTrue
    assertThat(trueTask).isSameAs(Task.forResult(true))
    val falseTask = Task.forResult(false)
    assertThat(falseTask.result).isFalse
    assertThat(falseTask).isSameAs(Task.forResult(false))
    assertThat(Task.cancelled<Any>()).isSameAs(Task.cancelled<Any>())
  }

  @Test
  fun testPrimitives() {
    val complete = Task.forResult(5)
    val error = Task.forError<Int>(RuntimeException())
    val cancelled = Task.cancelled<Int>()
    assertThat(complete.isCompleted).isTrue
    assertThat(complete.result).isEqualTo(5)
    assertThat(complete.isFaulted).isFalse
    assertThat(complete.isCancelled).isFalse
    assertThat(error.isCompleted).isTrue
    assertThat(error.error is RuntimeException).isTrue
    assertThat(error.isFaulted).isTrue
    assertThat(error.isCancelled).isFalse
    assertThat(cancelled.isCompleted).isTrue
    assertThat(cancelled.isFaulted).isFalse
    assertThat(cancelled.isCancelled).isTrue
  }

  @Test
  fun testDelay() {
    val delayed = Task.delay(200)
    Thread.sleep(50)
    assertThat(delayed.isCompleted).isFalse

    // make sure that all scheduled commands with 200ms in `BoltsExecutors.scheduled()` are cleared
    // before assertions since the current implementation `BoltsExecutors.schedule()` promises that
    // the commands are executed sequentially.
    BoltsExecutors.scheduled().schedule({}, 200, TimeUnit.MILLISECONDS).get()

    assertThat(delayed.isCompleted).isTrue
    assertThat(delayed.isFaulted).isFalse
    assertThat(delayed.isCancelled).isFalse
  }

  @Test
  fun testDelayWithCancelledToken() {
    val cts = CancellationTokenSource()
    cts.cancel()
    val delayed = Task.delay(200, cts.token)
    assertThat(delayed.isCancelled).isTrue
  }

  @Test
  fun testDelayWithToken() {
    val cts = CancellationTokenSource()
    val delayed = Task.delay(200, cts.token)
    assertThat(delayed.isCancelled).isFalse
    cts.cancel()
    assertThat(delayed.isCancelled).isTrue
  }

  @Test
  fun testSynchronousContinuation() {
    val complete = Task.forResult(5)
    val error = Task.forError<Int>(RuntimeException())
    val cancelled = Task.cancelled<Int>()
    complete.continueWith { task ->
      assertThat(task).isEqualTo(complete)
      assertThat(task.isCompleted).isTrue
      assertThat(task.result).isEqualTo(5)
      assertThat(task.isFaulted).isFalse
      assertThat(task.isCancelled).isFalse
      null
    }
    error.continueWith { task ->
      assertThat(task).isEqualTo(error)
      assertThat(task.isCompleted).isTrue
      assertThat(task.error is RuntimeException).isTrue
      assertThat(task.isFaulted).isTrue
      assertThat(task.isCancelled).isFalse
      null
    }
    cancelled.continueWith { task ->
      assertThat(task).isEqualTo(cancelled)
      assertThat(cancelled.isCompleted).isTrue
      assertThat(cancelled.isFaulted).isFalse
      assertThat(cancelled.isCancelled).isTrue
      null
    }
  }

  @Test
  fun testSynchronousChaining() {
    val first = Task.forResult(1)
    val second = first.continueWith { 2 }
    val third = second.continueWithTask { Task.forResult(3) }
    assertThat(first.isCompleted).isTrue
    assertThat(second.isCompleted).isTrue
    assertThat(third.isCompleted).isTrue
    assertThat(first.result).isEqualTo(1)
    assertThat(second.result).isEqualTo(2)
    assertThat(third.result).isEqualTo(3)
  }

  @Test
  fun testSynchronousCancellation() {
    val first = Task.forResult(1)
    val second = first.continueWith { throw CancellationException() }
    assertThat(first.isCompleted).isTrue
    assertThat(second.isCancelled).isTrue
  }

  @Test
  fun testSynchronousContinuationTokenAlreadyCancelled() {
    val cts = CancellationTokenSource()
    var continuationRun = false
    cts.cancel()
    val first = Task.forResult(1)
    val second =
        first.continueWith(
            {
              continuationRun = true
              2
            },
            cts.token)
    assertThat(first.isCompleted).isTrue
    assertThat(second.isCancelled).isTrue
    assertThat(continuationRun).isFalse
  }

  @Test
  fun testSynchronousTaskCancellation() {
    val first = Task.forResult(1)
    val second =
        first.continueWithTask(Continuation<Int, Task<Int>> { throw CancellationException() })
    assertThat(first.isCompleted).isTrue
    assertThat(second.isCancelled).isTrue
  }

  @Test
  fun testBackgroundCall() {
    runTaskTest {
      Task.callInBackground {
            Thread.sleep(100)
            5
          }
          .continueWith { task ->
            assertThat(task.result).isEqualTo(5)
            null
          }
    }
  }

  @Test
  fun testBackgroundCallTokenCancellation() {
    val cts = CancellationTokenSource()
    val ct = cts.token
    var waitingToBeCancelled = false
    val cancelLock = ReentrantLock()
    val cancelCondition = cancelLock.newCondition()
    val task =
        Task.callInBackground {
          cancelLock.lock()
          try {
            waitingToBeCancelled = true
            cancelCondition.await()
          } finally {
            cancelLock.unlock()
          }
          ct.throwIfCancellationRequested()
          5
        }
    while (true) {
      cancelLock.lock()
      try {
        if (waitingToBeCancelled) {
          cts.cancel()
          cancelCondition.signal()
          break
        }
      } finally {
        cancelLock.unlock()
      }
      try {
        Thread.sleep(5)
      } catch (e: InterruptedException) {
        throw RuntimeException(e)
      }
    }
    try {
      task.waitForCompletion()
    } catch (e: InterruptedException) {
      throw RuntimeException(e)
    }
    assertThat(task.isCancelled).isTrue
  }

  @Test
  fun testBackgroundCallTokenAlreadyCancelled() {
    val cts = CancellationTokenSource()
    cts.cancel()
    runTaskTest {
      Task.callInBackground(
              {
                Thread.sleep(100)
                5
              },
              cts.token)
          .continueWith { task ->
            assertThat(task.isCancelled).isTrue
            null
          }
    }
  }

  @Test
  fun testBackgroundCallWaiting() {
    val task =
        Task.callInBackground {
          Thread.sleep(100)
          5
        }
    task.waitForCompletion()
    assertThat(task.isCompleted).isTrue
    assertThat(task.result).isEqualTo(5)
  }

  @Test
  fun testBackgroundCallWaitingWithTimeouts() {
    val sync = ReentrantLock()
    val condition = sync.newCondition()
    val task =
        Task.callInBackground {
          sync.lock()
          try {
            condition.await()
            Thread.sleep(100)
          } finally {
            sync.unlock()
          }
          5
        }
    // wait -> timeout
    assertThat(task.waitForCompletion(100, TimeUnit.MILLISECONDS)).isFalse
    sync.lock()
    try {
      condition.signal()
    } finally {
      sync.unlock()
    }
    // wait -> completes
    assertThat(task.waitForCompletion(1_000, TimeUnit.MILLISECONDS)).isTrue
    // wait -> already completed
    assertThat(task.waitForCompletion(100, TimeUnit.MILLISECONDS)).isTrue
    assertThat(task.result).isEqualTo(5)
  }

  @Test
  fun testBackgroundCallWaitingOnError() {
    val task =
        Task.callInBackground<Int> {
          Thread.sleep(100)
          throw RuntimeException()
        }
    task.waitForCompletion()
    assertThat(task.isCompleted).isTrue
    assertThat(task.isFaulted).isTrue
  }

  @Test
  fun testBackgroundCallWaitOnCancellation() {
    val task =
        Task.callInBackground {
              Thread.sleep(100)
              5
            }
            .continueWithTask(Continuation<Int, Task<Int>> { Task.cancelled() })
    task.waitForCompletion()
    assertThat(task.isCompleted).isTrue
    assertThat(task.isCancelled).isTrue
  }

  @Test
  fun testBackgroundError() {
    runTaskTest {
      Task.callInBackground<Int> { throw IllegalStateException() }
          .continueWith { task ->
            assertThat(task.isFaulted).isTrue
            assertThat(task.error is IllegalStateException).isTrue
            null
          }
    }
  }

  @Test
  fun testBackgroundCancellation() {
    runTaskTest {
      Task.callInBackground<Void> { throw CancellationException() }
          .continueWith { task ->
            assertThat(task.isCancelled).isTrue
            null
          }
    }
  }

  @Suppress("ExplicitGarbageCollectionCall")
  @Test
  fun testUnobservedError() {
    try {
      val lock = ReentrantLock()
      val condition = lock.newCondition()
      Task.setUnobservedExceptionHandler { t, e ->
        lock.lock()
        try {
          condition.signal()
        } finally {
          lock.unlock()
        }
      }

      lock.lock()
      try {
        startFailedTask()
        // Intentionally call GC to invoke unobserved exception handler
        System.gc()
        condition.await()
      } finally {
        lock.unlock()
      }
    } finally {
      Task.setUnobservedExceptionHandler(null)
    }
  }

  @Test
  fun testWhenAllNoTasks() {
    val task = Task.whenAll(ArrayList<Task<Void>>())
    assertThat(task.isCompleted).isTrue
    assertThat(task.isCancelled).isFalse
    assertThat(task.isFaulted).isFalse
  }

  @Test
  fun testWhenAnyResultFirstSuccess() {
    runTaskTest {
      val tasks = ArrayList<Task<Int>>()
      val firstToCompleteSuccess =
          Task.callInBackground {
            Thread.sleep(50)
            10
          }
      tasks.addAll(launchTasksWithRandomCompletions(5))
      tasks.add(firstToCompleteSuccess)
      tasks.addAll(launchTasksWithRandomCompletions(5))
      Task.whenAnyResult(tasks).continueWith { task ->
        assertThat(task.isCompleted).isTrue
        assertThat(task.isFaulted).isFalse
        assertThat(task.isCancelled).isFalse
        assertThat(task.result).isEqualTo(firstToCompleteSuccess)
        assertThat(task.result?.isCompleted).isTrue
        assertThat(task.result?.isCancelled).isFalse
        assertThat(task.result?.isFaulted).isFalse
        assertThat(task.result?.result).isEqualTo(10)
        null
      }
    }
  }

  @Test
  fun testWhenAnyFirstSuccess() {
    runTaskTest {
      val tasks = ArrayList<Task<*>>()
      val firstToCompleteSuccess =
          Task.callInBackground {
            Thread.sleep(50)
            "SUCCESS"
          }
      tasks.addAll(launchTasksWithRandomCompletions(5))
      tasks.add(firstToCompleteSuccess)
      tasks.addAll(launchTasksWithRandomCompletions(5))
      Task.whenAny(tasks).continueWith { task ->
        assertThat(task.isCompleted).isTrue
        assertThat(task.isFaulted).isFalse
        assertThat(task.isCancelled).isFalse
        assertThat(task.result).isEqualTo(firstToCompleteSuccess)
        assertThat(task.result?.isCompleted).isTrue
        assertThat(task.result?.isCancelled).isFalse
        assertThat(task.result?.isFaulted).isFalse
        assertThat(task.result?.result).isEqualTo("SUCCESS")
        null
      }
    }
  }

  @Test
  fun testWhenAnyResultFirstError() {
    val error: Exception = RuntimeException("This task failed.")
    runTaskTest {
      val tasks = ArrayList<Task<Int>>()
      val firstToCompleteError =
          Task.callInBackground<Int> {
            Thread.sleep(50)
            throw error
          }
      tasks.addAll(launchTasksWithRandomCompletions(5))
      tasks.add(firstToCompleteError)
      tasks.addAll(launchTasksWithRandomCompletions(5))
      Task.whenAnyResult(tasks).continueWith { task ->
        assertThat(task.isCompleted).isTrue
        assertThat(task.isFaulted).isFalse
        assertThat(task.isCancelled).isFalse
        assertThat(task.result).isEqualTo(firstToCompleteError)
        assertThat(task.result?.isCompleted).isTrue
        assertThat(task.result?.isCancelled).isFalse
        assertThat(task.result?.isFaulted).isTrue
        assertThat(task.result?.error).isEqualTo(error)
        null
      }
    }
  }

  @Test
  fun testWhenAnyFirstError() {
    val error: Exception = RuntimeException("This task failed.")
    runTaskTest {
      val tasks = ArrayList<Task<*>>()
      val firstToCompleteError =
          Task.callInBackground<String> {
            Thread.sleep(50)
            throw error
          }
      tasks.addAll(launchTasksWithRandomCompletions(5))
      tasks.add(firstToCompleteError)
      tasks.addAll(launchTasksWithRandomCompletions(5))
      Task.whenAny(tasks).continueWith { task ->
        assertThat(task.isCompleted).isTrue
        assertThat(task.isFaulted).isFalse
        assertThat(task.isCancelled).isFalse
        assertThat(task.result).isEqualTo(firstToCompleteError)
        assertThat(task.result?.isCompleted).isTrue
        assertThat(task.result?.isCancelled).isFalse
        assertThat(task.result?.isFaulted).isTrue
        assertThat(task.result?.error).isEqualTo(error)
        null
      }
    }
  }

  @Test
  fun testWhenAnyResultFirstCancelled() {
    runTaskTest {
      val tasks = ArrayList<Task<Int>>()
      val firstToCompleteCancelled =
          Task.callInBackground<Int> {
            Thread.sleep(50)
            throw CancellationException()
          }
      tasks.addAll(launchTasksWithRandomCompletions(5))
      tasks.add(firstToCompleteCancelled)
      tasks.addAll(launchTasksWithRandomCompletions(5))
      Task.whenAnyResult(tasks).continueWith { task ->
        assertThat(task.isCompleted).isTrue
        assertThat(task.isFaulted).isFalse
        assertThat(task.isCancelled).isFalse
        assertThat(task.result).isEqualTo(firstToCompleteCancelled)
        assertThat(task.result?.isCompleted).isTrue
        assertThat(task.result?.isCancelled).isTrue
        assertThat(task.result?.isFaulted).isFalse
        null
      }
    }
  }

  @Test
  fun testWhenAnyFirstCancelled() {
    runTaskTest {
      val tasks = ArrayList<Task<*>>()
      val firstToCompleteCancelled =
          Task.callInBackground<String> {
            Thread.sleep(50)
            throw CancellationException()
          }
      tasks.addAll(launchTasksWithRandomCompletions(5))
      tasks.add(firstToCompleteCancelled)
      tasks.addAll(launchTasksWithRandomCompletions(5))
      Task.whenAny(tasks).continueWith { task ->
        assertThat(task.isCompleted).isTrue
        assertThat(task.isFaulted).isFalse
        assertThat(task.isCancelled).isFalse
        assertThat(task.result).isEqualTo(firstToCompleteCancelled)
        assertThat(task.result?.isCompleted).isTrue
        assertThat(task.result?.isCancelled).isTrue
        assertThat(task.result?.isFaulted).isFalse
        null
      }
    }
  }

  @Test
  fun testWhenAllSuccess() {
    runTaskTest {
      val tasks = ArrayList<Task<Void>>()
      for (i in 0..19) {
        val task =
            Task.callInBackground<Void> {
              Thread.sleep((Math.random() * 100).toLong())
              null
            }
        tasks.add(task)
      }
      Task.whenAll(tasks).continueWith { task ->
        assertThat(task.isCompleted).isTrue
        assertThat(task.isFaulted).isFalse
        assertThat(task.isCancelled).isFalse
        for (t in tasks) {
          assertThat(t.isCompleted).isTrue
        }
        null
      }
    }
  }

  @Test
  fun testWhenAllOneError() {
    val error: Exception = RuntimeException("This task failed.")
    runTaskTest {
      val tasks = ArrayList<Task<Void>>()
      for (i in 0..19) {
        val task =
            Task.callInBackground<Void> {
              Thread.sleep((Math.random() * 100).toLong())
              if (i == 10) {
                throw error
              }
              null
            }
        tasks.add(task)
      }
      Task.whenAll(tasks).continueWith { task ->
        assertThat(task.isCompleted).isTrue
        assertThat(task.isFaulted).isTrue
        assertThat(task.isCancelled).isFalse
        assertThat(task.error is AggregateException).isFalse
        assertThat(task.error).isEqualTo(error)
        for (t in tasks) {
          assertThat(t.isCompleted).isTrue
        }
        null
      }
    }
  }

  @Test
  fun testWhenAllCancel() {
    runTaskTest {
      val tasks = ArrayList<Task<Void?>>()
      for (i in 0..19) {
        val tcs = TaskCompletionSource<Void?>()
        Task.callInBackground<Void> {
          Thread.sleep((Math.random() * 100).toLong())
          if (i == 10) {
            tcs.setCancelled()
          } else {
            tcs.setResult(null)
          }
          null
        }
        tasks.add(tcs.task)
      }
      Task.whenAll(tasks).continueWith { task ->
        assertThat(task.isCompleted).isTrue
        assertThat(task.isFaulted).isFalse
        assertThat(task.isCancelled).isTrue
        for (t in tasks) {
          assertThat(t.isCompleted).isTrue
        }
        null
      }
    }
  }

  @Test
  fun testWhenAllResultNoTasks() {
    val task = Task.whenAllResult(ArrayList<Task<Void>>())
    assertThat(task.isCompleted).isTrue
    assertThat(task.isCancelled).isFalse
    assertThat(task.isFaulted).isFalse
    assertThat(task.result?.isEmpty()).isTrue
  }

  @Test
  fun testWhenAllResultSuccess() {
    runTaskTest {
      val tasks: MutableList<Task<Int>> = ArrayList()
      for (i in 0..19) {
        val number = i + 1
        val task =
            Task.callInBackground {
              Thread.sleep((Math.random() * 100).toLong())
              number
            }
        tasks.add(task)
      }
      Task.whenAllResult(tasks).continueWith { task ->
        assertThat(task.isCompleted).isTrue
        assertThat(task.isFaulted).isFalse
        assertThat(task.isCancelled).isFalse
        assertThat(task.result?.size).isEqualTo(tasks.size)
        for (i in tasks.indices) {
          val t = tasks[i]
          assertThat(t.isCompleted).isTrue
          assertThat(t.result)?.isEqualTo(task.result?.get(i))
        }
        null
      }
    }
  }

  @Test
  fun testAsyncChaining() {
    runTaskTest {
      val sequence = ArrayList<Int>()
      var result = Task.forResult<Void?>(null)
      for (i in 0..19) {
        result =
            result.continueWithTask {
              Task.callInBackground {
                sequence.add(i)
                null
              }
            }
      }
      result =
          result.continueWith {
            assertThat(sequence.size).isEqualTo(20)
            for (i in 0..19) {
              assertThat(sequence[i]).isEqualTo(i)
            }
            null
          }
      result
    }
  }

  @Test
  fun testOnSuccess() {
    val continuation: Continuation<Int, Int> =
        Continuation<Int, Int> { task -> checkNotNull(task.result) + 1 }
    val complete = Task.forResult(5).onSuccess(continuation)
    val error = Task.forError<Int>(IllegalStateException()).onSuccess(continuation)
    val cancelled = Task.cancelled<Int>().onSuccess(continuation)
    assertThat(complete.isCompleted).isTrue
    assertThat(complete.result).isEqualTo(6)
    assertThat(complete.isFaulted).isFalse
    assertThat(complete.isCancelled).isFalse
    assertThat(error.isCompleted).isTrue
    assertThat(error.error is RuntimeException).isTrue
    assertThat(error.isFaulted).isTrue
    assertThat(error.isCancelled).isFalse
    assertThat(cancelled.isCompleted).isTrue
    assertThat(cancelled.isFaulted).isFalse
    assertThat(cancelled.isCancelled).isTrue
  }

  @Test
  fun testOnSuccessTask() {
    val continuation: Continuation<Int, Task<Int>> =
        Continuation<Int, Task<Int>> { task -> Task.forResult(checkNotNull(task.result) + 1) }
    val complete = Task.forResult(5).onSuccessTask(continuation)
    val error = Task.forError<Int>(IllegalStateException()).onSuccessTask(continuation)
    val cancelled = Task.cancelled<Int>().onSuccessTask(continuation)
    assertThat(complete.isCompleted).isTrue
    assertThat(complete.result).isEqualTo(6)
    assertThat(complete.isFaulted).isFalse
    assertThat(complete.isCancelled).isFalse
    assertThat(error.isCompleted).isTrue
    assertThat(error.error is RuntimeException).isTrue
    assertThat(error.isFaulted).isTrue
    assertThat(error.isCancelled).isFalse
    assertThat(cancelled.isCompleted).isTrue
    assertThat(cancelled.isFaulted).isFalse
    assertThat(cancelled.isCancelled).isTrue
  }

  @Test
  fun testContinueWhile() {
    val count = AtomicInteger(0)
    runTaskTest {
      Task.forResult<Any>(null)
          .continueWhile({ count.get() < 10 }) {
            count.incrementAndGet()
            null
          }
          .continueWith {
            assertThat(count.get()).isEqualTo(10)
            null
          }
    }
  }

  @Test
  fun testContinueWhileAsync() {
    val count = AtomicInteger(0)
    runTaskTest {
      Task.forResult<Any>(null)
          .continueWhile(
              { count.get() < 10 },
              {
                count.incrementAndGet()
                null
              },
              Executors.newCachedThreadPool())
          .continueWith {
            assertThat(count.get()).isEqualTo(10)
            null
          }
    }
  }

  @Test
  fun testContinueWhileAsyncCancellation() {
    val count = AtomicInteger(0)
    val cts = CancellationTokenSource()
    runTaskTest {
      Task.forResult<Any>(null)
          .continueWhile(
              { count.get() < 10 },
              {
                if (count.incrementAndGet() == 5) {
                  cts.cancel()
                }
                null
              },
              Executors.newCachedThreadPool(),
              cts.token)
          .continueWith { task ->
            assertThat(task.isCancelled).isTrue
            assertThat(count.get()).isEqualTo(5)
            null
          }
    }
  }

  @Test
  fun testCallWithBadExecutor() {
    val exception = RuntimeException("BAD EXECUTORS")
    Task.call({ 1 }) { throw exception }
        .continueWith { task ->
          assertThat(task.isFaulted).isTrue
          assertThat(task.error is ExecutorException).isTrue
          assertThat(task.error?.cause).isEqualTo(exception)
          null
        }
  }

  @Test
  fun testContinueWithBadExecutor() {
    val exception = RuntimeException("BAD EXECUTORS")
    Task.call { 1 }
        .continueWith({ task -> task.result }) { throw exception }
        .continueWith { task ->
          assertThat(task.isFaulted).isTrue
          assertThat(task.error is ExecutorException).isTrue
          assertThat(task.error?.cause).isEqualTo(exception)
          null
        }
  }

  @Test
  fun testContinueWithTaskAndBadExecutor() {
    val exception = RuntimeException("BAD EXECUTORS")
    Task.call { 1 }
        .continueWithTask({ task -> task }) { throw exception }
        .continueWith { task ->
          assertThat(task.isFaulted).isTrue
          assertThat(task.error is ExecutorException).isTrue
          assertThat(task.error?.cause).isEqualTo(exception)
          null
        }
  }

  // region TaskCompletionSource
  @Test
  fun testTrySetResult() {
    val tcs = TaskCompletionSource<String>()
    val task = tcs.task
    assertThat(task.isCompleted).isFalse
    val success = tcs.trySetResult("SHOW ME WHAT YOU GOT")
    assertThat(success).isTrue
    assertThat(task.isCompleted).isTrue
    assertThat(task.result).isEqualTo("SHOW ME WHAT YOU GOT")
  }

  @Test
  fun testTrySetError() {
    val tcs = TaskCompletionSource<Void>()
    val task = tcs.task
    assertThat(task.isCompleted).isFalse
    val exception: Exception = RuntimeException("DISQUALIFIED")
    val success = tcs.trySetError(exception)
    assertThat(success).isTrue
    assertThat(task.isCompleted).isTrue
    assertThat(task.error).isEqualTo(exception)
  }

  @Test
  fun testTrySetCanceled() {
    val tcs = TaskCompletionSource<Void>()
    val task = tcs.task
    assertThat(task.isCompleted).isFalse
    val success = tcs.trySetCancelled()
    assertThat(success).isTrue
    assertThat(task.isCompleted).isTrue
    assertThat(task.isCancelled).isTrue
  }

  @Test
  fun testTrySetOnCompletedTask() {
    val tcs = TaskCompletionSource<Void?>()
    tcs.setResult(null)
    assertThat(tcs.trySetResult(null)).isFalse
    assertThat(tcs.trySetError(RuntimeException())).isFalse
    assertThat(tcs.trySetCancelled()).isFalse
  }

  @Test(expected = IllegalStateException::class)
  fun testSetResultOnCompletedTask() {
    val tcs = TaskCompletionSource<Void?>()
    tcs.setResult(null)
    tcs.setResult(null)
  }

  @Test(expected = IllegalStateException::class)
  fun testSetErrorOnCompletedTask() {
    val tcs = TaskCompletionSource<Void?>()
    tcs.setResult(null)
    tcs.setError(RuntimeException())
  }

  @Test(expected = IllegalStateException::class)
  fun testSetCancelledOnCompletedTask() {
    val tcs = TaskCompletionSource<Void?>()
    tcs.setResult(null)
    tcs.setCancelled()
  }
}


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

import com.facebook.FacebookPowerMockTestCase
import java.lang.RuntimeException
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

class UnobservedErrorNotifierTest : FacebookPowerMockTestCase() {
  private lateinit var mockTask: Task<*>
  private lateinit var testException: Exception
  private lateinit var notifier: UnobservedErrorNotifier

  override fun setup() {
    super.setup()
    mockTask = mock()
    testException = RuntimeException("test exception")
    whenever(mockTask.error).thenReturn(testException)
    notifier = UnobservedErrorNotifier(mockTask)
  }

  @Test
  fun `test notifier call unobserved exception handler`() {
    val unobservedExceptionHandler = mock<Task.UnobservedExceptionHandler>()
    Task.setUnobservedExceptionHandler(unobservedExceptionHandler)
    notifier.finalize()
    verify(unobservedExceptionHandler).unobservedException(eq(mockTask), any())
  }

  @Test
  fun `test when unobserved exception handler is null`() {
    Task.setUnobservedExceptionHandler(null)
    notifier.finalize()
  }

  @Test
  fun `test after setObserved no handler will be called`() {
    notifier.setObserved()
    val unobservedExceptionHandler = mock<Task.UnobservedExceptionHandler>()
    Task.setUnobservedExceptionHandler(unobservedExceptionHandler)
    notifier.finalize()
    verify(unobservedExceptionHandler, never()).unobservedException(eq(mockTask), any())
  }
}


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

package com.facebook

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

/**
 * This class can be extended to receive notifications of access token changes. The {@link
 * #stopTracking()} method should be called in the onDestroy() method of the receiving Activity or
 * Fragment.
 */
abstract class AccessTokenTracker {
  private val receiver: BroadcastReceiver
  private val broadcastManager: LocalBroadcastManager

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

  /**
   * The method that will be called with the access token changes.
   *
   * @param oldAccessToken The access token before the change.
   * @param currentAccessToken The new access token.
   */
  protected abstract fun onCurrentAccessTokenChanged(
      oldAccessToken: AccessToken?,
      currentAccessToken: AccessToken?
  )

  /** The constructor. */
  init {
    Validate.sdkInitialized()
    receiver = CurrentAccessTokenBroadcastReceiver()
    broadcastManager = LocalBroadcastManager.getInstance(FacebookSdk.getApplicationContext())
    startTracking()
  }

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

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

  private inner class CurrentAccessTokenBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
      if (AccessTokenManager.ACTION_CURRENT_ACCESS_TOKEN_CHANGED == intent.action) {
        logd(TAG, "AccessTokenChanged")
        val oldAccessToken: AccessToken? =
            intent.getParcelableExtra(AccessTokenManager.EXTRA_OLD_ACCESS_TOKEN)
        val newAccessToken: AccessToken? =
            intent.getParcelableExtra(AccessTokenManager.EXTRA_NEW_ACCESS_TOKEN)
        onCurrentAccessTokenChanged(oldAccessToken, newAccessToken)
      }
    }
  }

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

  companion object {
    private val TAG = AccessTokenTracker::class.java.simpleName
  }
}


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

package com.facebook

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Parcelable
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.facebook.internal.Utility
import com.facebook.internal.Validate

/**
 * This class can be extended to receive notifications of authentication token changes. The {@link
 * #stopTracking()} method should be called in the onDestroy() method of the receiving Activity or
 * Fragment.
 */
abstract class AuthenticationTokenTracker {
  private val receiver: BroadcastReceiver
  private val broadcastManager: LocalBroadcastManager

  /** The constructor init */
  init {
    Validate.sdkInitialized()
    receiver = CurrentAuthenticationTokenBroadcastReceiver()
    broadcastManager = LocalBroadcastManager.getInstance(FacebookSdk.getApplicationContext())
    startTracking()
  }

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

  /**
   * The method that will be called with the authentication token changes.
   *
   * @param oldAuthenticationToken The authentication token before the change.
   * @param currentAuthenticationToken The new authentication token.
   */
  protected abstract fun onCurrentAuthenticationTokenChanged(
      oldAuthenticationToken: AuthenticationToken?,
      currentAuthenticationToken: AuthenticationToken?
  )

  /** Starts tracking the current authentication token */
  fun startTracking() {
    if (isTracking) {
      return
    }

    addBroadcastReceiver()
    isTracking = true
  }

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

  private inner class CurrentAuthenticationTokenBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
      if (AuthenticationTokenManager.ACTION_CURRENT_AUTHENTICATION_TOKEN_CHANGED == intent.action) {
        Utility.logd(TAG, "AuthenticationTokenChanged")
        val oldAuthenticationToken =
            intent.getParcelableExtra<Parcelable>(
                AuthenticationTokenManager.EXTRA_OLD_AUTHENTICATION_TOKEN) as AuthenticationToken?
        val newAuthenticationToken =
            intent.getParcelableExtra<Parcelable>(
                AuthenticationTokenManager.EXTRA_NEW_AUTHENTICATION_TOKEN) as AuthenticationToken?
        onCurrentAuthenticationTokenChanged(oldAuthenticationToken, newAuthenticationToken)
      }
    }
  }

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

  companion object {
    private val TAG = AuthenticationTokenTracker::class.java.simpleName
  }
}


/*
 * 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.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import androidx.localbroadcastmanager.content.LocalBroadcastManager

/*
 * Login from a custom tab redirects here. Pass the url on to CustomTabMainActivity so it can return
 * the result.
 */
class CustomTabActivity : Activity() {
  private var closeReceiver: BroadcastReceiver? = null
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val intent = Intent(this, CustomTabMainActivity::class.java)
    intent.action = CUSTOM_TAB_REDIRECT_ACTION
    intent.putExtra(CustomTabMainActivity.EXTRA_URL, getIntent().dataString)

    // these flags will open CustomTabMainActivity from the back stack as well as closing this
    // activity and the custom tab opened by CustomTabMainActivity.
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
    startActivityForResult(intent, CUSTOM_TAB_REDIRECT_REQUEST_CODE)
  }

  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (resultCode == RESULT_CANCELED) {
      // We weren't able to open CustomTabMainActivity from the back stack. Send a broadcast
      // instead.
      val broadcast = Intent(CUSTOM_TAB_REDIRECT_ACTION)
      broadcast.putExtra(CustomTabMainActivity.EXTRA_URL, intent.dataString)
      LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast)

      // Wait for the custom tab to be removed from the back stack before finishing.
      val closeReceiver =
          object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
              finish()
            }
          }
      LocalBroadcastManager.getInstance(this)
          .registerReceiver(closeReceiver, IntentFilter(DESTROY_ACTION))
      this.closeReceiver = closeReceiver
    }
  }

  override fun onDestroy() {
    closeReceiver?.let { LocalBroadcastManager.getInstance(this).unregisterReceiver(it) }
    super.onDestroy()
  }

  companion object {
    private const val CUSTOM_TAB_REDIRECT_REQUEST_CODE = 2

    @JvmField
    val CUSTOM_TAB_REDIRECT_ACTION =
        CustomTabActivity::class.java.simpleName + ".action_customTabRedirect"

    @JvmField val DESTROY_ACTION = CustomTabActivity::class.java.simpleName + ".action_destroy"
  }
}


/*
 * 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.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Bundle
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.facebook.internal.CustomTab
import com.facebook.internal.InstagramCustomTab
import com.facebook.internal.NativeProtocol.createProtocolResultIntent
import com.facebook.internal.Utility.parseUrlQueryString
import com.facebook.login.LoginTargetApp
import com.facebook.login.LoginTargetApp.Companion.fromString

class CustomTabMainActivity : Activity() {
  private var shouldCloseCustomTab = true
  private var redirectReceiver: BroadcastReceiver? = null

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

    // Custom Tab Redirects should not be creating a new instance of this activity
    if (CustomTabActivity.CUSTOM_TAB_REDIRECT_ACTION == intent.action) {
      setResult(RESULT_CANCELED)
      finish()
      return
    }

    if (savedInstanceState == null) {
      val action = intent.getStringExtra(EXTRA_ACTION) ?: return
      val parameters = intent.getBundleExtra(EXTRA_PARAMS)
      val chromePackage = intent.getStringExtra(EXTRA_CHROME_PACKAGE)
      val targetApp = fromString(intent.getStringExtra(EXTRA_TARGET_APP))
      val customTab: CustomTab =
          when (targetApp) {
            LoginTargetApp.INSTAGRAM -> InstagramCustomTab(action, parameters)
            else -> CustomTab(action, parameters)
          }
      val couldOpenCustomTab = customTab.openCustomTab(this, chromePackage)
      shouldCloseCustomTab = false
      if (!couldOpenCustomTab) {
        setResult(RESULT_CANCELED, intent.putExtra(NO_ACTIVITY_EXCEPTION, true))
        finish()
        return
      }

      // This activity will receive a broadcast if it can't be opened from the back stack
      val redirectReceiver =
          object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
              // Remove the custom tab on top of this activity.
              val newIntent = Intent(this@CustomTabMainActivity, CustomTabMainActivity::class.java)
              newIntent.action = REFRESH_ACTION
              newIntent.putExtra(EXTRA_URL, intent.getStringExtra(EXTRA_URL))
              newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
              startActivity(newIntent)
            }
          }
      this.redirectReceiver = redirectReceiver
      LocalBroadcastManager.getInstance(this)
          .registerReceiver(
              redirectReceiver, IntentFilter(CustomTabActivity.CUSTOM_TAB_REDIRECT_ACTION))
    }
  }

  override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    if (REFRESH_ACTION == intent.action) {
      // The custom tab is now destroyed so we can finish the redirect activity
      val broadcast = Intent(CustomTabActivity.DESTROY_ACTION)
      LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast)
      sendResult(RESULT_OK, intent)
    } else if (CustomTabActivity.CUSTOM_TAB_REDIRECT_ACTION == intent.action) {
      // We have successfully redirected back to this activity. Return the result and close.
      sendResult(RESULT_OK, intent)
    }
  }

  override fun onResume() {
    super.onResume()
    if (shouldCloseCustomTab) {
      // The custom tab was closed without getting a result.
      sendResult(RESULT_CANCELED, null)
    }
    shouldCloseCustomTab = true
  }

  private fun sendResult(resultCode: Int, resultIntent: Intent?) {
    redirectReceiver?.let {
      LocalBroadcastManager.getInstance(this@CustomTabMainActivity).unregisterReceiver(it)
    }
    if (resultIntent != null) {
      val responseURL = resultIntent.getStringExtra(EXTRA_URL)
      val results = if (responseURL != null) parseResponseUri(responseURL) else Bundle()
      val nativeProtocolResultIntent = createProtocolResultIntent(intent, results, null)
      setResult(resultCode, nativeProtocolResultIntent ?: resultIntent)
    } else {
      setResult(resultCode, createProtocolResultIntent(intent, null, null))
    }
    finish()
  }

  companion object {
    @JvmField val EXTRA_ACTION = CustomTabMainActivity::class.java.simpleName + ".extra_action"
    @JvmField val EXTRA_PARAMS = CustomTabMainActivity::class.java.simpleName + ".extra_params"
    @JvmField
    val EXTRA_CHROME_PACKAGE = CustomTabMainActivity::class.java.simpleName + ".extra_chromePackage"
    @JvmField val EXTRA_URL = CustomTabMainActivity::class.java.simpleName + ".extra_url"
    @JvmField
    val EXTRA_TARGET_APP = CustomTabMainActivity::class.java.simpleName + ".extra_targetApp"
    @JvmField val REFRESH_ACTION = CustomTabMainActivity::class.java.simpleName + ".action_refresh"
    @JvmField
    val NO_ACTIVITY_EXCEPTION =
        CustomTabMainActivity::class.java.simpleName + ".no_activity_exception"

    private fun parseResponseUri(urlString: String): Bundle {
      val uri = Uri.parse(urlString)
      val bundle = parseUrlQueryString(uri.query)
      bundle.putAll(parseUrlQueryString(uri.fragment))
      return bundle
    }
  }
}


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

package com.facebook

import android.content.res.Configuration
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.facebook.FacebookSdk.isInitialized
import com.facebook.FacebookSdk.sdkInitialize
import com.facebook.common.R
import com.facebook.internal.FacebookDialogFragment
import com.facebook.internal.NativeProtocol.createProtocolResultIntent
import com.facebook.internal.NativeProtocol.getExceptionFromErrorData
import com.facebook.internal.NativeProtocol.getMethodArgumentsFromIntent
import com.facebook.internal.Utility.logd
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import com.facebook.internal.logging.dumpsys.EndToEndDumper
import com.facebook.login.LoginFragment
import java.io.FileDescriptor
import java.io.PrintWriter

/**
 * This Activity is a necessary part of the overall Facebook SDK, but is not meant to be used
 * directly. Add this Activity to your AndroidManifest.xml to ensure proper handling of Facebook SDK
 * features.
 *
 * <pre>`<activity android:name="com.facebook.FacebookActivity"
 * android:theme="@android:style/Theme.Translucent.NoTitleBar"
 * android:configChanges="keyboard|keyboardHidden|screenLayout|screenSize|orientation"
 * android:label="@string/app_name" /> `</pre> *
 *
 * Do not start this activity directly.
 */
open class FacebookActivity : FragmentActivity() {
  var currentFragment: Fragment? = null
    private set

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

    // Some apps using this sdk don't put the sdk initialize code in the application
    // on create method. This can cause issues when opening this activity after an application
    // has been killed since the sdk won't be initialized. Attempt to initialize the sdk
    // here if it hasn't already been initialized.
    if (!isInitialized()) {
      logd(
          TAG,
          "Facebook SDK not initialized. Make sure you call sdkInitialize inside " +
              "your Application's onCreate method.")
      sdkInitialize(applicationContext)
    }
    setContentView(R.layout.com_facebook_activity_layout)
    if (PASS_THROUGH_CANCEL_ACTION == intent.action) {
      handlePassThroughError()
      return
    }
    currentFragment = getFragment()
  }

  protected open fun getFragment(): Fragment {
    val intent = intent
    val manager = supportFragmentManager
    var fragment = manager.findFragmentByTag(FRAGMENT_TAG)
    if (fragment == null) {
      if (FacebookDialogFragment.TAG == intent.action) {
        val dialogFragment = FacebookDialogFragment()
        dialogFragment.retainInstance = true
        dialogFragment.show(manager, FRAGMENT_TAG)
        fragment = dialogFragment
      } else {
        fragment = LoginFragment()
        fragment.setRetainInstance(true)
        manager
            .beginTransaction()
            .add(R.id.com_facebook_fragment_container, fragment, FRAGMENT_TAG)
            .commit()
      }
    }
    return fragment
  }

  override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)
    currentFragment?.onConfigurationChanged(newConfig)
  }

  private fun handlePassThroughError() {
    val requestIntent = intent

    // The error we need to respond with is passed to us as method arguments.
    val errorResults = getMethodArgumentsFromIntent(requestIntent)
    val exception = getExceptionFromErrorData(errorResults)
    val resultIntent = createProtocolResultIntent(intent, null, exception)
    setResult(RESULT_CANCELED, resultIntent)
    finish()
  }

  @AutoHandleExceptions
  override fun dump(
      prefix: String,
      fd: FileDescriptor?,
      writer: PrintWriter,
      args: Array<String>?
  ) {
    if (EndToEndDumper.instance?.maybeDump(prefix, writer, args) == true) {
      return
    }
    super.dump(prefix, fd, writer, args)
  }

  companion object {
    const val PASS_THROUGH_CANCEL_ACTION = "PassThrough"
    private const val FRAGMENT_TAG = "SingleFragment"
    private val TAG = FacebookActivity::class.java.name
  }
}


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

package com.facebook

/** An Exception indicating that Login failed. */
class FacebookAuthorizationException : FacebookException {
  /** Constructs a FacebookAuthorizationException with no additional information. */
  constructor() : super() {}

  /**
   * Constructs a FacebookAuthorizationException with a message.
   *
   * @param message A String to be returned from getMessage.
   */
  constructor(message: String?) : super(message) {}

  /**
   * Constructs a FacebookAuthorizationException with a message and inner error.
   *
   * @param message A String to be returned from getMessage.
   * @param throwable A Throwable to be returned from getCause.
   */
  constructor(message: String?, throwable: Throwable?) : super(message, throwable) {}

  /**
   * Constructs a FacebookAuthorizationException with an inner error.
   *
   * @param throwable A Throwable to be returned from getCause.
   */
  constructor(throwable: Throwable?) : super(throwable) {}

  companion object {
    const val serialVersionUID: Long = 1
  }
}


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

package com.facebook

import android.annotation.SuppressLint
import android.app.Activity
import android.app.Fragment
import android.content.Context
import android.content.ContextWrapper
import android.graphics.Canvas
import android.graphics.Typeface
import android.util.AttributeSet
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.widget.Button
import androidx.activity.result.ActivityResultRegistryOwner
import androidx.core.content.ContextCompat
import com.facebook.appevents.InternalAppEventsLogger
import com.facebook.internal.FragmentWrapper
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions

/** A base class for a facebook button. */
@AutoHandleExceptions
@SuppressLint("ResourceType")
abstract class FacebookButtonBase
protected constructor(
    context: Context,
    attrs: AttributeSet?,
    defStyleAttr: Int,
    defStyleRes: Int,
    analyticsButtonCreatedEventName: String,
    analyticsButtonTappedEventName: String
) : Button(context, attrs, 0) {
  /** Gets the logger eventName when login button is created. */
  protected val analyticsButtonCreatedEventName: String

  /** The logger eventName when login button is tap. */
  protected val analyticsButtonTappedEventName: String
  private var externalOnClickListener: OnClickListener? = null
  private var internalOnClickListener: OnClickListener? = null
  private var overrideCompoundPadding = false
  private var overrideCompoundPaddingLeft = 0
  private var overrideCompoundPaddingRight = 0
  private var parentFragment: FragmentWrapper? = null
  protected abstract val defaultRequestCode: Int

  /** The native fragment that contains this control. */
  val nativeFragment: Fragment?
    get() = parentFragment?.nativeFragment

  /**
   * Sets the fragment that contains this control. This allows the button to be embedded inside a
   * native Fragment, and will allow the fragment to receive the [onActivityResult]
   * [Fragment.onActivityResult] call rather than the Activity.
   *
   * @param fragment the android.app.Fragment that contains this control
   */
  fun setFragment(fragment: Fragment) {
    parentFragment = FragmentWrapper(fragment)
  }

  /**
   * Gets the AndroidX fragment that contains this control.
   *
   * @return The android.app.Fragment that contains this control.
   */
  val fragment: androidx.fragment.app.Fragment?
    get() = parentFragment?.supportFragment

  /**
   * Set the fragment that contains this control. This allows the button to be embedded inside an
   * AndroidX Fragment, and will allow the fragment to receive the [onActivityResult]
   * [Fragment.onActivityResult] call rather than the Activity.
   *
   * @param fragment the androidx.fragment.app.Fragment that contains this control
   */
  fun setFragment(fragment: androidx.fragment.app.Fragment) {
    parentFragment = FragmentWrapper(fragment)
  }

  /**
   * Get the context of activity result register owner that controls this button. Return null if the
   * context is not available.
   */
  val androidxActivityResultRegistryOwner: ActivityResultRegistryOwner?
    get() {
      val activity = activity
      return if (activity is ActivityResultRegistryOwner) {
        activity
      } else null
    }

  override fun setOnClickListener(l: OnClickListener?) {
    externalOnClickListener = l
  }

  /** Returns the request code used for this Button. */
  open val requestCode: Int
    get() = defaultRequestCode

  override fun onAttachedToWindow() {
    super.onAttachedToWindow()
    if (!isInEditMode) {
      logButtonCreated(context)
    }
  }

  override fun onDraw(canvas: Canvas) {
    val centered = this.gravity and Gravity.CENTER_HORIZONTAL != 0
    if (centered) {
      // if the text is centered, we need to adjust the frame for the titleLabel based on the
      // size of the text in order to keep the text centered in the button without adding
      // extra blank space to the right when unnecessary
      // 1. the text fits centered within the button without colliding with the image
      //    (imagePaddingWidth)
      // 2. the text would run into the image, so adjust the insets to effectively left align
      //    it (textPaddingWidth)
      val compoundPaddingLeft = compoundPaddingLeft
      val compoundPaddingRight = compoundPaddingRight
      val compoundDrawablePadding = compoundDrawablePadding
      val textX = compoundPaddingLeft + compoundDrawablePadding
      val textContentWidth = width - textX - compoundPaddingRight
      val textWidth = measureTextWidth(text.toString())
      val textPaddingWidth = (textContentWidth - textWidth) / 2
      val imagePaddingWidth = (compoundPaddingLeft - paddingLeft) / 2
      val inset = Math.min(textPaddingWidth, imagePaddingWidth)
      overrideCompoundPaddingLeft = compoundPaddingLeft - inset
      overrideCompoundPaddingRight = compoundPaddingRight + inset
      overrideCompoundPadding = true
    }
    super.onDraw(canvas)
    overrideCompoundPadding = false
  }

  override fun getCompoundPaddingLeft(): Int {
    return if (overrideCompoundPadding) overrideCompoundPaddingLeft
    else super.getCompoundPaddingLeft()
  }

  override fun getCompoundPaddingRight(): Int {
    return if (overrideCompoundPadding) overrideCompoundPaddingRight
    else super.getCompoundPaddingRight()
  }

  protected open val activity: Activity
    get() {
      var context = context
      while (context !is Activity && context is ContextWrapper) {
        context = context.baseContext
      }
      if (context is Activity) {
        return context
      }
      throw FacebookException("Unable to get Activity.")
    }

  protected open val defaultStyleResource: Int = 0

  protected open fun measureTextWidth(text: String?): Int {
    return Math.ceil(paint.measureText(text).toDouble()).toInt()
  }

  protected open fun configureButton(
      context: Context,
      attrs: AttributeSet?,
      defStyleAttr: Int,
      defStyleRes: Int
  ) {
    parseBackgroundAttributes(context, attrs, defStyleAttr, defStyleRes)
    parseCompoundDrawableAttributes(context, attrs, defStyleAttr, defStyleRes)
    parseContentAttributes(context, attrs, defStyleAttr, defStyleRes)
    parseTextAttributes(context, attrs, defStyleAttr, defStyleRes)
    setupOnClickListener()
  }

  protected open fun callExternalOnClickListener(v: View?) {
    externalOnClickListener?.onClick(v)
  }

  protected open fun setInternalOnClickListener(l: OnClickListener?) {
    internalOnClickListener = l
  }

  protected open fun logButtonCreated(context: Context?) {
    val logger = InternalAppEventsLogger.createInstance(context, null)
    logger.logEventImplicitly(analyticsButtonCreatedEventName)
  }

  protected open fun logButtonTapped(context: Context?) {
    val logger = InternalAppEventsLogger.createInstance(context, null)
    logger.logEventImplicitly(analyticsButtonTappedEventName)
  }

  private fun parseBackgroundAttributes(
      context: Context,
      attrs: AttributeSet?,
      defStyleAttr: Int,
      defStyleRes: Int
  ) {
    // TODO, figure out why com_facebook_button_like_background.xml doesn't work in designers
    if (isInEditMode) {
      return
    }
    val attrsResources = intArrayOf(android.R.attr.background)
    val a = context.theme.obtainStyledAttributes(attrs, attrsResources, defStyleAttr, defStyleRes)
    try {
      if (a.hasValue(0)) {
        val backgroundResource = a.getResourceId(0, 0)
        if (backgroundResource != 0) {
          setBackgroundResource(backgroundResource)
        } else {
          setBackgroundColor(a.getColor(0, 0))
        }
      } else {
        // fallback, if no background specified, fill with Facebook blue
        setBackgroundColor(
            ContextCompat.getColor(context, com.facebook.common.R.color.com_facebook_blue))
      }
    } finally {
      a.recycle()
    }
  }

  @SuppressLint("ResourceType")
  private fun parseCompoundDrawableAttributes(
      context: Context,
      attrs: AttributeSet?,
      defStyleAttr: Int,
      defStyleRes: Int
  ) {
    val attrsResources =
        intArrayOf(
            android.R.attr.drawableLeft,
            android.R.attr.drawableTop,
            android.R.attr.drawableRight,
            android.R.attr.drawableBottom,
            android.R.attr.drawablePadding)
    val a = context.theme.obtainStyledAttributes(attrs, attrsResources, defStyleAttr, defStyleRes)
    compoundDrawablePadding =
        try {
          setCompoundDrawablesWithIntrinsicBounds(
              a.getResourceId(0, 0),
              a.getResourceId(1, 0),
              a.getResourceId(2, 0),
              a.getResourceId(3, 0))
          a.getDimensionPixelSize(4, 0)
        } finally {
          a.recycle()
        }
  }

  private fun parseContentAttributes(
      context: Context,
      attrs: AttributeSet?,
      defStyleAttr: Int,
      defStyleRes: Int
  ) {
    val attrsResources =
        intArrayOf(
            android.R.attr.paddingLeft,
            android.R.attr.paddingTop,
            android.R.attr.paddingRight,
            android.R.attr.paddingBottom)
    val a = context.theme.obtainStyledAttributes(attrs, attrsResources, defStyleAttr, defStyleRes)
    try {
      setPadding(
          a.getDimensionPixelSize(0, 0),
          a.getDimensionPixelSize(1, 0),
          a.getDimensionPixelSize(2, 0),
          a.getDimensionPixelSize(3, 0))
    } finally {
      a.recycle()
    }
  }

  private fun parseTextAttributes(
      context: Context,
      attrs: AttributeSet?,
      defStyleAttr: Int,
      defStyleRes: Int
  ) {
    val colorResources = intArrayOf(android.R.attr.textColor)
    val colorAttrs =
        context.theme.obtainStyledAttributes(attrs, colorResources, defStyleAttr, defStyleRes)
    try {
      setTextColor(colorAttrs.getColorStateList(0))
    } finally {
      colorAttrs.recycle()
    }
    val gravityResources = intArrayOf(android.R.attr.gravity)
    val gravityAttrs =
        context.theme.obtainStyledAttributes(attrs, gravityResources, defStyleAttr, defStyleRes)
    gravity =
        try {
          gravityAttrs.getInt(0, Gravity.CENTER)
        } finally {
          gravityAttrs.recycle()
        }
    val attrsResources =
        intArrayOf(android.R.attr.textSize, android.R.attr.textStyle, android.R.attr.text)
    val a = context.theme.obtainStyledAttributes(attrs, attrsResources, defStyleAttr, defStyleRes)
    text =
        try {
          setTextSize(TypedValue.COMPLEX_UNIT_PX, a.getDimensionPixelSize(0, 0).toFloat())
          val typeface = typeface
          setTypeface(Typeface.create(typeface, Typeface.BOLD))
          a.getString(2)
        } finally {
          a.recycle()
        }
  }

  private fun setupOnClickListener() {
    // set the listener on super so that consumers can set another listener that this will
    // forward to
    super.setOnClickListener { v ->
      logButtonTapped(context)
      val internalOnClickListener = internalOnClickListener
      if (internalOnClickListener != null) {
        internalOnClickListener.onClick(v)
      } else externalOnClickListener?.onClick(v)
    }
  }

  init {
    var defStyleRes = defStyleRes
    defStyleRes = if (defStyleRes == 0) defaultStyleResource else defStyleRes
    defStyleRes =
        if (defStyleRes == 0) com.facebook.common.R.style.com_facebook_button else defStyleRes
    configureButton(context, attrs, defStyleAttr, defStyleRes)
    this.analyticsButtonCreatedEventName = analyticsButtonCreatedEventName
    this.analyticsButtonTappedEventName = analyticsButtonTappedEventName
    isClickable = true
    isFocusable = true
  }
}


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

/** A callback class for the Facebook SDK. */
interface FacebookCallback<RESULT> {
  /**
   * Called when the dialog completes without error.
   *
   * Note: This will be called instead of [.onCancel] if any of the following conditions are true.
   *
   * * [com.facebook.share.widget.MessageDialog] is used.
   * * The logged in Facebook user has not authorized the app that has initiated the dialog.
   *
   * @param result Result from the dialog
   */
  fun onSuccess(result: RESULT)

  /**
   * Called when the dialog is canceled.
   *
   * Note: [.onSuccess] will be called instead if any of the following conditions are true.
   *
   * * [com.facebook.share.widget.MessageDialog] is used.
   * * The logged in Facebook user has not authorized the app that has initiated the dialog.
   */
  fun onCancel()

  /**
   * Called when the dialog finishes with an error.
   *
   * @param error The error that occurred
   */
  fun onError(error: FacebookException)
}


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

package com.facebook

import androidx.activity.result.contract.ActivityResultContract

/** Represents dialogs provided by Facebook */
interface FacebookDialog<CONTENT, RESULT> {
  /**
   * Indicates whether the dialog can be shown for the content passed in.
   *
   * @param content the content to check
   * @return true if the dialog can be shown
   */
  fun canShow(content: CONTENT): Boolean

  /**
   * Shows the dialog for the content passed in.
   *
   * @param content the content to show
   */
  fun show(content: CONTENT)

  /**
   * Allows the registration of a callback that will be executed once the dialog is closed, with
   * success, cancel or error details. This should be called in the [ ]
   * [android.app.Activity.onCreate] or [ ][androidx.fragment.app.Fragment.onCreate] methods.
   *
   * @param callbackManager CallbackManager instance that will handle the onActivityResult
   * @param callback Callback to be called upon dialog completion
   */
  fun registerCallback(callbackManager: CallbackManager, callback: FacebookCallback<RESULT>)

  /**
   * Allows the registration of a callback that will be executed once the dialog is closed, with
   * success, cancel or error details. This should be called in the [ ]
   * [android.app.Activity.onCreate] or [ ][androidx.fragment.app.Fragment.onCreate] methods.
   *
   * @param callbackManager CallbackManager instance that will handle the Activity Result
   * @param callback Callback to be called upon dialog completion
   * @param requestCode The request code to use, this should be outside of the range of those
   * reserved for the Facebook SDK [com.facebook.FacebookSdk.isFacebookRequestCode].
   */
  fun registerCallback(
      callbackManager: CallbackManager,
      callback: FacebookCallback<RESULT>,
      requestCode: Int
  )
  /**
   * Creates the ActivityResultContract instance for showing the dialog with Androidx activities and
   * fragments.
   *
   * @param callbackManager CallbackManager instance that will handle the onActivityResult
   */
  fun createActivityResultContractForShowingDialog(
      callbackManager: CallbackManager?
  ): ActivityResultContract<CONTENT, CallbackManager.ActivityResultParameters>
}


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

/** Represents an error condition relating to displaying a Facebook Web dialog. */
class FacebookDialogException
/** Constructs a new FacebookException. */
(
    message: String?,
    /**
     * The error code received by the WebView. See:
     * http://developer.android.com/reference/android/webkit/WebViewClient.html
     */
    val errorCode: Int,
    /** The URL that the dialog was trying to load. */
    val failingUrl: String?
) : FacebookException(message) {
  override fun toString(): String {
    return StringBuilder()
        .append("{FacebookDialogException: ")
        .append("errorCode: ")
        .append(errorCode)
        .append(", message: ")
        .append(message)
        .append(", url: ")
        .append(failingUrl)
        .append("}")
        .toString()
  }

  companion object {
    const val serialVersionUID: Long = 1
  }
}


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

package com.facebook

/** A callback class for getting the Login Status of a user. */
interface LoginStatusCallback {
  /**
   * Called when an access token is able to be retrieved successfully.
   *
   * @param accessToken The access token retrieved for the user
   */
  fun onCompleted(accessToken: AccessToken)

  /** Called when an access token could not be retrieved. */
  fun onFailure()

  /**
   * Called when there was an error getting the login status of a user.
   *
   * @param exception The error that occurred
   */
  fun onError(exception: 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

object WebDialog {
  /**
   * Gets the theme used by [com.facebook.internal.WebDialog]
   *
   * @return the theme
   */
  @JvmStatic
  fun getWebDialogTheme(): Int {
    return com.facebook.internal.WebDialog.getWebDialogTheme()
  }
  /**
   * Sets the theme used by [com.facebook.internal.WebDialog]
   *
   * @param theme A theme to use
   */
  @JvmStatic
  fun setWebDialogTheme(theme: Int) {
    com.facebook.internal.WebDialog.setWebDialogTheme(theme)
  }
}


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

class Common


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

import android.annotation.TargetApi
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
import android.os.Build
import com.facebook.FacebookSdk
import com.facebook.internal.FetchedAppSettingsManager
import com.facebook.internal.SmartLoginOption
import com.facebook.internal.Utility
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.MultiFormatWriter
import com.google.zxing.WriterException
import java.lang.IllegalArgumentException
import java.util.EnumMap
import org.json.JSONObject

/**
 * com.facebook.devicerequests.internal is solely for the use of other packages within the Facebook
 * SDK for Android. Use of any of the classes in this package is unsupported, and they may be
 * modified or removed without warning at any time.
 */
@AutoHandleExceptions
object DeviceRequestsHelper {
  private val TAG = DeviceRequestsHelper::class.java.canonicalName
  const val DEVICE_INFO_PARAM = "device_info"
  const val DEVICE_TARGET_USER_ID = "target_user_id"
  const val DEVICE_INFO_DEVICE = "device"
  const val DEVICE_INFO_MODEL = "model"
  const val SDK_HEADER = "fbsdk"
  const val SDK_FLAVOR = "android"
  const val SERVICE_TYPE = "_fb._tcp."
  private val deviceRequestsListeners = HashMap<String?, NsdManager.RegistrationListener>()

  @JvmStatic
  fun getDeviceInfo(deviceInfo: MutableMap<String, String>?): String {
    // Device info
    // We don't need all the information in Utility.setAppEventExtendedDeviceInfoParameters
    // We only want the model so we can show it to the user, so they know which device
    // the login request comes from
    val deviceInfo = deviceInfo ?: HashMap()
    deviceInfo[DEVICE_INFO_DEVICE] = Build.DEVICE
    deviceInfo[DEVICE_INFO_MODEL] = Build.MODEL
    return JSONObject(deviceInfo as Map<*, *>).toString()
  }

  @JvmStatic
  fun getDeviceInfo(): String {
    return getDeviceInfo(null)
  }

  @JvmStatic
  fun startAdvertisementService(userCode: String?): Boolean {
    return if (isAvailable()) {
      startAdvertisementServiceImpl(userCode)
    } else false
  }

  /*
  returns true if smart login is enabled and the android api level is supported.
   */
  @JvmStatic
  fun isAvailable(): Boolean {
    val settings =
        FetchedAppSettingsManager.getAppSettingsWithoutQuery(FacebookSdk.getApplicationId())
    return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN &&
        settings != null &&
        settings.smartLoginOptions.contains(SmartLoginOption.Enabled))
  }

  @JvmStatic
  fun generateQRCode(url: String?): Bitmap? {
    var qrCode: Bitmap? = null
    val hints: MutableMap<EncodeHintType, Any?> = EnumMap(EncodeHintType::class.java)
    hints[EncodeHintType.MARGIN] = 2
    try {
      val matrix = MultiFormatWriter().encode(url, BarcodeFormat.QR_CODE, 200, 200, hints)
      val h = matrix.height
      val w = matrix.width
      val pixels = IntArray(h * w)
      for (i in 0 until h) {
        val offset = i * w
        for (j in 0 until w) {
          pixels[offset + j] = if (matrix[j, i]) Color.BLACK else Color.WHITE
        }
      }
      qrCode = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
      qrCode.setPixels(pixels, 0, w, 0, 0, w, h)
    } catch (ignored: WriterException) {
      // ignored because exception would be thrown from ZXing library.
    }
    return qrCode
  }

  @JvmStatic
  fun cleanUpAdvertisementService(userCode: String?) {
    cleanUpAdvertisementServiceImpl(userCode)
  }

  @TargetApi(16)
  private fun startAdvertisementServiceImpl(userCode: String?): Boolean {
    if (deviceRequestsListeners.containsKey(userCode)) {
      return true
    }
    // Dots in the version will mess up the Bonjour DNS record parsing
    val sdkVersion = FacebookSdk.getSdkVersion().replace('.', '|')
    // Other SDKs that adopt this feature should use different flavor name
    // The whole name should not exceed 60 characters
    val sdkVersionWithFlavor = "$SDK_FLAVOR-$sdkVersion"
    val nsdServiceName = "${SDK_HEADER}_${sdkVersionWithFlavor}_${userCode}"
    val nsdLoginAdvertisementService = NsdServiceInfo()
    nsdLoginAdvertisementService.serviceType = SERVICE_TYPE
    nsdLoginAdvertisementService.serviceName = nsdServiceName
    nsdLoginAdvertisementService.port = 80
    val nsdManager =
        FacebookSdk.getApplicationContext().getSystemService(Context.NSD_SERVICE) as NsdManager
    val nsdRegistrationListener: NsdManager.RegistrationListener =
        object : NsdManager.RegistrationListener {
          override fun onServiceRegistered(NsdServiceInfo: NsdServiceInfo) {
            // Android may have changed the service name in order to resolve a conflict
            if (nsdServiceName != NsdServiceInfo.serviceName) {
              cleanUpAdvertisementService(userCode)
            }
          }

          override fun onServiceUnregistered(serviceInfo: NsdServiceInfo) = Unit
          override fun onRegistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
            cleanUpAdvertisementService(userCode)
          }

          override fun onUnregistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) = Unit
        }
    deviceRequestsListeners[userCode] = nsdRegistrationListener
    nsdManager.registerService(
        nsdLoginAdvertisementService, NsdManager.PROTOCOL_DNS_SD, nsdRegistrationListener)
    return true
  }

  @TargetApi(16)
  private fun cleanUpAdvertisementServiceImpl(userCode: String?) {
    val nsdRegistrationListener = deviceRequestsListeners[userCode]
    if (nsdRegistrationListener != null) {
      val nsdManager =
          FacebookSdk.getApplicationContext().getSystemService(Context.NSD_SERVICE) as NsdManager
      try {
        nsdManager.unregisterService(nsdRegistrationListener)
      } catch (e: IllegalArgumentException) {
        Utility.logd(TAG, e)
      }
      deviceRequestsListeners.remove(userCode)
    }
  }
}


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

package com.facebook.internal

import android.content.Intent
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import java.util.UUID

/**
 * com.facebook.internal is solely for the use of other packages within the Facebook SDK for
 * Android. Use of any of the classes in this package is unsupported, and they may be modified or
 * removed without warning at any time.
 */
@AutoHandleExceptions
class AppCall
/**
 * Constructor.
 *
 * @param requestCode the request code for this app call
 */
@JvmOverloads
constructor(
    /**
     * Gets the request code for this call.
     *
     * @return the request code that will be passed to handleActivityResult upon completion.
     */
    var requestCode: Int,
    /**
     * Returns the unique ID of this call to the Facebook application.
     *
     * @return the unique ID
     */
    val callId: UUID = UUID.randomUUID()
) {

  /**
   * Returns the Intent that was used to initiate this call to the Facebook application.
   *
   * @return the Intent
   */
  var requestIntent: Intent? = null

  /**
   * @return Returns true if there was another AppCall that was already pending and is now canceled
   */
  fun setPending(): Boolean {
    return setCurrentPendingCall(this)
  }

  companion object {
    var currentPendingCall: AppCall? = null
      private set

    @Synchronized
    @JvmStatic
    fun finishPendingCall(callId: UUID, requestCode: Int): AppCall? {
      val pendingCall = currentPendingCall
      if (pendingCall == null ||
          pendingCall.callId != callId ||
          pendingCall.requestCode != requestCode) {
        return null
      }
      setCurrentPendingCall(null)
      return pendingCall
    }

    @Synchronized
    private fun setCurrentPendingCall(appCall: AppCall?): Boolean {
      val oldAppCall = currentPendingCall
      currentPendingCall = appCall
      return oldAppCall != null
    }
  }
}


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

package com.facebook.internal

import android.app.Activity
import android.content.ActivityNotFoundException
import android.net.Uri
import android.os.Bundle
import androidx.browser.customtabs.CustomTabsIntent
import com.facebook.FacebookSdk
import com.facebook.FacebookSdk.getGraphApiVersion
import com.facebook.internal.ServerProtocol.getDialogAuthority
import com.facebook.internal.ServerProtocol.getGamingDialogAuthority
import com.facebook.internal.Utility.buildUri
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
import com.facebook.login.CustomTabPrefetchHelper

@AutoHandleExceptions
open class CustomTab(action: String, parameters: Bundle?) {
  protected var uri: Uri
  fun openCustomTab(activity: Activity, packageName: String?): Boolean {
    val session = CustomTabPrefetchHelper.getPreparedSessionOnce()
    val customTabsIntent = CustomTabsIntent.Builder(session).build()
    customTabsIntent.intent.setPackage(packageName)
    try {
      customTabsIntent.launchUrl(activity, uri)
    } catch (e: ActivityNotFoundException) {
      return false
    }
    return true
  }

  companion object {
    @JvmStatic
    open fun getURIForAction(action: String, parameters: Bundle?): Uri {
      return buildUri(
          getDialogAuthority(),
          getGraphApiVersion() + "/" + ServerProtocol.DIALOG_PATH + action,
          parameters)
    }
  }

  init {
    var parameters = parameters ?: Bundle()
    uri =
        if (GamingAction.values().map { it.rawValue }.contains(action)) {
          buildUri(
              getGamingDialogAuthority(), "/" + ServerProtocol.DIALOG_PATH + action, parameters)
        } else {
          getURIForAction(action, parameters)
        }
  }
}


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

package com.facebook.internal

import android.content.Intent
import androidx.browser.customtabs.CustomTabsService
import com.facebook.FacebookSdk.getApplicationContext
import com.facebook.internal.Validate.hasCustomTabRedirectActivity
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions

@AutoHandleExceptions
object CustomTabUtils {
  private val CHROME_PACKAGES = arrayOf("com.android.chrome", "com.chrome.beta", "com.chrome.dev")

  @JvmStatic
  fun getDefaultRedirectURI(): String {
    return Validate.CUSTOM_TAB_REDIRECT_URI_PREFIX + getApplicationContext().packageName
  }

  @JvmStatic
  fun getValidRedirectURI(developerDefinedRedirectURI: String): String {
    val hasDeveloperDefinedRedirect =
        hasCustomTabRedirectActivity(getApplicationContext(), developerDefinedRedirectURI)
    if (hasDeveloperDefinedRedirect) {
      return developerDefinedRedirectURI
    } else {
      val hasDefaultRedirect =
          hasCustomTabRedirectActivity(getApplicationContext(), getDefaultRedirectURI())
      if (hasDefaultRedirect) {
        return getDefaultRedirectURI()
      }
    }
    return ""
  }

  @JvmStatic
  fun getChromePackage(): String? {
    val context = getApplicationContext()
    val serviceIntent = Intent(CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION)
    val resolveInfos = context.packageManager.queryIntentServices(serviceIntent, 0)
    if (resolveInfos != null) {
      val chromePackages: Set<String> = CHROME_PACKAGES.toHashSet()
      for (resolveInfo in resolveInfos) {
        val serviceInfo = resolveInfo.serviceInfo
        if (serviceInfo != null && chromePackages.contains(serviceInfo.packageName)) {
          return serviceInfo.packageName
        }
      }
    }
    return null
  }
}


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

package com.facebook.internal

/**
 * com.facebook.internal is solely for the use of other packages within the Facebook SDK for
 * Android. Use of any of the classes in this package is unsupported, and they may be modified or
 * removed without warning at any time.
 */
interface DialogFeature {
  /** This method is for internal use only. */
  fun getAction(): String

  /** This method is for internal use only. */
  fun getMinVersion(): Int

  /**
   * This method is for internal use only.
   *
   * For all Enums that implement this interface, the name() method (in Java) or the name property
   * (in Kotlin) is already present. It returns the String representation of the Enum value,
   * verbatim.
   */
  @Suppress("INAPPLICABLE_JVM_NAME") @get:JvmName("name") val name: String
}