Bug 970176 - Part 2: invoke LocaleManager locale switching code prior to handling strings or Locale in background services. r=nalexander
authorRichard Newman <rnewman@mozilla.com>
Tue, 15 Apr 2014 15:07:15 -0700
changeset 197087 661f96cbf4cef409e3805d3dc29a418d62b87204
parent 197086 4667003549589d566cd218f7aa903250d97b3cd2
child 197140 16e9cda442500ee95535e9b951adbd6deb664ac7
child 197264 578a88956f7d4816b8278bd3a9f33481ffe47cd7
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnalexander
bugs970176
milestone31.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 970176 - Part 2: invoke LocaleManager locale switching code prior to handling strings or Locale in background services. r=nalexander
mobile/android/base/android-services.mozbuild
mobile/android/base/background/announcements/AnnouncementsService.java
mobile/android/base/background/healthreport/HealthReportGenerator.java
mobile/android/base/background/healthreport/upload/AndroidSubmissionClient.java
mobile/android/base/fxa/activities/FxAccountAbstractActivity.java
mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java
mobile/android/base/fxa/activities/FxAccountStatusActivity.java
mobile/android/base/fxa/sync/FxAccountNotificationManager.java
mobile/android/base/sync/CommandProcessor.java
mobile/android/base/sync/setup/activities/LocaleAware.java
mobile/android/base/sync/setup/activities/SendTabActivity.java
mobile/android/services/manifests/SyncAndroidManifest_activities.xml.in
--- a/mobile/android/base/android-services.mozbuild
+++ b/mobile/android/base/android-services.mozbuild
@@ -769,16 +769,17 @@ sync_java_files = [
     'sync/repositories/StoreFailedException.java',
     'sync/repositories/StoreTracker.java',
     'sync/repositories/StoreTrackingRepositorySession.java',
     'sync/Server11PreviousPostFailedException.java',
     'sync/Server11RecordPostFailedException.java',
     'sync/setup/activities/AccountActivity.java',
     'sync/setup/activities/ActivityUtils.java',
     'sync/setup/activities/ClientRecordArrayAdapter.java',
+    'sync/setup/activities/LocaleAware.java',
     'sync/setup/activities/RedirectToSetupActivity.java',
     'sync/setup/activities/SendTabActivity.java',
     'sync/setup/activities/SendTabData.java',
     'sync/setup/activities/SetupFailureActivity.java',
     'sync/setup/activities/SetupSuccessActivity.java',
     'sync/setup/activities/SetupSyncActivity.java',
     'sync/setup/activities/SyncActivity.java',
     'sync/setup/activities/WebURLFinder.java',
--- a/mobile/android/base/background/announcements/AnnouncementsService.java
+++ b/mobile/android/base/background/announcements/AnnouncementsService.java
@@ -5,16 +5,17 @@
 package org.mozilla.gecko.background.announcements;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.net.URI;
 import java.util.List;
 import java.util.Locale;
 
+import org.mozilla.gecko.BrowserLocaleManager;
 import org.mozilla.gecko.background.BackgroundService;
 import org.mozilla.gecko.background.common.GlobalConstants;
 import org.mozilla.gecko.background.common.log.Logger;
 
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.os.IBinder;
 
@@ -123,16 +124,20 @@ public class AnnouncementsService extend
       return;
     }
 
     if (!shouldFetchAnnouncements()) {
       Logger.debug(LOG_TAG, "Not fetching.");
       return;
     }
 
+    // Ensure that our locale is up to date, so that the fetcher's
+    // Accept-Language header is, too.
+    BrowserLocaleManager.getInstance().getAndApplyPersistedLocale(getApplicationContext());
+
     // Otherwise, grab our announcements URL and process the contents.
     AnnouncementsFetcher.fetchAndProcessAnnouncements(getLastLaunch(), this);
   }
 
   @Override
   public IBinder onBind(Intent intent) {
     return null;
   }
--- a/mobile/android/base/background/healthreport/HealthReportGenerator.java
+++ b/mobile/android/base/background/healthreport/HealthReportGenerator.java
@@ -31,22 +31,26 @@ public class HealthReportGenerator {
   }
 
   @SuppressWarnings("static-method")
   protected long now() {
     return System.currentTimeMillis();
   }
 
   /**
+   * Ensure that you have initialized the Locale to your satisfaction
+   * prior to calling this method.
+   *
    * @return null if no environment could be computed, or else the resulting document.
    * @throws JSONException if there was an error adding environment data to the resulting document.
    */
   public JSONObject generateDocument(long since, long lastPingTime, String profilePath) throws JSONException {
     Logger.info(LOG_TAG, "Generating FHR document from " + since + "; last ping " + lastPingTime);
     Logger.pii(LOG_TAG, "Generating for profile " + profilePath);
+
     ProfileInformationCache cache = new ProfileInformationCache(profilePath);
     if (!cache.restoreUnlessInitialized()) {
       Logger.warn(LOG_TAG, "Not enough profile information to compute current environment.");
       return null;
     }
     Environment current = EnvironmentBuilder.getCurrentEnvironment(cache);
     return generateDocument(since, lastPingTime, current);
   }
