Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ google-services.json
crashlytics-build.properties
auth/src/main/res/values/com_crashlytics_export_strings.xml
*.log
.kotlin/
259 changes: 259 additions & 0 deletions app/scripts/run-demo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
#!/usr/bin/env bash

set -euo pipefail

APP_ID="com.firebaseui.android.demo"
MAIN_ACTIVITY="com.firebaseui.android.demo.MainActivity"
APK_RELATIVE_PATH="app/build/outputs/apk/debug/app-debug.apk"
EMULATOR_LOG="${TMPDIR:-/tmp}/firebaseui-android-emulator.log"
EMULATOR_PID=""
LAUNCHED_EMULATOR=0
CONNECTED_DEVICES=()
AVAILABLE_AVDS=()
KNOWN_DEVICE_SERIALS=()

usage() {
cat <<'EOF'
Usage: run-demo.sh [--help]

Builds, installs, and launches the FirebaseUI Android demo app.

The script lets you:
1. Use an already connected Android device or emulator.
2. Start an AVD selected from `emulator -list-avds`.
EOF
}

require_command() {
local command_name="$1"
if ! command -v "$command_name" >/dev/null 2>&1; then
echo "Missing required command: $command_name" >&2
exit 1
fi
}

collect_connected_devices() {
CONNECTED_DEVICES=()
while IFS= read -r serial; do
if [[ -n "$serial" ]]; then
CONNECTED_DEVICES+=("$serial")
fi
done < <(adb devices | awk 'NR > 1 && $2 == "device" { print $1 }')
}

collect_available_avds() {
AVAILABLE_AVDS=()
while IFS= read -r avd_name; do
if [[ -n "$avd_name" ]]; then
AVAILABLE_AVDS+=("$avd_name")
fi
done < <(emulator -list-avds 2>/dev/null)
}

