Skip to content

Add support for privacy policy URLs in sign-up #727

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions app/src/main/java/com/firebase/uidemo/auth/AuthUiActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public class AuthUiActivity extends AppCompatActivity {
private static final String UNCHANGED_CONFIG_VALUE = "CHANGE-ME";
private static final String GOOGLE_TOS_URL = "https://www.google.com/policies/terms/";
private static final String FIREBASE_TOS_URL = "https://firebase.google.com/terms/";
private static final String GOOGLE_PRIVACY_POLICY_URL = "https://www.google.com/policies/privacy/";
private static final String FIREBASE_PRIVACY_POLICY_URL = "https://firebase.google.com/terms/analytics/#7_privacy";
private static final int RC_SIGN_IN = 100;

@BindView(R.id.default_theme)
Expand Down Expand Up @@ -84,6 +86,12 @@ public class AuthUiActivity extends AppCompatActivity {
@BindView(R.id.firebase_tos)
RadioButton mUseFirebaseTos;

@BindView(R.id.google_privacy)
RadioButton mUseGooglePrivacyPolicy;

@BindView(R.id.firebase_privacy)
RadioButton mUseFirebasePrivacyPolicy;

@BindView(R.id.sign_in)
Button mSignIn;

Expand Down Expand Up @@ -185,6 +193,7 @@ public void signIn(View view) {
.setLogo(getSelectedLogo())
.setAvailableProviders(getSelectedProviders())
.setTosUrl(getSelectedTosUrl())
.setPrivacyPolicyUrl(getSelectedPrivacyPolicyUrl())
.setIsSmartLockEnabled(mEnableSmartLock.isChecked())
.setAllowNewEmailAccounts(mAllowNewEmailAccounts.isChecked())
.build(),
Expand Down Expand Up @@ -327,6 +336,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(
Expand Down
28 changes: 28 additions & 0 deletions app/src/main/res/layout/auth_ui_layout.xml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,34 @@

</RadioGroup>

<TextView
style="@style/Base.TextAppearance.AppCompat.Subhead"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginTop="16dp"
android:text="@string/privacy_policy_header"/>

<RadioGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">

<RadioButton
android:id="@+id/google_privacy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/google_privacy_label"/>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add another button like the Firebase Analytics privacy policy or something? Otherwise a single radio button is kinda useless... 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was looking for some kind of Fireabse privacy policy, but I could only find terms of service. That's a good idea.


<RadioButton
android:id="@+id/firebase_privacy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/firebase_privacy_label"/>

</RadioGroup>

<TextView
style="@style/Base.TextAppearance.AppCompat.Subhead"
android:layout_width="wrap_content"
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@
<string name="google_label_missing_config">Google - configuration missing</string>
<string name="google_tos_label">Google TOS</string>
<string name="firebase_tos_label">Firebase TOS</string>
<string name="google_privacy_label">Google Privacy Policy</string>
<string name="firebase_privacy_label">Firebase Privacy Policy</string>
<string name="tos_header">Terms of Service URL:</string>
<string name="privacy_policy_header">Privacy Policy URL:</string>
<string name="unknown_response">Unexpected onActivityResult response code</string>
<string name="unknown_sign_in_response">Unknown response from AuthUI sign-in</string>
<string name="sign_in_cancelled">Sign in cancelled</string>
Expand Down
11 changes: 11 additions & 0 deletions auth/src/main/java/com/firebase/ui/auth/AuthUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ private abstract class AuthIntentBuilder<T extends AuthIntentBuilder> {
int mTheme = getDefaultTheme();
List<IdpConfig> mProviders = new ArrayList<>();
String mTosUrl;
String mPrivacyPolicyUrl;
boolean mIsSmartLockEnabled = true;

private AuthIntentBuilder() {}
Expand Down Expand Up @@ -483,6 +484,14 @@ public T setTosUrl(@Nullable String tosUrl) {
return (T) this;
}

/**
* Specifies the privacy policy URL for the application.
*/
public T setPrivacyPolicyUrl(@Nullable String privacyPolicyUrl) {
mPrivacyPolicyUrl = privacyPolicyUrl;
return (T) this;
}

/**
* Specified the set of supported authentication providers. At least one provider must
* be specified. There may only be one instance of each provider.
Expand Down Expand Up @@ -631,6 +640,7 @@ protected FlowParameters getFlowParams() {
mTheme,
mLogo,
mTosUrl,
mPrivacyPolicyUrl,
mIsSmartLockEnabled,
false,
true,
Expand Down Expand Up @@ -666,6 +676,7 @@ protected FlowParameters getFlowParams() {
mTheme,
mLogo,
mTosUrl,
mPrivacyPolicyUrl,
mIsSmartLockEnabled,
mAllowNewEmailAccounts,
false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ public class FlowParameters implements Parcelable {
@Nullable
public final String termsOfServiceUrl;

@Nullable
public final String privacyPolicyUrl;

public final boolean smartLockEnabled;

public final boolean allowNewEmailAccounts;
Expand All @@ -62,6 +65,7 @@ public FlowParameters(
@StyleRes int themeId,
@DrawableRes int logoId,
@Nullable String termsOfServiceUrl,
@Nullable String privacyPolicyUrl,
boolean smartLockEnabled,
boolean allowNewEmailAccounts,
boolean isReauth,
Expand All @@ -72,6 +76,7 @@ public FlowParameters(
this.themeId = themeId;
this.logoId = logoId;
this.termsOfServiceUrl = termsOfServiceUrl;
this.privacyPolicyUrl = privacyPolicyUrl;
this.smartLockEnabled = smartLockEnabled;
this.allowNewEmailAccounts = allowNewEmailAccounts;
this.isReauth = isReauth;
Expand All @@ -85,6 +90,7 @@ public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(themeId);
dest.writeInt(logoId);
dest.writeString(termsOfServiceUrl);
dest.writeString(privacyPolicyUrl);
dest.writeInt(smartLockEnabled ? 1 : 0);
dest.writeInt(allowNewEmailAccounts ? 1 : 0);
dest.writeInt(isReauth ? 1 : 0);
Expand All @@ -104,6 +110,7 @@ public FlowParameters createFromParcel(Parcel in) {
int themeId = in.readInt();
int logoId = in.readInt();
String termsOfServiceUrl = in.readString();
String privacyPolicyUrl = in.readString();
boolean smartLockEnabled = in.readInt() != 0;
boolean allowNewEmailAccounts = in.readInt() != 0;
boolean isReauth = in.readInt() != 0;
Expand All @@ -115,6 +122,7 @@ public FlowParameters createFromParcel(Parcel in) {
themeId,
logoId,
termsOfServiceUrl,
privacyPolicyUrl,
smartLockEnabled,
allowNewEmailAccounts,
isReauth,
Expand Down
118 changes: 118 additions & 0 deletions auth/src/main/java/com/firebase/ui/auth/ui/email/PreambleHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@samtstern LGTM now, but can you think of ways to improve this?

private static final String TOS_TARGET = "%TOS%";
private static final String PP_TARGET = "%PP%";

private final Context mContext;
private final FlowParameters mFlowParameters;
private final ForegroundColorSpan mLinkSpan;

private SpannableStringBuilder mBuilder;

public PreambleHandler(Context context, FlowParameters parameters) {
mContext = context;
mFlowParameters = parameters;
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(TOS_TARGET, R.string.terms_of_service, mFlowParameters.termsOfServiceUrl);
replaceTarget(PP_TARGET, R.string.privacy_policy, mFlowParameters.privacyPolicyUrl);
}

private void replaceTarget(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
* <p>1 means we only have a TOS
* <p>2 means we only have a PP
* <p>-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));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
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;
Expand Down Expand Up @@ -164,7 +157,7 @@ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
getActivity().setTitle(R.string.title_register_email);

mSaveSmartLock = mHelper.getSaveSmartLockInstance(getActivity());
setUpTermsOfService();
new PreambleHandler(getContext(), mHelper.getFlowParams()).setPreamble(mAgreementText);
}

@Override
Expand All @@ -177,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
Expand Down
7 changes: 6 additions & 1 deletion auth/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,13 @@
</plurals>
<string name="email_account_creation_error">Email account registration unsuccessful</string>
<string name="error_user_collision">An account already exists with that email address.</string>
<string name="create_account_preamble">"By tapping SAVE you are indicating that you agree to the "</string>
<string-array name="create_account_preamble" formatted="false">
<item>By tapping SAVE you are indicating that you agree to the %TOS% and the %PP%.</item>
<item>By tapping SAVE you are indicating that you agree to the %TOS%.</item>
<item>By tapping SAVE you are indicating that you agree to the %PP%.</item>
</string-array>
<string name="terms_of_service">Terms of Service</string>
<string name="privacy_policy">Privacy Policy</string>

<!-- Idp/Email welcome back -->
<string name="title_welcome_back_idp_prompt">@string/sign_in_default</string>
Expand Down
2 changes: 2 additions & 0 deletions auth/src/test/java/com/firebase/ui/auth/AuthUITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,14 @@ public void testCreatingStartIntent() {
new IdpConfig.Builder(AuthUI.GOOGLE_PROVIDER).build(),
new IdpConfig.Builder(AuthUI.FACEBOOK_PROVIDER).build()))
.setTosUrl(TestConstants.TOS_URL)
.setPrivacyPolicyUrl(TestConstants.PRIVACY_URL)
.build()
.getParcelableExtra(ExtraConstants.EXTRA_FLOW_PARAMS);

assertEquals(3, flowParameters.providerInfo.size());
assertEquals(mFirebaseApp.getName(), flowParameters.appName);
assertEquals(TestConstants.TOS_URL, flowParameters.termsOfServiceUrl);
assertEquals(TestConstants.PRIVACY_URL, flowParameters.privacyPolicyUrl);
assertEquals(AuthUI.getDefaultTheme(), flowParameters.themeId);
}
}
Loading