--- a/mobile/android/base/background/healthreport/upload/AndroidSubmissionClient.java
+++ b/mobile/android/base/background/healthreport/upload/AndroidSubmissionClient.java
@@ -7,16 +7,17 @@ package org.mozilla.gecko.background.hea
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collection;
 
 import org.json.JSONException;
 import org.json.JSONObject;
+import org.mozilla.gecko.BrowserLocaleManager;
 import org.mozilla.gecko.background.bagheera.BagheeraClient;
 import org.mozilla.gecko.background.bagheera.BagheeraRequestDelegate;
 import org.mozilla.gecko.background.common.GlobalConstants;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.healthreport.Environment;
 import org.mozilla.gecko.background.healthreport.EnvironmentBuilder;
 import org.mozilla.gecko.background.healthreport.HealthReportConstants;
 import org.mozilla.gecko.background.healthreport.HealthReportDatabaseStorage;
@@ -394,16 +395,20 @@ public class AndroidSubmissionClient imp
     public class TrackingGenerator extends HealthReportGenerator {
       public TrackingGenerator() {
         super(storage);
       }
 
       @Override
       public JSONObject generateDocument(long since, long lastPingTime,
           String generationProfilePath) throws JSONException {
+
+        // Let's make sure we have an accurate locale.
+        BrowserLocaleManager.getInstance().getAndApplyPersistedLocale(context);
+
         final JSONObject document;
         // If the given profilePath matches the one we cached for the tracker, use the cached env.
         if (profilePath != null && profilePath.equals(generationProfilePath)) {
           final Environment environment = getCurrentEnvironment();
           document = super.generateDocument(since, lastPingTime, environment);
         } else {
           document = super.generateDocument(since, lastPingTime, generationProfilePath);
         }
--- a/mobile/android/base/fxa/activities/FxAccountAbstractActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountAbstractActivity.java
@@ -4,26 +4,27 @@
 
 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.FirefoxAccounts;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
+import org.mozilla.gecko.sync.setup.activities.LocaleAware.LocaleAwareActivity;
 
 import android.accounts.Account;
 import android.app.Activity;
 import android.content.Intent;
 import android.os.SystemClock;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.TextView;
 
-public abstract class FxAccountAbstractActivity extends Activity {
+public abstract class FxAccountAbstractActivity extends LocaleAwareActivity {
   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;
--- a/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java
@@ -9,16 +9,17 @@ import java.util.Locale;
 import org.mozilla.gecko.AppConstants;
 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.FirefoxAccounts;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
+import org.mozilla.gecko.sync.setup.activities.LocaleAware;
 
 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;
 import android.widget.TextView;
@@ -34,17 +35,20 @@ public class FxAccountGetStartedActivity
   /**
    * {@inheritDoc}
    */
   @Override
   public void onCreate(Bundle icicle) {
     Logger.setThreadLogTag(FxAccountConstants.GLOBAL_LOG_TAG);
     Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
 
+    LocaleAware.initializeLocale(getApplicationContext());
+
     super.onCreate(icicle);
+
     setContentView(R.layout.fxaccount_get_started);
 
     linkifyOldFirefoxLink();
 
     View button = findViewById(R.id.get_started_button);
     button.setOnClickListener(new OnClickListener() {
       @Override
       public void onClick(View v) {
@@ -102,15 +106,15 @@ public class FxAccountGetStartedActivity
     }
   }
 
   protected void linkifyOldFirefoxLink() {
     TextView oldFirefox = (TextView) findViewById(R.id.old_firefox);
     String text = getResources().getString(R.string.fxaccount_getting_started_old_firefox);
     String VERSION = AppConstants.MOZ_APP_VERSION;
     String OS = AppConstants.OS_TARGET;
-    // We'll need to adjust this when we have active locale switching.
+
     String LOCALE = Utils.getLanguageTag(Locale.getDefault());
     String url = getResources().getString(R.string.fxaccount_link_old_firefox, VERSION, OS, LOCALE);
     FxAccountConstants.pii(LOG_TAG, "Old Firefox url is: " + url); // Don't want to leak locale in particular.
     ActivityUtils.linkTextView(oldFirefox, text, url);
   }
 }
--- a/mobile/android/base/fxa/activities/FxAccountStatusActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountStatusActivity.java
@@ -2,30 +2,30 @@
  * 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.background.common.log.Logger;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
+import org.mozilla.gecko.sync.setup.activities.LocaleAware.LocaleAwareFragmentActivity;
 
 import android.accounts.Account;
 import android.annotation.TargetApi;
 import android.app.ActionBar;
 import android.content.Intent;
 import android.os.Build;
 import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
 import android.view.MenuItem;
 
 /**
  * Activity which displays account status.
  */
-public class FxAccountStatusActivity extends FragmentActivity {
+public class FxAccountStatusActivity extends LocaleAwareFragmentActivity {
   private static final String LOG_TAG = FxAccountStatusActivity.class.getSimpleName();
 
   protected FxAccountStatusFragment statusFragment;
 
   @Override
   protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
 
--- a/mobile/android/base/fxa/sync/FxAccountNotificationManager.java
+++ b/mobile/android/base/fxa/sync/FxAccountNotificationManager.java
@@ -1,14 +1,15 @@
 /* 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.sync;
 
+import org.mozilla.gecko.BrowserLocaleManager;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.activities.FxAccountStatusActivity;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.fxa.login.State.Action;
 
@@ -29,16 +30,19 @@ import android.support.v4.app.Notificati
  * <li>messages from other clients.</li>
  * </ul>
  */
 public class FxAccountNotificationManager {
   private static final String LOG_TAG = FxAccountNotificationManager.class.getSimpleName();
 
   protected final int notificationId;
 
+  // We're lazy about updating our locale info, because most syncs don't notify.
+  private volatile boolean localeUpdated;
+
   public FxAccountNotificationManager(int notificationId) {
     this.notificationId = notificationId;
   }
 
   /**
    * Reflect new Firefox Account state to the notification manager: show or hide
    * notifications reflecting the state of a Firefox Account.
    *
@@ -53,16 +57,21 @@ public class FxAccountNotificationManage
     final State state = fxAccount.getState();
     final Action action = state.getNeededAction();
     if (action == Action.None) {
       Logger.info(LOG_TAG, "State " + state.getStateLabel() + " needs no action; cancelling any existing notification.");
       notificationManager.cancel(notificationId);
       return;
     }
 
+    if (!localeUpdated) {
+      localeUpdated = true;
+      BrowserLocaleManager.getInstance().getAndApplyPersistedLocale(context);
+    }
+
     final String title = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_title);
     final String text = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_text, state.email);
     Logger.info(LOG_TAG, "State " + state.getStateLabel() + " needs action; offering notification with title: " + title);
     FxAccountConstants.pii(LOG_TAG, "And text: " + text);
 
     final Intent notificationIntent = new Intent(context, FxAccountStatusActivity.class);
     final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
 
--- a/mobile/android/base/sync/CommandProcessor.java
+++ b/mobile/android/base/sync/CommandProcessor.java
@@ -7,16 +7,17 @@ package org.mozilla.gecko.sync;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
+import org.mozilla.gecko.BrowserLocaleManager;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.sync.repositories.NullCursorException;
 import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
 import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
 
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -234,33 +235,42 @@ public class CommandProcessor {
       db.store(clientID, command);
     } catch (NullCursorException e) {
       Logger.error(LOG_TAG, "NullCursorException: Unable to send command.");
     } finally {
       db.close();
     }
   }
 
+  private static volatile boolean didUpdateLocale = false;
+
   @SuppressWarnings("deprecation")
   public static void displayURI(final List<String> args, final Context context) {
     // We trust the client sender that these exist.
     final String uri = args.get(0);
     final String clientId = args.get(1);
 
     Logger.pii(LOG_TAG, "Received a URI for display: " + uri + " from " + clientId);
 
     String title = null;
     if (args.size() == 3) {
       title = args.get(2);
     }
 
+    // We don't care too much about races, but let's try to avoid
+    // unnecessary work.
+    if (!didUpdateLocale) {
+      BrowserLocaleManager.getInstance().getAndApplyPersistedLocale(context);
+      didUpdateLocale = true;
+    }
+
     final String ns = Context.NOTIFICATION_SERVICE;
     final NotificationManager notificationManager = (NotificationManager) context.getSystemService(ns);
 
-    // Create a Notificiation.
+    // Create a Notification.
     final int icon = R.drawable.icon;
     String notificationTitle = context.getString(R.string.sync_new_tab);
     if (title != null) {
       notificationTitle = notificationTitle.concat(": " + title);
     }
 
     final long when = System.currentTimeMillis();
     Notification notification = new Notification(icon, notificationTitle, when);
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/sync/setup/activities/LocaleAware.java
@@ -0,0 +1,59 @@
+/* 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.sync.setup.activities;
+
+import org.mozilla.gecko.BrowserLocaleManager;
+import org.mozilla.gecko.LocaleManager;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.StrictMode;
+import android.support.v4.app.FragmentActivity;
+
+/**
+ * This is a helper class to do typical locale switching operations
+ * without hitting StrictMode errors or adding boilerplate to common
+ * activity subclasses.
+ *
+ * Either call {@link LocaleAware#initializeLocale(Context)} in your
+ * <code>onCreate</code> method, or inherit from <code>LocaleAwareFragmentActivity</code>
+ * or <code>LocaleAwareActivity</code>.
+ */
+public class LocaleAware {
+  @TargetApi(Build.VERSION_CODES.GINGERBREAD)
+  public static void initializeLocale(Context context) {
+    final LocaleManager localeManager = BrowserLocaleManager.getInstance();
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {
+      localeManager.getAndApplyPersistedLocale(context);
+    } else {
+      final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
+      StrictMode.allowThreadDiskWrites();
+      try {
+        localeManager.getAndApplyPersistedLocale(context);
+      } finally {
+        StrictMode.setThreadPolicy(savedPolicy);
+      }
+    }
+  }
+
+  public static class LocaleAwareFragmentActivity extends FragmentActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+      LocaleAware.initializeLocale(getApplicationContext());
+      super.onCreate(savedInstanceState);
+    }
+  }
+
+  public static class LocaleAwareActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+      LocaleAware.initializeLocale(getApplicationContext());
+      super.onCreate(savedInstanceState);
+    }
+  }
+}
--- a/mobile/android/base/sync/setup/activities/SendTabActivity.java
+++ b/mobile/android/base/sync/setup/activities/SendTabActivity.java
@@ -22,33 +22,34 @@ import org.mozilla.gecko.sync.CommandPro
 import org.mozilla.gecko.sync.CommandRunner;
 import org.mozilla.gecko.sync.GlobalSession;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.SyncConstants;
 import org.mozilla.gecko.sync.repositories.NullCursorException;
 import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
 import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
 import org.mozilla.gecko.sync.setup.SyncAccounts;
+import org.mozilla.gecko.sync.setup.activities.LocaleAware.LocaleAwareActivity;
 import org.mozilla.gecko.sync.stage.SyncClientsEngineStage;
 import org.mozilla.gecko.sync.syncadapter.SyncAdapter;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.view.View;
 import android.widget.ListView;
 import android.widget.TextView;
 import android.widget.Toast;
 
-public class SendTabActivity extends Activity {
+public class SendTabActivity extends LocaleAwareActivity {
   private interface TabSender {
     static final String[] CLIENTS_STAGE = new String[] { SyncClientsEngineStage.COLLECTION_NAME };
 
     /**
      * @return Return null if the account isn't correctly initialized. Return
      *         the account GUID otherwise.
      */
     String getAccountGUID();
--- a/mobile/android/services/manifests/SyncAndroidManifest_activities.xml.in
+++ b/mobile/android/services/manifests/SyncAndroidManifest_activities.xml.in
@@ -72,24 +72,21 @@
             <intent-filter>
                 <action android:name="@MOZ_ANDROID_SHARED_ACCOUNT_TYPE@.accounts.SYNC_ACCOUNT_DELETED_ACTION"/>
             </intent-filter>
         </receiver>
 
         <activity
             android:theme="@style/SyncTheme"
             android:excludeFromRecents="true"
-            android:exported="true"
             android:icon="@drawable/icon"
             android:label="@string/sync_app_name"
             android:configChanges="keyboardHidden|orientation|screenSize"
             android:windowSoftInputMode="adjustResize|stateHidden"
             android:taskAffinity="org.mozilla.gecko.sync.setup"
             android:name="org.mozilla.gecko.sync.setup.activities.SendTabActivity" >
 
-#ifndef RELEASE_BUILD
             <intent-filter>
                 <action android:name="android.intent.action.SEND" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:mimeType="text/plain" />
             </intent-filter>
-#endif
         </activity>