diff --git a/.travis.yml b/.travis.yml
index 847010aee..c8bc246f1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -13,7 +13,7 @@ cache:
android:
components:
- tools
- - build-tools-25.0.2
+ - build-tools-25.0.3
- android-25
- platform-tools
- extra-android-m2repository
diff --git a/README.md b/README.md
index e5bc96e0c..c9ed8246f 100644
--- a/README.md
+++ b/README.md
@@ -10,11 +10,22 @@ A compatible FirebaseUI client is also available for [iOS](https://github.com/fi
## Table of Contents
- 1. [Installation](#installation)
1. [Usage](#usage)
+ 1. [Installation](#installation)
+ 1. [Upgrading](#upgrading)
+ 1. [Dependencies](#dependencies)
1. [Sample App](#sample-app)
1. [Contributing](#contributing)
+## Usage
+
+FirebaseUI has separate modules for using Firebase Database, Auth, and Storage. To
+get started, see the individual instructions for each module:
+
+ * [firebase-ui-database](database/README.md)
+ * [firebase-ui-auth](auth/README.md)
+ * [firebase-ui-storage](storage/README.md)
+
## Installation
FirebaseUI is published as a collection of libraries separated by the
@@ -47,6 +58,15 @@ required.
After the project is synchronized, we're ready to start using Firebase functionality in our app.
+## Upgrading
+
+If you are using an old version of FirebaseUI and upgrading, please see the appropriate
+migration guide:
+
+ * [Upgrade from 1.2.0 to 2.0](./docs/upgrade-to-2.0.md)
+
+## Dependencies
+
### Compatibility with Firebase / Google Play Services Libraries
FirebaseUI libraries have the following transitive dependencies on the Firebase SDK:
@@ -67,7 +87,7 @@ in `common/constants.gradle`. If you are using any dependencies in your app of
`compile 'com.google.firebase:firebase-*:x.y.z'` or `compile 'com.google.android.gms:play-services-*:x.y.z'`
you need to make sure that you use the same version that your chosen version of FirebaseUI requires.
-For convenience, here are some examples:
+For convenience, here are some recent examples:
| FirebaseUI Version | Firebase/Play Services Version |
|--------------------|--------------------------------|
@@ -75,21 +95,44 @@ For convenience, here are some examples:
| 1.1.1 | 10.0.0 or 10.0.1 |
| 1.0.1 | 10.0.0 or 10.0.1 |
| 1.0.0 | 9.8.0 |
-| 0.6.2 | 9.8.0 |
-| 0.6.1 | 9.6.1 |
-| 0.6.0 | 9.6.0 |
-| 0.5.3 | 9.4.0 |
-| 0.4.4 | 9.4.0 |
-| 0.4.3 | 9.2.1 |
-| 0.4.2 | 9.2.0 |
-| 0.4.1 | 9.0.2 |
-| 0.4.0 | 9.0.0 |
-## Usage
- * [firebase-ui-database](database/README.md)
- * [firebase-ui-auth](auth/README.md)
- * [firebase-ui-storage](storage/README.md)
+## Upgrading dependencies
+
+If you would like to use a newer version of one of FirebaseUI's transitive dependencies, such
+as Firebase, Play services, or the Android support libraries you need add explicit
+`compile` declarations in your `build.gradle` for all of FirebaseUI's dependencies at the version
+you want to use. For example if you want to use Play services/Firebase version `FOO` and support
+libraries version `BAR` add the following extra lines for each FirebaseUI module you're using:
+
+Auth:
+
+```groovy
+compile "com.google.firebase:firebase-auth:$FOO"
+compile "com.google.android.gms:play-services-auth:$FOO"
+
+compile "com.android.support:design:$BAR"
+compile "com.android.support:customtabs:$BAR"
+compile "com.android.support:cardview-v7:$BAR"
+```
+
+Database:
+
+```groovy
+compile "com.google.firebase:firebase-database:$FOO"
+
+compile "com.android.support:recyclerview-v7:$BAR"
+compile "com.android.support:support-v4:$BAR"
+```
+
+Storage:
+
+```groovy
+compile "com.google.firebase:firebase-storage:$FOO"
+
+compile "com.android.support:appcompat-v7:$BAR"
+compile "com.android.support:palette-v7:$BAR"
+```
## Sample App
diff --git a/app/build.gradle b/app/build.gradle
index 0a7020479..2b22a1a84 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,5 +1,4 @@
apply plugin: 'com.android.application'
-apply plugin: 'com.neenbedankt.android-apt'
apply from: '../library/quality/quality.gradle'
android {
@@ -39,15 +38,18 @@ dependencies {
compile "com.google.firebase:firebase-database:$firebaseVersion"
compile "com.google.firebase:firebase-storage:$firebaseVersion"
+ compile('com.facebook.android:facebook-android-sdk:4.22.1')
+ compile("com.twitter.sdk.android:twitter-core:3.0.0@aar") { transitive = true }
+
// The following dependencies are not required to use the Firebase UI library.
// They are used to make some aspects of the demo app implementation simpler for
// demonstrative purposes, and you may find them useful in your own apps; YMMV.
- compile 'pub.devrel:easypermissions:0.3.0'
+ compile 'pub.devrel:easypermissions:0.4.0'
compile 'com.jakewharton:butterknife:8.5.1'
- apt 'com.jakewharton:butterknife-compiler:8.5.1'
+ annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
- releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
- testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
+ releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
+ testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
}
apply plugin: 'com.google.gms.google-services'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 52b5e1fca..ce2b85437 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -30,6 +30,9 @@
+ getSelectedProviders() {
List selectedProviders = new ArrayList<>();
- if (mUseEmailProvider.isChecked()) {
- selectedProviders.add(new IdpConfig.Builder(AuthUI.EMAIL_PROVIDER).build());
- }
-
- if (mUseTwitterProvider.isChecked()) {
- selectedProviders.add(new IdpConfig.Builder(AuthUI.TWITTER_PROVIDER).build());
+ if (mUseGoogleProvider.isChecked()) {
+ selectedProviders.add(
+ new IdpConfig.Builder(AuthUI.GOOGLE_PROVIDER)
+ .setPermissions(getGooglePermissions())
+ .build());
}
if (mUseFacebookProvider.isChecked()) {
@@ -295,11 +324,17 @@ private List getSelectedProviders() {
.build());
}
- if (mUseGoogleProvider.isChecked()) {
+ if (mUseTwitterProvider.isChecked()) {
+ selectedProviders.add(new IdpConfig.Builder(AuthUI.TWITTER_PROVIDER).build());
+ }
+
+ if (mUseEmailProvider.isChecked()) {
+ selectedProviders.add(new IdpConfig.Builder(AuthUI.EMAIL_PROVIDER).build());
+ }
+
+ if (mUsePhoneProvider.isChecked()) {
selectedProviders.add(
- new IdpConfig.Builder(AuthUI.GOOGLE_PROVIDER)
- .setPermissions(getGooglePermissions())
- .build());
+ new IdpConfig.Builder(AuthUI.PHONE_VERIFICATION_PROVIDER).build());
}
return selectedProviders;
@@ -314,6 +349,15 @@ private String getSelectedTosUrl() {
return FIREBASE_TOS_URL;
}
+ @MainThread
+ private String getSelectedPrivacyPolicyUrl() {
+ if (mUseGooglePrivacyPolicy.isChecked()) {
+ return GOOGLE_PRIVACY_POLICY_URL;
+ }
+
+ return FIREBASE_PRIVACY_POLICY_URL;
+ }
+
@MainThread
private boolean isGoogleConfigured() {
return !UNCHANGED_CONFIG_VALUE.equals(
@@ -356,8 +400,8 @@ private List getFacebookPermissions() {
@MainThread
private List getGooglePermissions() {
List result = new ArrayList<>();
- if (mGoogleScopeGames.isChecked()) {
- result.add(Scopes.GAMES);
+ if (mGoogleScopeYoutubeData.isChecked()) {
+ result.add("https://www.googleapis.com/auth/youtube.readonly");
}
if (mGoogleScopeDriveFile.isChecked()) {
result.add(Scopes.DRIVE_FILE);
diff --git a/app/src/main/java/com/firebase/uidemo/auth/SignedInActivity.java b/app/src/main/java/com/firebase/uidemo/auth/SignedInActivity.java
index 4642ff034..fedb61a13 100644
--- a/app/src/main/java/com/firebase/uidemo/auth/SignedInActivity.java
+++ b/app/src/main/java/com/firebase/uidemo/auth/SignedInActivity.java
@@ -18,6 +18,8 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
@@ -31,6 +33,7 @@
import com.bumptech.glide.Glide;
import com.firebase.ui.auth.AuthUI;
+import com.firebase.ui.auth.AuthUI.IdpConfig;
import com.firebase.ui.auth.IdpResponse;
import com.firebase.uidemo.R;
import com.google.android.gms.tasks.OnCompleteListener;
@@ -41,13 +44,18 @@
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.auth.GoogleAuthProvider;
+import java.util.ArrayList;
import java.util.Iterator;
+import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class SignedInActivity extends AppCompatActivity {
+
+ private static final String EXTRA_SIGNED_IN_CONFIG = "extra_signed_in_config";
+
@BindView(android.R.id.content)
View mRootView;
@@ -60,11 +68,17 @@ public class SignedInActivity extends AppCompatActivity {
@BindView(R.id.user_display_name)
TextView mUserDisplayName;
+
+ @BindView(R.id.user_phone_number)
+ TextView mUserPhoneNumber;
+
@BindView(R.id.user_enabled_providers)
TextView mEnabledProviders;
private IdpResponse mIdpResponse;
+ private SignedInConfig mSignedInConfig;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -77,6 +91,7 @@ public void onCreate(Bundle savedInstanceState) {
}
mIdpResponse = IdpResponse.fromResultIntent(getIntent());
+ mSignedInConfig = getIntent().getParcelableExtra(EXTRA_SIGNED_IN_CONFIG);
setContentView(R.layout.signed_in_layout);
ButterKnife.bind(this);
@@ -146,6 +161,8 @@ private void populateProfile() {
mUserEmail.setText(
TextUtils.isEmpty(user.getEmail()) ? "No email" : user.getEmail());
+ mUserPhoneNumber.setText(
+ TextUtils.isEmpty(user.getPhoneNumber()) ? "No phone number" : user.getPhoneNumber());
mUserDisplayName.setText(
TextUtils.isEmpty(user.getDisplayName()) ? "No display name" : user.getDisplayName());
@@ -185,14 +202,18 @@ private void populateIdpToken() {
token = mIdpResponse.getIdpToken();
secret = mIdpResponse.getIdpSecret();
}
+ View idpTokenLayout = findViewById(R.id.idp_token_layout);
if (token == null) {
- findViewById(R.id.idp_token_layout).setVisibility(View.GONE);
+ idpTokenLayout.setVisibility(View.GONE);
} else {
+ idpTokenLayout.setVisibility(View.VISIBLE);
((TextView) findViewById(R.id.idp_token)).setText(token);
}
+ View idpSecretLayout = findViewById(R.id.idp_secret_layout);
if (secret == null) {
- findViewById(R.id.idp_secret_layout).setVisibility(View.GONE);
+ idpSecretLayout.setVisibility(View.GONE);
} else {
+ idpSecretLayout.setVisibility(View.VISIBLE);
((TextView) findViewById(R.id.idp_secret)).setText(secret);
}
}
@@ -203,9 +224,73 @@ private void showSnackbar(@StringRes int errorMessageRes) {
.show();
}
- public static Intent createIntent(Context context, IdpResponse idpResponse) {
- Intent in = IdpResponse.getIntent(idpResponse);
- in.setClass(context, SignedInActivity.class);
- return in;
+ static final class SignedInConfig implements Parcelable {
+ int logo;
+ int theme;
+ List providerInfo;
+ String tosUrl;
+ boolean isCredentialSelectorEnabled;
+ boolean isHintSelectorEnabled;
+
+ SignedInConfig(int logo,
+ int theme,
+ List providerInfo,
+ String tosUrl,
+ boolean isCredentialSelectorEnabled,
+ boolean isHintSelectorEnabled) {
+ this.logo = logo;
+ this.theme = theme;
+ this.providerInfo = providerInfo;
+ this.tosUrl = tosUrl;
+ this.isCredentialSelectorEnabled = isCredentialSelectorEnabled;
+ this.isHintSelectorEnabled = isHintSelectorEnabled;
+ }
+
+ SignedInConfig(Parcel in) {
+ logo = in.readInt();
+ theme = in.readInt();
+ providerInfo = new ArrayList<>();
+ in.readList(providerInfo, IdpConfig.class.getClassLoader());
+ tosUrl = in.readString();
+ isCredentialSelectorEnabled = in.readInt() != 0;
+ isHintSelectorEnabled = in.readInt() != 0;
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public SignedInConfig createFromParcel(Parcel in) {
+ return new SignedInConfig(in);
+ }
+
+ @Override
+ public SignedInConfig[] newArray(int size) {
+ return new SignedInConfig[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(logo);
+ dest.writeInt(theme);
+ dest.writeList(providerInfo);
+ dest.writeString(tosUrl);
+ dest.writeInt(isCredentialSelectorEnabled ? 1 : 0);
+ dest.writeInt(isHintSelectorEnabled ? 1 : 0);
+ }
+ }
+
+ public static Intent createIntent(
+ Context context,
+ IdpResponse idpResponse,
+ SignedInConfig signedInConfig) {
+ Intent startIntent = idpResponse == null ? new Intent() : idpResponse.toIntent();
+
+ return startIntent.setClass(context, SignedInActivity.class)
+ .putExtra(EXTRA_SIGNED_IN_CONFIG, signedInConfig);
}
}
diff --git a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java
index 96426eb71..134076e71 100644
--- a/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java
+++ b/app/src/main/java/com/firebase/uidemo/database/ChatActivity.java
@@ -32,25 +32,23 @@
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
-import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.Query;
-public class ChatActivity extends AppCompatActivity implements FirebaseAuth.AuthStateListener {
+public class ChatActivity extends AppCompatActivity implements FirebaseAuth.AuthStateListener, View.OnClickListener {
private static final String TAG = "RecyclerViewDemo";
private FirebaseAuth mAuth;
- private DatabaseReference mRef;
- private DatabaseReference mChatRef;
+ protected DatabaseReference mChatRef;
private Button mSendButton;
- private EditText mMessageEdit;
+ protected EditText mMessageEdit;
private RecyclerView mMessages;
private LinearLayoutManager mManager;
- private FirebaseRecyclerAdapter mAdapter;
- private TextView mEmptyListMessage;
+ protected FirebaseRecyclerAdapter mAdapter;
+ protected TextView mEmptyListMessage;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -64,28 +62,9 @@ protected void onCreate(Bundle savedInstanceState) {
mMessageEdit = (EditText) findViewById(R.id.messageEdit);
mEmptyListMessage = (TextView) findViewById(R.id.emptyTextView);
- mRef = FirebaseDatabase.getInstance().getReference();
- mChatRef = mRef.child("chats");
+ mChatRef = FirebaseDatabase.getInstance().getReference().child("chats");
- mSendButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- String uid = mAuth.getCurrentUser().getUid();
- String name = "User " + uid.substring(0, 6);
-
- Chat chat = new Chat(name, mMessageEdit.getText().toString(), uid);
- mChatRef.push().setValue(chat, new DatabaseReference.CompletionListener() {
- @Override
- public void onComplete(DatabaseError error, DatabaseReference reference) {
- if (error != null) {
- Log.e(TAG, "Failed to write message", error.toException());
- }
- }
- });
-
- mMessageEdit.setText("");
- }
- });
+ mSendButton.setOnClickListener(this);
mManager = new LinearLayoutManager(this);
mManager.setReverseLayout(false);
@@ -126,33 +105,30 @@ public void onDestroy() {
}
@Override
- public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
- updateUI();
- }
+ public void onClick(View v) {
+ String uid = mAuth.getCurrentUser().getUid();
+ String name = "User " + uid.substring(0, 6);
- private void attachRecyclerViewAdapter() {
- Query lastFifty = mChatRef.limitToLast(50);
- mAdapter = new FirebaseRecyclerAdapter(
- Chat.class, R.layout.message, ChatHolder.class, lastFifty) {
+ Chat chat = new Chat(name, mMessageEdit.getText().toString(), uid);
+ mChatRef.push().setValue(chat, new DatabaseReference.CompletionListener() {
@Override
- public void populateViewHolder(ChatHolder holder, Chat chat, int position) {
- holder.setName(chat.getName());
- holder.setText(chat.getMessage());
-
- FirebaseUser currentUser = mAuth.getCurrentUser();
- if (currentUser != null && chat.getUid().equals(currentUser.getUid())) {
- holder.setIsSender(true);
- } else {
- holder.setIsSender(false);
+ public void onComplete(DatabaseError error, DatabaseReference reference) {
+ if (error != null) {
+ Log.e(TAG, "Failed to write message", error.toException());
}
}
+ });
- @Override
- protected void onDataChanged() {
- // If there are no chat messages, show a view that invites the user to add a message.
- mEmptyListMessage.setVisibility(mAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
- }
- };
+ mMessageEdit.setText("");
+ }
+
+ @Override
+ public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
+ updateUI();
+ }
+
+ private void attachRecyclerViewAdapter() {
+ mAdapter = getAdapter();
// Scroll to bottom on new messages
mAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@@ -165,6 +141,26 @@ public void onItemRangeInserted(int positionStart, int itemCount) {
mMessages.setAdapter(mAdapter);
}
+ protected FirebaseRecyclerAdapter getAdapter() {
+ Query lastFifty = mChatRef.limitToLast(50);
+ return new FirebaseRecyclerAdapter(
+ Chat.class,
+ R.layout.message,
+ ChatHolder.class,
+ lastFifty) {
+ @Override
+ public void populateViewHolder(ChatHolder holder, Chat chat, int position) {
+ holder.bind(chat);
+ }
+
+ @Override
+ public void onDataChanged() {
+ // If there are no chat messages, show a view that invites the user to add a message.
+ mEmptyListMessage.setVisibility(getItemCount() == 0 ? View.VISIBLE : View.GONE);
+ }
+ };
+ }
+
private void signInAnonymously() {
Toast.makeText(this, "Signing in...", Toast.LENGTH_SHORT).show();
mAuth.signInAnonymously()
diff --git a/app/src/main/java/com/firebase/uidemo/database/ChatHolder.java b/app/src/main/java/com/firebase/uidemo/database/ChatHolder.java
index 7c95dccdb..ce3be5248 100644
--- a/app/src/main/java/com/firebase/uidemo/database/ChatHolder.java
+++ b/app/src/main/java/com/firebase/uidemo/database/ChatHolder.java
@@ -13,6 +13,8 @@
import android.widget.TextView;
import com.firebase.uidemo.R;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.auth.FirebaseUser;
public class ChatHolder extends RecyclerView.ViewHolder {
private final TextView mNameField;
@@ -36,7 +38,23 @@ public ChatHolder(View itemView) {
mGray300 = ContextCompat.getColor(itemView.getContext(), R.color.material_gray_300);
}
- public void setIsSender(boolean isSender) {
+ public void bind(Chat chat) {
+ setName(chat.getName());
+ setText(chat.getMessage());
+
+ FirebaseUser currentUser = FirebaseAuth.getInstance().getCurrentUser();
+ setIsSender(currentUser != null && chat.getUid().equals(currentUser.getUid()));
+ }
+
+ private void setName(String name) {
+ mNameField.setText(name);
+ }
+
+ private void setText(String text) {
+ mTextField.setText(text);
+ }
+
+ private void setIsSender(boolean isSender) {
final int color;
if (isSender) {
color = mGreen300;
@@ -56,12 +74,4 @@ public void setIsSender(boolean isSender) {
((RotateDrawable) mRightArrow.getBackground()).getDrawable()
.setColorFilter(color, PorterDuff.Mode.SRC);
}
-
- public void setName(String name) {
- mNameField.setText(name);
- }
-
- public void setText(String text) {
- mTextField.setText(text);
- }
}
diff --git a/app/src/main/java/com/firebase/uidemo/database/ChatIndexActivity.java b/app/src/main/java/com/firebase/uidemo/database/ChatIndexActivity.java
new file mode 100644
index 000000000..2cad956ac
--- /dev/null
+++ b/app/src/main/java/com/firebase/uidemo/database/ChatIndexActivity.java
@@ -0,0 +1,55 @@
+package com.firebase.uidemo.database;
+
+import android.os.Bundle;
+import android.view.View;
+
+import com.firebase.ui.database.FirebaseIndexRecyclerAdapter;
+import com.firebase.ui.database.FirebaseRecyclerAdapter;
+import com.firebase.uidemo.R;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.database.DatabaseReference;
+import com.google.firebase.database.FirebaseDatabase;
+
+public class ChatIndexActivity extends ChatActivity {
+ private DatabaseReference mChatIndicesRef;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mChatIndicesRef = FirebaseDatabase.getInstance().getReference().child("chatIndices");
+ }
+
+ @Override
+ public void onClick(View v) {
+ String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();
+ String name = "User " + uid.substring(0, 6);
+ Chat chat = new Chat(name, mMessageEdit.getText().toString(), uid);
+
+ DatabaseReference chatRef = mChatRef.push();
+ mChatIndicesRef.child(chatRef.getKey()).setValue(true);
+ chatRef.setValue(chat);
+
+ mMessageEdit.setText("");
+ }
+
+ @Override
+ protected FirebaseRecyclerAdapter getAdapter() {
+ return new FirebaseIndexRecyclerAdapter(
+ Chat.class,
+ R.layout.message,
+ ChatHolder.class,
+ mChatIndicesRef.limitToLast(50),
+ mChatRef) {
+ @Override
+ public void populateViewHolder(ChatHolder holder, Chat chat, int position) {
+ holder.bind(chat);
+ }
+
+ @Override
+ public void onDataChanged() {
+ // If there are no chat messages, show a view that invites the user to add a message.
+ mEmptyListMessage.setVisibility(getItemCount() == 0 ? View.VISIBLE : View.GONE);
+ }
+ };
+ }
+}
diff --git a/app/src/main/res/layout/auth_ui_layout.xml b/app/src/main/res/layout/auth_ui_layout.xml
index ee35330d2..e9e96abff 100644
--- a/app/src/main/res/layout/auth_ui_layout.xml
+++ b/app/src/main/res/layout/auth_ui_layout.xml
@@ -54,13 +54,13 @@
android:id="@+id/default_theme"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:checked="true"
android:text="@string/default_theme"/>
+
+
+
+
+
+
+
+
+
+
+
+
+ android:text="@string/drive_file_scope"/>
+ android:text="@string/youtube_data_scope"/>
+
+
+ android:text="@string/enable_hint_selector"/>
+
+
-
-
-
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 4a0ad0672..e4040bae1 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -12,13 +12,11 @@
#EDE7F6#673AB7#512DA8
- #E040FB#E8F5E9#4CAF50#388E3C#AED581
- #69F0AE#E0E0E0#ff303030
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c81b56546..4bb747c6a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -23,6 +23,7 @@
NoneUse auth providers:Email
+ PhoneFacebookFacebook - configuration missingTwitter
@@ -31,7 +32,10 @@
Google - configuration missingGoogle TOSFirebase TOS
+ Google Privacy Policy
+ Firebase Privacy PolicyTerms of Service URL:
+ Privacy Policy URL:Unexpected onActivityResult response codeUnknown response from AuthUI sign-inSign in cancelled
@@ -46,11 +50,11 @@
Default themeConfiguration is required - see README.mdOther Options:
- Enable SmartLock for PasswordsThis sample will read an image from local storage to upload to Cloud Storage.Anonymous authentication failed, various components of the demo will not work. Make sure your device is online and that Anonymous Auth is configured in your Firebase project(https://console.firebase.google.com/project/_/authentication/providers)Example extra Google scopes
- Games
+ Drive File
+ Youtube dataExample extra Facebook scopesFriendsPhotos
@@ -62,10 +66,11 @@
UploadChoose ImageDownloaded image
- Drive FileAllow account creation if email does not exist.No messages. Start chatting at the bottom!Signed In
+ Enable Smart Lock\'s credential selector
+ Enable Smart Lock\'s hint selector
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 804934f63..f5b6c16fc 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -1,4 +1,5 @@
+
-
-
-
+
diff --git a/auth/README.md b/auth/README.md
index d853c8255..99ce9f904 100644
--- a/auth/README.md
+++ b/auth/README.md
@@ -47,17 +47,12 @@ Gradle, add the dependency:
dependencies {
// ...
compile 'com.firebaseui:firebase-ui-auth:1.2.0'
-}
-```
-
-and add the Fabric repository
-
-```groovy
-allprojects {
- repositories {
- // ...
- maven { url 'https://maven.fabric.io/public' }
- }
+
+ // Required only if Facebook login support is required
+ compile('com.facebook.android:facebook-android-sdk:4.22.1')
+
+ // Required only if Twitter login support is required
+ compile("com.twitter.sdk.android:twitter-core:3.0.0@aar") { transitive = true }
}
```
@@ -69,6 +64,7 @@ these authentication methods are first configured in the Firebase console.
FirebaseUI client-side configuration for Google sign-in is then provided
automatically by the
[google-services gradle plugin](https://developers.google.com/android/guides/google-services-plugin).
+
If support for Facebook Login is also required, define the
resource string `facebook_application_id` to match the application ID in
the [Facebook developer dashboard](https://developers.facebook.com):
@@ -96,6 +92,17 @@ Twitter app as reported by the [Twitter application manager](https://apps.twitte
In addition, you must enable the "Request email addresses from users" permission
in the "Permissions" tab of your Twitter app.
+In order to resolve the Twitter SDK, add the following repository to your `build.gradle`:
+
+```groovy
+allprojects {
+ repositories {
+ // ...
+ maven { url 'https://maven.fabric.io/public' }
+ }
+}
+```
+
## Using FirebaseUI for Authentication
Before invoking the FirebaseUI authentication flow, your app should check
@@ -161,16 +168,18 @@ is returned to your app in onActivityResult(...). See the [response codes](#resp
details on receiving the results of the sign in flow.
You can enable sign-in providers like Google Sign-In or Facebook Log In by calling the
-`setProviders` method:
+`setAvailableProviders` method:
```java
startActivityForResult(
AuthUI.getInstance()
.createSignInIntentBuilder()
- .setProviders(Arrays.asList(new AuthUI.IdpConfig.Builder(AuthUI.EMAIL_PROVIDER).build(),
- new AuthUI.IdpConfig.Builder(AuthUI.GOOGLE_PROVIDER).build(),
- new AuthUI.IdpConfig.Builder(AuthUI.FACEBOOK_PROVIDER).build(),
- new AuthUI.IdpConfig.Builder(AuthUI.TWITTER_PROVIDER).build()))
+ .setAvailableProviders(
+ Arrays.asList(new AuthUI.IdpConfig.Builder(AuthUI.EMAIL_PROVIDER).build(),
+ new AuthUI.IdpConfig.Builder(AuthUI.PHONE_VERIFICATION_PROVDER).build(),
+ new AuthUI.IdpConfig.Builder(AuthUI.GOOGLE_PROVIDER).build(),
+ new AuthUI.IdpConfig.Builder(AuthUI.FACEBOOK_PROVIDER).build(),
+ new AuthUI.IdpConfig.Builder(AuthUI.TWITTER_PROVIDER).build()))
.build(),
RC_SIGN_IN);
```
@@ -181,7 +190,7 @@ If a terms of service URL, privacy policy URL, and a custom theme are required:
startActivityForResult(
AuthUI.getInstance()
.createSignInIntentBuilder()
- .setProviders(...)
+ .setAvailableProviders(...)
.setTosUrl("https://superapp.example.com/terms-of-service.html")
.setPrivacyPolicyUrl("https://superapp.example.com/privacy-policy.html")
.setTheme(R.style.SuperAppTheme)
@@ -216,6 +225,18 @@ startActivityForResult(
RC_SIGN_IN);
```
+If you'd like to keep SmartLock's "hints" but disable the saving/retrieving of credentials, then
+you can use the two-argument version of `setIsSmartLockEnabled`:
+
+```java
+startActivityForResult(
+ AuthUI.getInstance()
+ .createSignInIntentBuilder()
+ .setIsSmartLockEnabled(false, true)
+ .build(),
+ RC_SIGN_IN);
+```
+
#### Handling the sign-in response
##### Response codes
@@ -264,9 +285,9 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
```
Alternatively, you can register a listener for authentication state changes;
-see the
-[Firebase Auth documentation](https://firebase.google.com/docs/auth/android/manage-users#get_the_currently_signed-in_user)
-for more information.
+see the Firebase Auth documentation to
+[get the currently signed-in user](https://firebase.google.com/docs/auth/android/manage-users#get_the_currently_signed-in_user)
+and [register an AuthStateListener](https://firebase.google.com/docs/reference/android/com/google/firebase/auth/FirebaseAuth.html#addAuthStateListener(com.google.firebase.auth.FirebaseAuth.AuthStateListener)).
##### ID Tokens
To retrieve the ID token that the IDP returned, you can extract an `IdpResponse` from the result
@@ -427,9 +448,9 @@ AuthUI.IdpConfig googleIdp = new AuthUI.IdpConfig.Builder(AuthUI.GOOGLE_PROVIDER
startActivityForResult(
AuthUI.getInstance()
.createSignInIntentBuilder()
- .setProviders(Arrays.asList(new IdpConfig.Builder(AuthUI.EMAIL_PROVIDER).build(),
- googleIdp,
- new IdpConfig.Builder(AuthUI.FACEBOOK_PROVIDER).build()))
+ .setAvailableProviders(Arrays.asList(new IdpConfig.Builder(AuthUI.EMAIL_PROVIDER).build(),
+ googleIdp,
+ new IdpConfig.Builder(AuthUI.FACEBOOK_PROVIDER).build()))
.build(),
RC_SIGN_IN);
```
@@ -453,8 +474,8 @@ AuthUI.IdpConfig facebookIdp = new AuthUI.IdpConfig.Builder(AuthUI.FACEBOOK_PROV
startActivityForResult(
AuthUI.getInstance()
.createSignInIntentBuilder()
- .setProviders(Arrays.asList(new AuthUI.IdpConfig.Builder(AuthUI.EMAIL_PROVIDER).build(),
- facebookIdp))
+ .setAvailableProviders(Arrays.asList(new AuthUI.IdpConfig.Builder(AuthUI.EMAIL_PROVIDER).build(),
+ facebookIdp))
.build(),
RC_SIGN_IN);
```
diff --git a/auth/auth-proguard.pro b/auth/auth-proguard.pro
new file mode 100644
index 000000000..02e0a6970
--- /dev/null
+++ b/auth/auth-proguard.pro
@@ -0,0 +1,2 @@
+-dontwarn com.twitter.**
+-dontwarn com.facebook.**
\ No newline at end of file
diff --git a/auth/build.gradle b/auth/build.gradle
index 3d7b2149c..73ae08779 100644
--- a/auth/build.gradle
+++ b/auth/build.gradle
@@ -1,5 +1,4 @@
apply plugin: 'com.android.library'
-apply plugin: 'io.fabric'
apply from: '../library/quality/quality.gradle'
android {
@@ -18,7 +17,7 @@ android {
manifestPlaceholders = [enableFbLogging: true]
minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt')
+ consumerProguardFiles getDefaultProguardFile('proguard-android.txt'), 'auth-proguard.pro'
}
debug {
@@ -44,19 +43,20 @@ dependencies {
compile "com.google.firebase:firebase-auth:$firebaseVersion"
compile "com.google.android.gms:play-services-auth:$firebaseVersion"
- compile 'com.facebook.android:facebook-android-sdk:4.19.0'
- compile("com.twitter.sdk.android:twitter:2.3.0@aar") { transitive = true }
+ provided 'com.facebook.android:facebook-android-sdk:4.23.0'
+ provided("com.twitter.sdk.android:twitter-core:3.0.0@aar") { transitive = true }
// The following libraries are needed to prevent incompatibilities with the facebook
// library when updating com.android.support libraries:
compile "com.android.support:cardview-v7:$supportLibraryVersion"
testCompile 'junit:junit:4.12'
- //noinspection NewerVersionAvailable, GradleDynamicVersion
- testCompile 'org.mockito:mockito-core:2.6.+'
+ //noinspection GradleDynamicVersion
+ testCompile 'org.mockito:mockito-core:2.8.+'
testCompile 'org.robolectric:robolectric:3.2.2'
// See https://github.com/robolectric/robolectric/issues/1932#issuecomment-219796474
testCompile 'org.khronos:opengl-api:gl1.1-android-2.1_r1'
+ testCompile 'com.facebook.android:facebook-android-sdk:4.23.0'
}
javadoc.exclude([
diff --git a/auth/src/main/AndroidManifest.xml b/auth/src/main/AndroidManifest.xml
index 59e72f1a1..d26d526c6 100644
--- a/auth/src/main/AndroidManifest.xml
+++ b/auth/src/main/AndroidManifest.xml
@@ -1,5 +1,6 @@
@@ -39,6 +40,12 @@
android:label="@string/sign_in_default"
android:exported="false"/>
+
+
+ android:configChanges="keyboard|keyboardHidden|screenLayout|screenSize|orientation"
+ tools:ignore="MissingRegistered" />
+ android:exported="true"
+ tools:ignore="MissingRegistered">
diff --git a/auth/src/main/java/com/firebase/ui/auth/AuthUI.java b/auth/src/main/java/com/firebase/ui/auth/AuthUI.java
index 566c510c4..a0f4c83a9 100644
--- a/auth/src/main/java/com/firebase/ui/auth/AuthUI.java
+++ b/auth/src/main/java/com/firebase/ui/auth/AuthUI.java
@@ -18,26 +18,22 @@
import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
+import android.support.annotation.CallSuper;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.annotation.StringDef;
import android.support.annotation.StyleRes;
-import android.support.annotation.VisibleForTesting;
import android.support.v4.app.FragmentActivity;
import com.facebook.login.LoginManager;
+import com.firebase.ui.auth.provider.TwitterProvider;
import com.firebase.ui.auth.ui.FlowParameters;
import com.firebase.ui.auth.ui.idp.AuthMethodPickerActivity;
-import com.firebase.ui.auth.util.CredentialTaskApi;
-import com.firebase.ui.auth.util.CredentialsApiHelper;
-import com.firebase.ui.auth.util.GoogleApiClientTaskHelper;
import com.firebase.ui.auth.util.GoogleSignInHelper;
import com.firebase.ui.auth.util.Preconditions;
import com.firebase.ui.auth.util.signincontainer.SmartLockBase;
-import com.google.android.gms.auth.api.Auth;
import com.google.android.gms.auth.api.credentials.Credential;
-import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
-import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.tasks.Continuation;
import com.google.android.gms.tasks.Task;
@@ -48,6 +44,7 @@
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.auth.GoogleAuthProvider;
+import com.google.firebase.auth.PhoneAuthProvider;
import com.google.firebase.auth.TwitterAuthProvider;
import java.util.ArrayList;
@@ -55,7 +52,6 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
-import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@@ -70,28 +66,41 @@
* for examples on how to get started with FirebaseUI Auth.
*/
public class AuthUI {
+ @StringDef({
+ EmailAuthProvider.PROVIDER_ID, EMAIL_PROVIDER,
+ PhoneAuthProvider.PROVIDER_ID, PHONE_VERIFICATION_PROVIDER,
+ GoogleAuthProvider.PROVIDER_ID, GOOGLE_PROVIDER,
+ FacebookAuthProvider.PROVIDER_ID, FACEBOOK_PROVIDER,
+ TwitterAuthProvider.PROVIDER_ID, TWITTER_PROVIDER,
+ })
+ public @interface SupportedProvider {}
/**
* Provider identifier for email and password credentials, for use with
- * {@link SignInIntentBuilder#setProviders}.
+ * {@link SignInIntentBuilder#setAvailableProviders(List)}.
*/
public static final String EMAIL_PROVIDER = EmailAuthProvider.PROVIDER_ID;
/**
- * Provider identifier for Google, for use with {@link SignInIntentBuilder#setProviders}.
+ * Provider identifier for Google, for use with {@link SignInIntentBuilder#setAvailableProviders(List)}.
*/
public static final String GOOGLE_PROVIDER = GoogleAuthProvider.PROVIDER_ID;
/**
- * Provider identifier for Facebook, for use with {@link SignInIntentBuilder#setProviders}.
+ * Provider identifier for Facebook, for use with {@link SignInIntentBuilder#setAvailableProviders(List)}.
*/
public static final String FACEBOOK_PROVIDER = FacebookAuthProvider.PROVIDER_ID;
/**
- * Provider identifier for Twitter, for use with {@link SignInIntentBuilder#setProviders}.
+ * Provider identifier for Twitter, for use with {@link SignInIntentBuilder#setAvailableProviders(List)}.
*/
public static final String TWITTER_PROVIDER = TwitterAuthProvider.PROVIDER_ID;
+ /**
+ * Provider identifier for Phone, for use with {@link SignInIntentBuilder#setProviders}.
+ */
+ public static final String PHONE_VERIFICATION_PROVIDER = PhoneAuthProvider.PROVIDER_ID;
+
/**
* Default value for logo resource, omits the logo from the {@link AuthMethodPickerActivity}.
*/
@@ -105,7 +114,8 @@ public class AuthUI {
EMAIL_PROVIDER,
GOOGLE_PROVIDER,
FACEBOOK_PROVIDER,
- TWITTER_PROVIDER
+ TWITTER_PROVIDER,
+ PHONE_VERIFICATION_PROVIDER
)));
private static final IdentityHashMap INSTANCES = new IdentityHashMap<>();
@@ -152,70 +162,6 @@ public static int getDefaultTheme() {
return R.style.FirebaseUI;
}
- /**
- * Signs the current user out, if one is signed in.
- *
- * @param activity The activity requesting the user be signed out.
- * @return a task which, upon completion, signals that the user has been signed out ({@code
- * result.isSuccess()}, or that the sign-out attempt failed unexpectedly ({@code
- * !result.isSuccess()}).
- * @deprecated use {@link #signOut(FragmentActivity)} instead
- */
- @Deprecated
- public Task signOut(@NonNull Activity activity) {
- // Get helper for Google Sign In and Credentials API
- GoogleApiClientTaskHelper taskHelper = GoogleApiClientTaskHelper.getInstance(activity);
- taskHelper.getBuilder()
- .addApi(Auth.CREDENTIALS_API)
- .addApi(Auth.GOOGLE_SIGN_IN_API, GoogleSignInOptions.DEFAULT_SIGN_IN);
-
- // Get Credentials Helper
- CredentialTaskApi credentialsHelper = CredentialsApiHelper.getInstance(taskHelper);
-
- // Firebase Sign out
- mAuth.signOut();
-
- // Disable credentials auto sign-in
- Task disableCredentialsTask = credentialsHelper.disableAutoSignIn();
-
- // Google sign out
- Task googleSignOutTask = taskHelper.getConnectedGoogleApiClient()
- .continueWith(new Continuation() {
- @Override
- public Void then(@NonNull Task task) throws Exception {
- if (task.isSuccessful()) {
- Auth.GoogleSignInApi.signOut(task.getResult());
- }
- return null;
- }
- });
-
- // Facebook sign out
- LoginManager.getInstance().logOut();
-
- // Wait for all tasks to complete
- return Tasks.whenAll(disableCredentialsTask, googleSignOutTask);
- }
-
- /**
- * Delete the use from FirebaseAuth and delete any associated credentials from the Credentials
- * API. Returns a {@code Task} that succeeds if the Firebase Auth user deletion succeeds and
- * fails if the Firebase Auth deletion fails. Credentials deletion failures are handled
- * silently.
- *
- * @param activity the calling {@link Activity}.
- * @deprecated use {@link #delete(FragmentActivity)} instead
- */
- @Deprecated
- public Task delete(@NonNull Activity activity) {
- // Initialize SmartLock helper
- GoogleApiClientTaskHelper gacHelper = GoogleApiClientTaskHelper.getInstance(activity);
- gacHelper.getBuilder().addApi(Auth.CREDENTIALS_API);
- CredentialTaskApi credentialHelper = CredentialsApiHelper.getInstance(gacHelper);
-
- return getDeleteTask(credentialHelper);
- }
-
/**
* Signs the current user out, if one is signed in.
*
@@ -226,20 +172,30 @@ public Task delete(@NonNull Activity activity) {
*/
public Task signOut(@NonNull FragmentActivity activity) {
// Get Credentials Helper
- GoogleSignInHelper credentialsHelper = GoogleSignInHelper.getInstance(activity);
+ GoogleSignInHelper signInHelper = GoogleSignInHelper.getInstance(activity);
// Firebase Sign out
mAuth.signOut();
// Disable credentials auto sign-in
- Task disableCredentialsTask = credentialsHelper.disableAutoSignIn();
+ Task disableCredentialsTask = signInHelper.disableAutoSignIn();
// Google sign out
- Task signOutTask = credentialsHelper.signOut();
+ Task signOutTask = signInHelper.signOut();
// Facebook sign out
- LoginManager.getInstance().logOut();
+ try {
+ LoginManager.getInstance().logOut();
+ } catch (NoClassDefFoundError e) {
+ // do nothing
+ }
+ // Twitter sign out
+ try {
+ TwitterProvider.signout(activity);
+ } catch (NoClassDefFoundError e) {
+ // do nothing
+ }
// Wait for all tasks to complete
return Tasks.whenAll(disableCredentialsTask, signOutTask);
}
@@ -254,12 +210,8 @@ public Task signOut(@NonNull FragmentActivity activity) {
*/
public Task delete(@NonNull FragmentActivity activity) {
// Initialize SmartLock helper
- CredentialTaskApi credentialHelper = GoogleSignInHelper.getInstance(activity);
+ GoogleSignInHelper signInHelper = GoogleSignInHelper.getInstance(activity);
- return getDeleteTask(credentialHelper);
- }
-
- private Task getDeleteTask(CredentialTaskApi credentialHelper) {
FirebaseUser firebaseUser = FirebaseAuth.getInstance().getCurrentUser();
if (firebaseUser == null) {
// If the current user is null, return a failed task immediately
@@ -275,7 +227,7 @@ private Task getDeleteTask(CredentialTaskApi credentialHelper) {
// For each Credential in the list, create a task to delete it.
List> credentialTasks = new ArrayList<>();
for (Credential credential : credentials) {
- credentialTasks.add(credentialHelper.delete(credential));
+ credentialTasks.add(signInHelper.delete(credential));
}
// Create a combined task that will succeed when all credential delete operations
@@ -315,7 +267,7 @@ public static class IdpConfig implements Parcelable {
private final String mProviderId;
private final List mScopes;
- private IdpConfig(@NonNull String providerId, List scopes) {
+ private IdpConfig(@SupportedProvider @NonNull String providerId, List scopes) {
mProviderId = providerId;
mScopes = Collections.unmodifiableList(scopes);
}
@@ -325,6 +277,7 @@ private IdpConfig(Parcel in) {
mScopes = Collections.unmodifiableList(in.createStringArrayList());
}
+ @SupportedProvider
public String getProviderId() {
return mProviderId;
}
@@ -356,11 +309,33 @@ public void writeToParcel(Parcel parcel, int i) {
parcel.writeStringList(mScopes);
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ IdpConfig config = (IdpConfig) o;
+
+ return mProviderId.equals(config.mProviderId);
+ }
+
+ @Override
+ public int hashCode() {
+ return mProviderId.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "IdpConfig{" +
+ "mProviderId='" + mProviderId + '\'' +
+ ", mScopes=" + mScopes +
+ '}';
+ }
+
public static class Builder {
- private String mProviderId;
+ @SupportedProvider private String mProviderId;
private List mScopes = new ArrayList<>();
-
/**
* Builds the configuration parameters for an identity provider.
*
@@ -368,7 +343,7 @@ public static class Builder {
* AuthUI#GOOGLE_PROVIDER}. See {@link AuthUI#SUPPORTED_PROVIDERS} for
* the complete list of supported Identity providers
*/
- public Builder(@NonNull String providerId) {
+ public Builder(@SupportedProvider @NonNull String providerId) {
if (!SUPPORTED_PROVIDERS.contains(providerId)) {
throw new IllegalArgumentException("Unkown provider: " + providerId);
}
@@ -401,48 +376,56 @@ public IdpConfig build() {
}
/**
- * Builder for the intent to start the user authentication flow.
+ * Base builder for both {@link SignInIntentBuilder}.
*/
- public final class SignInIntentBuilder {
- private int mLogo = NO_LOGO;
- private int mTheme = getDefaultTheme();
- private LinkedHashSet mProviders = new LinkedHashSet<>();
- private String mTosUrl;
- private boolean mIsSmartLockEnabled = true;
- private boolean mAllowNewEmailAccounts = true;
-
- private SignInIntentBuilder() {
- mProviders.add(new IdpConfig.Builder(EMAIL_PROVIDER).build());
- }
+ @SuppressWarnings(value = "unchecked")
+ private abstract class AuthIntentBuilder {
+ int mLogo = NO_LOGO;
+ int mTheme = getDefaultTheme();
+ List mProviders = new ArrayList<>();
+ String mTosUrl;
+ String mPrivacyPolicyUrl;
+ boolean mEnableCredentials = true;
+ boolean mEnableHints = true;
+
+ private AuthIntentBuilder() {}
/**
* Specifies the theme to use for the application flow. If no theme is specified,
* a default theme will be used.
*/
- public SignInIntentBuilder setTheme(@StyleRes int theme) {
+ public T setTheme(@StyleRes int theme) {
Preconditions.checkValidStyle(
mApp.getApplicationContext(),
theme,
"theme identifier is unknown or not a style definition");
mTheme = theme;
- return this;
+ return (T) this;
}
/**
* Specifies the logo to use for the {@link AuthMethodPickerActivity}. If no logo
* is specified, none will be used.
*/
- public SignInIntentBuilder setLogo(@DrawableRes int logo) {
+ public T setLogo(@DrawableRes int logo) {
mLogo = logo;
- return this;
+ return (T) this;
}
/**
* Specifies the terms-of-service URL for the application.
*/
- public SignInIntentBuilder setTosUrl(@Nullable String tosUrl) {
+ public T setTosUrl(@Nullable String tosUrl) {
mTosUrl = tosUrl;
- return this;
+ return (T) this;
+ }
+
+ /**
+ * Specifies the privacy policy URL for the application.
+ */
+ public T setPrivacyPolicyUrl(@Nullable String privacyPolicyUrl) {
+ mPrivacyPolicyUrl = privacyPolicyUrl;
+ return (T) this;
}
/**
@@ -456,87 +439,144 @@ public SignInIntentBuilder setTosUrl(@Nullable String tosUrl) {
* configuration parameters for the IDP.
* @see IdpConfig
*/
- public SignInIntentBuilder setProviders(@NonNull List idpConfigs) {
+ public T setAvailableProviders(@NonNull List idpConfigs) {
mProviders.clear();
- Set configuredProviders = new HashSet<>();
- for (IdpConfig idpConfig : idpConfigs) {
- if (configuredProviders.contains(idpConfig.getProviderId())) {
+
+ for (IdpConfig config : idpConfigs) {
+ if (mProviders.contains(config)) {
throw new IllegalArgumentException("Each provider can only be set once. "
- + idpConfig.getProviderId()
+ + config.getProviderId()
+ " was set twice.");
+ } else {
+ mProviders.add(config);
+ }
+
+ if (config.getProviderId().equals(FACEBOOK_PROVIDER)) {
+ try {
+ Class c = com.facebook.FacebookSdk.class;
+ } catch (NoClassDefFoundError e) {
+ throw new RuntimeException("Facebook provider cannot be configured " +
+ "without dependency. Did you forget to add " +
+ "'com.facebook.android:facebook-android-sdk:VERSION' dependency?");
+ }
+ }
+
+ if (config.getProviderId().equals(TWITTER_PROVIDER)) {
+ try {
+ Class c = com.twitter.sdk.android.core.TwitterCore.class;
+ } catch (NoClassDefFoundError e) {
+ throw new RuntimeException("Twitter provider cannot be configured " +
+ "without dependency. Did you forget to add " +
+ "'com.twitter.sdk.android:twitter-core:VERSION' dependency?");
+ }
}
- configuredProviders.add(idpConfig.getProviderId());
- mProviders.add(idpConfig);
}
- return this;
+
+ return (T) this;
}
/**
- * Specifies the set of supported authentication providers. At least one provider
- * must be specified, and the set of providers must be a subset of
- * {@link #SUPPORTED_PROVIDERS}. There may only be one instance of each provider.
+ * Specified the set of supported authentication providers. At least one provider must
+ * be specified. There may only be one instance of each provider.
*
- *
If no providers are explicitly specified by calling this method, then
- * {@link #EMAIL_PROVIDER email} is the default supported provider.
+ *
If no providers are explicitly specified by calling this method, then the email
+ * provider is the default supported provider.
*
- * @see #EMAIL_PROVIDER
- * @see #FACEBOOK_PROVIDER
- * @see #GOOGLE_PROVIDER
+ * @param idpConfigs a list of {@link IdpConfig}s, where each {@link IdpConfig} contains the
+ * configuration parameters for the IDP.
+ * @see IdpConfig
+ * @deprecated because the order in which providers were displayed was the inverse of the
+ * order in which they were supplied. Use {@link #setAvailableProviders(List)} to display
+ * the providers in the order in which they were supplied.
*/
@Deprecated
- public SignInIntentBuilder setProviders(@NonNull String... providers) {
- mProviders.clear(); // clear the default email provider
- for (String provider : providers) {
- if (isIdpAlreadyConfigured(provider)) {
- throw new IllegalArgumentException("Provider already configured: " + provider);
- }
- mProviders.add(new IdpConfig.Builder(provider).build());
+ public T setProviders(@NonNull List idpConfigs) {
+ setAvailableProviders(idpConfigs);
+
+ // Ensure email provider is at the bottom to keep backwards compatibility
+ int emailProviderIndex = mProviders.indexOf(new IdpConfig.Builder(EMAIL_PROVIDER).build());
+ if (emailProviderIndex != -1) {
+ mProviders.add(0, mProviders.remove(emailProviderIndex));
}
- return this;
+ Collections.reverse(mProviders);
+
+ return (T) this;
}
/**
* Enables or disables the use of Smart Lock for Passwords in the sign in flow.
+ * To (en)disable hint selector and credential selector independently
+ * use {@link #setIsSmartLockEnabled(boolean, boolean)}
*
*
SmartLock is enabled by default.
+ *
+ * @param enabled enables smartlock's credential selector and hint selector
*/
- public SignInIntentBuilder setIsSmartLockEnabled(boolean enabled) {
- mIsSmartLockEnabled = enabled;
- return this;
+ public T setIsSmartLockEnabled(boolean enabled) {
+ setIsSmartLockEnabled(enabled, enabled);
+ return (T) this;
}
/**
- * Enables or disables creating new accounts in the email sign in flow.
+ * Enables or disables the use of Smart Lock for Passwords credential selector and hint
+ * selector.
*
- *
Account creation is enabled by default.
+ *
Both selectors are enabled by default.
+
+ * @param enableCredentials enables credential selector before signup
+ * @param enableHints enable hint selector in respective signup screens
+ * @return
*/
- public SignInIntentBuilder setAllowNewEmailAccounts(boolean enabled) {
- mAllowNewEmailAccounts = enabled;
- return this;
+ public T setIsSmartLockEnabled(boolean enableCredentials, boolean enableHints) {
+ mEnableCredentials = enableCredentials;
+ mEnableHints = enableHints;
+ return (T) this;
}
- private boolean isIdpAlreadyConfigured(@NonNull String providerId) {
- for (IdpConfig config : mProviders) {
- if (config.getProviderId().equals(providerId)) {
- return true;
- }
+ @CallSuper
+ public Intent build() {
+ if (mProviders.isEmpty()) {
+ mProviders.add(new IdpConfig.Builder(EMAIL_PROVIDER).build());
}
- return false;
- }
- public Intent build() {
return KickoffActivity.createIntent(mApp.getApplicationContext(), getFlowParams());
}
- @VisibleForTesting()
- public FlowParameters getFlowParams() {
- return new FlowParameters(mApp.getName(),
- new ArrayList<>(mProviders),
- mTheme,
- mLogo,
- mTosUrl,
- mIsSmartLockEnabled,
- mAllowNewEmailAccounts);
+ protected abstract FlowParameters getFlowParams();
+ }
+
+ /**
+ * Builder for the intent to start the user authentication flow.
+ */
+ public final class SignInIntentBuilder extends AuthIntentBuilder {
+ private boolean mAllowNewEmailAccounts = true;
+
+ private SignInIntentBuilder() {
+ super();
+ }
+
+ /**
+ * Enables or disables creating new accounts in the email sign in flow.
+ *
+ *
Account creation is enabled by default.
+ */
+ public SignInIntentBuilder setAllowNewEmailAccounts(boolean enabled) {
+ mAllowNewEmailAccounts = enabled;
+ return this;
+ }
+
+ @Override
+ protected FlowParameters getFlowParams() {
+ return new FlowParameters(
+ mApp.getName(),
+ mProviders,
+ mTheme,
+ mLogo,
+ mTosUrl,
+ mPrivacyPolicyUrl,
+ mEnableCredentials,
+ mEnableHints,
+ mAllowNewEmailAccounts);
}
}
}
diff --git a/auth/src/main/java/com/firebase/ui/auth/IdpResponse.java b/auth/src/main/java/com/firebase/ui/auth/IdpResponse.java
index 9458d39cf..2c9797a0a 100644
--- a/auth/src/main/java/com/firebase/ui/auth/IdpResponse.java
+++ b/auth/src/main/java/com/firebase/ui/auth/IdpResponse.java
@@ -19,8 +19,14 @@
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.text.TextUtils;
import com.firebase.ui.auth.ui.ExtraConstants;
+import com.google.firebase.auth.FacebookAuthProvider;
+import com.google.firebase.auth.GithubAuthProvider;
+import com.google.firebase.auth.GoogleAuthProvider;
+import com.google.firebase.auth.TwitterAuthProvider;
/**
* A container that encapsulates the result of authenticating with an Identity Provider.
@@ -28,38 +34,25 @@
public class IdpResponse implements Parcelable {
private final String mProviderId;
private final String mEmail;
+ private final String mPhoneNumber;
private final String mToken;
private final String mSecret;
private final int mErrorCode;
private IdpResponse(int errorCode) {
- this(null, null, null, null, errorCode);
- }
-
- public IdpResponse(@NonNull String providerId, @NonNull String email) {
- this(providerId, email, null, null, ResultCodes.OK);
- }
-
- public IdpResponse(@NonNull String providerId, @NonNull String email, @NonNull String token) {
- this(providerId, email, token, null, ResultCodes.OK);
- }
-
- public IdpResponse(
- @NonNull String providerId,
- @NonNull String email,
- @NonNull String token,
- @NonNull String secret) {
- this(providerId, email, token, secret, ResultCodes.OK);
+ this(null, null, null, null, null, errorCode);
}
private IdpResponse(
String providerId,
String email,
+ String phoneNumber,
String token,
String secret,
int errorCode) {
mProviderId = providerId;
mEmail = email;
+ mPhoneNumber = phoneNumber;
mToken = token;
mSecret = secret;
mErrorCode = errorCode;
@@ -80,17 +73,21 @@ public static IdpResponse fromResultIntent(Intent resultIntent) {
}
}
- public static Intent getIntent(IdpResponse response) {
- return new Intent().putExtra(ExtraConstants.EXTRA_IDP_RESPONSE, response);
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public static Intent getErrorCodeIntent(int errorCode) {
+ return new IdpResponse(errorCode).toIntent();
}
- public static Intent getErrorCodeIntent(int errorCode) {
- return getIntent(new IdpResponse(errorCode));
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public Intent toIntent() {
+ return new Intent().putExtra(ExtraConstants.EXTRA_IDP_RESPONSE, this);
}
/**
* Get the type of provider. e.g. {@link AuthUI#GOOGLE_PROVIDER}
*/
+ @NonNull
+ @AuthUI.SupportedProvider
public String getProviderType() {
return mProviderId;
}
@@ -98,10 +95,19 @@ public String getProviderType() {
/**
* Get the email used to sign in.
*/
+ @Nullable
public String getEmail() {
return mEmail;
}
+ /**
+ * Get the phone number used to sign in.
+ */
+ @Nullable
+ public String getPhoneNumber() {
+ return mPhoneNumber;
+ }
+
/**
* Get the token received as a result of logging in with the specified IDP
*/
@@ -134,6 +140,7 @@ public int describeContents() {
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mProviderId);
dest.writeString(mEmail);
+ dest.writeString(mPhoneNumber);
dest.writeString(mToken);
dest.writeString(mSecret);
dest.writeInt(mErrorCode);
@@ -147,6 +154,7 @@ public IdpResponse createFromParcel(Parcel in) {
in.readString(),
in.readString(),
in.readString(),
+ in.readString(),
in.readInt()
);
}
@@ -156,4 +164,51 @@ public IdpResponse[] newArray(int size) {
return new IdpResponse[size];
}
};
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public static class Builder {
+ private String mProviderId;
+ private String mEmail;
+ private String mPhoneNumber;
+ private String mToken;
+ private String mSecret;
+
+ public Builder(@AuthUI.SupportedProvider @NonNull String providerId, @Nullable String email) {
+ mProviderId = providerId;
+ mEmail = email;
+ }
+
+ public Builder setPhoneNumber(String phoneNumber) {
+ mPhoneNumber = phoneNumber;
+ return this;
+ }
+
+ public Builder setToken(String token) {
+ mToken = token;
+ return this;
+ }
+
+ public Builder setSecret(String secret) {
+ mSecret = secret;
+ return this;
+ }
+
+ public IdpResponse build() {
+ if ((mProviderId.equalsIgnoreCase(GoogleAuthProvider.PROVIDER_ID)
+ || mProviderId.equalsIgnoreCase(FacebookAuthProvider.PROVIDER_ID)
+ || mProviderId.equalsIgnoreCase(TwitterAuthProvider.PROVIDER_ID)
+ || mProviderId.equalsIgnoreCase(GithubAuthProvider.PROVIDER_ID))
+ && TextUtils.isEmpty(mToken)) {
+ throw new IllegalStateException(
+ "Token cannot be null when using a non-email provider.");
+ }
+ if (mProviderId.equalsIgnoreCase(TwitterAuthProvider.PROVIDER_ID)
+ && TextUtils.isEmpty(mSecret)) {
+ throw new IllegalStateException(
+ "Secret cannot be null when using the Twitter provider.");
+ }
+
+ return new IdpResponse(mProviderId, mEmail, mPhoneNumber, mToken, mSecret, ResultCodes.OK);
+ }
+ }
}
diff --git a/auth/src/main/java/com/firebase/ui/auth/KickoffActivity.java b/auth/src/main/java/com/firebase/ui/auth/KickoffActivity.java
index 072be6cf0..ff33ea219 100644
--- a/auth/src/main/java/com/firebase/ui/auth/KickoffActivity.java
+++ b/auth/src/main/java/com/firebase/ui/auth/KickoffActivity.java
@@ -9,9 +9,9 @@
import android.util.Log;
import com.firebase.ui.auth.ui.ActivityHelper;
-import com.firebase.ui.auth.ui.HelperActivityBase;
import com.firebase.ui.auth.ui.ExtraConstants;
import com.firebase.ui.auth.ui.FlowParameters;
+import com.firebase.ui.auth.ui.HelperActivityBase;
import com.firebase.ui.auth.util.PlayServicesHelper;
import com.firebase.ui.auth.util.signincontainer.SignInDelegate;
@@ -34,7 +34,7 @@ protected void onCreate(Bundle savedInstance) {
if (savedInstance == null || savedInstance.getBoolean(IS_WAITING_FOR_PLAY_SERVICES)) {
if (isOffline()) {
Log.d(TAG, "No network connection");
- finish(ErrorCodes.NO_NETWORK,
+ finish(ResultCodes.CANCELED,
IdpResponse.getErrorCodeIntent(ErrorCodes.NO_NETWORK));
return;
}
@@ -52,7 +52,7 @@ public void onCancel(DialogInterface dialog) {
});
if (isPlayServicesAvailable) {
- SignInDelegate.delegate(this, mActivityHelper.getFlowParams());
+ start();
} else {
mIsWaitingForPlayServices = true;
}
@@ -72,7 +72,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_PLAY_SERVICES) {
if (resultCode == ResultCodes.OK) {
- SignInDelegate.delegate(this, mActivityHelper.getFlowParams());
+ start();
} else {
finish(ResultCodes.CANCELED,
IdpResponse.getErrorCodeIntent(ErrorCodes.UNKNOWN_ERROR));
@@ -83,6 +83,11 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
}
}
+ private void start() {
+ FlowParameters flowParams = mActivityHelper.getFlowParams();
+ SignInDelegate.delegate(this, flowParams);
+ }
+
/**
* Check if there is an active or soon-to-be-active network connection.
*
diff --git a/auth/src/main/java/com/firebase/ui/auth/provider/EmailProvider.java b/auth/src/main/java/com/firebase/ui/auth/provider/EmailProvider.java
new file mode 100644
index 000000000..fd22120cf
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/provider/EmailProvider.java
@@ -0,0 +1,56 @@
+package com.firebase.ui.auth.provider;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.support.annotation.LayoutRes;
+
+import com.firebase.ui.auth.AuthUI;
+import com.firebase.ui.auth.R;
+import com.firebase.ui.auth.ResultCodes;
+import com.firebase.ui.auth.ui.BaseHelper;
+import com.firebase.ui.auth.ui.email.RegisterEmailActivity;
+import com.google.firebase.auth.EmailAuthProvider;
+
+public class EmailProvider implements Provider {
+ private static final int RC_EMAIL_FLOW = 2;
+
+ private Activity mActivity;
+ private BaseHelper mHelper;
+
+ public EmailProvider(Activity activity, BaseHelper helper) {
+ mActivity = activity;
+ mHelper = helper;
+ }
+
+ @Override
+ public String getName(Context context) {
+ return context.getString(R.string.provider_name_email);
+ }
+
+ @Override
+ @AuthUI.SupportedProvider
+ public String getProviderId() {
+ return EmailAuthProvider.PROVIDER_ID;
+ }
+
+ @Override
+ @LayoutRes
+ public int getButtonLayout() {
+ return R.layout.provider_button_email;
+ }
+
+ @Override
+ public void startLogin(Activity activity) {
+ activity.startActivityForResult(
+ RegisterEmailActivity.createIntent(activity, mHelper.getFlowParams()),
+ RC_EMAIL_FLOW);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == RC_EMAIL_FLOW && resultCode == ResultCodes.OK) {
+ mHelper.finishActivity(mActivity, ResultCodes.OK, data);
+ }
+ }
+}
diff --git a/auth/src/main/java/com/firebase/ui/auth/provider/FacebookProvider.java b/auth/src/main/java/com/firebase/ui/auth/provider/FacebookProvider.java
index dd0f071b4..c7c1dba91 100644
--- a/auth/src/main/java/com/firebase/ui/auth/provider/FacebookProvider.java
+++ b/auth/src/main/java/com/firebase/ui/auth/provider/FacebookProvider.java
@@ -18,6 +18,8 @@
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.Nullable;
import android.support.annotation.StyleRes;
import android.util.Log;
@@ -55,16 +57,7 @@ public class FacebookProvider implements IdpProvider, FacebookCallback scopes = idpConfig.getScopes();
if (scopes == null) {
mScopes = new ArrayList<>();
@@ -83,14 +76,21 @@ public static AuthCredential createAuthCredential(IdpResponse response) {
@Override
public String getName(Context context) {
- return context.getResources().getString(R.string.idp_name_facebook);
+ return context.getString(R.string.idp_name_facebook);
}
@Override
+ @AuthUI.SupportedProvider
public String getProviderId() {
return FacebookAuthProvider.PROVIDER_ID;
}
+ @Override
+ @LayoutRes
+ public int getButtonLayout() {
+ return R.layout.idp_button_facebook;
+ }
+
@Override
public void startLogin(Activity activity) {
sCallbackManager = CallbackManager.Factory.create();
@@ -146,8 +146,8 @@ public void onCompleted(JSONObject object, GraphResponse response) {
String email = object.getString("email");
onSuccess(email, loginResult);
} catch (JSONException e) {
- Log.e(TAG, "JSON Exception reading from Facebook GraphRequest", e);
- onFailure(new Bundle());
+ Log.e(TAG, "Failure retrieving Facebook email", e);
+ onSuccess(null, loginResult);
}
}
}
@@ -175,18 +175,17 @@ public void onError(FacebookException error) {
onFailure(extra);
}
- private IdpResponse createIdpResponse(String email, LoginResult loginResult) {
- return new IdpResponse(
- FacebookAuthProvider.PROVIDER_ID,
- email,
- loginResult.getAccessToken().getToken());
- }
-
- private void onSuccess(String email, LoginResult loginResult) {
+ private void onSuccess(@Nullable String email, LoginResult loginResult) {
gcCallbackManager();
mCallbackObject.onSuccess(createIdpResponse(email, loginResult));
}
+ private IdpResponse createIdpResponse(@Nullable String email, LoginResult loginResult) {
+ return new IdpResponse.Builder(FacebookAuthProvider.PROVIDER_ID, email)
+ .setToken(loginResult.getAccessToken().getToken())
+ .build();
+ }
+
private void onFailure(Bundle bundle) {
gcCallbackManager();
mCallbackObject.onFailure(bundle);
diff --git a/auth/src/main/java/com/firebase/ui/auth/provider/GoogleProvider.java b/auth/src/main/java/com/firebase/ui/auth/provider/GoogleProvider.java
index 3cf307944..c7cc79ec4 100644
--- a/auth/src/main/java/com/firebase/ui/auth/provider/GoogleProvider.java
+++ b/auth/src/main/java/com/firebase/ui/auth/provider/GoogleProvider.java
@@ -18,12 +18,15 @@
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.text.TextUtils;
import android.util.Log;
+import android.widget.Toast;
+import com.firebase.ui.auth.AuthUI;
import com.firebase.ui.auth.AuthUI.IdpConfig;
import com.firebase.ui.auth.IdpResponse;
import com.firebase.ui.auth.R;
@@ -49,6 +52,7 @@ public class GoogleProvider implements IdpProvider, GoogleApiClient.OnConnection
private FragmentActivity mActivity;
private IdpConfig mIdpConfig;
private IdpCallback mIdpCallback;
+ private boolean mSpecificAccount;
public GoogleProvider(FragmentActivity activity, IdpConfig idpConfig) {
this(activity, idpConfig, null);
@@ -57,7 +61,7 @@ public GoogleProvider(FragmentActivity activity, IdpConfig idpConfig) {
public GoogleProvider(FragmentActivity activity, IdpConfig idpConfig, @Nullable String email) {
mActivity = activity;
mIdpConfig = idpConfig;
-
+ mSpecificAccount = !TextUtils.isEmpty(email);
mGoogleApiClient = new GoogleApiClient.Builder(mActivity)
.enableAutoManage(mActivity, GoogleApiHelper.getSafeAutoManageId(), this)
.addApi(Auth.GOOGLE_SIGN_IN_API, getSignInOptions(email))
@@ -76,13 +80,6 @@ private GoogleSignInOptions getSignInOptions(@Nullable String email) {
.requestEmail()
.requestIdToken(clientId);
- if (mActivity.getResources().getIdentifier(
- "google_permissions", "array", mActivity.getPackageName()) != 0) {
- Log.w(TAG, "DEVELOPER WARNING: You have defined R.array.google_permissions but that is"
- + " no longer respected as of FirebaseUI 1.0.0. Please see README for IDP scope"
- + " configuration instructions.");
- }
-
// Add additional scopes
for (String scopeString : mIdpConfig.getScopes()) {
builder.requestScopes(new Scope(scopeString));
@@ -95,15 +92,23 @@ private GoogleSignInOptions getSignInOptions(@Nullable String email) {
return builder.build();
}
+ @Override
public String getName(Context context) {
- return context.getResources().getString(R.string.idp_name_google);
+ return context.getString(R.string.idp_name_google);
}
@Override
+ @AuthUI.SupportedProvider
public String getProviderId() {
return GoogleAuthProvider.PROVIDER_ID;
}
+ @Override
+ @LayoutRes
+ public int getButtonLayout() {
+ return R.layout.idp_button_google;
+ }
+
@Override
public void setAuthenticationCallback(IdpCallback callback) {
mIdpCallback = callback;
@@ -117,8 +122,9 @@ public void disconnect() {
}
private IdpResponse createIdpResponse(GoogleSignInAccount account) {
- return new IdpResponse(
- GoogleAuthProvider.PROVIDER_ID, account.getEmail(), account.getIdToken());
+ return new IdpResponse.Builder(GoogleAuthProvider.PROVIDER_ID, account.getEmail())
+ .setToken(account.getIdToken())
+ .build();
}
@Override
@@ -127,6 +133,14 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
if (result != null) {
if (result.isSuccess()) {
+ if (mSpecificAccount) {
+ Toast.makeText(
+ mActivity,
+ mActivity.getString(
+ R.string.signed_in_with_specific_account,
+ result.getSignInAccount().getEmail()),
+ Toast.LENGTH_SHORT).show();
+ }
mIdpCallback.onSuccess(createIdpResponse(result.getSignInAccount()));
} else {
onError(result);
diff --git a/auth/src/main/java/com/firebase/ui/auth/provider/IdpProvider.java b/auth/src/main/java/com/firebase/ui/auth/provider/IdpProvider.java
index e185a3bc1..b04a37fa1 100644
--- a/auth/src/main/java/com/firebase/ui/auth/provider/IdpProvider.java
+++ b/auth/src/main/java/com/firebase/ui/auth/provider/IdpProvider.java
@@ -14,28 +14,13 @@
package com.firebase.ui.auth.provider;
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
import android.os.Bundle;
import com.firebase.ui.auth.IdpResponse;
-public interface IdpProvider {
-
- /**
- * Retrieves the name of the IDP, for display on-screen.
- */
- String getName(Context context);
-
- String getProviderId();
-
+public interface IdpProvider extends Provider {
void setAuthenticationCallback(IdpCallback callback);
- void onActivityResult(int requestCode, int resultCode, Intent data);
-
- void startLogin(Activity activity);
-
interface IdpCallback {
void onSuccess(IdpResponse idpResponse);
diff --git a/auth/src/main/java/com/firebase/ui/auth/provider/PhoneProvider.java b/auth/src/main/java/com/firebase/ui/auth/provider/PhoneProvider.java
new file mode 100644
index 000000000..741d47cd7
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/provider/PhoneProvider.java
@@ -0,0 +1,57 @@
+package com.firebase.ui.auth.provider;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.support.annotation.LayoutRes;
+
+import com.firebase.ui.auth.AuthUI;
+import com.firebase.ui.auth.R;
+import com.firebase.ui.auth.ResultCodes;
+import com.firebase.ui.auth.ui.BaseHelper;
+import com.firebase.ui.auth.ui.phone.PhoneVerificationActivity;
+import com.google.firebase.auth.PhoneAuthProvider;
+
+public class PhoneProvider implements Provider {
+
+ private static final int RC_PHONE_FLOW = 4;
+
+ private Activity mActivity;
+ private BaseHelper mHelper;
+
+ public PhoneProvider(Activity activity, BaseHelper helper) {
+ mActivity = activity;
+ mHelper = helper;
+ }
+
+ @Override
+ public String getName(Context context) {
+ return context.getString(R.string.provider_name_phone);
+ }
+
+ @Override
+ @AuthUI.SupportedProvider
+ public String getProviderId() {
+ return PhoneAuthProvider.PROVIDER_ID;
+ }
+
+ @Override
+ @LayoutRes
+ public int getButtonLayout() {
+ return R.layout.provider_button_phone;
+ }
+
+ @Override
+ public void startLogin(Activity activity) {
+ activity.startActivityForResult(
+ PhoneVerificationActivity.createIntent(activity, mHelper.getFlowParams(), null),
+ RC_PHONE_FLOW);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == RC_PHONE_FLOW && resultCode == ResultCodes.OK) {
+ mHelper.finishActivity(mActivity, ResultCodes.OK, data);
+ }
+ }
+}
diff --git a/auth/src/main/java/com/firebase/ui/auth/provider/Provider.java b/auth/src/main/java/com/firebase/ui/auth/provider/Provider.java
new file mode 100644
index 000000000..b5f3435a7
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/provider/Provider.java
@@ -0,0 +1,29 @@
+package com.firebase.ui.auth.provider;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.support.annotation.LayoutRes;
+
+import com.google.firebase.auth.GoogleAuthProvider;
+
+public interface Provider {
+ /** Retrieves the name of the IDP, for display on-screen. */
+ String getName(Context context);
+
+ /** Retrieves the id of the IDP, e.g. {@link GoogleAuthProvider#PROVIDER_ID}. */
+ String getProviderId();
+
+ /** Retrieves the layout id of the button to inflate and/or set a click listener. */
+ @LayoutRes
+ int getButtonLayout();
+
+ /** Start the login process for the IDP, e.g. show the Google sign-in activity. */
+ void startLogin(Activity activity);
+
+ /**
+ * Handle the sign result by either finishing the calling activity or sending an {@link
+ * IdpProvider.IdpCallback} response.
+ */
+ void onActivityResult(int requestCode, int resultCode, Intent data);
+}
diff --git a/auth/src/main/java/com/firebase/ui/auth/provider/AuthCredentialHelper.java b/auth/src/main/java/com/firebase/ui/auth/provider/ProviderUtils.java
similarity index 50%
rename from auth/src/main/java/com/firebase/ui/auth/provider/AuthCredentialHelper.java
rename to auth/src/main/java/com/firebase/ui/auth/provider/ProviderUtils.java
index 7079c2b2e..28eccea7e 100644
--- a/auth/src/main/java/com/firebase/ui/auth/provider/AuthCredentialHelper.java
+++ b/auth/src/main/java/com/firebase/ui/auth/provider/ProviderUtils.java
@@ -14,15 +14,31 @@
package com.firebase.ui.auth.provider;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.text.TextUtils;
import com.firebase.ui.auth.IdpResponse;
+import com.firebase.ui.auth.ui.TaskFailureLogger;
+import com.google.android.gms.tasks.Continuation;
+import com.google.android.gms.tasks.Task;
+import com.google.android.gms.tasks.Tasks;
import com.google.firebase.auth.AuthCredential;
import com.google.firebase.auth.FacebookAuthProvider;
+import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.GoogleAuthProvider;
+import com.google.firebase.auth.ProviderQueryResult;
import com.google.firebase.auth.TwitterAuthProvider;
-public final class AuthCredentialHelper {
+import java.util.List;
+
+public final class ProviderUtils {
+ private static final String TAG = "ProviderUtils";
+
+ private ProviderUtils() {
+ throw new AssertionError("No instance for you!");
+ }
+
@Nullable
public static AuthCredential getAuthCredential(IdpResponse idpResponse) {
switch (idpResponse.getProviderType()) {
@@ -36,4 +52,24 @@ public static AuthCredential getAuthCredential(IdpResponse idpResponse) {
return null;
}
}
+
+ public static Task fetchTopProvider(FirebaseAuth auth, @NonNull String email) {
+ if (TextUtils.isEmpty(email)) {
+ return Tasks.forException(new NullPointerException("Email cannot be empty"));
+ }
+
+ return auth.fetchProvidersForEmail(email)
+ .addOnFailureListener(
+ new TaskFailureLogger(TAG, "Error fetching providers for email"))
+ .continueWith(new Continuation() {
+ @Override
+ public String then(@NonNull Task task) throws Exception {
+ if (!task.isSuccessful()) return null;
+
+ List providers = task.getResult().getProviders();
+ return providers == null || providers.isEmpty()
+ ? null : providers.get(0);
+ }
+ });
+ }
}
diff --git a/auth/src/main/java/com/firebase/ui/auth/provider/TwitterProvider.java b/auth/src/main/java/com/firebase/ui/auth/provider/TwitterProvider.java
index ac76df41c..dd64d077c 100644
--- a/auth/src/main/java/com/firebase/ui/auth/provider/TwitterProvider.java
+++ b/auth/src/main/java/com/firebase/ui/auth/provider/TwitterProvider.java
@@ -4,23 +4,26 @@
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.support.annotation.LayoutRes;
import android.util.Log;
+import com.firebase.ui.auth.AuthUI;
import com.firebase.ui.auth.IdpResponse;
import com.firebase.ui.auth.R;
import com.google.firebase.auth.AuthCredential;
import com.google.firebase.auth.TwitterAuthProvider;
-import com.twitter.sdk.android.Twitter;
import com.twitter.sdk.android.core.Callback;
import com.twitter.sdk.android.core.Result;
+import com.twitter.sdk.android.core.Twitter;
import com.twitter.sdk.android.core.TwitterAuthConfig;
+import com.twitter.sdk.android.core.TwitterConfig;
+import com.twitter.sdk.android.core.TwitterCore;
import com.twitter.sdk.android.core.TwitterException;
import com.twitter.sdk.android.core.TwitterSession;
import com.twitter.sdk.android.core.identity.TwitterAuthClient;
import java.lang.ref.WeakReference;
-import io.fabric.sdk.android.Fabric;
public class TwitterProvider extends Callback implements IdpProvider {
private static final String TAG = "TwitterProvider";
@@ -28,11 +31,8 @@ public class TwitterProvider extends Callback implements IdpProv
private IdpCallback mCallbackObject;
private TwitterAuthClient mTwitterAuthClient;
- public TwitterProvider(Context appContext) {
- TwitterAuthConfig authConfig = new TwitterAuthConfig(
- appContext.getString(R.string.twitter_consumer_key),
- appContext.getString(R.string.twitter_consumer_secret));
- Fabric.with(appContext.getApplicationContext(), new Twitter(authConfig));
+ public TwitterProvider(Context context) {
+ initialize(context);
mTwitterAuthClient = new TwitterAuthClient();
}
@@ -43,16 +43,43 @@ public static AuthCredential createAuthCredential(IdpResponse response) {
return TwitterAuthProvider.getCredential(response.getIdpToken(), response.getIdpSecret());
}
+ public static void initialize(Context context) {
+ TwitterAuthConfig authConfig = new TwitterAuthConfig(
+ context.getString(R.string.twitter_consumer_key),
+ context.getString(R.string.twitter_consumer_secret));
+ TwitterConfig config = new TwitterConfig.Builder(context)
+ .twitterAuthConfig(authConfig)
+ .build();
+ Twitter.initialize(config);
+ }
+
+ public static void signout(Context context) {
+ try {
+ Twitter.getInstance();
+ } catch (IllegalStateException e) {
+ initialize(context);
+ }
+
+ signOut();
+ }
+
@Override
public String getName(Context context) {
return context.getString(R.string.idp_name_twitter);
}
@Override
+ @AuthUI.SupportedProvider
public String getProviderId() {
return TwitterAuthProvider.PROVIDER_ID;
}
+ @Override
+ @LayoutRes
+ public int getButtonLayout() {
+ return R.layout.idp_button_twitter;
+ }
+
@Override
public void setAuthenticationCallback(IdpCallback callback) {
mCallbackObject = callback;
@@ -109,11 +136,14 @@ private void onSuccess(IdpResponse response) {
}
private IdpResponse createIdpResponse(String email) {
- return new IdpResponse(
- TwitterAuthProvider.PROVIDER_ID,
- email,
- mTwitterSession.getAuthToken().token,
- mTwitterSession.getAuthToken().secret);
+ return new IdpResponse.Builder(TwitterAuthProvider.PROVIDER_ID, email)
+ .setToken(mTwitterSession.getAuthToken().token)
+ .setSecret(mTwitterSession.getAuthToken().secret)
+ .build();
}
}
+
+ private static void signOut() throws IllegalStateException {
+ TwitterCore.getInstance().getSessionManager().clearActiveSession();
+ }
}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/BaseHelper.java b/auth/src/main/java/com/firebase/ui/auth/ui/BaseHelper.java
index bbe50f913..27df410fe 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/BaseHelper.java
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/BaseHelper.java
@@ -18,6 +18,7 @@
import com.google.firebase.FirebaseApp;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
+import com.google.firebase.auth.PhoneAuthProvider;
import static com.firebase.ui.auth.util.Preconditions.checkNotNull;
@@ -80,16 +81,8 @@ public boolean isProgressDialogShowing() {
return mProgressDialog != null && mProgressDialog.isShowing();
}
- public String getAppName() {
- return mFlowParams.appName;
- }
-
- public FirebaseApp getFirebaseApp() {
- return FirebaseApp.getInstance(mFlowParams.appName);
- }
-
public FirebaseAuth getFirebaseAuth() {
- return FirebaseAuth.getInstance(getFirebaseApp());
+ return FirebaseAuth.getInstance(FirebaseApp.getInstance(mFlowParams.appName));
}
public CredentialsApi getCredentialsApi() {
@@ -104,12 +97,8 @@ public SaveSmartLock getSaveSmartLockInstance(FragmentActivity activity) {
return SaveSmartLock.getInstance(activity, getFlowParams());
}
- public void saveCredentialsOrFinish(
- @Nullable SaveSmartLock saveSmartLock,
- Activity activity,
- FirebaseUser firebaseUser,
- @NonNull IdpResponse response) {
- saveCredentialsOrFinish(saveSmartLock, activity, firebaseUser, null, response);
+ public PhoneAuthProvider getPhoneAuthProviderInstance() {
+ return PhoneAuthProvider.getInstance();
}
public void saveCredentialsOrFinish(
@@ -119,7 +108,7 @@ public void saveCredentialsOrFinish(
@Nullable String password,
IdpResponse response) {
if (saveSmartLock == null) {
- finishActivity(activity, ResultCodes.OK, IdpResponse.getIntent(response));
+ finishActivity(activity, ResultCodes.OK, response.toIntent());
} else {
saveSmartLock.saveCredentialsOrFinish(
firebaseUser,
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/ExtraConstants.java b/auth/src/main/java/com/firebase/ui/auth/ui/ExtraConstants.java
index 9f9ee92db..5f28841da 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/ExtraConstants.java
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/ExtraConstants.java
@@ -25,5 +25,6 @@ public class ExtraConstants {
public static final String EXTRA_IDP_RESPONSE = "extra_idp_response";
public static final String EXTRA_USER = "extra_user";
public static final String EXTRA_EMAIL = "extra_email";
+ public static final String EXTRA_PHONE = "extra_phone_number";
public static final String HAS_EXISTING_INSTANCE = "has_existing_instance";
}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/FlowParameters.java b/auth/src/main/java/com/firebase/ui/auth/ui/FlowParameters.java
index f057886a2..7f3bd5e08 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/FlowParameters.java
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/FlowParameters.java
@@ -48,17 +48,23 @@ public class FlowParameters implements Parcelable {
@Nullable
public final String termsOfServiceUrl;
- public final boolean smartLockEnabled;
+ @Nullable
+ public final String privacyPolicyUrl;
public final boolean allowNewEmailAccounts;
+ public final boolean enableCredentials;
+ public final boolean enableHints;
+
public FlowParameters(
@NonNull String appName,
@NonNull List providerInfo,
@StyleRes int themeId,
@DrawableRes int logoId,
@Nullable String termsOfServiceUrl,
- boolean smartLockEnabled,
+ @Nullable String privacyPolicyUrl,
+ boolean enableCredentials,
+ boolean enableHints,
boolean allowNewEmailAccounts) {
this.appName = Preconditions.checkNotNull(appName, "appName cannot be null");
this.providerInfo = Collections.unmodifiableList(
@@ -66,7 +72,9 @@ public FlowParameters(
this.themeId = themeId;
this.logoId = logoId;
this.termsOfServiceUrl = termsOfServiceUrl;
- this.smartLockEnabled = smartLockEnabled;
+ this.privacyPolicyUrl = privacyPolicyUrl;
+ this.enableCredentials = enableCredentials;
+ this.enableHints = enableHints;
this.allowNewEmailAccounts = allowNewEmailAccounts;
}
@@ -77,7 +85,9 @@ public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(themeId);
dest.writeInt(logoId);
dest.writeString(termsOfServiceUrl);
- dest.writeInt(smartLockEnabled ? 1 : 0);
+ dest.writeString(privacyPolicyUrl);
+ dest.writeInt(enableCredentials ? 1 : 0);
+ dest.writeInt(enableHints ? 1 : 0);
dest.writeInt(allowNewEmailAccounts ? 1 : 0);
}
@@ -94,7 +104,9 @@ public FlowParameters createFromParcel(Parcel in) {
int themeId = in.readInt();
int logoId = in.readInt();
String termsOfServiceUrl = in.readString();
- boolean smartLockEnabled = in.readInt() != 0;
+ String privacyPolicyUrl = in.readString();
+ boolean enableCredentials = in.readInt() != 0;
+ boolean enableHints = in.readInt() != 0;
boolean allowNewEmailAccounts = in.readInt() != 0;
return new FlowParameters(
@@ -103,7 +115,9 @@ public FlowParameters createFromParcel(Parcel in) {
themeId,
logoId,
termsOfServiceUrl,
- smartLockEnabled,
+ privacyPolicyUrl,
+ enableCredentials,
+ enableHints,
allowNewEmailAccounts);
}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/ImeHelper.java b/auth/src/main/java/com/firebase/ui/auth/ui/ImeHelper.java
new file mode 100644
index 000000000..e50db76b0
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/ImeHelper.java
@@ -0,0 +1,30 @@
+package com.firebase.ui.auth.ui;
+
+import android.support.annotation.RestrictTo;
+import android.view.KeyEvent;
+import android.view.inputmethod.EditorInfo;
+import android.widget.EditText;
+import android.widget.TextView;
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class ImeHelper {
+ public interface DonePressedListener {
+ void onDonePressed();
+ }
+
+ public static void setImeOnDoneListener(EditText doneEditText,
+ final DonePressedListener listener) {
+ doneEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
+ if (event != null
+ && event.getKeyCode() == KeyEvent.KEYCODE_ENTER
+ || actionId == EditorInfo.IME_ACTION_DONE) {
+ listener.onDonePressed();
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/ResultCodes.java b/auth/src/main/java/com/firebase/ui/auth/ui/ResultCodes.java
deleted file mode 100644
index 41143c623..000000000
--- a/auth/src/main/java/com/firebase/ui/auth/ui/ResultCodes.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.firebase.ui.auth.ui;
-
-import com.firebase.ui.auth.AuthUI;
-
-/**
- * Result codes returned when using {@link AuthUI.SignInIntentBuilder#build()} with
- * {@code startActivityForResult}.
- */
-@Deprecated
-public class ResultCodes {
-
- /**
- * Sign in failed due to lack of network connection
- *
- * @deprecated Please use {@link com.firebase.ui.auth.ErrorCodes#NO_NETWORK}
- **/
- @Deprecated
- public static final int RESULT_NO_NETWORK = com.firebase.ui.auth.ErrorCodes.NO_NETWORK;
-
-}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/TaskFailureLogger.java b/auth/src/main/java/com/firebase/ui/auth/ui/TaskFailureLogger.java
index 734e52e46..986f35d2d 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/TaskFailureLogger.java
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/TaskFailureLogger.java
@@ -23,7 +23,7 @@ public class TaskFailureLogger implements OnFailureListener {
private String mTag;
private String mMessage;
- public TaskFailureLogger(String tag, String message) {
+ public TaskFailureLogger(@NonNull String tag, @NonNull String message) {
mTag = tag;
mMessage = message;
}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/TermsTextView.java b/auth/src/main/java/com/firebase/ui/auth/ui/TermsTextView.java
new file mode 100644
index 000000000..77058716a
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/TermsTextView.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.firebase.ui.auth.ui;
+
+import android.content.Context;
+import android.support.annotation.StringRes;
+import android.support.v7.widget.AppCompatTextView;
+import android.util.AttributeSet;
+
+import com.firebase.ui.auth.ui.email.PreambleHandler;
+
+/**
+ * Text view to display terms of service before completing signup.
+ * The view helps display TOS linking to the provided custom URI.
+ * It handles the styling of the link and opens the uri in a CustomTabs on click.
+ */
+public class TermsTextView extends AppCompatTextView {
+ public TermsTextView(Context context) {
+ super(context);
+ }
+
+ public TermsTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public TermsTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ /**
+ * @param params FlowParameters containing terms URLs.
+ * @param buttonText for the button that represents the "action" described in the terms.
+ */
+ public void showTerms(FlowParameters params, @StringRes int buttonText) {
+ PreambleHandler handler = new PreambleHandler(getContext(), params, buttonText);
+ handler.setPreamble(this);
+ }
+}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/User.java b/auth/src/main/java/com/firebase/ui/auth/ui/User.java
index 0acf6ec0f..7fab9ace1 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/User.java
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/User.java
@@ -9,6 +9,8 @@
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
+import com.firebase.ui.auth.AuthUI;
+
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class User implements Parcelable {
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
@@ -57,6 +59,7 @@ public String getName() {
}
@Nullable
+ @AuthUI.SupportedProvider
public String getProvider() {
return mProvider;
}
@@ -79,7 +82,7 @@ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeParcelable(mPhotoUri, flags);
}
- public static final class Builder {
+ public static class Builder {
private String mEmail;
private String mName;
private String mProvider;
@@ -94,7 +97,7 @@ public Builder setName(String name) {
return this;
}
- public Builder setProvider(String provider) {
+ public Builder setProvider(@AuthUI.SupportedProvider String provider) {
mProvider = provider;
return this;
}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/accountlink/WelcomeBackIdpPrompt.java b/auth/src/main/java/com/firebase/ui/auth/ui/accountlink/WelcomeBackIdpPrompt.java
index a9e1a76d7..c965cbb81 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/accountlink/WelcomeBackIdpPrompt.java
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/accountlink/WelcomeBackIdpPrompt.java
@@ -30,7 +30,7 @@
import com.firebase.ui.auth.IdpResponse;
import com.firebase.ui.auth.R;
import com.firebase.ui.auth.ResultCodes;
-import com.firebase.ui.auth.provider.AuthCredentialHelper;
+import com.firebase.ui.auth.provider.ProviderUtils;
import com.firebase.ui.auth.provider.FacebookProvider;
import com.firebase.ui.auth.provider.GoogleProvider;
import com.firebase.ui.auth.provider.IdpProvider;
@@ -76,7 +76,7 @@ protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.welcome_back_idp_prompt_layout);
IdpResponse newUserIdpResponse = IdpResponse.fromResultIntent(getIntent());
- mPrevCredential = AuthCredentialHelper.getAuthCredential(newUserIdpResponse);
+ mPrevCredential = ProviderUtils.getAuthCredential(newUserIdpResponse);
User oldUser = User.getUser(getIntent());
@@ -89,7 +89,7 @@ protected void onCreate(Bundle savedInstanceState) {
break;
case FacebookAuthProvider.PROVIDER_ID:
mIdpProvider = new FacebookProvider(
- this, idpConfig, mActivityHelper.getFlowParams().themeId);
+ idpConfig, mActivityHelper.getFlowParams().themeId);
break;
case TwitterAuthProvider.PROVIDER_ID:
mIdpProvider = new TwitterProvider(this);
@@ -140,7 +140,7 @@ public void onSuccess(final IdpResponse idpResponse) {
return; // do nothing
}
- AuthCredential newCredential = AuthCredentialHelper.getAuthCredential(idpResponse);
+ AuthCredential newCredential = ProviderUtils.getAuthCredential(idpResponse);
if (newCredential == null) {
Log.e(TAG, "No credential returned");
finish(ResultCodes.CANCELED, IdpResponse.getErrorCodeIntent(ErrorCodes.UNKNOWN_ERROR));
@@ -158,10 +158,11 @@ public void onSuccess(AuthResult result) {
result.getUser()
.linkWithCredential(mPrevCredential)
.addOnFailureListener(new TaskFailureLogger(
- TAG, "Error signing in with previous credential " + idpResponse.getProviderType()))
+ TAG, "Error signing in with previous credential " +
+ idpResponse.getProviderType()))
.addOnCompleteListener(new FinishListener(idpResponse));
} else {
- finish(ResultCodes.OK, IdpResponse.getIntent(idpResponse));
+ finish(ResultCodes.OK, idpResponse.toIntent());
}
}
})
@@ -172,12 +173,14 @@ public void onFailure(@NonNull Exception e) {
}
})
.addOnFailureListener(
- new TaskFailureLogger(TAG, "Error signing in with new credential " + idpResponse.getProviderType()));
+ new TaskFailureLogger(TAG, "Error signing in with new credential " +
+ idpResponse.getProviderType()));
} else {
currentUser
.linkWithCredential(newCredential)
.addOnFailureListener(
- new TaskFailureLogger(TAG, "Error linking with credential " + idpResponse.getProviderType()))
+ new TaskFailureLogger(TAG, "Error linking with credential " +
+ idpResponse.getProviderType()))
.addOnCompleteListener(new FinishListener(idpResponse));
}
}
@@ -200,7 +203,7 @@ private class FinishListener implements OnCompleteListener {
}
public void onComplete(@NonNull Task task) {
- finish(ResultCodes.OK, IdpResponse.getIntent(mIdpResponse));
+ finish(ResultCodes.OK, mIdpResponse.toIntent());
}
}
}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/accountlink/WelcomeBackPasswordPrompt.java b/auth/src/main/java/com/firebase/ui/auth/ui/accountlink/WelcomeBackPasswordPrompt.java
index 7bf7c1dab..6ac33f2e3 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/accountlink/WelcomeBackPasswordPrompt.java
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/accountlink/WelcomeBackPasswordPrompt.java
@@ -35,11 +35,12 @@
import com.firebase.ui.auth.IdpResponse;
import com.firebase.ui.auth.R;
import com.firebase.ui.auth.ResultCodes;
-import com.firebase.ui.auth.provider.AuthCredentialHelper;
+import com.firebase.ui.auth.provider.ProviderUtils;
import com.firebase.ui.auth.ui.AppCompatBase;
import com.firebase.ui.auth.ui.BaseHelper;
import com.firebase.ui.auth.ui.ExtraConstants;
import com.firebase.ui.auth.ui.FlowParameters;
+import com.firebase.ui.auth.ui.ImeHelper;
import com.firebase.ui.auth.ui.TaskFailureLogger;
import com.firebase.ui.auth.ui.email.RecoverPasswordActivity;
import com.firebase.ui.auth.util.signincontainer.SaveSmartLock;
@@ -55,7 +56,8 @@
* the password before initiating a link.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class WelcomeBackPasswordPrompt extends AppCompatBase implements View.OnClickListener {
+public class WelcomeBackPasswordPrompt extends AppCompatBase
+ implements View.OnClickListener, ImeHelper.DonePressedListener {
private static final String TAG = "WelcomeBackPassword";
private String mEmail;
@@ -85,11 +87,16 @@ protected void onCreate(Bundle savedInstanceState) {
mIdpResponse = IdpResponse.fromResultIntent(getIntent());
mEmail = mIdpResponse.getEmail();
+ TextView welcomeBackHeader = (TextView) findViewById(R.id.welcome_back_email_header);
mPasswordLayout = (TextInputLayout) findViewById(R.id.password_layout);
mPasswordField = (EditText) findViewById(R.id.password);
- // Create welcome back text with email bolded
+ ImeHelper.setImeOnDoneListener(mPasswordField, this);
+
+ // Create welcome back text with email bolded.
String bodyText = getString(R.string.welcome_back_password_prompt_body, mEmail);
+ FlowParameters flowParameters = mActivityHelper.getFlowParams();
+
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(bodyText);
int emailStart = bodyText.indexOf(mEmail);
spannableStringBuilder.setSpan(new StyleSpan(Typeface.BOLD),
@@ -109,7 +116,7 @@ protected void onCreate(Bundle savedInstanceState) {
public void onClick(View view) {
final int id = view.getId();
if (id == R.id.button_done) {
- next(mEmail, mPasswordField.getText().toString());
+ validateAndSignIn();
} else if (id == R.id.trouble_signing_in) {
startActivity(RecoverPasswordActivity.createIntent(
this,
@@ -119,7 +126,16 @@ public void onClick(View view) {
}
}
- private void next(final String email, final String password) {
+ @Override
+ public void onDonePressed() {
+ validateAndSignIn();
+ }
+
+ private void validateAndSignIn() {
+ validateAndSignIn(mEmail, mPasswordField.getText().toString());
+ }
+
+ private void validateAndSignIn(final String email, final String password) {
// Check for null or empty password
if (TextUtils.isEmpty(password)) {
mPasswordLayout.setError(getString(R.string.required_field));
@@ -138,7 +154,7 @@ private void next(final String email, final String password) {
@Override
public void onSuccess(AuthResult authResult) {
AuthCredential authCredential =
- AuthCredentialHelper.getAuthCredential(mIdpResponse);
+ ProviderUtils.getAuthCredential(mIdpResponse);
// If authCredential is null, the user only has an email account.
// Otherwise, the user has an email account that we need to link to an idp.
@@ -147,12 +163,14 @@ public void onSuccess(AuthResult authResult) {
mSaveSmartLock,
authResult.getUser(),
password,
- new IdpResponse(EmailAuthProvider.PROVIDER_ID, email));
+ new IdpResponse.Builder(EmailAuthProvider.PROVIDER_ID, email)
+ .build());
} else {
authResult.getUser()
.linkWithCredential(authCredential)
.addOnFailureListener(new TaskFailureLogger(
- TAG, "Error signing in with credential " + authCredential.getProvider()))
+ TAG, "Error signing in with credential " +
+ authCredential.getProvider()))
.addOnSuccessListener(new OnSuccessListener() {
@Override
public void onSuccess(AuthResult authResult) {
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/email/CheckEmailFragment.java b/auth/src/main/java/com/firebase/ui/auth/ui/email/CheckEmailFragment.java
index f486bec18..82d1dd12f 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/email/CheckEmailFragment.java
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/email/CheckEmailFragment.java
@@ -17,10 +17,11 @@
import android.widget.EditText;
import com.firebase.ui.auth.R;
+import com.firebase.ui.auth.provider.ProviderUtils;
import com.firebase.ui.auth.ui.ExtraConstants;
import com.firebase.ui.auth.ui.FlowParameters;
import com.firebase.ui.auth.ui.FragmentBase;
-import com.firebase.ui.auth.ui.TaskFailureLogger;
+import com.firebase.ui.auth.ui.ImeHelper;
import com.firebase.ui.auth.ui.User;
import com.firebase.ui.auth.ui.email.fieldvalidators.EmailFieldValidator;
import com.firebase.ui.auth.util.GoogleApiHelper;
@@ -34,9 +35,6 @@
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.EmailAuthProvider;
-import com.google.firebase.auth.ProviderQueryResult;
-
-import java.util.List;
/**
* Fragment that shows a form with an email field and checks for existing accounts with that
@@ -45,7 +43,10 @@
* Host Activities should implement {@link CheckEmailListener}.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class CheckEmailFragment extends FragmentBase implements View.OnClickListener {
+public class CheckEmailFragment extends FragmentBase implements
+ View.OnClickListener,
+ ImeHelper.DonePressedListener {
+
/**
* Interface to be implemented by Activities hosting this Fragment.
*/
@@ -62,7 +63,7 @@ interface CheckEmailListener {
void onExistingIdpUser(User user);
/**
- * Email entered does not beling to an existing user.
+ * Email entered does not belong to an existing user.
*/
void onNewUser(User user);
@@ -82,7 +83,7 @@ interface CheckEmailListener {
private Credential mLastCredential;
- public static CheckEmailFragment getInstance(@NonNull FlowParameters flowParameters,
+ public static CheckEmailFragment newInstance(@NonNull FlowParameters flowParameters,
@Nullable String email) {
CheckEmailFragment fragment = new CheckEmailFragment();
Bundle args = new Bundle();
@@ -107,6 +108,8 @@ public View onCreateView(LayoutInflater inflater,
mEmailLayout.setOnClickListener(this);
mEmailEditText.setOnClickListener(this);
+ ImeHelper.setImeOnDoneListener(mEmailEditText, this);
+
// "Next" button
v.findViewById(R.id.button_next).setOnClickListener(this);
@@ -133,7 +136,7 @@ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
// Use email passed in
mEmailEditText.setText(email);
validateAndProceed();
- } else if (mHelper.getFlowParams().smartLockEnabled) {
+ } else if (mHelper.getFlowParams().enableHints) {
// Try SmartLock email autocomplete hint
showEmailAutoCompleteHint();
}
@@ -168,59 +171,51 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
}
}
- public void validateAndProceed() {
+ private void validateAndProceed() {
String email = mEmailEditText.getText().toString();
if (mEmailFieldValidator.validate(email)) {
checkAccountExists(email);
}
}
- public void checkAccountExists(@NonNull final String email) {
+ private void checkAccountExists(@NonNull final String email) {
mHelper.showLoadingDialog(R.string.progress_dialog_checking_accounts);
- if (!TextUtils.isEmpty(email)) {
- mHelper.getFirebaseAuth()
- .fetchProvidersForEmail(email)
- .addOnFailureListener(
- new TaskFailureLogger(TAG, "Error fetching providers for email"))
- .addOnCompleteListener(
- getActivity(),
- new OnCompleteListener() {
- @Override
- public void onComplete(@NonNull Task task) {
- mHelper.dismissDialog();
- }
- })
- .addOnSuccessListener(
- getActivity(),
- new OnSuccessListener() {
- @Override
- public void onSuccess(ProviderQueryResult result) {
- List providers = result.getProviders();
- if (providers == null || providers.isEmpty()) {
- // Get name from SmartLock, if possible
- String name = null;
- Uri photoUri = null;
- if (mLastCredential != null && mLastCredential.getId().equals(email)) {
- name = mLastCredential.getName();
- photoUri = mLastCredential.getProfilePictureUri();
- }
-
- mListener.onNewUser(new User.Builder(email)
- .setName(name)
- .setPhotoUri(photoUri)
- .build());
- } else if (EmailAuthProvider.PROVIDER_ID.equalsIgnoreCase(providers.get(0))) {
- mListener.onExistingEmailUser(new User.Builder(email).build());
- } else {
- mListener.onExistingIdpUser(
- new User.Builder(email)
- .setProvider(providers.get(0))
- .build());
- }
- }
- });
+ // Get name from SmartLock, if possible
+ String name = null;
+ Uri photoUri = null;
+ if (mLastCredential != null && mLastCredential.getId().equals(email)) {
+ name = mLastCredential.getName();
+ photoUri = mLastCredential.getProfilePictureUri();
}
+
+ final String finalName = name;
+ final Uri finalPhotoUri = photoUri;
+ ProviderUtils.fetchTopProvider(mHelper.getFirebaseAuth(), email)
+ .addOnSuccessListener(getActivity(), new OnSuccessListener() {
+ @Override
+ public void onSuccess(String provider) {
+ if (provider == null) {
+ mListener.onNewUser(new User.Builder(email)
+ .setName(finalName)
+ .setPhotoUri(finalPhotoUri)
+ .build());
+ } else if (EmailAuthProvider.PROVIDER_ID.equalsIgnoreCase(provider)) {
+ mListener.onExistingEmailUser(new User.Builder(email).build());
+ } else {
+ mListener.onExistingIdpUser(
+ new User.Builder(email).setProvider(provider).build());
+ }
+ }
+ })
+ .addOnCompleteListener(
+ getActivity(),
+ new OnCompleteListener() {
+ @Override
+ public void onComplete(@NonNull Task task) {
+ mHelper.dismissDialog();
+ }
+ });
}
private void showEmailAutoCompleteHint() {
@@ -264,4 +259,9 @@ public void onClick(View view) {
mEmailLayout.setError(null);
}
}
+
+ @Override
+ public void onDonePressed() {
+ validateAndProceed();
+ }
}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/email/PreambleHandler.java b/auth/src/main/java/com/firebase/ui/auth/ui/email/PreambleHandler.java
new file mode 100644
index 000000000..24c0ca26e
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/email/PreambleHandler.java
@@ -0,0 +1,130 @@
+package com.firebase.ui.auth.ui.email;
+
+import android.content.Context;
+import android.net.Uri;
+import android.support.annotation.ColorInt;
+import android.support.annotation.StringRes;
+import android.support.customtabs.CustomTabsIntent;
+import android.support.v4.content.ContextCompat;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ClickableSpan;
+import android.text.style.ForegroundColorSpan;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.TextView;
+
+import com.firebase.ui.auth.R;
+import com.firebase.ui.auth.ui.FlowParameters;
+
+public class PreambleHandler {
+ private static final String BTN_TARGET = "%BTN%";
+ private static final String TOS_TARGET = "%TOS%";
+ private static final String PP_TARGET = "%PP%";
+
+ private final Context mContext;
+ private final FlowParameters mFlowParameters;
+ private final int mButtonText;
+ private final ForegroundColorSpan mLinkSpan;
+
+ private SpannableStringBuilder mBuilder;
+
+ public PreambleHandler(Context context, FlowParameters parameters, @StringRes int buttonText) {
+ mContext = context;
+ mFlowParameters = parameters;
+ mButtonText = buttonText;
+ mLinkSpan = new ForegroundColorSpan(ContextCompat.getColor(mContext, R.color.linkColor));
+
+ setupCreateAccountPreamble();
+ }
+
+ public void setPreamble(TextView textView) {
+ textView.setMovementMethod(LinkMovementMethod.getInstance());
+ textView.setText(mBuilder);
+ }
+
+ private void setupCreateAccountPreamble() {
+ int preambleType = getPreambleType();
+ if (preambleType == -1) {
+ return;
+ }
+
+ String[] preambles =
+ mContext.getResources().getStringArray(R.array.create_account_preamble);
+ mBuilder = new SpannableStringBuilder(preambles[preambleType]);
+
+ replaceTarget(BTN_TARGET, mButtonText);
+ replaceUrlTarget(TOS_TARGET, R.string.terms_of_service, mFlowParameters.termsOfServiceUrl);
+ replaceUrlTarget(PP_TARGET, R.string.privacy_policy, mFlowParameters.privacyPolicyUrl);
+ }
+
+ private void replaceTarget(String target, @StringRes int replacementRes) {
+ int targetIndex = mBuilder.toString().indexOf(target);
+ if (targetIndex != -1) {
+ String replacement = mContext.getString(replacementRes);
+ mBuilder.replace(targetIndex, targetIndex + target.length(), replacement);
+ }
+ }
+
+ private void replaceUrlTarget(String target, @StringRes int replacementRes, String url) {
+ int targetIndex = mBuilder.toString().indexOf(target);
+ if (targetIndex != -1) {
+ String replacement = mContext.getString(replacementRes);
+ mBuilder.replace(targetIndex, targetIndex + target.length(), replacement);
+
+ int end = targetIndex + replacement.length();
+ mBuilder.setSpan(mLinkSpan, targetIndex, end, 0);
+ mBuilder.setSpan(new CustomTabsSpan(url), targetIndex, end, 0);
+ }
+ }
+
+ /**
+ * 0 means we have both a TOS and a PP
+ *
1 means we only have a TOS
+ *
2 means we only have a PP
+ *
-1 means we have neither
+ */
+ private int getPreambleType() {
+ int preambleType;
+
+ boolean hasTos = !TextUtils.isEmpty(mFlowParameters.termsOfServiceUrl);
+ boolean hasPp = !TextUtils.isEmpty(mFlowParameters.privacyPolicyUrl);
+
+ if (hasTos && hasPp) {
+ preambleType = 0;
+ } else if (hasTos) {
+ preambleType = 1;
+ } else if (hasPp) {
+ preambleType = 2;
+ } else {
+ preambleType = -1;
+ }
+
+ return preambleType;
+ }
+
+ private class CustomTabsSpan extends ClickableSpan {
+ private final String mUrl;
+ private final CustomTabsIntent mCustomTabsIntent;
+
+ public CustomTabsSpan(String url) {
+ mUrl = url;
+
+ // Getting default color
+ TypedValue typedValue = new TypedValue();
+ mContext.getTheme().resolveAttribute(R.attr.colorPrimary, typedValue, true);
+ @ColorInt int color = typedValue.data;
+
+ mCustomTabsIntent = new CustomTabsIntent.Builder()
+ .setToolbarColor(color)
+ .setShowTitle(true)
+ .build();
+ }
+
+ @Override
+ public void onClick(View widget) {
+ mCustomTabsIntent.launchUrl(mContext, Uri.parse(mUrl));
+ }
+ }
+}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/email/RecoveryEmailSentDialog.java b/auth/src/main/java/com/firebase/ui/auth/ui/email/RecoveryEmailSentDialog.java
index 0c2424edb..b56a8f92c 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/email/RecoveryEmailSentDialog.java
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/email/RecoveryEmailSentDialog.java
@@ -29,7 +29,7 @@ public static void show(String email, FragmentManager manager) {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
- return new AlertDialog.Builder(getContext(), R.style.FirebaseUI_Dialog)
+ return new AlertDialog.Builder(getContext())
.setTitle(R.string.title_confirm_recover_password)
.setMessage(getString(R.string.confirm_recovery_body,
getArguments().getString(ExtraConstants.EXTRA_EMAIL)))
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/email/RegisterEmailActivity.java b/auth/src/main/java/com/firebase/ui/auth/ui/email/RegisterEmailActivity.java
index 4042455b4..97d720644 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/email/RegisterEmailActivity.java
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/email/RegisterEmailActivity.java
@@ -20,7 +20,6 @@
import android.support.annotation.RestrictTo;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.FragmentTransaction;
-import android.view.View;
import com.firebase.ui.auth.IdpResponse;
import com.firebase.ui.auth.R;
@@ -42,8 +41,8 @@
public class RegisterEmailActivity extends AppCompatBase implements
CheckEmailFragment.CheckEmailListener {
+ public static final int RC_WELCOME_BACK_IDP = 18;
private static final int RC_SIGN_IN = 17;
- private static final int RC_WELCOME_BACK_IDP = 18;
public static Intent createIntent(Context context, FlowParameters flowParams) {
return createIntent(context, flowParams, null);
@@ -67,7 +66,7 @@ protected void onCreate(Bundle savedInstanceState) {
String email = getIntent().getExtras().getString(ExtraConstants.EXTRA_EMAIL);
// Start with check email
- CheckEmailFragment fragment = CheckEmailFragment.getInstance(
+ CheckEmailFragment fragment = CheckEmailFragment.newInstance(
mActivityHelper.getFlowParams(), email);
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_register_email, fragment, CheckEmailFragment.TAG)
@@ -98,7 +97,8 @@ public void onExistingEmailUser(User user) {
WelcomeBackPasswordPrompt.createIntent(
this,
mActivityHelper.getFlowParams(),
- new IdpResponse(EmailAuthProvider.PROVIDER_ID, user.getEmail())),
+ new IdpResponse.Builder(EmailAuthProvider.PROVIDER_ID,
+ user.getEmail()).build()),
RC_SIGN_IN);
setSlideAnimation();
@@ -111,7 +111,7 @@ public void onExistingIdpUser(User user) {
this,
mActivityHelper.getFlowParams(),
user,
- new IdpResponse(EmailAuthProvider.PROVIDER_ID, user.getEmail()));
+ new IdpResponse.Builder(EmailAuthProvider.PROVIDER_ID, user.getEmail()).build());
mActivityHelper.startActivityForResult(intent, RC_WELCOME_BACK_IDP);
setSlideAnimation();
@@ -122,22 +122,20 @@ public void onNewUser(User user) {
// New user, direct them to create an account with email/password
// if account creation is enabled in SignInIntentBuilder
- boolean createAccount = mActivityHelper.getFlowParams().allowNewEmailAccounts;
+ TextInputLayout emailLayout = (TextInputLayout) findViewById(R.id.email_layout);
- TextInputLayout mEmailLayout = (TextInputLayout) findViewById(R.id.email_layout);
-
- if (createAccount) {
- RegisterEmailFragment fragment = RegisterEmailFragment.getInstance(
+ if (mActivityHelper.getFlowParams().allowNewEmailAccounts) {
+ RegisterEmailFragment fragment = RegisterEmailFragment.newInstance(
mActivityHelper.getFlowParams(),
user);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_register_email, fragment, RegisterEmailFragment.TAG);
- if (mEmailLayout != null) ft.addSharedElement(mEmailLayout, "email_field");
+ if (emailLayout != null) ft.addSharedElement(emailLayout, "email_field");
ft.disallowAddToBackStack().commit();
} else {
- mEmailLayout.setError(getString(R.string.error_email_does_not_exist));
+ emailLayout.setError(getString(R.string.error_email_does_not_exist));
}
}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/email/RegisterEmailFragment.java b/auth/src/main/java/com/firebase/ui/auth/ui/email/RegisterEmailFragment.java
index 55b73887c..29f1e5dbd 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/email/RegisterEmailFragment.java
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/email/RegisterEmailFragment.java
@@ -1,31 +1,29 @@
package com.firebase.ui.auth.ui.email;
-import android.net.Uri;
import android.os.Bundle;
-import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
-import android.support.customtabs.CustomTabsIntent;
import android.support.design.widget.TextInputLayout;
-import android.support.v4.content.ContextCompat;
-import android.text.SpannableStringBuilder;
import android.text.TextUtils;
-import android.text.style.ForegroundColorSpan;
-import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
-import android.widget.TextView;
+import android.widget.Toast;
import com.firebase.ui.auth.IdpResponse;
import com.firebase.ui.auth.R;
+import com.firebase.ui.auth.provider.ProviderUtils;
import com.firebase.ui.auth.ui.ExtraConstants;
import com.firebase.ui.auth.ui.FlowParameters;
import com.firebase.ui.auth.ui.FragmentBase;
+import com.firebase.ui.auth.ui.ImeHelper;
import com.firebase.ui.auth.ui.TaskFailureLogger;
+import com.firebase.ui.auth.ui.TermsTextView;
import com.firebase.ui.auth.ui.User;
+import com.firebase.ui.auth.ui.accountlink.WelcomeBackIdpPrompt;
+import com.firebase.ui.auth.ui.accountlink.WelcomeBackPasswordPrompt;
import com.firebase.ui.auth.ui.email.fieldvalidators.EmailFieldValidator;
import com.firebase.ui.auth.ui.email.fieldvalidators.PasswordFieldValidator;
import com.firebase.ui.auth.ui.email.fieldvalidators.RequiredFieldValidator;
@@ -47,14 +45,14 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class RegisterEmailFragment extends FragmentBase implements
- View.OnClickListener, View.OnFocusChangeListener {
+ View.OnClickListener, View.OnFocusChangeListener, ImeHelper.DonePressedListener {
public static final String TAG = "RegisterEmailFragment";
private EditText mEmailEditText;
private EditText mNameEditText;
private EditText mPasswordEditText;
- private TextView mAgreementText;
+ private TermsTextView mAgreementText;
private TextInputLayout mEmailInput;
private TextInputLayout mPasswordInput;
@@ -65,7 +63,7 @@ public class RegisterEmailFragment extends FragmentBase implements
private User mUser;
- public static RegisterEmailFragment getInstance(FlowParameters flowParameters, User user) {
+ public static RegisterEmailFragment newInstance(FlowParameters flowParameters, User user) {
RegisterEmailFragment fragment = new RegisterEmailFragment();
Bundle args = new Bundle();
@@ -94,21 +92,22 @@ public View onCreateView(LayoutInflater inflater,
View v = inflater.inflate(R.layout.register_email_layout, container, false);
- mPasswordFieldValidator = new PasswordFieldValidator(
- (TextInputLayout) v.findViewById(R.id.password_layout),
- getResources().getInteger(R.integer.min_password_length));
- mNameValidator = new RequiredFieldValidator(
- (TextInputLayout) v.findViewById(R.id.name_layout));
- mEmailFieldValidator = new EmailFieldValidator(
- (TextInputLayout) v.findViewById(R.id.email_layout));
-
mEmailEditText = (EditText) v.findViewById(R.id.email);
mNameEditText = (EditText) v.findViewById(R.id.name);
mPasswordEditText = (EditText) v.findViewById(R.id.password);
- mAgreementText = (TextView) v.findViewById(R.id.create_account_text);
+ mAgreementText = (TermsTextView) v.findViewById(R.id.create_account_text);
mEmailInput = (TextInputLayout) v.findViewById(R.id.email_layout);
mPasswordInput = (TextInputLayout) v.findViewById(R.id.password_layout);
+ mPasswordFieldValidator = new PasswordFieldValidator(
+ mPasswordInput,
+ getResources().getInteger(R.integer.min_password_length));
+ mNameValidator = new RequiredFieldValidator(
+ (TextInputLayout) v.findViewById(R.id.name_layout));
+ mEmailFieldValidator = new EmailFieldValidator(mEmailInput);
+
+ ImeHelper.setImeOnDoneListener(mPasswordEditText, this);
+
mEmailEditText.setOnFocusChangeListener(this);
mNameEditText.setOnFocusChangeListener(this);
mPasswordEditText.setOnFocusChangeListener(this);
@@ -158,7 +157,7 @@ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
getActivity().setTitle(R.string.title_register_email);
mSaveSmartLock = mHelper.getSaveSmartLockInstance(getActivity());
- setUpTermsOfService();
+ mAgreementText.showTerms(mHelper.getFlowParams(), R.string.button_text_save);
}
@Override
@@ -171,39 +170,6 @@ public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
- private void setUpTermsOfService() {
- if (TextUtils.isEmpty(mHelper.getFlowParams().termsOfServiceUrl)) {
- return;
- }
-
- ForegroundColorSpan foregroundColorSpan =
- new ForegroundColorSpan(ContextCompat.getColor(getContext(), R.color.linkColor));
-
- String preamble = getString(R.string.create_account_preamble);
- String link = getString(R.string.terms_of_service);
- SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(preamble + link);
- int start = preamble.length();
- spannableStringBuilder.setSpan(foregroundColorSpan, start, start + link.length(), 0);
-
- mAgreementText.setText(spannableStringBuilder);
- mAgreementText.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- // Getting default color
- TypedValue typedValue = new TypedValue();
- getActivity().getTheme().resolveAttribute(R.attr.colorPrimary, typedValue, true);
- @ColorInt int color = typedValue.data;
-
- new CustomTabsIntent.Builder()
- .setToolbarColor(color)
- .build()
- .launchUrl(
- getActivity(),
- Uri.parse(mHelper.getFlowParams().termsOfServiceUrl));
- }
- });
- }
-
@Override
public void onFocusChange(View view, boolean hasFocus) {
if (hasFocus) return; // Only consider fields losing focus
@@ -221,17 +187,26 @@ public void onFocusChange(View view, boolean hasFocus) {
@Override
public void onClick(View view) {
if (view.getId() == R.id.button_create) {
- String email = mEmailEditText.getText().toString();
- String password = mPasswordEditText.getText().toString();
- String name = mNameEditText.getText().toString();
-
- boolean emailValid = mEmailFieldValidator.validate(email);
- boolean passwordValid = mPasswordFieldValidator.validate(password);
- boolean nameValid = mNameValidator.validate(name);
- if (emailValid && passwordValid && nameValid) {
- mHelper.showLoadingDialog(R.string.progress_dialog_signing_up);
- registerUser(email, name, password);
- }
+ validateAndRegisterUser();
+ }
+ }
+
+ @Override
+ public void onDonePressed() {
+ validateAndRegisterUser();
+ }
+
+ private void validateAndRegisterUser() {
+ String email = mEmailEditText.getText().toString();
+ String password = mPasswordEditText.getText().toString();
+ String name = mNameEditText.getText().toString();
+
+ boolean emailValid = mEmailFieldValidator.validate(email);
+ boolean passwordValid = mPasswordFieldValidator.validate(password);
+ boolean nameValid = mNameValidator.validate(name);
+ if (emailValid && passwordValid && nameValid) {
+ mHelper.showLoadingDialog(R.string.progress_dialog_signing_up);
+ registerUser(email, name, password);
}
}
@@ -264,8 +239,9 @@ public void onComplete(@NonNull Task task) {
getActivity(),
user,
password,
- new IdpResponse(EmailAuthProvider.PROVIDER_ID,
- email));
+ new IdpResponse.Builder(EmailAuthProvider.PROVIDER_ID,
+ email)
+ .build());
}
});
}
@@ -273,8 +249,6 @@ public void onComplete(@NonNull Task task) {
.addOnFailureListener(getActivity(), new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
- mHelper.dismissDialog();
-
if (e instanceof FirebaseAuthWeakPasswordException) {
// Password too weak
mPasswordInput.setError(getResources().getQuantityString(
@@ -285,12 +259,62 @@ public void onFailure(@NonNull Exception e) {
} else if (e instanceof FirebaseAuthUserCollisionException) {
// Collision with existing user email, it should be very hard for
// the user to even get to this error due to CheckEmailFragment.
- mEmailInput.setError(getString(R.string.error_user_collision));
+
+ ProviderUtils.fetchTopProvider(mHelper.getFirebaseAuth(), email).addOnSuccessListener(
+ getActivity(),
+ new OnSuccessListener() {
+ @Override
+ public void onSuccess(String provider) {
+ Toast.makeText(getContext(),
+ R.string.error_user_collision,
+ Toast.LENGTH_LONG)
+ .show();
+
+ if (provider == null) {
+ String msg =
+ "User has no providers even though " +
+ "we got a " +
+ "FirebaseAuthUserCollisionException";
+ throw new IllegalStateException(msg);
+ } else if (EmailAuthProvider.PROVIDER_ID.equalsIgnoreCase(
+ provider)) {
+ getActivity().startActivityForResult(
+ WelcomeBackPasswordPrompt.createIntent(
+ getContext(),
+ mHelper.getFlowParams(),
+ new IdpResponse.Builder(
+ EmailAuthProvider.PROVIDER_ID,
+ email).build()),
+ RegisterEmailActivity.RC_WELCOME_BACK_IDP);
+ } else {
+ getActivity().startActivityForResult(
+ WelcomeBackIdpPrompt.createIntent(
+ getContext(),
+ mHelper.getFlowParams(),
+ new User.Builder(email)
+ .setProvider(provider)
+ .build(),
+ new IdpResponse.Builder(
+ EmailAuthProvider.PROVIDER_ID,
+ email).build()),
+ RegisterEmailActivity.RC_WELCOME_BACK_IDP);
+ }
+ }
+ })
+ .addOnCompleteListener(new OnCompleteListener() {
+ @Override
+ public void onComplete(@NonNull Task task) {
+ mHelper.dismissDialog();
+ }
+ });
+ return;
} else {
// General error message, this branch should not be invoked but
// covers future API changes
mEmailInput.setError(getString(R.string.email_account_creation_error));
}
+
+ mHelper.dismissDialog();
}
});
}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/idp/AuthMethodPickerActivity.java b/auth/src/main/java/com/firebase/ui/auth/ui/idp/AuthMethodPickerActivity.java
index c03d2ea87..de617fd23 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/idp/AuthMethodPickerActivity.java
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/idp/AuthMethodPickerActivity.java
@@ -28,23 +28,23 @@
import com.firebase.ui.auth.AuthUI.IdpConfig;
import com.firebase.ui.auth.IdpResponse;
import com.firebase.ui.auth.R;
-import com.firebase.ui.auth.ResultCodes;
-import com.firebase.ui.auth.provider.AuthCredentialHelper;
+import com.firebase.ui.auth.provider.ProviderUtils;
+import com.firebase.ui.auth.provider.EmailProvider;
import com.firebase.ui.auth.provider.FacebookProvider;
import com.firebase.ui.auth.provider.GoogleProvider;
import com.firebase.ui.auth.provider.IdpProvider;
import com.firebase.ui.auth.provider.IdpProvider.IdpCallback;
+import com.firebase.ui.auth.provider.PhoneProvider;
+import com.firebase.ui.auth.provider.Provider;
import com.firebase.ui.auth.provider.TwitterProvider;
import com.firebase.ui.auth.ui.AppCompatBase;
import com.firebase.ui.auth.ui.BaseHelper;
import com.firebase.ui.auth.ui.FlowParameters;
import com.firebase.ui.auth.ui.TaskFailureLogger;
import com.firebase.ui.auth.ui.email.RegisterEmailActivity;
+import com.firebase.ui.auth.ui.phone.PhoneVerificationActivity;
import com.firebase.ui.auth.util.signincontainer.SaveSmartLock;
import com.google.firebase.auth.AuthCredential;
-import com.google.firebase.auth.FacebookAuthProvider;
-import com.google.firebase.auth.GoogleAuthProvider;
-import com.google.firebase.auth.TwitterAuthProvider;
import java.util.ArrayList;
import java.util.List;
@@ -53,16 +53,16 @@
* Presents the list of authentication options for this app to the user. If an
* identity provider option is selected, a {@link CredentialSignInHandler}
* is launched to manage the IDP-specific sign-in flow. If email authentication is chosen,
- * the {@link RegisterEmailActivity} is started.
+ * the {@link RegisterEmailActivity} is started. if phone authentication is chosen, the
+ * {@link com.firebase.ui.auth.ui.phone.PhoneVerificationActivity} is started.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class AuthMethodPickerActivity extends AppCompatBase
- implements IdpCallback, View.OnClickListener {
+public class AuthMethodPickerActivity extends AppCompatBase implements IdpCallback {
private static final String TAG = "AuthMethodPicker";
- private static final int RC_EMAIL_FLOW = 2;
+
private static final int RC_ACCOUNT_LINK = 3;
- private ArrayList mIdpProviders;
+ private List mProviders;
@Nullable
private SaveSmartLock mSaveSmartLock;
@@ -75,7 +75,6 @@ protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.auth_method_picker_layout);
mSaveSmartLock = mActivityHelper.getSaveSmartLockInstance();
- findViewById(R.id.email_provider).setOnClickListener(this);
populateIdpList(mActivityHelper.getFlowParams().providerInfo);
@@ -89,73 +88,59 @@ protected void onCreate(Bundle savedInstanceState) {
}
private void populateIdpList(List providers) {
- mIdpProviders = new ArrayList<>();
+ mProviders = new ArrayList<>();
for (IdpConfig idpConfig : providers) {
switch (idpConfig.getProviderId()) {
case AuthUI.GOOGLE_PROVIDER:
- mIdpProviders.add(new GoogleProvider(this, idpConfig));
+ mProviders.add(new GoogleProvider(this, idpConfig));
break;
case AuthUI.FACEBOOK_PROVIDER:
- mIdpProviders.add(new FacebookProvider(
- this, idpConfig, mActivityHelper.getFlowParams().themeId));
+ mProviders.add(new FacebookProvider(
+ idpConfig, mActivityHelper.getFlowParams().themeId));
break;
case AuthUI.TWITTER_PROVIDER:
- mIdpProviders.add(new TwitterProvider(this));
+ mProviders.add(new TwitterProvider(this));
break;
case AuthUI.EMAIL_PROVIDER:
- findViewById(R.id.email_provider).setVisibility(View.VISIBLE);
+ mProviders.add(new EmailProvider(this, mActivityHelper));
+ break;
+ case AuthUI.PHONE_VERIFICATION_PROVIDER:
+ mProviders.add(new PhoneProvider(this, mActivityHelper));
break;
default:
- Log.e(TAG, "Encountered unknown IDPProvider parcel with type: "
+ Log.e(TAG, "Encountered unknown provider parcel with type: "
+ idpConfig.getProviderId());
}
}
ViewGroup btnHolder = (ViewGroup) findViewById(R.id.btn_holder);
- for (final IdpProvider provider : mIdpProviders) {
- View loginButton = null;
- switch (provider.getProviderId()) {
- case GoogleAuthProvider.PROVIDER_ID:
- loginButton = getLayoutInflater()
- .inflate(R.layout.idp_button_google, btnHolder, false);
- break;
- case FacebookAuthProvider.PROVIDER_ID:
- loginButton = getLayoutInflater()
- .inflate(R.layout.idp_button_facebook, btnHolder, false);
- break;
- case TwitterAuthProvider.PROVIDER_ID:
- loginButton = getLayoutInflater()
- .inflate(R.layout.idp_button_twitter, btnHolder, false);
- break;
- default:
- Log.e(TAG, "No button for provider " + provider.getProviderId());
- }
-
- if (loginButton != null) {
- loginButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
+ for (final Provider provider : mProviders) {
+ View loginButton = getLayoutInflater()
+ .inflate(provider.getButtonLayout(), btnHolder, false);
+
+ loginButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (provider instanceof IdpProvider) {
mActivityHelper.showLoadingDialog(R.string.progress_dialog_loading);
- provider.startLogin(AuthMethodPickerActivity.this);
}
- });
- provider.setAuthenticationCallback(this);
- btnHolder.addView(loginButton, 0);
+ provider.startLogin(AuthMethodPickerActivity.this);
+ }
+ });
+ if (provider instanceof IdpProvider) {
+ ((IdpProvider) provider).setAuthenticationCallback(this);
}
+ btnHolder.addView(loginButton);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
- if (requestCode == RC_EMAIL_FLOW) {
- if (resultCode == ResultCodes.OK) {
- finish(ResultCodes.OK, data);
- }
- } else if (requestCode == RC_ACCOUNT_LINK) {
+ if (requestCode == RC_ACCOUNT_LINK) {
finish(resultCode, data);
} else {
- for (IdpProvider provider : mIdpProviders) {
+ for (Provider provider : mProviders) {
provider.onActivityResult(requestCode, resultCode, data);
}
}
@@ -163,11 +148,13 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
@Override
public void onSuccess(final IdpResponse response) {
- AuthCredential credential = AuthCredentialHelper.getAuthCredential(response);
+ AuthCredential credential = ProviderUtils.getAuthCredential(response);
mActivityHelper.getFirebaseAuth()
.signInWithCredential(credential)
.addOnFailureListener(
- new TaskFailureLogger(TAG, "Firebase sign in with credential " + credential.getProvider() + " unsuccessful. Visit https://console.firebase.google.com to enable it."))
+ new TaskFailureLogger(TAG, "Firebase sign in with credential "
+ + credential.getProvider() + " unsuccessful. " +
+ "Visit https://console.firebase.google.com to enable it."))
.addOnCompleteListener(new CredentialSignInHandler(
this,
mActivityHelper,
@@ -182,20 +169,11 @@ public void onFailure(Bundle extra) {
mActivityHelper.dismissDialog();
}
- @Override
- public void onClick(View view) {
- if (view.getId() == R.id.email_provider) {
- startActivityForResult(
- RegisterEmailActivity.createIntent(this, mActivityHelper.getFlowParams()),
- RC_EMAIL_FLOW);
- }
- }
-
@Override
protected void onDestroy() {
super.onDestroy();
- if (mIdpProviders != null) {
- for (final IdpProvider provider : mIdpProviders) {
+ if (mProviders != null) {
+ for (Provider provider : mProviders) {
if (provider instanceof GoogleProvider) {
((GoogleProvider) provider).disconnect();
}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/idp/CredentialSignInHandler.java b/auth/src/main/java/com/firebase/ui/auth/ui/idp/CredentialSignInHandler.java
index d0d4a384c..9f7cba4f3 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/idp/CredentialSignInHandler.java
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/idp/CredentialSignInHandler.java
@@ -20,9 +20,12 @@
import android.support.annotation.RestrictTo;
import android.util.Log;
+import com.firebase.ui.auth.AuthUI;
+import com.firebase.ui.auth.ErrorCodes;
import com.firebase.ui.auth.IdpResponse;
+import com.firebase.ui.auth.ResultCodes;
+import com.firebase.ui.auth.provider.ProviderUtils;
import com.firebase.ui.auth.ui.BaseHelper;
-import com.firebase.ui.auth.ui.TaskFailureLogger;
import com.firebase.ui.auth.ui.User;
import com.firebase.ui.auth.ui.accountlink.WelcomeBackIdpPrompt;
import com.firebase.ui.auth.ui.accountlink.WelcomeBackPasswordPrompt;
@@ -35,7 +38,6 @@
import com.google.firebase.auth.EmailAuthProvider;
import com.google.firebase.auth.FirebaseAuthUserCollisionException;
import com.google.firebase.auth.FirebaseUser;
-import com.google.firebase.auth.ProviderQueryResult;
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class CredentialSignInHandler implements OnCompleteListener {
@@ -46,19 +48,19 @@ public class CredentialSignInHandler implements OnCompleteListener {
@Nullable
private SaveSmartLock mSmartLock;
private IdpResponse mResponse;
- private int mAccountLinkResultCode;
+ private int mAccountLinkRequestCode;
public CredentialSignInHandler(
Activity activity,
BaseHelper helper,
@Nullable SaveSmartLock smartLock,
- int accountLinkResultCode,
+ int accountLinkRequestCode,
IdpResponse response) {
mActivity = activity;
mHelper = helper;
mSmartLock = smartLock;
mResponse = response;
- mAccountLinkResultCode = accountLinkResultCode;
+ mAccountLinkRequestCode = accountLinkRequestCode;
}
@Override
@@ -69,50 +71,55 @@ public void onComplete(@NonNull Task task) {
mSmartLock,
mActivity,
firebaseUser,
+ null,
mResponse);
} else {
if (task.getException() instanceof FirebaseAuthUserCollisionException) {
- final String email = mResponse.getEmail();
+ String email = mResponse.getEmail();
if (email != null) {
- mHelper.getFirebaseAuth()
- .fetchProvidersForEmail(email)
- .addOnFailureListener(new TaskFailureLogger(
- TAG, "Error fetching providers for email"))
+ ProviderUtils.fetchTopProvider(mHelper.getFirebaseAuth(), email)
.addOnSuccessListener(new StartWelcomeBackFlow())
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
- // TODO: What to do when signing in with Credential fails
- // and we can't continue to Welcome back flow without
- // knowing providers?
+ mHelper.finishActivity(
+ mActivity,
+ ResultCodes.CANCELED,
+ IdpResponse.getErrorCodeIntent(ErrorCodes.UNKNOWN_ERROR));
}
});
return;
}
} else {
- Log.e(TAG, "Unexpected exception when signing in with credential " + mResponse.getProviderType() + " unsuccessful. Visit https://console.firebase.google.com to enable it.",
+ Log.e(TAG,
+ "Unexpected exception when signing in with credential "
+ + mResponse.getProviderType()
+ + " unsuccessful. Visit https://console.firebase.google.com to enable it.",
task.getException());
}
+
mHelper.dismissDialog();
}
}
- private class StartWelcomeBackFlow implements OnSuccessListener {
+ private class StartWelcomeBackFlow implements OnSuccessListener {
@Override
- public void onSuccess(@NonNull ProviderQueryResult result) {
+ public void onSuccess(String provider) {
mHelper.dismissDialog();
- String provider = result.getProviders().get(0);
- if (provider.equals(EmailAuthProvider.PROVIDER_ID)) {
+ if (provider == null) {
+ throw new IllegalStateException(
+ "No provider even though we received a FirebaseAuthUserCollisionException");
+ } else if (provider.equals(EmailAuthProvider.PROVIDER_ID)) {
// Start email welcome back flow
mActivity.startActivityForResult(
WelcomeBackPasswordPrompt.createIntent(
mActivity,
mHelper.getFlowParams(),
- mResponse
- ), mAccountLinkResultCode);
+ mResponse),
+ mAccountLinkRequestCode);
} else {
- // Start IDP welcome back flow
+ // Start Idp welcome back flow
mActivity.startActivityForResult(
WelcomeBackIdpPrompt.createIntent(
mActivity,
@@ -120,8 +127,8 @@ public void onSuccess(@NonNull ProviderQueryResult result) {
new User.Builder(mResponse.getEmail())
.setProvider(provider)
.build(),
- mResponse
- ), mAccountLinkResultCode);
+ mResponse),
+ mAccountLinkRequestCode);
}
}
}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/phone/BucketedTextChangeListener.java b/auth/src/main/java/com/firebase/ui/auth/ui/phone/BucketedTextChangeListener.java
new file mode 100644
index 000000000..0de8728c4
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/phone/BucketedTextChangeListener.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2015 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Modifications copyright (C) 2017 Google Inc
+ */
+
+package com.firebase.ui.auth.ui.phone;
+
+import android.annotation.SuppressLint;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.widget.EditText;
+
+import java.util.Collections;
+
+/**
+ * Listens for changes to a text field that has hyphens and replaces with the characted being
+ * typed
+ * ------
+ * 7-----
+ * 76----
+ * 764---
+ * 7641--
+ * 76417-
+ * 764176
+ */
+final class BucketedTextChangeListener implements TextWatcher {
+ private final EditText editText;
+ private final ContentChangeCallback callback;
+ private final String[] postFixes;
+ private final String placeHolder;
+ private final int expectedContentLength;
+
+ public BucketedTextChangeListener(EditText editText, int expectedContentLength, String
+ placeHolder, ContentChangeCallback callback) {
+ this.editText = editText;
+ this.expectedContentLength = expectedContentLength;
+ this.postFixes = generatePostfixArray(placeHolder, expectedContentLength);
+ this.callback = callback;
+ this.placeHolder = placeHolder;
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @SuppressLint("SetTextI18n")
+ @Override
+ public void onTextChanged(CharSequence s, int ignoredParam1, int ignoredParam2, int
+ ignoredParam3) {
+ // The listener is expected to be used in conjunction with the SpacedEditText.
+
+ // Approach
+ // 1) Strip all spaces and hyphens introduced by the SET for aesthetics
+ final String numericContents = s.toString().replaceAll(" ", "").replaceAll(placeHolder, "");
+
+ // 2) Trim the content to acceptable length.
+ final int enteredContentLength = Math.min(numericContents.length(), expectedContentLength);
+ final String enteredContent = numericContents.substring(0, enteredContentLength);
+
+ // 3) Reset the text to be the content + required hyphens. The SET automatically inserts
+ // spaces requires for aesthetics. This requires removing and reseting the listener to
+ // avoid recursion.
+ editText.removeTextChangedListener(this);
+ editText.setText(enteredContent + postFixes[expectedContentLength - enteredContentLength]);
+ editText.setSelection(enteredContentLength);
+ editText.addTextChangedListener(this);
+
+ // 4) Callback listeners waiting on content to be of expected length
+ if (enteredContentLength == expectedContentLength && callback != null) {
+ callback.whileComplete();
+ } else if (callback != null) {
+ callback.whileIncomplete();
+ }
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ }
+
+ /**
+ * {@link #generatePostfixArray(CharSequence, int)} with params ("-", 6) returns
+ * {"", "-", "--", "---", "----", "-----", "------"}
+ *
+ * @param repeatableChar
+ * @param length
+ * @return
+ */
+ private String[] generatePostfixArray(CharSequence repeatableChar, int length) {
+ final String[] ret = new String[length + 1];
+
+ for (int i = 0; i <= length; i++) {
+ ret[i] = TextUtils.join("", Collections.nCopies(i, repeatableChar));
+ }
+
+ return ret;
+ }
+
+ interface ContentChangeCallback {
+ /**
+ * Idempotent function invoked by the listener when the edit text changes and is of
+ * expected length
+ */
+ void whileComplete();
+
+ /**
+ * Idempotent function invoked by the listener when the edit text changes and is not of
+ * expected length
+ */
+ void whileIncomplete();
+ }
+}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/phone/CompletableProgressDialog.java b/auth/src/main/java/com/firebase/ui/auth/ui/phone/CompletableProgressDialog.java
new file mode 100644
index 000000000..66fc89e4c
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/phone/CompletableProgressDialog.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.firebase.ui.auth.ui.phone;
+
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.firebase.ui.auth.R;
+
+public final class CompletableProgressDialog extends ProgressDialog {
+ private ProgressBar mProgress;
+ private TextView mMessageView;
+ private CharSequence mMessage;
+ private ImageView mSuccessImage;
+
+ public CompletableProgressDialog(Context context) {
+ super(context);
+ }
+
+ public CompletableProgressDialog(Context context, int theme) {
+ super(context, theme);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.phone_progress_dialog);
+
+ mProgress = (ProgressBar) findViewById(R.id.progress_bar);
+ mMessageView = (TextView) findViewById(R.id.progress_msg);
+ mSuccessImage = (ImageView) findViewById(R.id.progress_success_imaage);
+
+ if (mMessage != null) {
+ setMessage(mMessage);
+ }
+ }
+
+ @Override
+ public void show() {
+ super.show();
+ }
+
+ public void complete(String msg) {
+ setMessage(msg);
+ mProgress.setVisibility(View.GONE);
+ mSuccessImage.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void setMessage(CharSequence message) {
+ if (mProgress != null) {
+ mMessageView.setText(message);
+ } else {
+ mMessage = message;
+ }
+ }
+}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/phone/CountryInfo.java b/auth/src/main/java/com/firebase/ui/auth/ui/phone/CountryInfo.java
new file mode 100644
index 000000000..f514e1057
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/phone/CountryInfo.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Modifications copyright (C) 2017 Google Inc
+ *
+ */
+package com.firebase.ui.auth.ui.phone;
+
+import java.text.Collator;
+import java.util.Locale;
+
+final class CountryInfo implements Comparable {
+ private final Collator collator;
+ public final Locale locale;
+ public final int countryCode;
+
+ public CountryInfo(Locale locale, int countryCode) {
+ collator = Collator.getInstance(Locale.getDefault());
+ collator.setStrength(Collator.PRIMARY);
+
+ this.locale = locale;
+ this.countryCode = countryCode;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final CountryInfo that = (CountryInfo) o;
+
+ if (countryCode != that.countryCode) return false;
+ return locale != null ? locale.equals(that.locale) : that.locale == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = locale != null ? locale.hashCode() : 0;
+ result = 31 * result + countryCode;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return localeToEmoji(locale) + " " + this.locale.getDisplayCountry() + " +" + countryCode;
+ }
+
+ public static String localeToEmoji(Locale locale) {
+ String countryCode = locale.getCountry();
+ // 0x41 is Letter A
+ // 0x1F1E6 is Regional Indicator Symbol Letter A
+ // Example :
+ // firstLetter U => 20 + 0x1F1E6
+ // secondLetter S => 18 + 0x1F1E6
+ // See: https://en.wikipedia.org/wiki/Regional_Indicator_Symbol
+ int firstLetter = Character.codePointAt(countryCode, 0) - 0x41 + 0x1F1E6;
+ int secondLetter = Character.codePointAt(countryCode, 1) - 0x41 + 0x1F1E6;
+ return new String(Character.toChars(firstLetter)) + new String(Character.toChars
+ (secondLetter));
+ }
+
+ @Override
+ public int compareTo(CountryInfo info) {
+ return collator.compare(this.locale.getDisplayCountry(), info.locale.getDisplayCountry());
+ }
+}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/phone/CountryListAdapter.java b/auth/src/main/java/com/firebase/ui/auth/ui/phone/CountryListAdapter.java
new file mode 100644
index 000000000..505896928
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/phone/CountryListAdapter.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2015 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Modifications copyright (C) 2017 Google Inc
+ */
+
+package com.firebase.ui.auth.ui.phone;
+
+import android.content.Context;
+import android.widget.ArrayAdapter;
+import android.widget.SectionIndexer;
+
+import com.firebase.ui.auth.R;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+
+/*
+ * Array adapter used to display a list of countries with section indices.
+ */
+final class CountryListAdapter extends ArrayAdapter implements SectionIndexer {
+ private final HashMap alphaIndex = new LinkedHashMap<>();
+ private final HashMap countryPosition = new LinkedHashMap<>();
+ private String[] sections;
+
+ public CountryListAdapter(Context context) {
+ super(context, R.layout.dgts__country_row, android.R.id.text1);
+ }
+
+ // The list of countries should be sorted using locale-sensitive string comparison
+ public void setData(List countries) {
+ // Create index and add entries to adapter
+ int index = 0;
+ for (CountryInfo countryInfo : countries) {
+ final String key = countryInfo.locale.getDisplayCountry().substring(0, 1).toUpperCase
+ (Locale.getDefault());
+
+ if (!alphaIndex.containsKey(key)) {
+ alphaIndex.put(key, index);
+ }
+ countryPosition.put(countryInfo.locale.getDisplayCountry(), index);
+
+ index++;
+ add(countryInfo);
+ }
+
+ sections = new String[alphaIndex.size()];
+ alphaIndex.keySet().toArray(sections);
+
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public Object[] getSections() {
+ return sections;
+ }
+
+ @Override
+ public int getPositionForSection(int index) {
+ if (sections == null) {
+ return 0;
+ }
+
+ // Check index bounds
+ if (index <= 0) {
+ return 0;
+ }
+ if (index >= sections.length) {
+ index = sections.length - 1;
+ }
+
+ // Return the position
+ return alphaIndex.get(sections[index]);
+ }
+
+ @Override
+ public int getSectionForPosition(int position) {
+ return 0;
+ }
+
+ public int getPositionForCountry(String country) {
+ final Integer position = countryPosition.get(country);
+ return position == null ? 0 : position;
+ }
+}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/phone/CountryListLoadTask.java b/auth/src/main/java/com/firebase/ui/auth/ui/phone/CountryListLoadTask.java
new file mode 100644
index 000000000..3c3c3f603
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/phone/CountryListLoadTask.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2015 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Modifications copyright (C) 2017 Google Inc
+ */
+
+package com.firebase.ui.auth.ui.phone;
+
+import android.os.AsyncTask;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+// We need to move away from ListView and AsyncTask in the future and use (say)
+// RecyclerView and Task/ThreadPoolExecutor .
+final class CountryListLoadTask extends AsyncTask> {
+ private static final int MAX_COUNTRIES = 291;
+
+ private final Listener listener;
+
+ public CountryListLoadTask(Listener listener) {
+ this.listener = listener;
+ }
+
+ @Override
+ protected List doInBackground(Void... params) {
+ final List countryInfoList = new ArrayList<>(MAX_COUNTRIES);
+ countryInfoList.add(new CountryInfo(new Locale("", "AF"), 93));
+ countryInfoList.add(new CountryInfo(new Locale("", "AX"), 358));
+ countryInfoList.add(new CountryInfo(new Locale("", "AL"), 355));
+ countryInfoList.add(new CountryInfo(new Locale("", "DZ"), 213));
+ countryInfoList.add(new CountryInfo(new Locale("", "AS"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "AD"), 376));
+ countryInfoList.add(new CountryInfo(new Locale("", "AO"), 244));
+ countryInfoList.add(new CountryInfo(new Locale("", "AI"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "AG"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "AR"), 54));
+ countryInfoList.add(new CountryInfo(new Locale("", "AM"), 374));
+ countryInfoList.add(new CountryInfo(new Locale("", "AW"), 297));
+ countryInfoList.add(new CountryInfo(new Locale("", "AC"), 247));
+ countryInfoList.add(new CountryInfo(new Locale("", "AU"), 61));
+ countryInfoList.add(new CountryInfo(new Locale("", "AT"), 43));
+ countryInfoList.add(new CountryInfo(new Locale("", "AZ"), 994));
+ countryInfoList.add(new CountryInfo(new Locale("", "BS"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "BH"), 973));
+ countryInfoList.add(new CountryInfo(new Locale("", "BD"), 880));
+ countryInfoList.add(new CountryInfo(new Locale("", "BB"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "BY"), 375));
+ countryInfoList.add(new CountryInfo(new Locale("", "BE"), 32));
+ countryInfoList.add(new CountryInfo(new Locale("", "BZ"), 501));
+ countryInfoList.add(new CountryInfo(new Locale("", "BJ"), 229));
+ countryInfoList.add(new CountryInfo(new Locale("", "BM"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "BT"), 975));
+ countryInfoList.add(new CountryInfo(new Locale("", "BO"), 591));
+ countryInfoList.add(new CountryInfo(new Locale("", "BA"), 387));
+ countryInfoList.add(new CountryInfo(new Locale("", "BW"), 267));
+ countryInfoList.add(new CountryInfo(new Locale("", "BR"), 55));
+ countryInfoList.add(new CountryInfo(new Locale("", "IO"), 246));
+ countryInfoList.add(new CountryInfo(new Locale("", "VG"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "BN"), 673));
+ countryInfoList.add(new CountryInfo(new Locale("", "BG"), 359));
+ countryInfoList.add(new CountryInfo(new Locale("", "BF"), 226));
+ countryInfoList.add(new CountryInfo(new Locale("", "BI"), 257));
+ countryInfoList.add(new CountryInfo(new Locale("", "KH"), 855));
+ countryInfoList.add(new CountryInfo(new Locale("", "CM"), 237));
+ countryInfoList.add(new CountryInfo(new Locale("", "CA"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "CV"), 238));
+ countryInfoList.add(new CountryInfo(new Locale("", "BQ"), 599));
+ countryInfoList.add(new CountryInfo(new Locale("", "KY"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "CF"), 236));
+ countryInfoList.add(new CountryInfo(new Locale("", "TD"), 235));
+ countryInfoList.add(new CountryInfo(new Locale("", "CL"), 56));
+ countryInfoList.add(new CountryInfo(new Locale("", "CN"), 86));
+ countryInfoList.add(new CountryInfo(new Locale("", "CX"), 61));
+ countryInfoList.add(new CountryInfo(new Locale("", "CC"), 61));
+ countryInfoList.add(new CountryInfo(new Locale("", "CO"), 57));
+ countryInfoList.add(new CountryInfo(new Locale("", "KM"), 269));
+ countryInfoList.add(new CountryInfo(new Locale("", "CD"), 243));
+ countryInfoList.add(new CountryInfo(new Locale("", "CG"), 242));
+ countryInfoList.add(new CountryInfo(new Locale("", "CK"), 682));
+ countryInfoList.add(new CountryInfo(new Locale("", "CR"), 506));
+ countryInfoList.add(new CountryInfo(new Locale("", "CI"), 225));
+ countryInfoList.add(new CountryInfo(new Locale("", "HR"), 385));
+ countryInfoList.add(new CountryInfo(new Locale("", "CU"), 53));
+ countryInfoList.add(new CountryInfo(new Locale("", "CW"), 599));
+ countryInfoList.add(new CountryInfo(new Locale("", "CY"), 357));
+ countryInfoList.add(new CountryInfo(new Locale("", "CZ"), 420));
+ countryInfoList.add(new CountryInfo(new Locale("", "DK"), 45));
+ countryInfoList.add(new CountryInfo(new Locale("", "DJ"), 253));
+ countryInfoList.add(new CountryInfo(new Locale("", "DM"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "DO"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "TL"), 670));
+ countryInfoList.add(new CountryInfo(new Locale("", "EC"), 593));
+ countryInfoList.add(new CountryInfo(new Locale("", "EG"), 20));
+ countryInfoList.add(new CountryInfo(new Locale("", "SV"), 503));
+ countryInfoList.add(new CountryInfo(new Locale("", "GQ"), 240));
+ countryInfoList.add(new CountryInfo(new Locale("", "ER"), 291));
+ countryInfoList.add(new CountryInfo(new Locale("", "EE"), 372));
+ countryInfoList.add(new CountryInfo(new Locale("", "ET"), 251));
+ countryInfoList.add(new CountryInfo(new Locale("", "FK"), 500));
+ countryInfoList.add(new CountryInfo(new Locale("", "FO"), 298));
+ countryInfoList.add(new CountryInfo(new Locale("", "FJ"), 679));
+ countryInfoList.add(new CountryInfo(new Locale("", "FI"), 358));
+ countryInfoList.add(new CountryInfo(new Locale("", "FR"), 33));
+ countryInfoList.add(new CountryInfo(new Locale("", "GF"), 594));
+ countryInfoList.add(new CountryInfo(new Locale("", "PF"), 689));
+ countryInfoList.add(new CountryInfo(new Locale("", "GA"), 241));
+ countryInfoList.add(new CountryInfo(new Locale("", "GM"), 220));
+ countryInfoList.add(new CountryInfo(new Locale("", "GE"), 995));
+ countryInfoList.add(new CountryInfo(new Locale("", "DE"), 49));
+ countryInfoList.add(new CountryInfo(new Locale("", "GH"), 233));
+ countryInfoList.add(new CountryInfo(new Locale("", "GI"), 350));
+ countryInfoList.add(new CountryInfo(new Locale("", "GR"), 30));
+ countryInfoList.add(new CountryInfo(new Locale("", "GL"), 299));
+ countryInfoList.add(new CountryInfo(new Locale("", "GD"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "GP"), 590));
+ countryInfoList.add(new CountryInfo(new Locale("", "GU"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "GT"), 502));
+ countryInfoList.add(new CountryInfo(new Locale("", "GG"), 44));
+ countryInfoList.add(new CountryInfo(new Locale("", "GN"), 224));
+ countryInfoList.add(new CountryInfo(new Locale("", "GW"), 245));
+ countryInfoList.add(new CountryInfo(new Locale("", "GY"), 592));
+ countryInfoList.add(new CountryInfo(new Locale("", "HT"), 509));
+ countryInfoList.add(new CountryInfo(new Locale("", "HM"), 672));
+ countryInfoList.add(new CountryInfo(new Locale("", "HN"), 504));
+ countryInfoList.add(new CountryInfo(new Locale("", "HK"), 852));
+ countryInfoList.add(new CountryInfo(new Locale("", "HU"), 36));
+ countryInfoList.add(new CountryInfo(new Locale("", "IS"), 354));
+ countryInfoList.add(new CountryInfo(new Locale("", "IN"), 91));
+ countryInfoList.add(new CountryInfo(new Locale("", "ID"), 62));
+ countryInfoList.add(new CountryInfo(new Locale("", "IR"), 98));
+ countryInfoList.add(new CountryInfo(new Locale("", "IQ"), 964));
+ countryInfoList.add(new CountryInfo(new Locale("", "IE"), 353));
+ countryInfoList.add(new CountryInfo(new Locale("", "IM"), 44));
+ countryInfoList.add(new CountryInfo(new Locale("", "IL"), 972));
+ countryInfoList.add(new CountryInfo(new Locale("", "IT"), 39));
+ countryInfoList.add(new CountryInfo(new Locale("", "JM"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "JP"), 81));
+ countryInfoList.add(new CountryInfo(new Locale("", "JE"), 44));
+ countryInfoList.add(new CountryInfo(new Locale("", "JO"), 962));
+ countryInfoList.add(new CountryInfo(new Locale("", "KZ"), 7));
+ countryInfoList.add(new CountryInfo(new Locale("", "KE"), 254));
+ countryInfoList.add(new CountryInfo(new Locale("", "KI"), 686));
+ countryInfoList.add(new CountryInfo(new Locale("", "XK"), 381));
+ countryInfoList.add(new CountryInfo(new Locale("", "KW"), 965));
+ countryInfoList.add(new CountryInfo(new Locale("", "KG"), 996));
+ countryInfoList.add(new CountryInfo(new Locale("", "LA"), 856));
+ countryInfoList.add(new CountryInfo(new Locale("", "LV"), 371));
+ countryInfoList.add(new CountryInfo(new Locale("", "LB"), 961));
+ countryInfoList.add(new CountryInfo(new Locale("", "LS"), 266));
+ countryInfoList.add(new CountryInfo(new Locale("", "LR"), 231));
+ countryInfoList.add(new CountryInfo(new Locale("", "LY"), 218));
+ countryInfoList.add(new CountryInfo(new Locale("", "LI"), 423));
+ countryInfoList.add(new CountryInfo(new Locale("", "LT"), 370));
+ countryInfoList.add(new CountryInfo(new Locale("", "LU"), 352));
+ countryInfoList.add(new CountryInfo(new Locale("", "MO"), 853));
+ countryInfoList.add(new CountryInfo(new Locale("", "MK"), 389));
+ countryInfoList.add(new CountryInfo(new Locale("", "MG"), 261));
+ countryInfoList.add(new CountryInfo(new Locale("", "MW"), 265));
+ countryInfoList.add(new CountryInfo(new Locale("", "MY"), 60));
+ countryInfoList.add(new CountryInfo(new Locale("", "MV"), 960));
+ countryInfoList.add(new CountryInfo(new Locale("", "ML"), 223));
+ countryInfoList.add(new CountryInfo(new Locale("", "MT"), 356));
+ countryInfoList.add(new CountryInfo(new Locale("", "MH"), 692));
+ countryInfoList.add(new CountryInfo(new Locale("", "MQ"), 596));
+ countryInfoList.add(new CountryInfo(new Locale("", "MR"), 222));
+ countryInfoList.add(new CountryInfo(new Locale("", "MU"), 230));
+ countryInfoList.add(new CountryInfo(new Locale("", "YT"), 262));
+ countryInfoList.add(new CountryInfo(new Locale("", "MX"), 52));
+ countryInfoList.add(new CountryInfo(new Locale("", "FM"), 691));
+ countryInfoList.add(new CountryInfo(new Locale("", "MD"), 373));
+ countryInfoList.add(new CountryInfo(new Locale("", "MC"), 377));
+ countryInfoList.add(new CountryInfo(new Locale("", "MN"), 976));
+ countryInfoList.add(new CountryInfo(new Locale("", "ME"), 382));
+ countryInfoList.add(new CountryInfo(new Locale("", "MS"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "MA"), 212));
+ countryInfoList.add(new CountryInfo(new Locale("", "MZ"), 258));
+ countryInfoList.add(new CountryInfo(new Locale("", "MM"), 95));
+ countryInfoList.add(new CountryInfo(new Locale("", "NA"), 264));
+ countryInfoList.add(new CountryInfo(new Locale("", "NR"), 674));
+ countryInfoList.add(new CountryInfo(new Locale("", "NP"), 977));
+ countryInfoList.add(new CountryInfo(new Locale("", "NL"), 31));
+ countryInfoList.add(new CountryInfo(new Locale("", "NC"), 687));
+ countryInfoList.add(new CountryInfo(new Locale("", "NZ"), 64));
+ countryInfoList.add(new CountryInfo(new Locale("", "NI"), 505));
+ countryInfoList.add(new CountryInfo(new Locale("", "NE"), 227));
+ countryInfoList.add(new CountryInfo(new Locale("", "NG"), 234));
+ countryInfoList.add(new CountryInfo(new Locale("", "NU"), 683));
+ countryInfoList.add(new CountryInfo(new Locale("", "NF"), 672));
+ countryInfoList.add(new CountryInfo(new Locale("", "KP"), 850));
+ countryInfoList.add(new CountryInfo(new Locale("", "MP"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "NO"), 47));
+ countryInfoList.add(new CountryInfo(new Locale("", "OM"), 968));
+ countryInfoList.add(new CountryInfo(new Locale("", "PK"), 92));
+ countryInfoList.add(new CountryInfo(new Locale("", "PW"), 680));
+ countryInfoList.add(new CountryInfo(new Locale("", "PS"), 970));
+ countryInfoList.add(new CountryInfo(new Locale("", "PA"), 507));
+ countryInfoList.add(new CountryInfo(new Locale("", "PG"), 675));
+ countryInfoList.add(new CountryInfo(new Locale("", "PY"), 595));
+ countryInfoList.add(new CountryInfo(new Locale("", "PE"), 51));
+ countryInfoList.add(new CountryInfo(new Locale("", "PH"), 63));
+ countryInfoList.add(new CountryInfo(new Locale("", "PL"), 48));
+ countryInfoList.add(new CountryInfo(new Locale("", "PT"), 351));
+ countryInfoList.add(new CountryInfo(new Locale("", "PR"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "QA"), 974));
+ countryInfoList.add(new CountryInfo(new Locale("", "RE"), 262));
+ countryInfoList.add(new CountryInfo(new Locale("", "RO"), 40));
+ countryInfoList.add(new CountryInfo(new Locale("", "RU"), 7));
+ countryInfoList.add(new CountryInfo(new Locale("", "RW"), 250));
+ countryInfoList.add(new CountryInfo(new Locale("", "BL"), 590));
+ countryInfoList.add(new CountryInfo(new Locale("", "SH"), 290));
+ countryInfoList.add(new CountryInfo(new Locale("", "KN"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "LC"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "MF"), 590));
+ countryInfoList.add(new CountryInfo(new Locale("", "PM"), 508));
+ countryInfoList.add(new CountryInfo(new Locale("", "VC"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "WS"), 685));
+ countryInfoList.add(new CountryInfo(new Locale("", "SM"), 378));
+ countryInfoList.add(new CountryInfo(new Locale("", "ST"), 239));
+ countryInfoList.add(new CountryInfo(new Locale("", "SA"), 966));
+ countryInfoList.add(new CountryInfo(new Locale("", "SN"), 221));
+ countryInfoList.add(new CountryInfo(new Locale("", "RS"), 381));
+ countryInfoList.add(new CountryInfo(new Locale("", "SC"), 248));
+ countryInfoList.add(new CountryInfo(new Locale("", "SL"), 232));
+ countryInfoList.add(new CountryInfo(new Locale("", "SG"), 65));
+ countryInfoList.add(new CountryInfo(new Locale("", "SX"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "SK"), 421));
+ countryInfoList.add(new CountryInfo(new Locale("", "SI"), 386));
+ countryInfoList.add(new CountryInfo(new Locale("", "SB"), 677));
+ countryInfoList.add(new CountryInfo(new Locale("", "SO"), 252));
+ countryInfoList.add(new CountryInfo(new Locale("", "ZA"), 27));
+ countryInfoList.add(new CountryInfo(new Locale("", "GS"), 500));
+ countryInfoList.add(new CountryInfo(new Locale("", "KR"), 82));
+ countryInfoList.add(new CountryInfo(new Locale("", "SS"), 211));
+ countryInfoList.add(new CountryInfo(new Locale("", "ES"), 34));
+ countryInfoList.add(new CountryInfo(new Locale("", "LK"), 94));
+ countryInfoList.add(new CountryInfo(new Locale("", "SD"), 249));
+ countryInfoList.add(new CountryInfo(new Locale("", "SR"), 597));
+ countryInfoList.add(new CountryInfo(new Locale("", "SJ"), 47));
+ countryInfoList.add(new CountryInfo(new Locale("", "SZ"), 268));
+ countryInfoList.add(new CountryInfo(new Locale("", "SE"), 46));
+ countryInfoList.add(new CountryInfo(new Locale("", "CH"), 41));
+ countryInfoList.add(new CountryInfo(new Locale("", "SY"), 963));
+ countryInfoList.add(new CountryInfo(new Locale("", "TW"), 886));
+ countryInfoList.add(new CountryInfo(new Locale("", "TJ"), 992));
+ countryInfoList.add(new CountryInfo(new Locale("", "TZ"), 255));
+ countryInfoList.add(new CountryInfo(new Locale("", "TH"), 66));
+ countryInfoList.add(new CountryInfo(new Locale("", "TG"), 228));
+ countryInfoList.add(new CountryInfo(new Locale("", "TK"), 690));
+ countryInfoList.add(new CountryInfo(new Locale("", "TO"), 676));
+ countryInfoList.add(new CountryInfo(new Locale("", "TT"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "TN"), 216));
+ countryInfoList.add(new CountryInfo(new Locale("", "TR"), 90));
+ countryInfoList.add(new CountryInfo(new Locale("", "TM"), 993));
+ countryInfoList.add(new CountryInfo(new Locale("", "TC"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "TV"), 688));
+ countryInfoList.add(new CountryInfo(new Locale("", "VI"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "UG"), 256));
+ countryInfoList.add(new CountryInfo(new Locale("", "UA"), 380));
+ countryInfoList.add(new CountryInfo(new Locale("", "AE"), 971));
+ countryInfoList.add(new CountryInfo(new Locale("", "GB"), 44));
+ countryInfoList.add(new CountryInfo(new Locale("", "US"), 1));
+ countryInfoList.add(new CountryInfo(new Locale("", "UY"), 598));
+ countryInfoList.add(new CountryInfo(new Locale("", "UZ"), 998));
+ countryInfoList.add(new CountryInfo(new Locale("", "VU"), 678));
+ countryInfoList.add(new CountryInfo(new Locale("", "VA"), 379));
+ countryInfoList.add(new CountryInfo(new Locale("", "VE"), 58));
+ countryInfoList.add(new CountryInfo(new Locale("", "VN"), 84));
+ countryInfoList.add(new CountryInfo(new Locale("", "WF"), 681));
+ countryInfoList.add(new CountryInfo(new Locale("", "EH"), 212));
+ countryInfoList.add(new CountryInfo(new Locale("", "YE"), 967));
+ countryInfoList.add(new CountryInfo(new Locale("", "ZM"), 260));
+ countryInfoList.add(new CountryInfo(new Locale("", "ZW"), 263));
+ Collections.sort(countryInfoList);
+ return countryInfoList;
+ }
+
+ @Override
+ protected void onPostExecute(List result) {
+ if (listener != null) {
+ listener.onLoadComplete(result);
+ }
+ }
+
+ public interface Listener {
+ void onLoadComplete(List result);
+ }
+}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/phone/CountryListSpinner.java b/auth/src/main/java/com/firebase/ui/auth/ui/phone/CountryListSpinner.java
new file mode 100644
index 000000000..dda0e7c16
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/phone/CountryListSpinner.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2015 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Modifications copyright (C) 2017 Google Inc
+ */
+package com.firebase.ui.auth.ui.phone;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.AsyncTask;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.widget.AppCompatEditText;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.ListView;
+
+import java.util.List;
+import java.util.Locale;
+
+public final class CountryListSpinner extends AppCompatEditText implements
+ View.OnClickListener, CountryListLoadTask.Listener {
+ private String textFormat;
+ private DialogPopup dialogPopup;
+ private CountryListAdapter countryListAdapter;
+ private OnClickListener listener;
+ private String selectedCountryName;
+
+ public CountryListSpinner(Context context) {
+ this(context, null, android.R.attr.spinnerStyle);
+ }
+
+ public CountryListSpinner(Context context, AttributeSet attrs) {
+ this(context, attrs, android.R.attr.spinnerStyle);
+ }
+
+ public CountryListSpinner(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ init();
+ }
+
+ @VisibleForTesting
+ void setDialogPopup(DialogPopup dialog) {
+ this.dialogPopup = dialog;
+ }
+
+ private void init() {
+ super.setOnClickListener(this);
+
+ countryListAdapter = new CountryListAdapter(getContext());
+ dialogPopup = new DialogPopup(countryListAdapter);
+ textFormat = "%1$s +%2$d";
+ selectedCountryName = "";
+ final CountryInfo countryInfo = PhoneNumberUtils.getCurrentCountryInfo(getContext());
+ setSpinnerText(countryInfo.countryCode, countryInfo.locale);
+ }
+
+ private void setSpinnerText(int countryCode, Locale locale) {
+ setText(String.format(textFormat, CountryInfo.localeToEmoji(locale), countryCode));
+ setTag(new CountryInfo(locale, countryCode));
+ }
+
+ public void setSelectedForCountry(final Locale locale, String countryCode) {
+ final String countryName = locale.getDisplayName();
+ if (!TextUtils.isEmpty(countryName) && !TextUtils.isEmpty(countryCode)) {
+ selectedCountryName = countryName;
+ setSpinnerText(Integer.parseInt(countryCode), locale);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ if (dialogPopup.isShowing()) {
+ dialogPopup.dismiss();
+ }
+ }
+
+ @Override
+ public void setOnClickListener(OnClickListener l) {
+ listener = l;
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (countryListAdapter.getCount() == 0) {
+ loadCountryList();
+ } else {
+ dialogPopup.show(countryListAdapter.getPositionForCountry(selectedCountryName));
+ }
+ hideKeyboard(getContext(), CountryListSpinner.this);
+ executeUserClickListener(view);
+ }
+
+ private void loadCountryList() {
+ new CountryListLoadTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ private void executeUserClickListener(View view) {
+ if (listener != null) {
+ listener.onClick(view);
+ }
+ }
+
+ @Override
+ public void onLoadComplete(List result) {
+ countryListAdapter.setData(result);
+ dialogPopup.show(countryListAdapter.getPositionForCountry(selectedCountryName));
+ }
+
+ public class DialogPopup implements DialogInterface.OnClickListener {
+ //Delay for postDelayed to set selection without showing the scroll animation
+ private static final long DELAY_MILLIS = 10L;
+ private final CountryListAdapter listAdapter;
+ private AlertDialog dialog;
+
+ DialogPopup(CountryListAdapter adapter) {
+ listAdapter = adapter;
+ }
+
+ public void dismiss() {
+ if (dialog != null) {
+ dialog.dismiss();
+ dialog = null;
+ }
+ }
+
+ public boolean isShowing() {
+ return dialog != null && dialog.isShowing();
+ }
+
+ public void show(final int selected) {
+ if (listAdapter == null) {
+ return;
+ }
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+ dialog = builder.setSingleChoiceItems(listAdapter, 0, this).create();
+ dialog.setCanceledOnTouchOutside(true);
+ final ListView listView = dialog.getListView();
+ listView.setFastScrollEnabled(true);
+ listView.setScrollbarFadingEnabled(false);
+ listView.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ listView.setSelection(selected);
+ }
+ }, DELAY_MILLIS);
+ dialog.show();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final CountryInfo countryInfo = listAdapter.getItem(which);
+ selectedCountryName = countryInfo.locale.getDisplayCountry();
+ setSpinnerText(countryInfo.countryCode, countryInfo.locale);
+ dismiss();
+ }
+ }
+
+ public static void hideKeyboard(Context context, View view) {
+ final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context
+ .INPUT_METHOD_SERVICE);
+ if (imm != null) {
+ imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
+ }
+ }
+}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/phone/CustomCountDownTimer.java b/auth/src/main/java/com/firebase/ui/auth/ui/phone/CustomCountDownTimer.java
new file mode 100644
index 000000000..3bed8ac85
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/phone/CustomCountDownTimer.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.firebase.ui.auth.ui.phone;
+
+import android.os.CountDownTimer;
+
+abstract class CustomCountDownTimer {
+ private final long mMillisInFuture;
+ private final long mCountDownInterval;
+ private CountDownTimer mCountDownTimer;
+
+ CustomCountDownTimer(long millisInFuture, long countDownInterval) {
+ mMillisInFuture = millisInFuture;
+ mCountDownInterval = countDownInterval;
+ mCountDownTimer = create(millisInFuture, countDownInterval);
+ }
+
+ public void update(long millisInFuture) {
+ mCountDownTimer.cancel();
+ mCountDownTimer = create(millisInFuture, mCountDownInterval);
+ mCountDownTimer.start();
+ }
+
+ public void renew() {
+ update(mMillisInFuture);
+ }
+
+ CountDownTimer create(long millisInFuture, long counDownInterval) {
+ return new CountDownTimer(millisInFuture, counDownInterval) {
+ @Override
+ public void onTick(long millisUntilFinished) {
+ CustomCountDownTimer.this.onTick(millisUntilFinished);
+ }
+
+ @Override
+ public void onFinish() {
+ CustomCountDownTimer.this.onFinish();
+ }
+ };
+ }
+
+ public void cancel() {
+ mCountDownTimer.cancel();
+ }
+
+ public void start() {
+ mCountDownTimer.start();
+ }
+
+ protected abstract void onFinish();
+
+ protected abstract void onTick(long millisUntilFinished);
+}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/phone/PhoneNumber.java b/auth/src/main/java/com/firebase/ui/auth/ui/phone/PhoneNumber.java
new file mode 100644
index 000000000..90eb9eb88
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/phone/PhoneNumber.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Modifications copyright (C) 2017 Google Inc
+ */
+package com.firebase.ui.auth.ui.phone;
+
+import android.text.TextUtils;
+
+final class PhoneNumber {
+ private static final PhoneNumber EMPTY_PHONE_NUMBER = new PhoneNumber("", "", "");
+
+ private final String phoneNumber;
+ private final String countryIso;
+ private final String countryCode;
+
+ public PhoneNumber(String phoneNumber, String countryIso, String countryCode) {
+ this.phoneNumber = phoneNumber;
+ this.countryIso = countryIso;
+ this.countryCode = countryCode;
+ }
+
+ /**
+ * Returns an empty instance of this class
+ */
+ public static PhoneNumber emptyPhone() {
+ return EMPTY_PHONE_NUMBER;
+ }
+
+ /**
+ * Returns country code
+ */
+ public String getCountryCode() {
+ return countryCode;
+ }
+
+ /**
+ * Returns phone number without country code
+ */
+ public String getPhoneNumber() {
+ return phoneNumber;
+ }
+
+ /**
+ * Returns 2 char country ISO
+ */
+ public String getCountryIso() {
+ return countryIso;
+ }
+
+ public static boolean isValid(PhoneNumber phoneNumber) {
+ return phoneNumber != null && !EMPTY_PHONE_NUMBER.equals(phoneNumber) && !TextUtils
+ .isEmpty(phoneNumber.getPhoneNumber()) && !TextUtils.isEmpty(phoneNumber
+ .getCountryCode()) && !TextUtils.isEmpty(phoneNumber.getCountryIso());
+ }
+
+ public static boolean isCountryValid(PhoneNumber phoneNumber) {
+ return phoneNumber != null && !EMPTY_PHONE_NUMBER.equals(phoneNumber) && !TextUtils
+ .isEmpty(phoneNumber.getCountryCode()) && !TextUtils.isEmpty(phoneNumber
+ .getCountryIso());
+ }
+}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/phone/PhoneNumberUtils.java b/auth/src/main/java/com/firebase/ui/auth/ui/phone/PhoneNumberUtils.java
new file mode 100644
index 000000000..bd43ff9c8
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/phone/PhoneNumberUtils.java
@@ -0,0 +1,1350 @@
+/*
+ * Copyright (C) 2015 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Modifications copyright (C) 2017 Google Inc
+ */
+package com.firebase.ui.auth.ui.phone;
+
+import android.content.Context;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+final class PhoneNumberUtils {
+ private static final int DEFAULT_COUNTRY_CODE_INT = 1;
+ private static final String DEFAULT_COUNTRY_CODE = String.valueOf(DEFAULT_COUNTRY_CODE_INT);
+ private static final Locale DEFAULT_LOCALE = Locale.US;
+ private static final CountryInfo DEFAULT_COUNTRY =
+ new CountryInfo(DEFAULT_LOCALE, DEFAULT_COUNTRY_CODE_INT);
+
+ private static final int MAX_COUNTRIES = 291;
+ private static final int MAX_COUNTRY_CODES = 286;
+ private static final int MAX_LENGTH_COUNTRY_CODE = 3;
+
+ private static final Map> CountryCodeToRegionCodeMap;
+ private static final Map CountryCodeByIsoMap;
+
+ static {
+ CountryCodeToRegionCodeMap = Collections.unmodifiableMap(createCountryCodeToRegionCodeMap
+ ());
+ CountryCodeByIsoMap = Collections.unmodifiableMap(createCountryCodeByIsoMap());
+ }
+
+ /**
+ * This method may be used to force initialize the static members in the class.
+ * It is recommended to do this in the background since the HashMaps created above
+ * may be time consuming operations on some devices.
+ */
+ static void load() {
+ }
+
+ /**
+ * This method works as follow:
+ *
When the android version is LOLLIPOP or greater, the reliable
+ * {{@link android.telephony.PhoneNumberUtils#formatNumberToE164}} is used to format.
+ *
For lower versions, we construct a value with the input phone number stripped of
+ * non numeric characters and prefix it with a "+" and country code
+ *
+
+ * @param phoneNumber that may or may not itself have country code
+ * @param countryInfo must have locale with ISO 3166 2-letter code for country
+ * @return
+ */
+ @Nullable
+ static String formatPhoneNumber(@NonNull String phoneNumber, @NonNull CountryInfo countryInfo) {
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ return android.telephony.PhoneNumberUtils
+ .formatNumberToE164(phoneNumber, countryInfo.locale.getCountry());
+ }
+ return phoneNumber.startsWith("+")
+ ? phoneNumber
+ : ("+" + String.valueOf(countryInfo.countryCode)
+ + phoneNumber.replaceAll("[^\\d.]", ""));
+ }
+
+
+ /**
+ * This method uses the country returned by {@link #getCurrentCountryInfo(Context)} to
+ * format the phone number. Internall invokes {@link #formatPhoneNumber(String, CountryInfo)}
+ * @param phoneNumber that may or may not itself have country code
+ * @return
+ */
+
+ @Nullable
+ static String formatPhoneNumberUsingCurrentCountry(
+ @NonNull String phoneNumber, Context context) {
+ final CountryInfo currentCountry = PhoneNumberUtils.getCurrentCountryInfo(context);
+ return formatPhoneNumber(phoneNumber, currentCountry);
+ }
+
+ @NonNull
+ static CountryInfo getCurrentCountryInfo(@NonNull Context context) {
+ Locale locale = getSimBasedLocale(context);
+
+ if (locale == null) {
+ locale = getOSLocale();
+ }
+
+ if (locale == null) {
+ return DEFAULT_COUNTRY;
+ }
+
+ Integer countryCode = PhoneNumberUtils.getCountryCode(locale.getCountry());
+
+ return countryCode == null ? DEFAULT_COUNTRY : new CountryInfo(locale, countryCode);
+ }
+
+ /**
+ * This method should not be called on UI thread. Potentially creates a country code by iso
+ * map which can take long in some devices
+ * @param providedPhoneNumber works best when formatted as e164
+ *
+ * @return an instance of the PhoneNumber using the SIM information
+ */
+
+ protected static PhoneNumber getPhoneNumber(@NonNull String providedPhoneNumber) {
+ String countryCode = DEFAULT_COUNTRY_CODE;
+ String countryIso = DEFAULT_LOCALE.getCountry();
+
+ String phoneNumber = providedPhoneNumber;
+ if (providedPhoneNumber.startsWith("+")) {
+ countryCode = countryCodeForPhoneNumber(providedPhoneNumber);
+ countryIso = countryIsoForCountryCode(countryCode);
+ phoneNumber = stripCountryCode(providedPhoneNumber, countryCode);
+ }
+ return new PhoneNumber(phoneNumber, countryIso, countryCode);
+ }
+
+ private static String countryIsoForCountryCode(String countryCode) {
+ final List countries = CountryCodeToRegionCodeMap.get(Integer.valueOf(countryCode));
+ if (countries != null) {
+ return countries.get(0);
+ }
+ return DEFAULT_LOCALE.getCountry();
+ }
+
+ /**
+ * Country code extracted using shortest matching prefix like libPhoneNumber. See:
+ * https://github.com/googlei18n/libphonenumber/blob/master/java/libphonenumber/src/com
+ * /google/i18n/phonenumbers/PhoneNumberUtil.java#L2395
+ */
+ private static String countryCodeForPhoneNumber(String normalizedPhoneNumber) {
+ final String phoneWithoutPlusPrefix = normalizedPhoneNumber
+ .replaceFirst("^\\+", "");
+ final int numberLength = phoneWithoutPlusPrefix.length();
+
+ for (int i = 1; i <= MAX_LENGTH_COUNTRY_CODE && i <= numberLength; i++) {
+ final String potentialCountryCode = phoneWithoutPlusPrefix.substring(0, i);
+ final Integer countryCodeKey = Integer.valueOf(potentialCountryCode);
+
+ if (CountryCodeToRegionCodeMap.containsKey(countryCodeKey)) {
+ return potentialCountryCode;
+ }
+ }
+
+ return DEFAULT_COUNTRY_CODE;
+ }
+
+ private static Map> createCountryCodeToRegionCodeMap() {
+ final Map> countryCodeToRegionCodeMap = new ConcurrentHashMap<>
+ (MAX_COUNTRY_CODES);
+
+ ArrayList listWithRegionCode;
+
+ listWithRegionCode = new ArrayList<>(25);
+ listWithRegionCode.add("US");
+ listWithRegionCode.add("AG");
+ listWithRegionCode.add("AI");
+ listWithRegionCode.add("AS");
+ listWithRegionCode.add("BB");
+ listWithRegionCode.add("BM");
+ listWithRegionCode.add("BS");
+ listWithRegionCode.add("CA");
+ listWithRegionCode.add("DM");
+ listWithRegionCode.add("DO");
+ listWithRegionCode.add("GD");
+ listWithRegionCode.add("GU");
+ listWithRegionCode.add("JM");
+ listWithRegionCode.add("KN");
+ listWithRegionCode.add("KY");
+ listWithRegionCode.add("LC");
+ listWithRegionCode.add("MP");
+ listWithRegionCode.add("MS");
+ listWithRegionCode.add("PR");
+ listWithRegionCode.add("SX");
+ listWithRegionCode.add("TC");
+ listWithRegionCode.add("TT");
+ listWithRegionCode.add("VC");
+ listWithRegionCode.add("VG");
+ listWithRegionCode.add("VI");
+ countryCodeToRegionCodeMap.put(1, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(2);
+ listWithRegionCode.add("RU");
+ listWithRegionCode.add("KZ");
+ countryCodeToRegionCodeMap.put(7, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("EG");
+ countryCodeToRegionCodeMap.put(20, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("ZA");
+ countryCodeToRegionCodeMap.put(27, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("GR");
+ countryCodeToRegionCodeMap.put(30, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("NL");
+ countryCodeToRegionCodeMap.put(31, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("BE");
+ countryCodeToRegionCodeMap.put(32, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("FR");
+ countryCodeToRegionCodeMap.put(33, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("ES");
+ countryCodeToRegionCodeMap.put(34, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("HU");
+ countryCodeToRegionCodeMap.put(36, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("IT");
+ countryCodeToRegionCodeMap.put(39, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("RO");
+ countryCodeToRegionCodeMap.put(40, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("CH");
+ countryCodeToRegionCodeMap.put(41, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("AT");
+ countryCodeToRegionCodeMap.put(43, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(4);
+ listWithRegionCode.add("GB");
+ listWithRegionCode.add("GG");
+ listWithRegionCode.add("IM");
+ listWithRegionCode.add("JE");
+ countryCodeToRegionCodeMap.put(44, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("DK");
+ countryCodeToRegionCodeMap.put(45, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("SE");
+ countryCodeToRegionCodeMap.put(46, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(2);
+ listWithRegionCode.add("NO");
+ listWithRegionCode.add("SJ");
+ countryCodeToRegionCodeMap.put(47, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("PL");
+ countryCodeToRegionCodeMap.put(48, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("DE");
+ countryCodeToRegionCodeMap.put(49, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("PE");
+ countryCodeToRegionCodeMap.put(51, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("MX");
+ countryCodeToRegionCodeMap.put(52, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("CU");
+ countryCodeToRegionCodeMap.put(53, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("AR");
+ countryCodeToRegionCodeMap.put(54, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("BR");
+ countryCodeToRegionCodeMap.put(55, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("CL");
+ countryCodeToRegionCodeMap.put(56, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("CO");
+ countryCodeToRegionCodeMap.put(57, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("VE");
+ countryCodeToRegionCodeMap.put(58, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("MY");
+ countryCodeToRegionCodeMap.put(60, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(3);
+ listWithRegionCode.add("AU");
+ listWithRegionCode.add("CC");
+ listWithRegionCode.add("CX");
+ countryCodeToRegionCodeMap.put(61, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("ID");
+ countryCodeToRegionCodeMap.put(62, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("PH");
+ countryCodeToRegionCodeMap.put(63, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("NZ");
+ countryCodeToRegionCodeMap.put(64, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("SG");
+ countryCodeToRegionCodeMap.put(65, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("TH");
+ countryCodeToRegionCodeMap.put(66, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("JP");
+ countryCodeToRegionCodeMap.put(81, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("KR");
+ countryCodeToRegionCodeMap.put(82, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("VN");
+ countryCodeToRegionCodeMap.put(84, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("CN");
+ countryCodeToRegionCodeMap.put(86, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("TR");
+ countryCodeToRegionCodeMap.put(90, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("IN");
+ countryCodeToRegionCodeMap.put(91, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("PK");
+ countryCodeToRegionCodeMap.put(92, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("AF");
+ countryCodeToRegionCodeMap.put(93, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("LK");
+ countryCodeToRegionCodeMap.put(94, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("MM");
+ countryCodeToRegionCodeMap.put(95, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("IR");
+ countryCodeToRegionCodeMap.put(98, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("SS");
+ countryCodeToRegionCodeMap.put(211, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(2);
+ listWithRegionCode.add("MA");
+ listWithRegionCode.add("EH");
+ countryCodeToRegionCodeMap.put(212, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("DZ");
+ countryCodeToRegionCodeMap.put(213, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("TN");
+ countryCodeToRegionCodeMap.put(216, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("LY");
+ countryCodeToRegionCodeMap.put(218, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("GM");
+ countryCodeToRegionCodeMap.put(220, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("SN");
+ countryCodeToRegionCodeMap.put(221, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("MR");
+ countryCodeToRegionCodeMap.put(222, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("ML");
+ countryCodeToRegionCodeMap.put(223, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("GN");
+ countryCodeToRegionCodeMap.put(224, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("CI");
+ countryCodeToRegionCodeMap.put(225, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("BF");
+ countryCodeToRegionCodeMap.put(226, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("NE");
+ countryCodeToRegionCodeMap.put(227, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("TG");
+ countryCodeToRegionCodeMap.put(228, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("BJ");
+ countryCodeToRegionCodeMap.put(229, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("MU");
+ countryCodeToRegionCodeMap.put(230, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("LR");
+ countryCodeToRegionCodeMap.put(231, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("SL");
+ countryCodeToRegionCodeMap.put(232, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("GH");
+ countryCodeToRegionCodeMap.put(233, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("NG");
+ countryCodeToRegionCodeMap.put(234, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("TD");
+ countryCodeToRegionCodeMap.put(235, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("CF");
+ countryCodeToRegionCodeMap.put(236, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("CM");
+ countryCodeToRegionCodeMap.put(237, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("CV");
+ countryCodeToRegionCodeMap.put(238, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("ST");
+ countryCodeToRegionCodeMap.put(239, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("GQ");
+ countryCodeToRegionCodeMap.put(240, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("GA");
+ countryCodeToRegionCodeMap.put(241, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("CG");
+ countryCodeToRegionCodeMap.put(242, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("CD");
+ countryCodeToRegionCodeMap.put(243, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("AO");
+ countryCodeToRegionCodeMap.put(244, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("GW");
+ countryCodeToRegionCodeMap.put(245, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("IO");
+ countryCodeToRegionCodeMap.put(246, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("AC");
+ countryCodeToRegionCodeMap.put(247, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("SC");
+ countryCodeToRegionCodeMap.put(248, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("SD");
+ countryCodeToRegionCodeMap.put(249, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("RW");
+ countryCodeToRegionCodeMap.put(250, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("ET");
+ countryCodeToRegionCodeMap.put(251, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("SO");
+ countryCodeToRegionCodeMap.put(252, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("DJ");
+ countryCodeToRegionCodeMap.put(253, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("KE");
+ countryCodeToRegionCodeMap.put(254, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("TZ");
+ countryCodeToRegionCodeMap.put(255, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("UG");
+ countryCodeToRegionCodeMap.put(256, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("BI");
+ countryCodeToRegionCodeMap.put(257, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("MZ");
+ countryCodeToRegionCodeMap.put(258, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("ZM");
+ countryCodeToRegionCodeMap.put(260, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("MG");
+ countryCodeToRegionCodeMap.put(261, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(2);
+ listWithRegionCode.add("RE");
+ listWithRegionCode.add("YT");
+ countryCodeToRegionCodeMap.put(262, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("ZW");
+ countryCodeToRegionCodeMap.put(263, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("NA");
+ countryCodeToRegionCodeMap.put(264, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("MW");
+ countryCodeToRegionCodeMap.put(265, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("LS");
+ countryCodeToRegionCodeMap.put(266, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("BW");
+ countryCodeToRegionCodeMap.put(267, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("SZ");
+ countryCodeToRegionCodeMap.put(268, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("KM");
+ countryCodeToRegionCodeMap.put(269, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(2);
+ listWithRegionCode.add("SH");
+ listWithRegionCode.add("TA");
+ countryCodeToRegionCodeMap.put(290, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("ER");
+ countryCodeToRegionCodeMap.put(291, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("AW");
+ countryCodeToRegionCodeMap.put(297, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("FO");
+ countryCodeToRegionCodeMap.put(298, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("GL");
+ countryCodeToRegionCodeMap.put(299, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("GI");
+ countryCodeToRegionCodeMap.put(350, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("PT");
+ countryCodeToRegionCodeMap.put(351, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("LU");
+ countryCodeToRegionCodeMap.put(352, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("IE");
+ countryCodeToRegionCodeMap.put(353, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("IS");
+ countryCodeToRegionCodeMap.put(354, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("AL");
+ countryCodeToRegionCodeMap.put(355, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("MT");
+ countryCodeToRegionCodeMap.put(356, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("CY");
+ countryCodeToRegionCodeMap.put(357, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(2);
+ listWithRegionCode.add("FI");
+ listWithRegionCode.add("AX");
+ countryCodeToRegionCodeMap.put(358, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("BG");
+ countryCodeToRegionCodeMap.put(359, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("LT");
+ countryCodeToRegionCodeMap.put(370, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("LV");
+ countryCodeToRegionCodeMap.put(371, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("EE");
+ countryCodeToRegionCodeMap.put(372, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("MD");
+ countryCodeToRegionCodeMap.put(373, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("AM");
+ countryCodeToRegionCodeMap.put(374, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("BY");
+ countryCodeToRegionCodeMap.put(375, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("AD");
+ countryCodeToRegionCodeMap.put(376, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("MC");
+ countryCodeToRegionCodeMap.put(377, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("SM");
+ countryCodeToRegionCodeMap.put(378, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("VA");
+ countryCodeToRegionCodeMap.put(379, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("UA");
+ countryCodeToRegionCodeMap.put(380, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("RS");
+ countryCodeToRegionCodeMap.put(381, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("ME");
+ countryCodeToRegionCodeMap.put(382, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("HR");
+ countryCodeToRegionCodeMap.put(385, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("SI");
+ countryCodeToRegionCodeMap.put(386, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("BA");
+ countryCodeToRegionCodeMap.put(387, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("MK");
+ countryCodeToRegionCodeMap.put(389, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("CZ");
+ countryCodeToRegionCodeMap.put(420, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("SK");
+ countryCodeToRegionCodeMap.put(421, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("LI");
+ countryCodeToRegionCodeMap.put(423, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("FK");
+ countryCodeToRegionCodeMap.put(500, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("BZ");
+ countryCodeToRegionCodeMap.put(501, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("GT");
+ countryCodeToRegionCodeMap.put(502, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("SV");
+ countryCodeToRegionCodeMap.put(503, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("HN");
+ countryCodeToRegionCodeMap.put(504, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("NI");
+ countryCodeToRegionCodeMap.put(505, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("CR");
+ countryCodeToRegionCodeMap.put(506, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("PA");
+ countryCodeToRegionCodeMap.put(507, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("PM");
+ countryCodeToRegionCodeMap.put(508, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("HT");
+ countryCodeToRegionCodeMap.put(509, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(3);
+ listWithRegionCode.add("GP");
+ listWithRegionCode.add("BL");
+ listWithRegionCode.add("MF");
+ countryCodeToRegionCodeMap.put(590, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("BO");
+ countryCodeToRegionCodeMap.put(591, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("GY");
+ countryCodeToRegionCodeMap.put(592, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("EC");
+ countryCodeToRegionCodeMap.put(593, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("GF");
+ countryCodeToRegionCodeMap.put(594, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("PY");
+ countryCodeToRegionCodeMap.put(595, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("MQ");
+ countryCodeToRegionCodeMap.put(596, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("SR");
+ countryCodeToRegionCodeMap.put(597, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("UY");
+ countryCodeToRegionCodeMap.put(598, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(2);
+ listWithRegionCode.add("CW");
+ listWithRegionCode.add("BQ");
+ countryCodeToRegionCodeMap.put(599, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("TL");
+ countryCodeToRegionCodeMap.put(670, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("NF");
+ countryCodeToRegionCodeMap.put(672, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("BN");
+ countryCodeToRegionCodeMap.put(673, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("NR");
+ countryCodeToRegionCodeMap.put(674, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("PG");
+ countryCodeToRegionCodeMap.put(675, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("TO");
+ countryCodeToRegionCodeMap.put(676, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("SB");
+ countryCodeToRegionCodeMap.put(677, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("VU");
+ countryCodeToRegionCodeMap.put(678, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("FJ");
+ countryCodeToRegionCodeMap.put(679, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("PW");
+ countryCodeToRegionCodeMap.put(680, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("WF");
+ countryCodeToRegionCodeMap.put(681, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("CK");
+ countryCodeToRegionCodeMap.put(682, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("NU");
+ countryCodeToRegionCodeMap.put(683, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("WS");
+ countryCodeToRegionCodeMap.put(685, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("KI");
+ countryCodeToRegionCodeMap.put(686, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("NC");
+ countryCodeToRegionCodeMap.put(687, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("TV");
+ countryCodeToRegionCodeMap.put(688, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("PF");
+ countryCodeToRegionCodeMap.put(689, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("TK");
+ countryCodeToRegionCodeMap.put(690, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("FM");
+ countryCodeToRegionCodeMap.put(691, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("MH");
+ countryCodeToRegionCodeMap.put(692, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("001");
+ countryCodeToRegionCodeMap.put(800, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("001");
+ countryCodeToRegionCodeMap.put(808, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("KP");
+ countryCodeToRegionCodeMap.put(850, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("HK");
+ countryCodeToRegionCodeMap.put(852, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("MO");
+ countryCodeToRegionCodeMap.put(853, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("KH");
+ countryCodeToRegionCodeMap.put(855, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("LA");
+ countryCodeToRegionCodeMap.put(856, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("001");
+ countryCodeToRegionCodeMap.put(870, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("001");
+ countryCodeToRegionCodeMap.put(878, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("BD");
+ countryCodeToRegionCodeMap.put(880, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("001");
+ countryCodeToRegionCodeMap.put(881, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("001");
+ countryCodeToRegionCodeMap.put(882, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("001");
+ countryCodeToRegionCodeMap.put(883, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("TW");
+ countryCodeToRegionCodeMap.put(886, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("001");
+ countryCodeToRegionCodeMap.put(888, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("MV");
+ countryCodeToRegionCodeMap.put(960, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("LB");
+ countryCodeToRegionCodeMap.put(961, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("JO");
+ countryCodeToRegionCodeMap.put(962, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("SY");
+ countryCodeToRegionCodeMap.put(963, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("IQ");
+ countryCodeToRegionCodeMap.put(964, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("KW");
+ countryCodeToRegionCodeMap.put(965, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("SA");
+ countryCodeToRegionCodeMap.put(966, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("YE");
+ countryCodeToRegionCodeMap.put(967, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("OM");
+ countryCodeToRegionCodeMap.put(968, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("PS");
+ countryCodeToRegionCodeMap.put(970, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("AE");
+ countryCodeToRegionCodeMap.put(971, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("IL");
+ countryCodeToRegionCodeMap.put(972, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("BH");
+ countryCodeToRegionCodeMap.put(973, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("QA");
+ countryCodeToRegionCodeMap.put(974, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("BT");
+ countryCodeToRegionCodeMap.put(975, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("MN");
+ countryCodeToRegionCodeMap.put(976, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("NP");
+ countryCodeToRegionCodeMap.put(977, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("001");
+ countryCodeToRegionCodeMap.put(979, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("TJ");
+ countryCodeToRegionCodeMap.put(992, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("TM");
+ countryCodeToRegionCodeMap.put(993, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("AZ");
+ countryCodeToRegionCodeMap.put(994, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("GE");
+ countryCodeToRegionCodeMap.put(995, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("KG");
+ countryCodeToRegionCodeMap.put(996, listWithRegionCode);
+
+ listWithRegionCode = new ArrayList<>(1);
+ listWithRegionCode.add("UZ");
+ countryCodeToRegionCodeMap.put(998, listWithRegionCode);
+
+ return countryCodeToRegionCodeMap;
+ }
+
+ private static synchronized Map createCountryCodeByIsoMap() {
+ final Map countryCodeByIso = new HashMap<>(MAX_COUNTRIES);
+ countryCodeByIso.put("AF", 93);
+ countryCodeByIso.put("AX", 358);
+ countryCodeByIso.put("AL", 355);
+ countryCodeByIso.put("DZ", 213);
+ countryCodeByIso.put("AS", 1);
+ countryCodeByIso.put("AD", 376);
+ countryCodeByIso.put("AO", 244);
+ countryCodeByIso.put("AI", 1);
+ countryCodeByIso.put("AG", 1);
+ countryCodeByIso.put("AR", 54);
+ countryCodeByIso.put("AM", 374);
+ countryCodeByIso.put("AW", 297);
+ countryCodeByIso.put("AC", 247);
+ countryCodeByIso.put("AU", 61);
+ countryCodeByIso.put("AT", 43);
+ countryCodeByIso.put("AZ", 994);
+ countryCodeByIso.put("BS", 1);
+ countryCodeByIso.put("BH", 973);
+ countryCodeByIso.put("BD", 880);
+ countryCodeByIso.put("BB", 1);
+ countryCodeByIso.put("BY", 375);
+ countryCodeByIso.put("BE", 32);
+ countryCodeByIso.put("BZ", 501);
+ countryCodeByIso.put("BJ", 229);
+ countryCodeByIso.put("BM", 1);
+ countryCodeByIso.put("BT", 975);
+ countryCodeByIso.put("BO", 591);
+ countryCodeByIso.put("BA", 387);
+ countryCodeByIso.put("BW", 267);
+ countryCodeByIso.put("BR", 55);
+ countryCodeByIso.put("IO", 246);
+ countryCodeByIso.put("VG", 1);
+ countryCodeByIso.put("BN", 673);
+ countryCodeByIso.put("BG", 359);
+ countryCodeByIso.put("BF", 226);
+ countryCodeByIso.put("BI", 257);
+ countryCodeByIso.put("KH", 855);
+ countryCodeByIso.put("CM", 237);
+ countryCodeByIso.put("CA", 1);
+ countryCodeByIso.put("CV", 238);
+ countryCodeByIso.put("BQ", 599);
+ countryCodeByIso.put("KY", 1);
+ countryCodeByIso.put("CF", 236);
+ countryCodeByIso.put("TD", 235);
+ countryCodeByIso.put("CL", 56);
+ countryCodeByIso.put("CN", 86);
+ countryCodeByIso.put("CX", 61);
+ countryCodeByIso.put("CC", 61);
+ countryCodeByIso.put("CO", 57);
+ countryCodeByIso.put("KM", 269);
+ countryCodeByIso.put("CD", 243);
+ countryCodeByIso.put("CG", 242);
+ countryCodeByIso.put("CK", 682);
+ countryCodeByIso.put("CR", 506);
+ countryCodeByIso.put("CI", 225);
+ countryCodeByIso.put("HR", 385);
+ countryCodeByIso.put("CU", 53);
+ countryCodeByIso.put("CW", 599);
+ countryCodeByIso.put("CY", 357);
+ countryCodeByIso.put("CZ", 420);
+ countryCodeByIso.put("DK", 45);
+ countryCodeByIso.put("DJ", 253);
+ countryCodeByIso.put("DM", 1);
+ countryCodeByIso.put("DO", 1);
+ countryCodeByIso.put("TL", 670);
+ countryCodeByIso.put("EC", 593);
+ countryCodeByIso.put("EG", 20);
+ countryCodeByIso.put("SV", 503);
+ countryCodeByIso.put("GQ", 240);
+ countryCodeByIso.put("ER", 291);
+ countryCodeByIso.put("EE", 372);
+ countryCodeByIso.put("ET", 251);
+ countryCodeByIso.put("FK", 500);
+ countryCodeByIso.put("FO", 298);
+ countryCodeByIso.put("FJ", 679);
+ countryCodeByIso.put("FI", 358);
+ countryCodeByIso.put("FR", 33);
+ countryCodeByIso.put("GF", 594);
+ countryCodeByIso.put("PF", 689);
+ countryCodeByIso.put("GA", 241);
+ countryCodeByIso.put("GM", 220);
+ countryCodeByIso.put("GE", 995);
+ countryCodeByIso.put("DE", 49);
+ countryCodeByIso.put("GH", 233);
+ countryCodeByIso.put("GI", 350);
+ countryCodeByIso.put("GR", 30);
+ countryCodeByIso.put("GL", 299);
+ countryCodeByIso.put("GD", 1);
+ countryCodeByIso.put("GP", 590);
+ countryCodeByIso.put("GU", 1);
+ countryCodeByIso.put("GT", 502);
+ countryCodeByIso.put("GG", 44);
+ countryCodeByIso.put("GN", 224);
+ countryCodeByIso.put("GW", 245);
+ countryCodeByIso.put("GY", 592);
+ countryCodeByIso.put("HT", 509);
+ countryCodeByIso.put("HM", 672);
+ countryCodeByIso.put("HN", 504);
+ countryCodeByIso.put("HK", 852);
+ countryCodeByIso.put("HU", 36);
+ countryCodeByIso.put("IS", 354);
+ countryCodeByIso.put("IN", 91);
+ countryCodeByIso.put("ID", 62);
+ countryCodeByIso.put("IR", 98);
+ countryCodeByIso.put("IQ", 964);
+ countryCodeByIso.put("IE", 353);
+ countryCodeByIso.put("IM", 44);
+ countryCodeByIso.put("IL", 972);
+ countryCodeByIso.put("IT", 39);
+ countryCodeByIso.put("JM", 1);
+ countryCodeByIso.put("JP", 81);
+ countryCodeByIso.put("JE", 44);
+ countryCodeByIso.put("JO", 962);
+ countryCodeByIso.put("KZ", 7);
+ countryCodeByIso.put("KE", 254);
+ countryCodeByIso.put("KI", 686);
+ countryCodeByIso.put("XK", 381);
+ countryCodeByIso.put("KW", 965);
+ countryCodeByIso.put("KG", 996);
+ countryCodeByIso.put("LA", 856);
+ countryCodeByIso.put("LV", 371);
+ countryCodeByIso.put("LB", 961);
+ countryCodeByIso.put("LS", 266);
+ countryCodeByIso.put("LR", 231);
+ countryCodeByIso.put("LY", 218);
+ countryCodeByIso.put("LI", 423);
+ countryCodeByIso.put("LT", 370);
+ countryCodeByIso.put("LU", 352);
+ countryCodeByIso.put("MO", 853);
+ countryCodeByIso.put("MK", 389);
+ countryCodeByIso.put("MG", 261);
+ countryCodeByIso.put("MW", 265);
+ countryCodeByIso.put("MY", 60);
+ countryCodeByIso.put("MV", 960);
+ countryCodeByIso.put("ML", 223);
+ countryCodeByIso.put("MT", 356);
+ countryCodeByIso.put("MH", 692);
+ countryCodeByIso.put("MQ", 596);
+ countryCodeByIso.put("MR", 222);
+ countryCodeByIso.put("MU", 230);
+ countryCodeByIso.put("YT", 262);
+ countryCodeByIso.put("MX", 52);
+ countryCodeByIso.put("FM", 691);
+ countryCodeByIso.put("MD", 373);
+ countryCodeByIso.put("MC", 377);
+ countryCodeByIso.put("MN", 976);
+ countryCodeByIso.put("ME", 382);
+ countryCodeByIso.put("MS", 1);
+ countryCodeByIso.put("MA", 212);
+ countryCodeByIso.put("MZ", 258);
+ countryCodeByIso.put("MM", 95);
+ countryCodeByIso.put("NA", 264);
+ countryCodeByIso.put("NR", 674);
+ countryCodeByIso.put("NP", 977);
+ countryCodeByIso.put("NL", 31);
+ countryCodeByIso.put("NC", 687);
+ countryCodeByIso.put("NZ", 64);
+ countryCodeByIso.put("NI", 505);
+ countryCodeByIso.put("NE", 227);
+ countryCodeByIso.put("NG", 234);
+ countryCodeByIso.put("NU", 683);
+ countryCodeByIso.put("NF", 672);
+ countryCodeByIso.put("KP", 850);
+ countryCodeByIso.put("MP", 1);
+ countryCodeByIso.put("NO", 47);
+ countryCodeByIso.put("OM", 968);
+ countryCodeByIso.put("PK", 92);
+ countryCodeByIso.put("PW", 680);
+ countryCodeByIso.put("PS", 970);
+ countryCodeByIso.put("PA", 507);
+ countryCodeByIso.put("PG", 675);
+ countryCodeByIso.put("PY", 595);
+ countryCodeByIso.put("PE", 51);
+ countryCodeByIso.put("PH", 63);
+ countryCodeByIso.put("PL", 48);
+ countryCodeByIso.put("PT", 351);
+ countryCodeByIso.put("PR", 1);
+ countryCodeByIso.put("QA", 974);
+ countryCodeByIso.put("RE", 262);
+ countryCodeByIso.put("RO", 40);
+ countryCodeByIso.put("RU", 7);
+ countryCodeByIso.put("RW", 250);
+ countryCodeByIso.put("BL", 590);
+ countryCodeByIso.put("SH", 290);
+ countryCodeByIso.put("KN", 1);
+ countryCodeByIso.put("LC", 1);
+ countryCodeByIso.put("MF", 590);
+ countryCodeByIso.put("PM", 508);
+ countryCodeByIso.put("VC", 1);
+ countryCodeByIso.put("WS", 685);
+ countryCodeByIso.put("SM", 378);
+ countryCodeByIso.put("ST", 239);
+ countryCodeByIso.put("SA", 966);
+ countryCodeByIso.put("SN", 221);
+ countryCodeByIso.put("RS", 381);
+ countryCodeByIso.put("SC", 248);
+ countryCodeByIso.put("SL", 232);
+ countryCodeByIso.put("SG", 65);
+ countryCodeByIso.put("SX", 1);
+ countryCodeByIso.put("SK", 421);
+ countryCodeByIso.put("SI", 386);
+ countryCodeByIso.put("SB", 677);
+ countryCodeByIso.put("SO", 252);
+ countryCodeByIso.put("ZA", 27);
+ countryCodeByIso.put("GS", 500);
+ countryCodeByIso.put("KR", 82);
+ countryCodeByIso.put("SS", 211);
+ countryCodeByIso.put("ES", 34);
+ countryCodeByIso.put("LK", 94);
+ countryCodeByIso.put("SD", 249);
+ countryCodeByIso.put("SR", 597);
+ countryCodeByIso.put("SJ", 47);
+ countryCodeByIso.put("SZ", 268);
+ countryCodeByIso.put("SE", 46);
+ countryCodeByIso.put("CH", 41);
+ countryCodeByIso.put("SY", 963);
+ countryCodeByIso.put("TW", 886);
+ countryCodeByIso.put("TJ", 992);
+ countryCodeByIso.put("TZ", 255);
+ countryCodeByIso.put("TH", 66);
+ countryCodeByIso.put("TG", 228);
+ countryCodeByIso.put("TK", 690);
+ countryCodeByIso.put("TO", 676);
+ countryCodeByIso.put("TT", 1);
+ countryCodeByIso.put("TN", 216);
+ countryCodeByIso.put("TR", 90);
+ countryCodeByIso.put("TM", 993);
+ countryCodeByIso.put("TC", 1);
+ countryCodeByIso.put("TV", 688);
+ countryCodeByIso.put("VI", 1);
+ countryCodeByIso.put("UG", 256);
+ countryCodeByIso.put("UA", 380);
+ countryCodeByIso.put("AE", 971);
+ countryCodeByIso.put("GB", 44);
+ countryCodeByIso.put("US", 1);
+ countryCodeByIso.put("UY", 598);
+ countryCodeByIso.put("UZ", 998);
+ countryCodeByIso.put("VU", 678);
+ countryCodeByIso.put("VA", 379);
+ countryCodeByIso.put("VE", 58);
+ countryCodeByIso.put("VN", 84);
+ countryCodeByIso.put("WF", 681);
+ countryCodeByIso.put("EH", 212);
+ countryCodeByIso.put("YE", 967);
+ countryCodeByIso.put("ZM", 260);
+ countryCodeByIso.put("ZW", 263);
+ return countryCodeByIso;
+ }
+
+ @Nullable
+ public static Integer getCountryCode(String countryIso) {
+ return countryIso == null
+ ? null
+ : CountryCodeByIsoMap.get(countryIso.toUpperCase(Locale.getDefault()));
+ }
+
+ private static String stripCountryCode(String phoneNumber, String countryCode) {
+ return phoneNumber.replaceFirst("^\\+?" + countryCode, "");
+ }
+
+ private static Locale getSimBasedLocale(@NonNull Context context) {
+ final TelephonyManager tm =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ final String countryIso = tm != null ? tm.getSimCountryIso() : null;
+ return TextUtils.isEmpty(countryIso) ? null : new Locale("", countryIso);
+ }
+
+ private static Locale getOSLocale() {
+ return Locale.getDefault();
+ }
+}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/phone/PhoneVerificationActivity.java b/auth/src/main/java/com/firebase/ui/auth/ui/phone/PhoneVerificationActivity.java
new file mode 100644
index 000000000..a31cebac2
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/phone/PhoneVerificationActivity.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.firebase.ui.auth.ui.phone;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v7.app.AlertDialog;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.firebase.ui.auth.IdpResponse;
+import com.firebase.ui.auth.R;
+import com.firebase.ui.auth.ResultCodes;
+import com.firebase.ui.auth.ui.AppCompatBase;
+import com.firebase.ui.auth.ui.BaseHelper;
+import com.firebase.ui.auth.ui.ExtraConstants;
+import com.firebase.ui.auth.ui.FlowParameters;
+import com.firebase.ui.auth.util.signincontainer.SaveSmartLock;
+import com.google.android.gms.tasks.OnFailureListener;
+import com.google.android.gms.tasks.OnSuccessListener;
+import com.google.firebase.FirebaseException;
+import com.google.firebase.auth.AuthResult;
+import com.google.firebase.auth.FirebaseAuthException;
+import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException;
+import com.google.firebase.auth.FirebaseUser;
+import com.google.firebase.auth.PhoneAuthCredential;
+import com.google.firebase.auth.PhoneAuthProvider;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity to control the entire phone verification flow. Plays host to
+ * {@link VerifyPhoneNumberFragment} and {@link SubmitConfirmationCodeFragment}
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class PhoneVerificationActivity extends AppCompatBase {
+ private static final String PHONE_VERIFICATION_LOG_TAG = "PhoneVerification";
+ static final long SHORT_DELAY_MILLIS = 750;
+ static final long AUTO_RETRIEVAL_TIMEOUT_MILLIS = 120000;
+ static final String ERROR_INVALID_PHONE = "ERROR_INVALID_PHONE_NUMBER";
+ static final String ERROR_INVALID_VERIFICATION = "ERROR_INVALID_VERIFICATION_CODE";
+ static final String ERROR_TOO_MANY_REQUESTS = "ERROR_TOO_MANY_REQUESTS";
+ static final String ERROR_QUOTA_EXCEEDED = "ERROR_QUOTA_EXCEEDED";
+ static final String ERROR_SESSION_EXPIRED = "ERROR_SESSION_EXPIRED";
+ static final String KEY_VERIFICATION_PHONE = "KEY_VERIFICATION_PHONE";
+ static final String KEY_STATE = "KEY_STATE";
+
+ enum VerificationState {
+ VERIFICATION_NOT_STARTED, VERIFICATION_STARTED, VERIFIED;
+ }
+
+ private AlertDialog mAlertDialog;
+ private SaveSmartLock mSaveSmartLock;
+ private CompletableProgressDialog mProgressDialog;
+ private Handler mHandler;
+ private String mPhoneNumber;
+ private String mVerificationId;
+ private Boolean mIsDestroyed = false;
+ private PhoneAuthProvider.ForceResendingToken mForceResendingToken;
+ private VerificationState mVerificationState;
+
+ public static Intent createIntent(Context context, FlowParameters flowParams, String phone) {
+ return BaseHelper.createBaseIntent(context, PhoneVerificationActivity.class, flowParams)
+ .putExtra(ExtraConstants.EXTRA_PHONE, phone);
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstance) {
+ super.onCreate(savedInstance);
+ setContentView(R.layout.activity_register_phone);
+
+ mSaveSmartLock = mActivityHelper.getSaveSmartLockInstance();
+ mHandler = new Handler();
+ mVerificationState = VerificationState.VERIFICATION_NOT_STARTED;
+ if (savedInstance != null && !savedInstance.isEmpty()) {
+ mPhoneNumber = savedInstance.getString(KEY_VERIFICATION_PHONE);
+ if (savedInstance.getSerializable(KEY_STATE) != null) {
+ mVerificationState = (VerificationState) savedInstance.getSerializable(KEY_STATE);
+ }
+ return;
+ }
+
+ String phone = getIntent().getExtras().getString(ExtraConstants.EXTRA_PHONE);
+ VerifyPhoneNumberFragment fragment = VerifyPhoneNumberFragment.newInstance
+ (mActivityHelper.getFlowParams(), phone);
+ getSupportFragmentManager().beginTransaction().replace(R.id.fragment_verify_phone,
+ fragment, VerifyPhoneNumberFragment.TAG).disallowAddToBackStack().commit();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ //Actvitiy can be restarted in any of the following states
+ // 1) VERIFICATION_STARTED
+ // 2) SMS_RETRIEVED
+ // 3) INSTANT_VERIFIED
+ // 4) VERIFIED
+ // For the first three cases, we can simply resubscribe to the
+ // OnVerificationStateChangedCallbacks
+ // For 4, we simply finish the activity
+ if (mVerificationState.equals(VerificationState.VERIFICATION_STARTED)) {
+ sendCode(mPhoneNumber, false);
+ } else if (mVerificationState == VerificationState.VERIFIED) {
+ // activity was recreated when verified dialog was displayed
+ finish(mActivityHelper.getFirebaseAuth().getCurrentUser());
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
+ mVerificationState = VerificationState.VERIFICATION_NOT_STARTED;
+ getSupportFragmentManager().popBackStack();
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ outState.putSerializable(KEY_STATE, mVerificationState);
+ outState.putString(KEY_VERIFICATION_PHONE, mPhoneNumber);
+ super.onSaveInstanceState(outState);
+ }
+
+ @Override
+ protected void onDestroy() {
+ mIsDestroyed = true;
+ mHandler.removeCallbacksAndMessages(null);
+ dismissLoadingDialog();
+ super.onDestroy();
+ }
+
+ void verifyPhoneNumber(String phoneNumber, boolean forceResend) {
+ sendCode(phoneNumber, forceResend);
+ if (forceResend) {
+ showLoadingDialog(getString(R.string.resending));
+ } else {
+ showLoadingDialog(getString(R.string.verifying));
+ }
+ }
+
+ public void submitConfirmationCode(String confirmationCode) {
+ showLoadingDialog(getString(R.string.verifying));
+ signingWithCreds(PhoneAuthProvider.getCredential(mVerificationId, confirmationCode));
+ }
+
+ void onVerificationSuccess(@NonNull final PhoneAuthCredential phoneAuthCredential) {
+ if (TextUtils.isEmpty(phoneAuthCredential.getSmsCode())) {
+ signingWithCreds(phoneAuthCredential);
+ } else {
+ //Show Fragment if it is not already visible
+ showSubmitCodeFragment();
+ SubmitConfirmationCodeFragment submitConfirmationCodeFragment =
+ getSubmitConfirmationCodeFragment();
+
+
+ showLoadingDialog(getString(R.string.retrieving_sms));
+ if (submitConfirmationCodeFragment != null) {
+ submitConfirmationCodeFragment.setConfirmationCode(String.valueOf
+ (phoneAuthCredential.getSmsCode()));
+ }
+ signingWithCreds(phoneAuthCredential);
+ }
+ }
+
+ void onCodeSent() {
+ completeLoadingDialog(getString(R.string.code_sent));
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ dismissLoadingDialog();
+ showSubmitCodeFragment();
+ }
+ }, SHORT_DELAY_MILLIS);
+ }
+
+ void onVerificationFailed(@NonNull FirebaseException ex) {
+ VerifyPhoneNumberFragment verifyPhoneNumberFragment = (VerifyPhoneNumberFragment)
+ getSupportFragmentManager().findFragmentByTag(VerifyPhoneNumberFragment.TAG);
+
+ if (verifyPhoneNumberFragment == null) {
+ return;
+ }
+ if (ex instanceof FirebaseAuthException) {
+ FirebaseAuthException firebaseAuthException = (FirebaseAuthException) ex;
+ switch (firebaseAuthException.getErrorCode()) {
+ case ERROR_INVALID_PHONE:
+ verifyPhoneNumberFragment.showError(getString(R.string.invalid_phone_number));
+ dismissLoadingDialog();
+ break;
+ case ERROR_TOO_MANY_REQUESTS:
+ showAlertDialog(getString(R.string.error_too_many_attempts), null);
+ dismissLoadingDialog();
+ break;
+ case ERROR_QUOTA_EXCEEDED:
+ showAlertDialog(getString(R.string.error_quota_exceeded), null);
+ dismissLoadingDialog();
+ break;
+ default:
+ Log.w(PHONE_VERIFICATION_LOG_TAG, ex.getLocalizedMessage());
+ dismissLoadingDialog();
+ showAlertDialog(ex.getLocalizedMessage(), null);
+ }
+ } else {
+ Log.w(PHONE_VERIFICATION_LOG_TAG, ex.getLocalizedMessage());
+ dismissLoadingDialog();
+ showAlertDialog(ex.getLocalizedMessage(), null);
+ }
+ }
+
+
+ private void sendCode(String phoneNumber, boolean forceResend) {
+ mPhoneNumber = phoneNumber;
+ mVerificationState = VerificationState.VERIFICATION_STARTED;
+
+ mActivityHelper.getPhoneAuthProviderInstance().verifyPhoneNumber(phoneNumber,
+ AUTO_RETRIEVAL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS, this, new PhoneAuthProvider
+ .OnVerificationStateChangedCallbacks() {
+ @Override
+ public void onVerificationCompleted(@NonNull PhoneAuthCredential phoneAuthCredential) {
+ if (!mIsDestroyed) {
+ PhoneVerificationActivity.this.onVerificationSuccess(phoneAuthCredential);
+ }
+ }
+
+ @Override
+ public void onVerificationFailed(FirebaseException ex) {
+ if (!mIsDestroyed) {
+ PhoneVerificationActivity.this.onVerificationFailed(ex);
+ }
+ }
+
+ @Override
+ public void onCodeSent(@NonNull String verificationId, @NonNull PhoneAuthProvider
+ .ForceResendingToken forceResendingToken) {
+ mVerificationId = verificationId;
+ mForceResendingToken = forceResendingToken;
+ if (!mIsDestroyed) {
+ PhoneVerificationActivity.this.onCodeSent();
+ }
+ }
+ }, forceResend ? mForceResendingToken : null);
+ }
+
+
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ protected AlertDialog getAlertDialog() {
+ // It is hard to test AlertDialogs currently with robo electric. See:
+ // https://github.com/robolectric/robolectric/issues/1944
+ // We just test that the error was not displayed inline
+ return mAlertDialog;
+ }
+
+ private void showSubmitCodeFragment() {
+ // idempotent function
+ if (getSubmitConfirmationCodeFragment() == null) {
+ SubmitConfirmationCodeFragment f = SubmitConfirmationCodeFragment.newInstance
+ (mActivityHelper.getFlowParams(), mPhoneNumber);
+ FragmentTransaction t = getSupportFragmentManager().beginTransaction().replace(R.id
+ .fragment_verify_phone, f, SubmitConfirmationCodeFragment.TAG).addToBackStack
+ (null);
+
+ if (!isFinishing() && !mIsDestroyed) {
+ t.commitAllowingStateLoss();
+ }
+ }
+ }
+
+ private void finish(FirebaseUser user) {
+ IdpResponse response = new IdpResponse.Builder(PhoneAuthProvider.PROVIDER_ID, null)
+ .setPhoneNumber(user.getPhoneNumber())
+ .build();
+ setResult(ResultCodes.OK, response.toIntent());
+ finish();
+ }
+
+ private void showAlertDialog(@NonNull String s, DialogInterface.OnClickListener
+ onClickListener) {
+ mAlertDialog = new AlertDialog.Builder(this)
+ .setMessage(s)
+ .setPositiveButton(R.string.incorrect_code_dialog_positive_button_text, onClickListener)
+ .show();
+ }
+
+ private void signingWithCreds(@NonNull PhoneAuthCredential phoneAuthCredential) {
+ mActivityHelper.getFirebaseAuth().signInWithCredential(phoneAuthCredential)
+ .addOnSuccessListener(this, new OnSuccessListener() {
+ @Override
+ public void onSuccess(final AuthResult authResult) {
+ mVerificationState = VerificationState.VERIFIED;
+ completeLoadingDialog(getString(R.string.verified));
+
+ // Activity can be recreated before this message is handled
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (!mIsDestroyed) {
+ dismissLoadingDialog();
+ finish(authResult.getUser());
+ }
+ }
+ }, SHORT_DELAY_MILLIS);
+ }
+ }).addOnFailureListener(this, new OnFailureListener() {
+ @Override
+ public void onFailure(@NonNull Exception e) {
+ dismissLoadingDialog();
+ //incorrect confirmation code
+ if (e instanceof FirebaseAuthInvalidCredentialsException) {
+ FirebaseAuthInvalidCredentialsException firebaseAuthInvalidCredentialsException
+ = (FirebaseAuthInvalidCredentialsException) e;
+ switch (firebaseAuthInvalidCredentialsException.getErrorCode()) {
+ case ERROR_INVALID_VERIFICATION:
+ showAlertDialog(getString(R.string.incorrect_code_dialog_body), new
+ DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ SubmitConfirmationCodeFragment f
+ = getSubmitConfirmationCodeFragment();
+ f.setConfirmationCode("");
+ }
+ });
+ break;
+ case ERROR_SESSION_EXPIRED:
+ showAlertDialog(getString(R.string.error_session_expired), new
+ DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ SubmitConfirmationCodeFragment f
+ = getSubmitConfirmationCodeFragment();
+ f.setConfirmationCode("");
+ }
+ });
+ break;
+ default:
+ showAlertDialog(e.getLocalizedMessage(), null);
+ }
+ } else {
+ showAlertDialog(e.getLocalizedMessage(), null);
+ }
+ }
+ });
+ }
+
+ private void completeLoadingDialog(String content) {
+ if (mProgressDialog != null) {
+ mProgressDialog.complete(content);
+ }
+ }
+
+ private void showLoadingDialog(String message) {
+ dismissLoadingDialog();
+
+ if (mProgressDialog == null) {
+ mProgressDialog = new CompletableProgressDialog(this);
+ mProgressDialog.setIndeterminate(true);
+ mProgressDialog.setTitle("");
+ }
+
+ mProgressDialog.setMessage(message);
+ mProgressDialog.show();
+ }
+
+ private void dismissLoadingDialog() {
+ if (mProgressDialog != null) {
+ mProgressDialog.dismiss();
+ mProgressDialog = null;
+ }
+ }
+
+ private SubmitConfirmationCodeFragment getSubmitConfirmationCodeFragment() {
+ return (SubmitConfirmationCodeFragment) getSupportFragmentManager().findFragmentByTag
+ (SubmitConfirmationCodeFragment.TAG);
+ }
+}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/phone/SpacedEditText.java b/auth/src/main/java/com/firebase/ui/auth/ui/phone/SpacedEditText.java
new file mode 100644
index 000000000..998391141
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/phone/SpacedEditText.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2015 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Modifications copyright (C) 2017 Google Inc
+ */
+
+package com.firebase.ui.auth.ui.phone;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.v7.widget.AppCompatEditText;
+import android.text.Editable;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.style.ScaleXSpan;
+import android.util.AttributeSet;
+
+import com.firebase.ui.auth.R;
+
+/**
+ * This element inserts spaces between characters in the edit text and expands the width of the
+ * spaces using spannables.
+ * This is required since Android's letter spacing is not available until API 21.
+ */
+public final class SpacedEditText extends AppCompatEditText {
+ private float proportion;
+ private SpannableStringBuilder originalText;
+
+ public SpacedEditText(Context context) {
+ super(context);
+ }
+
+ public SpacedEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initAttrs(context, attrs);
+ }
+
+ void initAttrs(Context context, AttributeSet attrs) {
+ originalText = new SpannableStringBuilder("");
+ final TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SpacedEditText);
+ //Controls the ScaleXSpan applied on the injected spaces
+ proportion = array.getFloat(R.styleable.SpacedEditText_spacingProportion, 1);
+ array.recycle();
+ }
+
+ @Override
+ public void setText(CharSequence text, BufferType type) {
+ originalText = new SpannableStringBuilder(text);
+ final SpannableStringBuilder spacedOutString = getSpacedOutString(text);
+ super.setText(spacedOutString, BufferType.SPANNABLE);
+ }
+
+ /**
+ * Set the selection after recalculating the index intended by the caller.
+ *
+ * @param index
+ */
+ @Override
+ public void setSelection(int index) {
+ //if the index is the leading edge, there are no spaces before it.
+ //for all other cases, the index is preceeded by index - 1 spaces.
+ int spacesUptoIndex;
+ if (index == 0) {
+ spacesUptoIndex = 0;
+ } else {
+ spacesUptoIndex = index - 1;
+ }
+ final int recalculatedIndex = index + spacesUptoIndex;
+
+ super.setSelection(recalculatedIndex);
+ }
+
+ private SpannableStringBuilder getSpacedOutString(CharSequence text) {
+ final SpannableStringBuilder builder = new SpannableStringBuilder();
+ final int textLength = text.length();
+ int lastSpaceIndex = -1;
+
+ //Insert a space in front of all characters upto the last character
+ //Scale the space without scaling the character to preserve font appearance
+ for (int i = 0; i < textLength - 1; i++) {
+ builder.append(text.charAt(i));
+ builder.append(" ");
+ lastSpaceIndex += 2;
+ builder.setSpan(new ScaleXSpan(proportion), lastSpaceIndex, lastSpaceIndex + 1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
+ //Append the last character
+ if (textLength != 0) builder.append(text.charAt(textLength - 1));
+
+ return builder;
+ }
+
+ public Editable getUnspacedText() {
+ return this.originalText;
+ }
+}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/phone/SubmitConfirmationCodeFragment.java b/auth/src/main/java/com/firebase/ui/auth/ui/phone/SubmitConfirmationCodeFragment.java
new file mode 100644
index 000000000..dead802e1
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/phone/SubmitConfirmationCodeFragment.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.firebase.ui.auth.ui.phone;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.app.FragmentActivity;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.firebase.ui.auth.R;
+import com.firebase.ui.auth.ui.ExtraConstants;
+import com.firebase.ui.auth.ui.FlowParameters;
+import com.firebase.ui.auth.ui.FragmentBase;
+import com.firebase.ui.auth.ui.TermsTextView;
+
+/**
+ * Display confirmation code to verify phone numbers input in {{@link VerifyPhoneNumberFragment}}
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class SubmitConfirmationCodeFragment extends FragmentBase {
+
+ public static final String TAG = "SubmitConfirmationCodeFragment";
+
+ private static final long RESEND_WAIT_MILLIS = 15000;
+ private static final String EXTRA_MILLIS_UNTIL_FINISHED = "EXTRA_MILLIS_UNTIL_FINISHED";
+
+ private TextView mEditPhoneTextView;
+ private TextView mResendCodeTextView;
+ private TextView mCountDownTextView;
+ private SpacedEditText mConfirmationCodeEditText;
+ private Button mSubmitConfirmationButton;
+ private CustomCountDownTimer mCountdownTimer;
+ private PhoneVerificationActivity mVerifier;
+ private TermsTextView mAgreementText;
+ private long mMillisUntilFinished;
+
+ public static SubmitConfirmationCodeFragment newInstance(FlowParameters flowParameters,
+ String phoneNumber) {
+ SubmitConfirmationCodeFragment fragment = new SubmitConfirmationCodeFragment();
+
+ Bundle args = new Bundle();
+ args.putParcelable(ExtraConstants.EXTRA_FLOW_PARAMS, flowParameters);
+ args.putString(ExtraConstants.EXTRA_PHONE, phoneNumber);
+
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable
+ Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.confirmation_code_layout, container, false);
+ FragmentActivity parentActivity = getActivity();
+
+ mEditPhoneTextView = (TextView) v.findViewById(R.id.edit_phone_number);
+ mCountDownTextView = (TextView) v.findViewById(R.id.ticker);
+ mResendCodeTextView = (TextView) v.findViewById(R.id.resend_code);
+ mConfirmationCodeEditText = (SpacedEditText) v.findViewById(R.id.confirmation_code);
+ mSubmitConfirmationButton = (Button) v.findViewById(R.id.submit_confirmation_code);
+ mAgreementText = (TermsTextView) v.findViewById(R.id.create_account_tos);
+
+ final String phoneNumber = getArguments().getString(ExtraConstants.EXTRA_PHONE);
+
+ parentActivity.setTitle(getString(R.string.verify_your_phone_title));
+ setupConfirmationCodeEditText();
+ setupEditPhoneNumberTextView(phoneNumber);
+ setupCountDown(RESEND_WAIT_MILLIS);
+ setupSubmitConfirmationCodeButton();
+ setupResendConfirmationCodeTextView(phoneNumber);
+ setUpTermsOfService();
+ return v;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ mConfirmationCodeEditText.requestFocus();
+ InputMethodManager imgr = (InputMethodManager) getActivity().getSystemService(
+ Context.INPUT_METHOD_SERVICE);
+ imgr.showSoftInput(mConfirmationCodeEditText, 0);
+ }
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ if (savedInstanceState != null) {
+ Long t = savedInstanceState.getLong(EXTRA_MILLIS_UNTIL_FINISHED);
+ if (t != null) {
+ mCountdownTimer.update(t);
+ }
+ }
+
+ if (!(getActivity() instanceof PhoneVerificationActivity)) {
+ throw new IllegalStateException("Activity must implement PhoneVerificationHandler");
+ }
+ mVerifier = (PhoneVerificationActivity) getActivity();
+ }
+
+ @Override
+ public void onDestroy() {
+ cancelTimer();
+ super.onDestroy();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putLong(EXTRA_MILLIS_UNTIL_FINISHED, mMillisUntilFinished);
+ }
+
+ private void setTimer(long millisUntilFinished) {
+ mCountDownTextView.setText(String.format(getString(R.string.resend_code_in),
+ timeRoundedToSeconds(millisUntilFinished)));
+ }
+
+ private void setupResendConfirmationCodeTextView(final String phoneNumber) {
+ mResendCodeTextView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mVerifier.verifyPhoneNumber(phoneNumber, true);
+ mResendCodeTextView.setVisibility(View.GONE);
+ mCountDownTextView.setVisibility(View.VISIBLE);
+ mCountDownTextView.setText(String.format(getString(R.string.resend_code_in),
+ RESEND_WAIT_MILLIS / 1000));
+ mCountdownTimer.renew();
+ }
+ });
+ }
+
+ private void setupCountDown(long startTimeMillis) {
+ //set the timer view
+ setTimer(startTimeMillis / 1000);
+
+ //create a countdown
+ mCountdownTimer = createCountDownTimer(mCountDownTextView, mResendCodeTextView, this,
+ startTimeMillis);
+
+ //start the countdown
+ startTimer();
+ }
+
+ private void setupSubmitConfirmationCodeButton() {
+ mSubmitConfirmationButton.setEnabled(false);
+
+ mSubmitConfirmationButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final String confirmationCode = mConfirmationCodeEditText.getUnspacedText()
+ .toString();
+ mVerifier.submitConfirmationCode(confirmationCode);
+ }
+ });
+ }
+
+ private void setupEditPhoneNumberTextView(@Nullable String phoneNumber) {
+ mEditPhoneTextView.setText(TextUtils.isEmpty(phoneNumber) ? "" : phoneNumber);
+ mEditPhoneTextView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (getFragmentManager().getBackStackEntryCount() > 0) {
+ getFragmentManager().popBackStack();
+ }
+ }
+ });
+ }
+
+ private void setupConfirmationCodeEditText() {
+ mConfirmationCodeEditText.setText("------");
+ BucketedTextChangeListener listener = createBucketedTextChangeListener();
+ mConfirmationCodeEditText.addTextChangedListener(listener);
+ }
+
+ @NonNull
+ private View.OnFocusChangeListener createOnFocusChangeListener() {
+ return new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (hasFocus) {
+ mConfirmationCodeEditText.setSelection(0);
+ }
+ }
+ };
+ }
+
+ private BucketedTextChangeListener createBucketedTextChangeListener() {
+ return new BucketedTextChangeListener(this.mConfirmationCodeEditText, 6, "-",
+ createBucketOnEditCallback(mSubmitConfirmationButton));
+ }
+
+ private void startTimer() {
+ if (mCountdownTimer != null) {
+ mCountdownTimer.start();
+ }
+ }
+
+ private void cancelTimer() {
+ if (mCountdownTimer != null) {
+ mCountdownTimer.cancel();
+ }
+ }
+
+ private void setUpTermsOfService() {
+ mAgreementText.showTerms(mHelper.getFlowParams(), R.string.continue_phone_login);
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ CustomCountDownTimer getmCountdownTimer() {
+ return mCountdownTimer;
+ }
+
+ private int timeRoundedToSeconds(double millis) {
+ return (int) Math.ceil(millis / 1000);
+ }
+
+ private CustomCountDownTimer createCountDownTimer(final TextView timerText, final TextView
+ resendCode, final SubmitConfirmationCodeFragment fragment, final long startTimeMillis) {
+ return new CustomCountDownTimer(startTimeMillis, 500) {
+ SubmitConfirmationCodeFragment mSubmitConfirmationCodeFragment = fragment;
+
+ public void onTick(long millisUntilFinished) {
+ mMillisUntilFinished = millisUntilFinished;
+ mSubmitConfirmationCodeFragment.setTimer(millisUntilFinished);
+ }
+
+ public void onFinish() {
+ timerText.setText("");
+ timerText.setVisibility(View.GONE);
+ resendCode.setVisibility(View.VISIBLE);
+ }
+ };
+ }
+
+ private BucketedTextChangeListener.ContentChangeCallback createBucketOnEditCallback(final
+ Button button) {
+ return new BucketedTextChangeListener.ContentChangeCallback() {
+ @Override
+ public void whileComplete() {
+ button.setEnabled(true);
+ }
+
+ @Override
+ public void whileIncomplete() {
+ button.setEnabled(false);
+ }
+ };
+ }
+
+ void setConfirmationCode(String code) {
+ mConfirmationCodeEditText.setText(code);
+ }
+}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/phone/VerifyPhoneNumberFragment.java b/auth/src/main/java/com/firebase/ui/auth/ui/phone/VerifyPhoneNumberFragment.java
new file mode 100644
index 000000000..e2a3b0253
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/phone/VerifyPhoneNumberFragment.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.firebase.ui.auth.ui.phone;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v4.app.FragmentActivity;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.firebase.ui.auth.R;
+import com.firebase.ui.auth.ui.ExtraConstants;
+import com.firebase.ui.auth.ui.FlowParameters;
+import com.firebase.ui.auth.ui.FragmentBase;
+import com.firebase.ui.auth.util.GoogleApiHelper;
+import com.google.android.gms.auth.api.Auth;
+import com.google.android.gms.auth.api.credentials.Credential;
+import com.google.android.gms.auth.api.credentials.CredentialPickerConfig;
+import com.google.android.gms.auth.api.credentials.HintRequest;
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.api.GoogleApiClient;
+
+import java.util.Locale;
+
+/**
+ * Displays country selector and phone number input form for users
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class VerifyPhoneNumberFragment extends FragmentBase implements View.OnClickListener {
+ public static final String TAG = "VerifyPhoneFragment";
+
+ private CountryListSpinner countryListSpinner;
+ private EditText mPhoneEditText;
+ private TextView errorEditText;
+ private Button sendCodeButton;
+ private PhoneVerificationActivity mVerifier;
+ private TextView mSmsTermsText;
+
+ private static final int RC_PHONE_HINT = 22;
+
+ public static VerifyPhoneNumberFragment newInstance(FlowParameters flowParameters,
+ String phone) {
+ VerifyPhoneNumberFragment fragment = new VerifyPhoneNumberFragment();
+
+ Bundle args = new Bundle();
+ args.putParcelable(ExtraConstants.EXTRA_FLOW_PARAMS, flowParameters);
+ args.putString(ExtraConstants.EXTRA_PHONE, phone);
+
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable
+ Bundle savedInstanceState) {
+
+ View v = inflater.inflate(R.layout.phone_layout, container, false);
+
+ countryListSpinner = (CountryListSpinner) v.findViewById(R.id.country_list);
+ mPhoneEditText = (EditText) v.findViewById(R.id.phone_number);
+ errorEditText = (TextView) v.findViewById(R.id.phone_number_error);
+ sendCodeButton = (Button) v.findViewById(R.id.send_code);
+ mSmsTermsText = (TextView) v.findViewById(R.id.send_sms_tos);
+
+ FragmentActivity parentActivity = getActivity();
+ parentActivity.setTitle(getString(R.string.verify_phone_number_title));
+ setUpCountrySpinner();
+ setupSendCodeButton();
+ setupTerms();
+
+ return v;
+ }
+
+ private void setupTerms() {
+ final String verifyPhoneButtonText = getString(R.string.verify_phone_number);
+ final String terms = getString(R.string.sms_terms_of_service, verifyPhoneButtonText);
+ mSmsTermsText.setText(terms);
+ }
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ // Set listener
+ if (!(getActivity() instanceof PhoneVerificationActivity)) {
+ throw new IllegalStateException("Activity must implement PhoneVerificationHandler");
+ }
+ mVerifier = (PhoneVerificationActivity) getActivity();
+
+ if (savedInstanceState != null) {
+ return;
+ }
+
+ // Check for phone
+ // It is assumed that the phone number that are being wired in via Credential Selector
+ // are e164 since we store it.
+ String phone = getArguments().getString(ExtraConstants.EXTRA_PHONE);
+ if (!TextUtils.isEmpty(phone)) {
+ // Use phone passed in
+ PhoneNumber phoneNumber = PhoneNumberUtils.getPhoneNumber(phone);
+ setPhoneNumber(phoneNumber);
+ setCountryCode(phoneNumber);
+ } else if (mHelper.getFlowParams().enableHints) {
+ // Try SmartLock phone autocomplete hint
+ showPhoneAutoCompleteHint();
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == RC_PHONE_HINT) {
+ if (data != null) {
+ Credential cred = data.getParcelableExtra(Credential.EXTRA_KEY);
+ if (cred != null) {
+ // Hint selector does not always return phone numbers in e164 format.
+ // To accommodate either case, we normalize to e164 with best effort
+ final String unformattedPhone = cred.getId();
+ final String formattedPhone =
+ PhoneNumberUtils
+ .formatPhoneNumberUsingCurrentCountry(unformattedPhone,
+ getContext());
+ if (formattedPhone == null) {
+ Log.e(TAG, "Unable to normalize phone number from hint selector:"
+ + unformattedPhone);
+ return;
+ }
+ final PhoneNumber phoneNumberObj =
+ PhoneNumberUtils.getPhoneNumber(formattedPhone);
+ setPhoneNumber(phoneNumberObj);
+ setCountryCode(phoneNumberObj);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ String phoneNumber = getPseudoValidPhoneNumber();
+ if (phoneNumber == null) {
+ errorEditText.setText(R.string.invalid_phone_number);
+ } else {
+ mVerifier.verifyPhoneNumber(phoneNumber, false);
+ }
+ }
+
+ @Nullable
+ String getPseudoValidPhoneNumber() {
+ final CountryInfo countryInfo = (CountryInfo) countryListSpinner.getTag();
+ final String everythingElse = mPhoneEditText.getText().toString();
+
+ if (TextUtils.isEmpty(everythingElse)) {
+ return null;
+ }
+
+ return PhoneNumberUtils.formatPhoneNumber(everythingElse, countryInfo);
+ }
+
+ private void setUpCountrySpinner() {
+ //clear error when spinner is clicked on
+ countryListSpinner.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ errorEditText.setText("");
+ }
+ });
+ }
+
+ private void setupSendCodeButton() {
+ sendCodeButton.setOnClickListener(this);
+ }
+
+ private void showPhoneAutoCompleteHint() {
+ try {
+ mHelper.startIntentSenderForResult(getPhoneHintIntent().getIntentSender(),
+ RC_PHONE_HINT);
+ } catch (IntentSender.SendIntentException e) {
+ Log.e(TAG, "Unable to start hint intent", e);
+ }
+ }
+
+ private PendingIntent getPhoneHintIntent() {
+ GoogleApiClient client = new GoogleApiClient.Builder(getContext()).addApi(Auth
+ .CREDENTIALS_API).enableAutoManage(getActivity(), GoogleApiHelper
+ .getSafeAutoManageId(), new GoogleApiClient.OnConnectionFailedListener() {
+ @Override
+ public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
+ Log.e(TAG, "Client connection failed: " + connectionResult.getErrorMessage());
+ }
+ }).build();
+
+
+ HintRequest hintRequest = new HintRequest.Builder().setHintPickerConfig(new
+ CredentialPickerConfig.Builder().setShowCancelButton(true).build())
+ .setPhoneNumberIdentifierSupported(true).setEmailAddressIdentifierSupported
+ (false).build();
+
+ return Auth.CredentialsApi.getHintPickerIntent(client, hintRequest);
+ }
+
+ private void setPhoneNumber(PhoneNumber phoneNumber) {
+ if (PhoneNumber.isValid(phoneNumber)) {
+ mPhoneEditText.setText(phoneNumber.getPhoneNumber());
+ mPhoneEditText.setSelection(phoneNumber.getPhoneNumber().length());
+ }
+ }
+
+ private void setCountryCode(PhoneNumber phoneNumber) {
+ if (PhoneNumber.isCountryValid(phoneNumber)) {
+ countryListSpinner.setSelectedForCountry(new Locale("", phoneNumber.getCountryIso()),
+ phoneNumber.getCountryCode());
+ }
+ }
+
+ void showError(String e) {
+ errorEditText.setText(e);
+ }
+}
diff --git a/auth/src/main/java/com/firebase/ui/auth/util/CredentialTaskApi.java b/auth/src/main/java/com/firebase/ui/auth/util/CredentialTaskApi.java
deleted file mode 100644
index dd60131e5..000000000
--- a/auth/src/main/java/com/firebase/ui/auth/util/CredentialTaskApi.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.firebase.ui.auth.util;
-
-import com.google.android.gms.auth.api.credentials.Credential;
-import com.google.android.gms.common.api.Status;
-import com.google.android.gms.tasks.Task;
-
-public interface CredentialTaskApi {
- Task disableAutoSignIn();
-
- Task delete(Credential credential);
-}
diff --git a/auth/src/main/java/com/firebase/ui/auth/util/CredentialsApiHelper.java b/auth/src/main/java/com/firebase/ui/auth/util/CredentialsApiHelper.java
deleted file mode 100644
index 17a1bb069..000000000
--- a/auth/src/main/java/com/firebase/ui/auth/util/CredentialsApiHelper.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.firebase.ui.auth.util;
-
-import android.app.Activity;
-import android.support.annotation.NonNull;
-
-import com.google.android.gms.auth.api.Auth;
-import com.google.android.gms.auth.api.credentials.Credential;
-import com.google.android.gms.common.api.GoogleApiClient;
-import com.google.android.gms.common.api.Result;
-import com.google.android.gms.common.api.ResultCallback;
-import com.google.android.gms.common.api.Status;
-import com.google.android.gms.tasks.Continuation;
-import com.google.android.gms.tasks.Task;
-import com.google.android.gms.tasks.TaskCompletionSource;
-
-/**
- * A {@link com.google.android.gms.tasks.Task Task} based wrapper for the Smart Lock for Passwords
- * API.
- */
-@Deprecated
-public class CredentialsApiHelper implements CredentialTaskApi {
- @NonNull
- private final GoogleApiClientTaskHelper mClientHelper;
-
- private CredentialsApiHelper(GoogleApiClientTaskHelper gacHelper) {
- mClientHelper = gacHelper;
- }
-
- public static CredentialsApiHelper getInstance(Activity activity) {
- // Get a task helper with the Credentials Api
- GoogleApiClientTaskHelper taskHelper = GoogleApiClientTaskHelper.getInstance(activity);
- taskHelper.getBuilder().addApi(Auth.CREDENTIALS_API);
-
- return getInstance(taskHelper);
- }
-
- public static CredentialsApiHelper getInstance(GoogleApiClientTaskHelper taskHelper) {
- return new CredentialsApiHelper(taskHelper);
- }
-
- @Override
- public Task delete(final Credential credential) {
- return mClientHelper.getConnectedGoogleApiClient().continueWithTask(
- new ExceptionForwardingContinuation() {
- @Override
- protected void process(
- GoogleApiClient client,
- TaskCompletionSource source) {
- Auth.CredentialsApi.delete(client, credential)
- .setResultCallback(new TaskResultCaptor<>(source));
- }
- });
- }
-
- @Override
- public Task disableAutoSignIn() {
- return mClientHelper.getConnectedGoogleApiClient().continueWithTask(
- new ExceptionForwardingContinuation() {
- @Override
- protected void process(
- final GoogleApiClient client,
- final TaskCompletionSource source) {
- Auth.CredentialsApi.disableAutoSignIn(client)
- .setResultCallback(new ResultCallback() {
- @Override
- public void onResult(@NonNull Status status) {
- source.setResult(status);
- }
- });
- }
- });
- }
-
- private abstract static class ExceptionForwardingContinuation
- implements Continuation> {
-
- @Override
- public final Task then(@NonNull Task task) throws Exception {
- TaskCompletionSource source = new TaskCompletionSource<>();
- // calling task.getResult() will implicitly re-throw the exception of the original
- // task, which will be returned as the result for the output task. Similarly,
- // if process() throws an exception, this will be turned into the task result.
- process(task.getResult(), source);
- return source.getTask();
- }
-
- protected abstract void process(TIn in, TaskCompletionSource result);
- }
-
- private static final class TaskResultCaptor implements ResultCallback {
-
- private TaskCompletionSource mSource;
-
- public TaskResultCaptor(TaskCompletionSource source) {
- mSource = source;
- }
-
- @Override
- public void onResult(@NonNull R result) {
- mSource.setResult(result);
- }
- }
-}
diff --git a/auth/src/main/java/com/firebase/ui/auth/util/GoogleApiClientTaskHelper.java b/auth/src/main/java/com/firebase/ui/auth/util/GoogleApiClientTaskHelper.java
deleted file mode 100644
index fc27ee528..000000000
--- a/auth/src/main/java/com/firebase/ui/auth/util/GoogleApiClientTaskHelper.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.firebase.ui.auth.util;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import com.google.android.gms.common.ConnectionResult;
-import com.google.android.gms.common.api.GoogleApiClient;
-import com.google.android.gms.tasks.Task;
-import com.google.android.gms.tasks.TaskCompletionSource;
-
-import java.io.IOException;
-import java.util.IdentityHashMap;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * A {@link com.google.android.gms.tasks.Task Task} based wrapper for acquiring a connected
- * {@link com.google.android.gms.common.api.GoogleApiClient} instance, which manages a single
- * instance per activity.
- */
-@Deprecated
-public class GoogleApiClientTaskHelper {
-
- private static final IdentityHashMap INSTANCES
- = new IdentityHashMap<>();
-
- @NonNull
- private final Activity mActivity;
-
- @NonNull
- private final AtomicReference mClientRef;
-
- @NonNull
- private final AtomicReference> mConnectTaskRef;
-
- @NonNull
- private final GoogleApiClient.Builder mBuilder;
-
- private GoogleApiClientTaskHelper(@NonNull Activity activity) {
- if (activity == null) {
- throw new IllegalArgumentException("activity must not be null");
- }
-
- mActivity = activity;
- mBuilder = new GoogleApiClient.Builder(mActivity);
-
- mClientRef = new AtomicReference<>();
- mConnectTaskRef = new AtomicReference<>();
-
- // ensure that when the activity is stopped, we release the reference to the
- // GoogleApiClient completion task, so that it can be garbage collected
- activity.getApplication().registerActivityLifecycleCallbacks(new GacLifecycleCallbacks());
- }
-
- /**
- * Retrieve the instance for the specified activity, reusing an instance if it exists,
- * otherwise creates a new one.
- */
- public static GoogleApiClientTaskHelper getInstance(Activity activity) {
- GoogleApiClientTaskHelper helper;
- synchronized (INSTANCES) {
- helper = INSTANCES.get(activity);
- if (helper == null) {
- helper = new GoogleApiClientTaskHelper(activity);
- INSTANCES.put(activity, helper);
- }
- }
- return helper;
- }
-
- private static void clearInstance(Activity activity) {
- synchronized (INSTANCES) {
- INSTANCES.remove(activity);
- }
- }
-
- public Task getConnectedGoogleApiClient() {
- final TaskCompletionSource source = new TaskCompletionSource<>();
- if (!mConnectTaskRef.compareAndSet(null, source)) {
- // mConnectTaskRef Task was not null, return Task
- return mConnectTaskRef.get().getTask();
- }
-
- final GoogleApiClient client = mBuilder
- .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
- @Override
- public void onConnected(@Nullable Bundle bundle) {
- source.setResult(mClientRef.get());
- if (mClientRef.get() != null) {
- mClientRef.get().unregisterConnectionCallbacks(this);
- }
- }
-
- @Override
- public void onConnectionSuspended(int i) {}
- })
- .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
- @Override
- public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
- source.setException(new IOException(
- "Failed to connect GoogleApiClient: "
- + connectionResult.getErrorMessage()));
- if (mClientRef.get() != null) {
- mClientRef.get().unregisterConnectionFailedListener(this);
- }
- }
- }).build();
-
- mClientRef.set(client);
- client.connect();
-
- return source.getTask();
- }
-
- @NonNull
- public GoogleApiClient.Builder getBuilder() {
- return mBuilder;
- }
-
- private final class GacLifecycleCallbacks extends AbstractActivityLifecycleCallbacks {
-
- @Override
- public void onActivityStarted(Activity activity) {
- if (mActivity.equals(activity)) {
- GoogleApiClient client = mClientRef.get();
- if (client != null) {
- client.connect();
- }
- }
- }
-
- @Override
- public void onActivityStopped(Activity activity) {
- if (mActivity.equals(activity)) {
- GoogleApiClient client = mClientRef.get();
- if (client != null) {
- client.disconnect();
- }
- }
- }
-
- @Override
- public void onActivityDestroyed(Activity activity) {
- activity.getApplication().unregisterActivityLifecycleCallbacks(this);
- clearInstance(activity);
- }
- }
-}
diff --git a/auth/src/main/java/com/firebase/ui/auth/util/GoogleApiHelper.java b/auth/src/main/java/com/firebase/ui/auth/util/GoogleApiHelper.java
index 61882c895..c64da3d25 100644
--- a/auth/src/main/java/com/firebase/ui/auth/util/GoogleApiHelper.java
+++ b/auth/src/main/java/com/firebase/ui/auth/util/GoogleApiHelper.java
@@ -20,7 +20,10 @@
/**
* A {@link Task} based wrapper to get a connect {@link GoogleApiClient}.
*/
-public abstract class GoogleApiHelper implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
+public abstract class GoogleApiHelper implements
+ GoogleApiClient.ConnectionCallbacks,
+ GoogleApiClient.OnConnectionFailedListener {
+
private static final AtomicInteger SAFE_ID = new AtomicInteger(10);
protected GoogleApiClient mClient;
diff --git a/auth/src/main/java/com/firebase/ui/auth/util/GoogleSignInHelper.java b/auth/src/main/java/com/firebase/ui/auth/util/GoogleSignInHelper.java
index 8ae81c596..53e9e1d99 100644
--- a/auth/src/main/java/com/firebase/ui/auth/util/GoogleSignInHelper.java
+++ b/auth/src/main/java/com/firebase/ui/auth/util/GoogleSignInHelper.java
@@ -12,7 +12,7 @@
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.TaskCompletionSource;
-public class GoogleSignInHelper extends GoogleApiHelper implements CredentialTaskApi {
+public class GoogleSignInHelper extends GoogleApiHelper {
protected GoogleSignInHelper(FragmentActivity activity, GoogleApiClient.Builder builder) {
super(activity, builder);
}
@@ -38,7 +38,6 @@ public void onSuccess(Bundle status) {
return statusTask.getTask();
}
- @Override
public Task disableAutoSignIn() {
final TaskCompletionSource statusTask = new TaskCompletionSource<>();
getConnectedApiTask().addOnCompleteListener(new ExceptionForwarder<>(
@@ -53,7 +52,6 @@ public void onSuccess(Bundle status) {
return statusTask.getTask();
}
- @Override
public Task delete(final Credential credential) {
final TaskCompletionSource statusTask = new TaskCompletionSource<>();
getConnectedApiTask().addOnCompleteListener(new ExceptionForwarder<>(
diff --git a/auth/src/main/java/com/firebase/ui/auth/util/signincontainer/IdpSignInContainer.java b/auth/src/main/java/com/firebase/ui/auth/util/signincontainer/IdpSignInContainer.java
index c1f03204d..b16e17451 100644
--- a/auth/src/main/java/com/firebase/ui/auth/util/signincontainer/IdpSignInContainer.java
+++ b/auth/src/main/java/com/firebase/ui/auth/util/signincontainer/IdpSignInContainer.java
@@ -27,7 +27,7 @@
import com.firebase.ui.auth.ErrorCodes;
import com.firebase.ui.auth.IdpResponse;
import com.firebase.ui.auth.ResultCodes;
-import com.firebase.ui.auth.provider.AuthCredentialHelper;
+import com.firebase.ui.auth.provider.ProviderUtils;
import com.firebase.ui.auth.provider.FacebookProvider;
import com.firebase.ui.auth.provider.GoogleProvider;
import com.firebase.ui.auth.provider.IdpProvider;
@@ -109,8 +109,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) {
providerConfig,
user.getEmail());
} else if (provider.equalsIgnoreCase(FacebookAuthProvider.PROVIDER_ID)) {
- mIdpProvider = new FacebookProvider(
- getContext(), providerConfig, mHelper.getFlowParams().themeId);
+ mIdpProvider = new FacebookProvider(providerConfig, mHelper.getFlowParams().themeId);
} else if (provider.equalsIgnoreCase(TwitterAuthProvider.PROVIDER_ID)) {
mIdpProvider = new TwitterProvider(getContext());
}
@@ -130,11 +129,12 @@ public void onSaveInstanceState(Bundle outState) {
@Override
public void onSuccess(final IdpResponse response) {
- AuthCredential credential = AuthCredentialHelper.getAuthCredential(response);
+ AuthCredential credential = ProviderUtils.getAuthCredential(response);
mHelper.getFirebaseAuth()
.signInWithCredential(credential)
.addOnFailureListener(
- new TaskFailureLogger(TAG, "Failure authenticating with credential " + credential.getProvider()))
+ new TaskFailureLogger(TAG, "Failure authenticating with credential " +
+ credential.getProvider()))
.addOnCompleteListener(new CredentialSignInHandler(
getActivity(),
mHelper,
diff --git a/auth/src/main/java/com/firebase/ui/auth/util/signincontainer/SaveSmartLock.java b/auth/src/main/java/com/firebase/ui/auth/util/signincontainer/SaveSmartLock.java
index 48c984038..4a5a09539 100644
--- a/auth/src/main/java/com/firebase/ui/auth/util/signincontainer/SaveSmartLock.java
+++ b/auth/src/main/java/com/firebase/ui/auth/util/signincontainer/SaveSmartLock.java
@@ -15,6 +15,7 @@
package com.firebase.ui.auth.util.signincontainer;
import android.app.PendingIntent;
+import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.net.Uri;
@@ -49,6 +50,8 @@ public class SaveSmartLock extends SmartLockBase {
private static final int RC_SAVE = 100;
private static final int RC_UPDATE_SERVICE = 28;
+ private Context mAppContext;
+
private String mName;
private String mEmail;
private String mPassword;
@@ -77,6 +80,12 @@ public static SaveSmartLock getInstance(FragmentActivity activity, FlowParameter
return result;
}
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mAppContext = context.getApplicationContext();
+ }
+
@Override
public void onConnected(Bundle bundle) {
if (TextUtils.isEmpty(mEmail)) {
@@ -145,7 +154,7 @@ public void onResult(@NonNull Status status) {
finish();
}
} else {
- Log.w(TAG, status.getStatusMessage());
+ Log.w(TAG, "Status message:\n" + status.getStatusMessage());
finish();
}
}
@@ -175,7 +184,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
}
private void finish() {
- finish(ResultCodes.OK, IdpResponse.getIntent(mResponse));
+ finish(ResultCodes.OK, mResponse.toIntent());
}
/**
@@ -194,7 +203,7 @@ public void saveCredentialsOrFinish(FirebaseUser firebaseUser,
@Nullable IdpResponse response) {
mResponse = response;
- if (!mHelper.getFlowParams().smartLockEnabled) {
+ if (!mHelper.getFlowParams().enableCredentials) {
finish();
return;
}
@@ -205,7 +214,7 @@ public void saveCredentialsOrFinish(FirebaseUser firebaseUser,
mProfilePictureUri = firebaseUser.getPhotoUrl() != null ? firebaseUser.getPhotoUrl()
.toString() : null;
- mGoogleApiClient = new Builder(getContext().getApplicationContext())
+ mGoogleApiClient = new Builder(mAppContext)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Auth.CREDENTIALS_API)
diff --git a/auth/src/main/java/com/firebase/ui/auth/util/signincontainer/SignInDelegate.java b/auth/src/main/java/com/firebase/ui/auth/util/signincontainer/SignInDelegate.java
index df6256f5d..15ef6f22f 100644
--- a/auth/src/main/java/com/firebase/ui/auth/util/signincontainer/SignInDelegate.java
+++ b/auth/src/main/java/com/firebase/ui/auth/util/signincontainer/SignInDelegate.java
@@ -13,6 +13,7 @@
import android.util.Log;
import com.firebase.ui.auth.AuthUI;
+import com.firebase.ui.auth.AuthUI.IdpConfig;
import com.firebase.ui.auth.IdpResponse;
import com.firebase.ui.auth.R;
import com.firebase.ui.auth.ResultCodes;
@@ -23,6 +24,7 @@
import com.firebase.ui.auth.ui.User;
import com.firebase.ui.auth.ui.email.RegisterEmailActivity;
import com.firebase.ui.auth.ui.idp.AuthMethodPickerActivity;
+import com.firebase.ui.auth.ui.phone.PhoneVerificationActivity;
import com.firebase.ui.auth.util.GoogleApiHelper;
import com.firebase.ui.auth.util.GoogleSignInHelper;
import com.google.android.gms.auth.api.Auth;
@@ -42,10 +44,12 @@
import com.google.firebase.auth.FacebookAuthProvider;
import com.google.firebase.auth.FirebaseAuthInvalidUserException;
import com.google.firebase.auth.GoogleAuthProvider;
+import com.google.firebase.auth.PhoneAuthProvider;
import com.google.firebase.auth.TwitterAuthProvider;
-
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Attempts to acquire a credential from Smart Lock for Passwords to sign in
@@ -62,6 +66,7 @@ public class SignInDelegate extends SmartLockBase {
private static final int RC_IDP_SIGNIN = 3;
private static final int RC_AUTH_METHOD_PICKER = 4;
private static final int RC_EMAIL_FLOW = 5;
+ private static final int RC_PHONE_FLOW = 6;
private Credential mCredential;
@@ -92,7 +97,8 @@ public void onCreate(Bundle savedInstance) {
return;
}
- if (mHelper.getFlowParams().smartLockEnabled) {
+ FlowParameters flowParams = mHelper.getFlowParams();
+ if (flowParams.enableCredentials) {
mHelper.showLoadingDialog(R.string.progress_dialog_loading);
mGoogleApiClient = new GoogleApiClient.Builder(getContext().getApplicationContext())
@@ -129,21 +135,20 @@ public void onResult(@NonNull CredentialRequestResult result) {
// Auto sign-in success
handleCredential(result.getCredential());
return;
- } else if (status.hasResolution()) {
- try {
- if (status.getStatusCode() == CommonStatusCodes.RESOLUTION_REQUIRED) {
- mHelper.startIntentSenderForResult(
- status.getResolution().getIntentSender(),
- RC_CREDENTIALS_READ);
- return;
- } else if (!getSupportedAccountTypes().isEmpty()) {
- mHelper.startIntentSenderForResult(
- status.getResolution().getIntentSender(),
- RC_CREDENTIALS_READ);
- return;
+ } else {
+ if (status.hasResolution()) {
+ try {
+ if (status.getStatusCode() == CommonStatusCodes.RESOLUTION_REQUIRED) {
+ mHelper.startIntentSenderForResult(
+ status.getResolution().getIntentSender(),
+ RC_CREDENTIALS_READ);
+ return;
+ }
+ } catch (IntentSender.SendIntentException e) {
+ Log.e(TAG, "Failed to send Credentials intent.", e);
}
- } catch (IntentSender.SendIntentException e) {
- Log.e(TAG, "Failed to send Credentials intent.", e);
+ } else {
+ Log.e(TAG, "Status message:\n" + status.getStatusMessage());
}
}
startAuthMethodChoice();
@@ -184,7 +189,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
private List getSupportedAccountTypes() {
List accounts = new ArrayList<>();
for (AuthUI.IdpConfig idpConfig : mHelper.getFlowParams().providerInfo) {
- String providerId = idpConfig.getProviderId();
+ @AuthUI.SupportedProvider String providerId = idpConfig.getProviderId();
if (providerId.equals(GoogleAuthProvider.PROVIDER_ID)
|| providerId.equals(FacebookAuthProvider.PROVIDER_ID)
|| providerId.equals(TwitterAuthProvider.PROVIDER_ID)) {
@@ -231,36 +236,51 @@ private void handleCredential(Credential credential) {
}
private void startAuthMethodChoice() {
- List idpConfigs = mHelper.getFlowParams().providerInfo;
+ FlowParameters flowParams = mHelper.getFlowParams();
+ List idpConfigs = flowParams.providerInfo;
+ Map providerIdToConfig = new HashMap<>();
+ for (IdpConfig providerConfig : idpConfigs) {
+ providerIdToConfig.put(providerConfig.getProviderId(), providerConfig);
+ }
- // If the only provider is Email, immediately launch the email flow. Otherwise, launch
- // the auth method picker screen.
- if (idpConfigs.size() == 1) {
- if (idpConfigs.get(0).getProviderId().equals(EmailAuthProvider.PROVIDER_ID)) {
+ List visibleProviders = idpConfigs;
+
+ // If there is only one provider selected, launch the flow directly
+ if (visibleProviders.size() == 1) {
+ String firstProvider = visibleProviders.get(0).getProviderId();
+ if (firstProvider.equals(EmailAuthProvider.PROVIDER_ID)) {
+ // Go directly to email flow
startActivityForResult(
- RegisterEmailActivity.createIntent(getContext(), mHelper.getFlowParams()),
+ RegisterEmailActivity.createIntent(getContext(), flowParams),
RC_EMAIL_FLOW);
+ } else if (firstProvider.equals(PhoneAuthProvider.PROVIDER_ID)) {
+ // Go directly to phone flow
+ startActivityForResult(
+ PhoneVerificationActivity.createIntent(getContext(), flowParams, null),
+ RC_PHONE_FLOW);
} else {
- redirectToIdpSignIn(null,
- providerIdToAccountType(idpConfigs.get(0).getProviderId()));
+ // Launch IDP flow
+ redirectToIdpSignIn(null, providerIdToAccountType(firstProvider));
}
} else {
startActivityForResult(
AuthMethodPickerActivity.createIntent(
getContext(),
- mHelper.getFlowParams()),
+ flowParams),
RC_AUTH_METHOD_PICKER);
}
mHelper.dismissDialog();
}
/**
- * Begin sign in process with email and password from a SmartLock credential.
- * On success, finish with {@link ResultCodes#OK RESULT_OK}.
- * On failure, delete the credential from SmartLock (if applicable) and then launch the
- * auth method picker flow.
+ * Begin sign in process with email and password from a SmartLock credential. On success, finish
+ * with {@link ResultCodes#OK RESULT_OK}. On failure, delete the credential from SmartLock (if
+ * applicable) and then launch the auth method picker flow.
*/
- private void signInWithEmailAndPassword(final String email, String password) {
+ private void signInWithEmailAndPassword(String email, String password) {
+ final IdpResponse response =
+ new IdpResponse.Builder(EmailAuthProvider.PROVIDER_ID, email).build();
+
mHelper.getFirebaseAuth()
.signInWithEmailAndPassword(email, password)
.addOnFailureListener(new TaskFailureLogger(
@@ -268,9 +288,7 @@ private void signInWithEmailAndPassword(final String email, String password) {
.addOnSuccessListener(new OnSuccessListener() {
@Override
public void onSuccess(AuthResult authResult) {
- finish(ResultCodes.OK,
- IdpResponse.getIntent(new IdpResponse(EmailAuthProvider.PROVIDER_ID,
- email)));
+ finish(ResultCodes.OK, response.toIntent());
}
})
.addOnFailureListener(new OnFailureListener() {
@@ -289,8 +307,8 @@ public void onFailure(@NonNull Exception e) {
}
/**
- * Delete the last credential retrieved from SmartLock and then redirect to the
- * auth method choice flow.
+ * Delete the last credential retrieved from SmartLock and then redirect to the auth method
+ * choice flow.
*/
private void deleteCredentialAndRedirect() {
if (mCredential == null) {
@@ -333,7 +351,7 @@ private void redirectToIdpSignIn(String email, String accountType) {
.setProvider(accountTypeToProviderId(accountType))
.build());
} else {
- Log.w(TAG, "unknown provider: " + accountType);
+ Log.w(TAG, "Unknown provider: " + accountType);
startActivityForResult(
AuthMethodPickerActivity.createIntent(
getContext(),
diff --git a/auth/src/main/java/com/firebase/ui/auth/util/signincontainer/SmartLockBase.java b/auth/src/main/java/com/firebase/ui/auth/util/signincontainer/SmartLockBase.java
index 812e6f094..6c63a9f7e 100644
--- a/auth/src/main/java/com/firebase/ui/auth/util/signincontainer/SmartLockBase.java
+++ b/auth/src/main/java/com/firebase/ui/auth/util/signincontainer/SmartLockBase.java
@@ -1,12 +1,15 @@
package com.firebase.ui.auth.util.signincontainer;
+import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.widget.Toast;
+import com.firebase.ui.auth.AuthUI;
import com.firebase.ui.auth.ui.FragmentBase;
import com.google.android.gms.auth.api.credentials.Credential;
import com.google.android.gms.auth.api.credentials.IdentityProviders;
@@ -18,6 +21,7 @@
import com.google.firebase.auth.FacebookAuthProvider;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.auth.GoogleAuthProvider;
+import com.google.firebase.auth.PhoneAuthProvider;
import com.google.firebase.auth.TwitterAuthProvider;
import com.google.firebase.auth.UserInfo;
@@ -32,13 +36,15 @@ public abstract class SmartLockBase extends FragmentBase imple
private static final String TAG = "SmartLockBase";
protected GoogleApiClient mGoogleApiClient;
+
private boolean mWasProgressDialogShowing;
+ private Pair mActivityResultPair;
/**
* Translate a Firebase Auth provider ID (such as {@link GoogleAuthProvider#PROVIDER_ID}) to
* a Credentials API account type (such as {@link IdentityProviders#GOOGLE}).
*/
- public static String providerIdToAccountType(@NonNull String providerId) {
+ public static String providerIdToAccountType(@AuthUI.SupportedProvider @NonNull String providerId) {
switch (providerId) {
case GoogleAuthProvider.PROVIDER_ID:
return IdentityProviders.GOOGLE;
@@ -49,11 +55,15 @@ public static String providerIdToAccountType(@NonNull String providerId) {
case EmailAuthProvider.PROVIDER_ID:
// The account type for email/password creds is null
return null;
+ case PhoneAuthProvider.PROVIDER_ID:
+ // The account type for phone creds is null
+ return null;
default:
return null;
}
}
+ @AuthUI.SupportedProvider
public static String accountTypeToProviderId(@NonNull String accountType) {
switch (accountType) {
case IdentityProviders.GOOGLE:
@@ -80,7 +90,7 @@ public static List credentialsFromFirebaseUser(@NonNull FirebaseUser
List credentials = new ArrayList<>();
for (UserInfo userInfo : user.getProviderData()) {
// Get provider ID from Firebase Auth
- String providerId = userInfo.getProviderId();
+ @AuthUI.SupportedProvider String providerId = userInfo.getProviderId();
// Convert to Credentials API account type
String accountType = providerIdToAccountType(providerId);
@@ -110,7 +120,9 @@ public void onCreate(@Nullable Bundle savedInstanceState) {
@Override
public void onStart() {
super.onStart();
- if (mWasProgressDialogShowing) {
+ if (mActivityResultPair != null) {
+ mHelper.finish(mActivityResultPair.first, mActivityResultPair.second);
+ } else if (mWasProgressDialogShowing) {
mHelper.showLoadingDialog(com.firebase.ui.auth.R.string.progress_dialog_loading);
mWasProgressDialogShowing = false;
}
@@ -126,15 +138,23 @@ public void onStop() {
@Override
public void onDestroy() {
super.onDestroy();
- cleanup();
- }
-
- public void cleanup() {
if (mGoogleApiClient != null) {
mGoogleApiClient.disconnect();
}
}
+ @Override
+ public void finish(int resultCode, Intent resultIntent) {
+ if (getActivity() == null) {
+ // Because this fragment lives beyond the activity lifecycle, Fragment#getActivity()
+ // might return null and we'll throw a NPE. To get around this, we wait until the
+ // activity comes back to life in onStart and we finish it there.
+ mActivityResultPair = new Pair<>(resultCode, resultIntent);
+ } else {
+ super.finish(resultCode, resultIntent);
+ }
+ }
+
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
Toast.makeText(getContext(),
diff --git a/auth/src/main/res/drawable/done_check_mark.xml b/auth/src/main/res/drawable/done_check_mark.xml
new file mode 100644
index 000000000..9671f43d6
--- /dev/null
+++ b/auth/src/main/res/drawable/done_check_mark.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/auth/src/main/res/drawable/ic_settings_phone_white_24dp.xml b/auth/src/main/res/drawable/ic_settings_phone_white_24dp.xml
new file mode 100644
index 000000000..ef75bd7e7
--- /dev/null
+++ b/auth/src/main/res/drawable/ic_settings_phone_white_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/auth/src/main/res/drawable/idp_button_background_facebook.xml b/auth/src/main/res/drawable/idp_button_background_facebook.xml
index fd5ae41c0..0589c181c 100644
--- a/auth/src/main/res/drawable/idp_button_background_facebook.xml
+++ b/auth/src/main/res/drawable/idp_button_background_facebook.xml
@@ -3,7 +3,7 @@
-
+
diff --git a/auth/src/main/res/drawable/idp_button_background_phone.xml b/auth/src/main/res/drawable/idp_button_background_phone.xml
new file mode 100644
index 000000000..7cfd58030
--- /dev/null
+++ b/auth/src/main/res/drawable/idp_button_background_phone.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/auth/src/main/res/drawable/idp_button_background_twitter.xml b/auth/src/main/res/drawable/idp_button_background_twitter.xml
index 1bb555374..84314b897 100644
--- a/auth/src/main/res/drawable/idp_button_background_twitter.xml
+++ b/auth/src/main/res/drawable/idp_button_background_twitter.xml
@@ -3,7 +3,7 @@
-
+
diff --git a/auth/src/main/res/layout-land/auth_method_picker_layout.xml b/auth/src/main/res/layout-land/auth_method_picker_layout.xml
index 7a200c15e..ea383952a 100644
--- a/auth/src/main/res/layout-land/auth_method_picker_layout.xml
+++ b/auth/src/main/res/layout-land/auth_method_picker_layout.xml
@@ -1,6 +1,6 @@
-
+ android:layout_weight="1.0"/>
-
-
-
-
+ android:paddingTop="@dimen/auth_method_button_marginBottom"/>
diff --git a/auth/src/main/res/layout/activity_register_phone.xml b/auth/src/main/res/layout/activity_register_phone.xml
new file mode 100644
index 000000000..94f5ce4bd
--- /dev/null
+++ b/auth/src/main/res/layout/activity_register_phone.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/auth/src/main/res/layout/auth_method_picker_layout.xml b/auth/src/main/res/layout/auth_method_picker_layout.xml
index 895e1c0c8..dafd30da8 100644
--- a/auth/src/main/res/layout/auth_method_picker_layout.xml
+++ b/auth/src/main/res/layout/auth_method_picker_layout.xml
@@ -1,27 +1,18 @@
-
-
+
-
-
-
-
+ android:layout_height="wrap_content"/>
diff --git a/auth/src/main/res/layout/check_email_layout.xml b/auth/src/main/res/layout/check_email_layout.xml
index 7b10cf151..cafc9ae3d 100644
--- a/auth/src/main/res/layout/check_email_layout.xml
+++ b/auth/src/main/res/layout/check_email_layout.xml
@@ -1,6 +1,6 @@
-
+ style="@style/FirebaseUI.TextInputEditText.EmailField"/>
@@ -30,6 +28,6 @@
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_below="@id/email_layout"
- android:text="@string/next_default" />
+ android:text="@string/next_default"/>
diff --git a/auth/src/main/res/layout/confirmation_code_layout.xml b/auth/src/main/res/layout/confirmation_code_layout.xml
new file mode 100644
index 000000000..865898934
--- /dev/null
+++ b/auth/src/main/res/layout/confirmation_code_layout.xml
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/auth/src/main/res/layout/dgts__country_row.xml b/auth/src/main/res/layout/dgts__country_row.xml
new file mode 100644
index 000000000..abcb1b865
--- /dev/null
+++ b/auth/src/main/res/layout/dgts__country_row.xml
@@ -0,0 +1,11 @@
+
+
+
\ No newline at end of file
diff --git a/auth/src/main/res/layout/forgot_password_layout.xml b/auth/src/main/res/layout/forgot_password_layout.xml
index cdc9d37c0..b5e2c5780 100644
--- a/auth/src/main/res/layout/forgot_password_layout.xml
+++ b/auth/src/main/res/layout/forgot_password_layout.xml
@@ -1,42 +1,47 @@
-
-
-
-
-
-
-
-
-
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ style="@style/FirebaseUI.WrapperStyle"
+ android:orientation="vertical">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/auth/src/main/res/layout/idp_button_facebook.xml b/auth/src/main/res/layout/idp_button_facebook.xml
index 611fc8b77..2aae38272 100644
--- a/auth/src/main/res/layout/idp_button_facebook.xml
+++ b/auth/src/main/res/layout/idp_button_facebook.xml
@@ -1,5 +1,7 @@
+ tools:ignore="UnusedIds"/>
diff --git a/auth/src/main/res/layout/idp_button_google.xml b/auth/src/main/res/layout/idp_button_google.xml
index 621bb1621..fa4b42bdf 100644
--- a/auth/src/main/res/layout/idp_button_google.xml
+++ b/auth/src/main/res/layout/idp_button_google.xml
@@ -1,5 +1,7 @@
+ tools:ignore="UnusedIds"/>
diff --git a/auth/src/main/res/layout/idp_button_twitter.xml b/auth/src/main/res/layout/idp_button_twitter.xml
index 8e5571d41..f6c673aa3 100644
--- a/auth/src/main/res/layout/idp_button_twitter.xml
+++ b/auth/src/main/res/layout/idp_button_twitter.xml
@@ -1,5 +1,7 @@
+ tools:ignore="UnusedIds"/>
diff --git a/auth/src/main/res/layout/phone_layout.xml b/auth/src/main/res/layout/phone_layout.xml
new file mode 100644
index 000000000..d3e62a474
--- /dev/null
+++ b/auth/src/main/res/layout/phone_layout.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/auth/src/main/res/layout/phone_progress_dialog.xml b/auth/src/main/res/layout/phone_progress_dialog.xml
new file mode 100644
index 000000000..698c92f87
--- /dev/null
+++ b/auth/src/main/res/layout/phone_progress_dialog.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/auth/src/main/res/layout/provider_button_email.xml b/auth/src/main/res/layout/provider_button_email.xml
new file mode 100644
index 000000000..0c58ba6ec
--- /dev/null
+++ b/auth/src/main/res/layout/provider_button_email.xml
@@ -0,0 +1,8 @@
+
+
diff --git a/auth/src/main/res/layout/provider_button_phone.xml b/auth/src/main/res/layout/provider_button_phone.xml
new file mode 100644
index 000000000..ccc4cbbe1
--- /dev/null
+++ b/auth/src/main/res/layout/provider_button_phone.xml
@@ -0,0 +1,8 @@
+
+
diff --git a/auth/src/main/res/layout/register_email_layout.xml b/auth/src/main/res/layout/register_email_layout.xml
index e8d99807b..3c7e3a127 100644
--- a/auth/src/main/res/layout/register_email_layout.xml
+++ b/auth/src/main/res/layout/register_email_layout.xml
@@ -1,14 +1,16 @@
-
+ android:layout_height="match_parent">
-
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ style="@style/FirebaseUI.TextInputEditText.EmailField"/>
+ android:hint="@string/name_hint"
+ android:paddingTop="@dimen/field_padding_vert"
+ app:errorEnabled="true">
+ android:inputType="textCapWords"/>
+ style="@style/FirebaseUI.TextInputEditText.PasswordField"/>
-
-
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/field_padding_vert"
+ android:textIsSelectable="false"
+ tools:text="@string/create_account_preamble"/>
+ style="@style/FirebaseUI.Button"
+ android:layout_gravity="right"
+ android:text="@string/button_text_save"
+ tools:ignore="RtlHardcoded"/>
-
+
diff --git a/auth/src/main/res/layout/welcome_back_idp_prompt_layout.xml b/auth/src/main/res/layout/welcome_back_idp_prompt_layout.xml
index 207726847..12659970a 100644
--- a/auth/src/main/res/layout/welcome_back_idp_prompt_layout.xml
+++ b/auth/src/main/res/layout/welcome_back_idp_prompt_layout.xml
@@ -18,6 +18,7 @@
android:id="@+id/welcome_back_idp_prompt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:textIsSelectable="false"
tools:text="Good news! You've already used jane.doe@example.com. Sign in with Google to continue to SuperApp"/>