prompt_for_choice() {
local prompt="$1"
shift
local options=("$@")
local selection
local index=1

echo "$prompt"
for option in "${options[@]}"; do
printf " %d) %s\n" "$index" "$option"
index=$((index + 1))
done

while true; do
printf "Select an option [1-%d]: " "${#options[@]}"
read -r selection

if [[ "$selection" =~ ^[0-9]+$ ]] && (( selection >= 1 && selection <= ${#options[@]} )); then
CHOICE_INDEX=$((selection - 1))
return 0
fi

echo "Please enter a number between 1 and ${#options[@]}."
done
}

is_known_device() {
local candidate="$1"
local known
local index

for (( index=0; index<${#KNOWN_DEVICE_SERIALS[@]}; index++ )); do
known="${KNOWN_DEVICE_SERIALS[$index]}"
if [[ "$known" == "$candidate" ]]; then
return 0
fi
done

return 1
}

cleanup_on_exit() {
local exit_code="$?"

if (( exit_code != 0 )) && (( LAUNCHED_EMULATOR == 1 )) && [[ -n "$EMULATOR_PID" ]]; then
echo "Stopping emulator started by this script..." >&2
kill "$EMULATOR_PID" 2>/dev/null || true
fi

exit "$exit_code"
}

wait_for_device_boot() {
local serial="$1"
local attempt
local boot_completed

adb -s "$serial" wait-for-device >/dev/null

echo "Waiting for $serial to finish booting..."
for (( attempt=1; attempt<=120; attempt++ )); do
boot_completed="$(adb -s "$serial" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')"
if [[ "$boot_completed" == "1" ]]; then
echo "$serial is ready."
return 0
fi
sleep 2
done

echo "Timed out waiting for $serial to boot." >&2
exit 1
}

start_selected_avd() {
local avd_name="$1"
local attempt
local serial
local index

collect_connected_devices
KNOWN_DEVICE_SERIALS=()
for (( index=0; index<${#CONNECTED_DEVICES[@]}; index++ )); do
KNOWN_DEVICE_SERIALS+=("${CONNECTED_DEVICES[$index]}")
done

echo "Starting emulator '$avd_name'..."
echo "Emulator logs: $EMULATOR_LOG"
emulator -avd "$avd_name" >"$EMULATOR_LOG" 2>&1 &
EMULATOR_PID=$!
LAUNCHED_EMULATOR=1

for (( attempt=1; attempt<=120; attempt++ )); do
sleep 2
collect_connected_devices
for (( index=0; index<${#CONNECTED_DEVICES[@]}; index++ )); do
serial="${CONNECTED_DEVICES[$index]}"
case "$serial" in
emulator-*)
if ! is_known_device "$serial"; then
TARGET_SERIAL="$serial"
wait_for_device_boot "$TARGET_SERIAL"
return 0
fi
;;
esac
done
done

echo "Failed to detect the new emulator for AVD '$avd_name'." >&2
exit 1
}

choose_target_device() {
local option_labels=()
local option_types=()
local option_values=()
local serial
local avd_name
local index

collect_connected_devices
collect_available_avds

for (( index=0; index<${#CONNECTED_DEVICES[@]}; index++ )); do
serial="${CONNECTED_DEVICES[$index]}"
option_labels+=("Use connected device: $serial")
option_types+=("device")
option_values+=("$serial")
done

for (( index=0; index<${#AVAILABLE_AVDS[@]}; index++ )); do
avd_name="${AVAILABLE_AVDS[$index]}"
option_labels+=("Start emulator: $avd_name")
option_types+=("avd")
option_values+=("$avd_name")
done

if (( ${#option_labels[@]} == 0 )); then
cat >&2 <<'EOF'
No connected Android devices were found, and no AVDs are available.
Connect a device or create an emulator first, then rerun this script.
EOF
exit 1
fi

prompt_for_choice "Choose a target for the demo app:" "${option_labels[@]}"

case "${option_types[$CHOICE_INDEX]}" in
device)
TARGET_SERIAL="${option_values[$CHOICE_INDEX]}"
;;
avd)
start_selected_avd "${option_values[$CHOICE_INDEX]}"
;;
esac
}

main() {
local script_dir
local repo_root
local apk_path
local launch_output

if [[ "${1:-}" == "--help" ]]; then
usage
exit 0
fi

trap cleanup_on_exit EXIT

require_command adb
require_command emulator

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd "$script_dir/../.." && pwd)"
apk_path="$repo_root/$APK_RELATIVE_PATH"

if [[ ! -x "$repo_root/gradlew" ]]; then
echo "gradlew not found or not executable at $repo_root/gradlew" >&2
exit 1
fi

choose_target_device

echo "Building debug APK..."
"$repo_root/gradlew" :app:assembleDebug

if [[ ! -f "$apk_path" ]]; then
echo "APK not found at $apk_path" >&2
exit 1
fi

echo "Installing app on $TARGET_SERIAL..."
adb -s "$TARGET_SERIAL" install -r "$apk_path"

echo "Launching demo app on $TARGET_SERIAL..."
launch_output="$(adb -s "$TARGET_SERIAL" shell am start -n "$APP_ID/$MAIN_ACTIVITY")"
echo "$launch_output"
if [[ "$launch_output" == *"Error:"* ]]; then
echo "Failed to launch the demo app." >&2
exit 1
fi

trap - EXIT
}

main "$@"
20 changes: 20 additions & 0 deletions auth/src/main/java/com/firebase/ui/auth/AuthException.kt
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,26 @@ abstract class AuthException(
cause: Throwable? = null
) : AuthException(message, cause)

/**
* A different sign-in method should be used for this email address.
*
* This exception is used for the opt-in legacy recovery path backed by
* `fetchSignInMethodsForEmail`, allowing the UI to guide users toward a previously
* used provider when email enumeration protection has been disabled.
*
* @property email The email address being recovered
* @property signInMethods The sign-in methods returned by Firebase Auth
* @property suggestedSignInMethod The preferred method the UI should direct the user toward
* @property cause The underlying authentication failure that triggered the lookup
*/
class DifferentSignInMethodRequiredException(
message: String,
val email: String,
val signInMethods: List<String>,
val suggestedSignInMethod: String,
cause: Throwable? = null
) : AuthException(message, cause)

/**
* Authentication was cancelled by the user.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class AuthUIConfigurationBuilder {
var isNewEmailAccountsAllowed: Boolean = true
var isDisplayNameRequired: Boolean = true
var isProviderChoiceAlwaysShown: Boolean = false
var legacyFetchSignInWithEmail: Boolean = false
var transitions: AuthUITransitions? = null

fun providers(block: AuthProvidersBuilder.() -> Unit) =
Expand Down Expand Up @@ -114,6 +115,7 @@ class AuthUIConfigurationBuilder {
isNewEmailAccountsAllowed = isNewEmailAccountsAllowed,
isDisplayNameRequired = isDisplayNameRequired,
isProviderChoiceAlwaysShown = isProviderChoiceAlwaysShown,
legacyFetchSignInWithEmail = legacyFetchSignInWithEmail,
transitions = transitions
)
}
Expand Down Expand Up @@ -199,6 +201,15 @@ class AuthUIConfiguration(
*/
val isProviderChoiceAlwaysShown: Boolean = false,

/**
* Enables legacy provider recovery via `fetchSignInMethodsForEmail`.
*
* This should only be enabled when email enumeration protection is disabled for the
* Firebase project and the application explicitly wants to use the legacy API to
* recover from email/password attempts made with the wrong provider.
*/
val legacyFetchSignInWithEmail: Boolean = false,

/**
* Custom screen transition animations.
* If null, uses default fade in/out transitions.
Expand Down
Loading
Loading