mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-04-03 04:52:54 +00:00
Bug 951264 - COPPA support for Firefox Accounts on Android. r=rnewman
This commit is contained in:
parent
323df6a9f8
commit
73e4ce9303
@ -503,6 +503,7 @@ sync_java_files = [
|
||||
'background/fxa/FxAccount10CreateDelegate.java',
|
||||
'background/fxa/FxAccount20CreateDelegate.java',
|
||||
'background/fxa/FxAccount20LoginDelegate.java',
|
||||
'background/fxa/FxAccountAgeLockoutHelper.java',
|
||||
'background/fxa/FxAccountClient.java',
|
||||
'background/fxa/FxAccountClient10.java',
|
||||
'background/fxa/FxAccountClient20.java',
|
||||
@ -546,6 +547,7 @@ sync_java_files = [
|
||||
'fxa/activities/FxAccountAbstractSetupActivity.java',
|
||||
'fxa/activities/FxAccountCreateAccountActivity.java',
|
||||
'fxa/activities/FxAccountCreateAccountFragment.java',
|
||||
'fxa/activities/FxAccountCreateAccountNotAllowedActivity.java',
|
||||
'fxa/activities/FxAccountCreateSuccessActivity.java',
|
||||
'fxa/activities/FxAccountGetStartedActivity.java',
|
||||
'fxa/activities/FxAccountSetupTask.java',
|
||||
|
@ -0,0 +1,90 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.background.fxa;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
|
||||
/**
|
||||
* Utility to manage COPPA age verification requirements.
|
||||
* <p>
|
||||
* A user who fails an age verification check when trying to create an account
|
||||
* is denied the ability to make an account for a period of time. We refer to
|
||||
* this state as being "locked out".
|
||||
* <p>
|
||||
* For now we maintain "locked out" state as a static variable. In the future we
|
||||
* might need to persist this state across process restarts, so we'll force
|
||||
* consumers to create an instance of this class. Then, we can drop in a class
|
||||
* backed by shared preferences.
|
||||
*/
|
||||
public class FxAccountAgeLockoutHelper {
|
||||
private static final String LOG_TAG = FxAccountAgeLockoutHelper.class.getSimpleName();
|
||||
|
||||
protected static long ELAPSED_REALTIME_OF_LAST_FAILED_AGE_CHECK = 0;
|
||||
|
||||
public static synchronized boolean isLockedOut(long elapsedRealtime) {
|
||||
long millsecondsSinceLastFailedAgeCheck = elapsedRealtime - ELAPSED_REALTIME_OF_LAST_FAILED_AGE_CHECK;
|
||||
boolean isLockedOut = millsecondsSinceLastFailedAgeCheck < FxAccountConstants.MINIMUM_TIME_TO_WAIT_AFTER_AGE_CHECK_FAILED_IN_MILLISECONDS;
|
||||
FxAccountConstants.pii(LOG_TAG, "Checking if locked out: it's been " + millsecondsSinceLastFailedAgeCheck + "ms " +
|
||||
"since last lockout, so " + (isLockedOut ? "yes." : "no."));
|
||||
return isLockedOut;
|
||||
}
|
||||
|
||||
public static synchronized void lockOut(long elapsedRealtime) {
|
||||
FxAccountConstants.pii(LOG_TAG, "Locking out at time: " + elapsedRealtime);
|
||||
ELAPSED_REALTIME_OF_LAST_FAILED_AGE_CHECK = Math.max(elapsedRealtime, ELAPSED_REALTIME_OF_LAST_FAILED_AGE_CHECK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the age of somebody born in <code>yearOfBirth</code> is
|
||||
* definitely old enough to create an account.
|
||||
* <p>
|
||||
* This errs towards locking out users who might be old enough, but are not
|
||||
* definitely old enough.
|
||||
*
|
||||
* @param yearOfBirth
|
||||
* @return true if somebody born in <code>yearOfBirth</code> is definitely old
|
||||
* enough.
|
||||
*/
|
||||
public static boolean passesAgeCheck(int yearOfBirth) {
|
||||
int thisYear = Calendar.getInstance().get(Calendar.YEAR);
|
||||
int approximateAge = thisYear - yearOfBirth;
|
||||
boolean oldEnough = approximateAge >= FxAccountConstants.MINIMUM_AGE_TO_CREATE_AN_ACCOUNT;
|
||||
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||
FxAccountConstants.pii(LOG_TAG, "Age check " + (oldEnough ? "passes" : "fails") +
|
||||
": age is " + approximateAge + " = " + thisYear + " - " + yearOfBirth);
|
||||
}
|
||||
return oldEnough;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom function for UI use only.
|
||||
*/
|
||||
public static boolean passesAgeCheck(String yearText, String[] yearItems) {
|
||||
if (yearText == null) {
|
||||
throw new IllegalArgumentException("yearText must not be null");
|
||||
}
|
||||
if (yearItems == null) {
|
||||
throw new IllegalArgumentException("yearItems must not be null");
|
||||
}
|
||||
if (!Arrays.asList(yearItems).contains(yearText)) {
|
||||
// This should never happen, but let's be careful.
|
||||
FxAccountConstants.pii(LOG_TAG, "Failed age check: year text was not found in item list.");
|
||||
return false;
|
||||
}
|
||||
Integer yearOfBirth;
|
||||
try {
|
||||
yearOfBirth = Integer.valueOf(yearText, 10);
|
||||
} catch (NumberFormatException e) {
|
||||
// Any non-numbers in the list are ranges (and we say as much to
|
||||
// translators in the resource file), so these people pass the age check.
|
||||
FxAccountConstants.pii(LOG_TAG, "Passed age check: year text was found in item list but was not a number.");
|
||||
return true;
|
||||
}
|
||||
return passesAgeCheck(yearOfBirth.intValue());
|
||||
}
|
||||
}
|
@ -25,4 +25,10 @@ public class FxAccountConstants {
|
||||
Logger.info(tag, "$$FxA PII$$: " + message);
|
||||
}
|
||||
}
|
||||
|
||||
// You must be at least 14 years old to create a Firefox Account.
|
||||
public static final int MINIMUM_AGE_TO_CREATE_AN_ACCOUNT = 14;
|
||||
|
||||
// You must wait 15 minutes after failing an age check before trying to create a different account.
|
||||
public static final long MINIMUM_TIME_TO_WAIT_AFTER_AGE_CHECK_FAILED_IN_MILLISECONDS = 15 * 60 * 1000;
|
||||
}
|
||||
|
@ -5,9 +5,13 @@
|
||||
package org.mozilla.gecko.fxa.activities;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper;
|
||||
import org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticator;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.SystemClock;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.View;
|
||||
@ -17,6 +21,55 @@ import android.widget.TextView;
|
||||
public abstract class FxAccountAbstractActivity extends Activity {
|
||||
private static final String LOG_TAG = FxAccountAbstractActivity.class.getSimpleName();
|
||||
|
||||
protected final boolean cannotResumeWhenAccountsExist;
|
||||
protected final boolean cannotResumeWhenNoAccountsExist;
|
||||
protected final boolean cannotResumeWhenLockedOut;
|
||||
|
||||
public static final int CAN_ALWAYS_RESUME = 0;
|
||||
public static final int CANNOT_RESUME_WHEN_ACCOUNTS_EXIST = 1 << 0;
|
||||
public static final int CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST = 1 << 1;
|
||||
public static final int CANNOT_RESUME_WHEN_LOCKED_OUT = 1 << 2;
|
||||
|
||||
public FxAccountAbstractActivity(int resume) {
|
||||
super();
|
||||
this.cannotResumeWhenAccountsExist = 0 != (resume & CANNOT_RESUME_WHEN_ACCOUNTS_EXIST);
|
||||
this.cannotResumeWhenNoAccountsExist = 0 != (resume & CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST);
|
||||
this.cannotResumeWhenLockedOut = 0 != (resume & CANNOT_RESUME_WHEN_LOCKED_OUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Many Firefox Accounts activities shouldn't display if an account already
|
||||
* exists or if account creation is locked out due to an age verification
|
||||
* check failing (getting started, create account, sign in). This function
|
||||
* redirects as appropriate.
|
||||
*/
|
||||
protected void redirectIfAppropriate() {
|
||||
if (cannotResumeWhenAccountsExist || cannotResumeWhenNoAccountsExist) {
|
||||
Account accounts[] = FxAccountAuthenticator.getFirefoxAccounts(this);
|
||||
if (cannotResumeWhenAccountsExist && accounts.length > 0) {
|
||||
redirectToActivity(FxAccountStatusActivity.class);
|
||||
return;
|
||||
}
|
||||
if (cannotResumeWhenNoAccountsExist && accounts.length < 1) {
|
||||
redirectToActivity(FxAccountGetStartedActivity.class);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (cannotResumeWhenLockedOut) {
|
||||
if (FxAccountAgeLockoutHelper.isLockedOut(SystemClock.elapsedRealtime())) {
|
||||
this.setResult(RESULT_CANCELED);
|
||||
redirectToActivity(FxAccountCreateAccountNotAllowedActivity.class);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
redirectIfAppropriate();
|
||||
}
|
||||
|
||||
protected void launchActivity(Class<? extends Activity> activityClass) {
|
||||
Intent intent = new Intent(this, activityClass);
|
||||
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
|
||||
|
@ -22,6 +22,14 @@ import android.widget.TextView;
|
||||
import android.widget.TextView.OnEditorActionListener;
|
||||
|
||||
abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractActivity {
|
||||
public FxAccountAbstractSetupActivity() {
|
||||
super(CANNOT_RESUME_WHEN_ACCOUNTS_EXIST | CANNOT_RESUME_WHEN_LOCKED_OUT);
|
||||
}
|
||||
|
||||
protected FxAccountAbstractSetupActivity(int resume) {
|
||||
super(resume);
|
||||
}
|
||||
|
||||
private static final String LOG_TAG = FxAccountAbstractSetupActivity.class.getSimpleName();
|
||||
|
||||
protected int minimumPasswordLength = 8;
|
||||
@ -46,7 +54,7 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
|
||||
showPasswordButton.setText(R.string.fxaccount_password_show);
|
||||
} else {
|
||||
showPasswordButton.setText(R.string.fxaccount_password_hide);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -103,15 +111,19 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean updateButtonState() {
|
||||
protected boolean shouldButtonBeEnabled() {
|
||||
final String email = emailEdit.getText().toString();
|
||||
final String password = passwordEdit.getText().toString();
|
||||
|
||||
boolean enabled =
|
||||
(email.length() > 0) &&
|
||||
Patterns.EMAIL_ADDRESS.matcher(email).matches() &&
|
||||
(password.length() >= minimumPasswordLength);
|
||||
(password.length() >= minimumPasswordLength);
|
||||
return enabled;
|
||||
}
|
||||
|
||||
protected boolean updateButtonState() {
|
||||
boolean enabled = shouldButtonBeEnabled();
|
||||
if (enabled != button.isEnabled()) {
|
||||
Logger.debug(LOG_TAG, (enabled ? "En" : "Dis") + "abling button.");
|
||||
button.setEnabled(enabled);
|
||||
|
@ -9,6 +9,7 @@ import java.util.concurrent.Executors;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
@ -25,6 +26,7 @@ import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.Button;
|
||||
@ -40,6 +42,7 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
|
||||
|
||||
private static final int CHILD_REQUEST_CODE = 2;
|
||||
|
||||
protected String[] yearItems;
|
||||
protected EditText yearEdit;
|
||||
|
||||
/**
|
||||
@ -105,24 +108,21 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
|
||||
}
|
||||
|
||||
protected void createYearEdit() {
|
||||
yearItems = getResources().getStringArray(R.array.fxaccount_create_account_ages_array);
|
||||
|
||||
yearEdit.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
final String[] years = new String[20];
|
||||
for (int i = 0; i < years.length; i++) {
|
||||
years[i] = Integer.toString(2014 - i);
|
||||
}
|
||||
|
||||
android.content.DialogInterface.OnClickListener listener = new Dialog.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
yearEdit.setText(years[which]);
|
||||
yearEdit.setText(yearItems[which]);
|
||||
updateButtonState();
|
||||
}
|
||||
};
|
||||
|
||||
AlertDialog dialog = new AlertDialog.Builder(FxAccountCreateAccountActivity.this)
|
||||
final AlertDialog dialog = new AlertDialog.Builder(FxAccountCreateAccountActivity.this)
|
||||
.setTitle(R.string.fxaccount_when_were_you_born)
|
||||
.setItems(years, listener)
|
||||
.setItems(yearItems, listener)
|
||||
.setIcon(R.drawable.fxaccount_icon)
|
||||
.create();
|
||||
|
||||
@ -197,6 +197,12 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldButtonBeEnabled() {
|
||||
return super.shouldButtonBeEnabled() &&
|
||||
(yearEdit.length() > 0);
|
||||
}
|
||||
|
||||
protected void createCreateAccountButton() {
|
||||
button.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
@ -206,7 +212,15 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
|
||||
}
|
||||
final String email = emailEdit.getText().toString();
|
||||
final String password = passwordEdit.getText().toString();
|
||||
createAccount(email, password);
|
||||
if (FxAccountAgeLockoutHelper.passesAgeCheck(yearEdit.getText().toString(), yearItems)) {
|
||||
FxAccountConstants.pii(LOG_TAG, "Passed age check.");
|
||||
createAccount(email, password);
|
||||
} else {
|
||||
FxAccountConstants.pii(LOG_TAG, "Failed age check!");
|
||||
FxAccountAgeLockoutHelper.lockOut(SystemClock.elapsedRealtime());
|
||||
setResult(RESULT_CANCELED);
|
||||
redirectToActivity(FxAccountCreateAccountNotAllowedActivity.class);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -0,0 +1,34 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.fxa.activities;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* Activity which displays sign up/sign in screen to the user.
|
||||
*/
|
||||
public class FxAccountCreateAccountNotAllowedActivity extends FxAccountAbstractActivity {
|
||||
protected static final String LOG_TAG = FxAccountCreateAccountNotAllowedActivity.class.getSimpleName();
|
||||
|
||||
public FxAccountCreateAccountNotAllowedActivity() {
|
||||
super(CANNOT_RESUME_WHEN_ACCOUNTS_EXIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
|
||||
|
||||
super.onCreate(icicle);
|
||||
setContentView(R.layout.fxaccount_create_account_not_allowed);
|
||||
|
||||
linkifyTextViews(null, new int[] { R.id.learn_more_link });
|
||||
}
|
||||
}
|
@ -6,12 +6,14 @@ package org.mozilla.gecko.fxa.activities;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticator;
|
||||
|
||||
import android.accounts.AccountAuthenticatorActivity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
|
||||
@ -51,10 +53,16 @@ public class FxAccountGetStartedActivity extends AccountAuthenticatorActivity {
|
||||
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (FxAccountAuthenticator.getFirefoxAccounts(this).length > 0) {
|
||||
|
||||
Intent intent = null;
|
||||
if (FxAccountAgeLockoutHelper.isLockedOut(SystemClock.elapsedRealtime())) {
|
||||
intent = new Intent(this, FxAccountCreateAccountNotAllowedActivity.class);
|
||||
} else if (FxAccountAuthenticator.firefoxAccountsExist(this)) {
|
||||
intent = new Intent(this, FxAccountStatusActivity.class);
|
||||
}
|
||||
if (intent != null) {
|
||||
this.setAccountAuthenticatorResult(null);
|
||||
setResult(RESULT_CANCELED);
|
||||
Intent intent = new Intent(this, FxAccountStatusActivity.class);
|
||||
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
|
||||
// the soft keyboard not being shown for the started activity. Why, Android, why?
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||
|
@ -25,6 +25,10 @@ public class FxAccountStatusActivity extends FxAccountAbstractActivity {
|
||||
protected View connectionStatusSignInView;
|
||||
protected View connectionStatusSyncingView;
|
||||
|
||||
public FxAccountStatusActivity() {
|
||||
super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
@ -41,6 +41,14 @@ public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupAc
|
||||
|
||||
protected Account account;
|
||||
|
||||
public FxAccountUpdateCredentialsActivity() {
|
||||
// We want to share code with the other setup activities, but this activity
|
||||
// doesn't create a new Android Account, it modifies an existing one. If you
|
||||
// manage to get an account, and somehow be locked out too, we'll let you
|
||||
// update it.
|
||||
super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@ -73,11 +81,12 @@ public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupAc
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
Account accounts[] = FxAccountAuthenticator.getFirefoxAccounts(this);
|
||||
if (accounts.length < 1) {
|
||||
redirectToActivity(FxAccountGetStartedActivity.class);
|
||||
finish();
|
||||
}
|
||||
account = accounts[0];
|
||||
if (account == null) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
emailEdit.setText(account.name);
|
||||
}
|
||||
|
||||
|
@ -145,4 +145,14 @@ public class FxAccountAuthenticator extends AbstractAccountAuthenticator {
|
||||
public static Account[] getFirefoxAccounts(final Context context) {
|
||||
return AccountManager.get(context).getAccountsByType(FxAccountConstants.ACCOUNT_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if at least one Firefox Account exists.
|
||||
*
|
||||
* @param context Android context.
|
||||
* @return true if at least one Firefox Account exists.
|
||||
*/
|
||||
public static boolean firefoxAccountsExist(final Context context) {
|
||||
return getFirefoxAccounts(context).length > 0;
|
||||
}
|
||||
}
|
||||
|
@ -159,3 +159,31 @@
|
||||
<!ENTITY fxaccount.status.tabs 'Open tabs'>
|
||||
<!ENTITY fxaccount.update.credentials 'Update password'>
|
||||
<!ENTITY fxaccount.update.credentials.button.label 'Re-connect'>
|
||||
<!ENTITY fxaccount.account.create.not.allowed 'Cannot create account'>
|
||||
<!ENTITY fxaccount.account.create.not.allowed.description 'You must meet certain age requirements to create a Firefox Account.'>
|
||||
|
||||
<!-- Note to translators:
|
||||
|
||||
These strings represent ranges of birth years, used to determine if a
|
||||
potential user is old enough to meet legal requirements. A user born
|
||||
in 1984 should understand that they should pick the translation of
|
||||
"1980s".
|
||||
|
||||
Any years that are *not* old enough to create an account must be
|
||||
appear as individual numbers, so that they can be converted to a year
|
||||
and compared to the current year. Year ranges (any entry that cannot
|
||||
be converted to a number) are *always* considered old enough to create
|
||||
an account. -->
|
||||
|
||||
<!ENTITY fxaccount.create.account.age.1960s '1960s or earlier'>
|
||||
<!ENTITY fxaccount.create.account.age.1970s '1970s'>
|
||||
<!ENTITY fxaccount.create.account.age.1980s '1980s'>
|
||||
<!ENTITY fxaccount.create.account.age.1990s '1990s'>
|
||||
<!ENTITY fxaccount.create.account.age.2000 '2000'>
|
||||
<!ENTITY fxaccount.create.account.age.2001 '2001'>
|
||||
<!ENTITY fxaccount.create.account.age.2002 '2002'>
|
||||
<!ENTITY fxaccount.create.account.age.2003 '2003'>
|
||||
<!ENTITY fxaccount.create.account.age.2004 '2004'>
|
||||
<!ENTITY fxaccount.create.account.age.2005 '2005'>
|
||||
<!ENTITY fxaccount.create.account.age.2006 '2006'>
|
||||
<!ENTITY fxaccount.create.account.age.2007 '2007'>
|
||||
|
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:fillViewport="true" >
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/create_account_not_allowed_view"
|
||||
style="@style/FxAccountMiddle" >
|
||||
|
||||
<LinearLayout style="@style/FxAccountSpacer" />
|
||||
|
||||
<TextView
|
||||
style="@style/FxAccountHeaderItem"
|
||||
android:text="@string/firefox_accounts" >
|
||||
</TextView>
|
||||
|
||||
<TextView
|
||||
style="@style/FxAccountSubHeaderItem"
|
||||
android:text="@string/fxaccount_account_create_not_allowed" >
|
||||
</TextView>
|
||||
|
||||
<TextView
|
||||
style="@style/FxAccountTextItem"
|
||||
android:layout_marginBottom="45dp"
|
||||
android:layout_marginTop="45dp"
|
||||
android:text="@string/fxaccount_account_create_not_allowed_description" >
|
||||
</TextView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/learn_more_link"
|
||||
style="@style/FxAccountLinkifiedItem"
|
||||
android:text="@string/fxaccount_account_create_not_allowed_learn_more" />
|
||||
|
||||
<LinearLayout style="@style/FxAccountSpacer" />
|
||||
|
||||
<ImageView
|
||||
style="@style/FxAccountIcon"
|
||||
android:contentDescription="@string/fxaccount_icon_contentDescription" />
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
21
mobile/android/base/resources/values/fxaccount_strings.xml
Normal file
21
mobile/android/base/resources/values/fxaccount_strings.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<resources>
|
||||
<string-array name="fxaccount_create_account_ages_array">
|
||||
<item>@string/fxaccount_create_account_age_1960s</item>
|
||||
<item>@string/fxaccount_create_account_age_1970s</item>
|
||||
<item>@string/fxaccount_create_account_age_1980s</item>
|
||||
<item>@string/fxaccount_create_account_age_1990s</item>
|
||||
<item>@string/fxaccount_create_account_age_2000</item>
|
||||
<item>@string/fxaccount_create_account_age_2001</item>
|
||||
<item>@string/fxaccount_create_account_age_2002</item>
|
||||
<item>@string/fxaccount_create_account_age_2003</item>
|
||||
<item>@string/fxaccount_create_account_age_2004</item>
|
||||
<item>@string/fxaccount_create_account_age_2005</item>
|
||||
<item>@string/fxaccount_create_account_age_2006</item>
|
||||
<item>@string/fxaccount_create_account_age_2007</item>
|
||||
</string-array>
|
||||
</resources>
|
@ -55,3 +55,10 @@
|
||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountUpdateCredentialsActivity"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:theme="@style/FxAccountTheme"
|
||||
android:icon="@drawable/fxaccount_icon"
|
||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountCreateAccountNotAllowedActivity"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
|
@ -152,3 +152,20 @@
|
||||
<string name="fxaccount_status_tabs">&fxaccount.status.tabs;</string>
|
||||
<string name="fxaccount_update_credentials">&fxaccount.update.credentials;</string>
|
||||
<string name="fxaccount_update_credentials_button_label">&fxaccount.update.credentials.button.label;</string>
|
||||
<string name="fxaccount_account_create_not_allowed">&fxaccount.account.create.not.allowed;</string>
|
||||
<string name="fxaccount_account_create_not_allowed_description">&fxaccount.account.create.not.allowed.description;</string>
|
||||
<string name="fxaccount_create_account_age_1960s">&fxaccount.create.account.age.1960s;</string>
|
||||
<string name="fxaccount_create_account_age_1970s">&fxaccount.create.account.age.1970s;</string>
|
||||
<string name="fxaccount_create_account_age_1980s">&fxaccount.create.account.age.1980s;</string>
|
||||
<string name="fxaccount_create_account_age_1990s">&fxaccount.create.account.age.1990s;</string>
|
||||
<string name="fxaccount_create_account_age_2000">&fxaccount.create.account.age.2000;</string>
|
||||
<string name="fxaccount_create_account_age_2001">&fxaccount.create.account.age.2001;</string>
|
||||
<string name="fxaccount_create_account_age_2002">&fxaccount.create.account.age.2002;</string>
|
||||
<string name="fxaccount_create_account_age_2003">&fxaccount.create.account.age.2003;</string>
|
||||
<string name="fxaccount_create_account_age_2004">&fxaccount.create.account.age.2004;</string>
|
||||
<string name="fxaccount_create_account_age_2005">&fxaccount.create.account.age.2005;</string>
|
||||
<string name="fxaccount_create_account_age_2006">&fxaccount.create.account.age.2006;</string>
|
||||
<string name="fxaccount_create_account_age_2007">&fxaccount.create.account.age.2007;</string>
|
||||
<!-- We haven't yet decided how to linkify, so to save translation
|
||||
time we're holding this back. -->
|
||||
<string name="fxaccount_account_create_not_allowed_learn_more"><a href="http://www.ftc.gov/news-events/media-resources/protecting-consumer-privacy/kids-privacy-coppa">Learn more</a></string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user