Bug 1436271: Replace calls to String.getBytes(String) with String.getBytes(Charset) r=nalexander
☠☠ backed out by 1ef299d3c4d5 ☠ ☠
authorAndrew Gaul <andrew@gaul.org>
Tue, 06 Feb 2018 22:12:33 -0800
changeset 402850 1889332abc68656da75d1c9f0253e27b95f140ad
parent 402849 c3d4f0c67903f5dd69753a3ff8aa16254fa242aa
child 402851 1ef299d3c4d519a6348e186f92127d70917df113
push id33405
push usershindli@mozilla.com
push dateThu, 08 Feb 2018 10:04:47 +0000
treeherdermozilla-central@0ac953fcddf1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnalexander
bugs1436271
milestone60.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 1436271: Replace calls to String.getBytes(String) with String.getBytes(Charset) r=nalexander Also replace calls to String(byte[], String) with String(byte[], Charset). This removes some cannot-happen exception handling.
mobile/android/app/src/test/java/org/mozilla/gecko/dlc/TestDownloadAction.java
mobile/android/app/src/test/java/org/mozilla/gecko/dlc/catalog/TestDownloadContentCatalog.java
mobile/android/app/src/test/java/org/mozilla/gecko/push/TestPushState.java
mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java
mobile/android/base/java/org/mozilla/gecko/db/LoginsProvider.java
mobile/android/base/java/org/mozilla/gecko/dlc/catalog/DownloadContentCatalog.java
mobile/android/base/java/org/mozilla/gecko/icons/storage/DiskStorage.java
mobile/android/base/java/org/mozilla/gecko/push/PushState.java
mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccount20CreateDelegate.java
mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccount20LoginDelegate.java
mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient20.java
mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountUtils.java
mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/QuickPasswordStretcher.java
mobile/android/services/src/main/java/org/mozilla/gecko/browserid/JSONWebTokenUtils.java
mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AndroidFxAccount.java
mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncAdapter.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/CryptoRecord.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/ExtendedJSONObject.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/Utils.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/HKDF.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/KeyBundle.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/Crypto5MiddlewareRepositorySession.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HMACAuthHeaderProvider.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HawkAuthHeaderProvider.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/Record.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SyncClientsEngineStage.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/telemetry/TelemetryCollector.java
mobile/android/services/src/main/java/org/mozilla/gecko/util/PRNGFixes.java
mobile/android/services/src/test/java/org/mozilla/android/sync/net/test/TestCredentialsEndToEnd.java
mobile/android/services/src/test/java/org/mozilla/android/sync/test/TestCollectionKeys.java
mobile/android/services/src/test/java/org/mozilla/android/sync/test/TestCryptoRecord.java
mobile/android/services/src/test/java/org/mozilla/gecko/fxa/login/TestFxAccountLoginStateMachine.java
mobile/android/services/src/test/java/org/mozilla/gecko/sync/crypto/test/TestCryptoInfo.java
mobile/android/services/src/test/java/org/mozilla/gecko/sync/crypto/test/TestKeyBundle.java
mobile/android/services/src/test/java/org/mozilla/gecko/sync/net/test/TestHawkAuthHeaderProvider.java
mobile/android/services/src/test/java/org/mozilla/gecko/sync/net/test/TestLiveHawkAuth.java
mobile/android/services/src/test/java/org/mozilla/gecko/sync/telemetry/TelemetryCollectorTest.java
mobile/android/services/src/test/java/org/mozilla/gecko/sync/test/TestExtendedJSONObject.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testNativeCrypto.java
--- a/mobile/android/app/src/test/java/org/mozilla/gecko/dlc/TestDownloadAction.java
+++ b/mobile/android/app/src/test/java/org/mozilla/gecko/dlc/TestDownloadAction.java
@@ -10,16 +10,17 @@ import android.content.Context;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.dlc.catalog.DownloadContent;
 import org.mozilla.gecko.dlc.catalog.DownloadContentBuilder;
 import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
+import org.mozilla.gecko.util.StringUtils;
 import org.robolectric.RuntimeEnvironment;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.net.HttpURLConnection;
 import java.util.Arrays;
@@ -218,17 +219,17 @@ public class TestDownloadAction {
         doReturn(true).when(action).verify(eq(temporaryFile), anyString());
         doNothing().when(action).extract(eq(temporaryFile), eq(destinationFile), anyString());
 
         action.perform(RuntimeEnvironment.application, catalog);
 
         verify(connection).getInputStream();
         verify(connection).setRequestProperty("Range", "bytes=1337-");
 
-        Assert.assertEquals("HelloWorld", new String(outputStream.toByteArray(), "UTF-8"));
+        Assert.assertEquals("HelloWorld", new String(outputStream.toByteArray(), StringUtils.UTF_8));
 
         verify(action).openFile(eq(temporaryFile), eq(true));
         verify(catalog).markAsDownloaded(content);
         verify(temporaryFile).delete();
     }
 
     /**
      * Scenario: Download fails with IOException.
@@ -630,13 +631,13 @@ public class TestDownloadAction {
 
         return file;
     }
 
     private static HttpURLConnection mockHttpURLConnection(int statusCode, String content) throws Exception {
         HttpURLConnection connection = mock(HttpURLConnection.class);
 
         doReturn(statusCode).when(connection).getResponseCode();
-        doReturn(new ByteArrayInputStream(content.getBytes("UTF-8"))).when(connection).getInputStream();
+        doReturn(new ByteArrayInputStream(content.getBytes(StringUtils.UTF_8))).when(connection).getInputStream();
 
         return connection;
     }
 }
--- a/mobile/android/app/src/test/java/org/mozilla/gecko/dlc/catalog/TestDownloadContentCatalog.java
+++ b/mobile/android/app/src/test/java/org/mozilla/gecko/dlc/catalog/TestDownloadContentCatalog.java
@@ -9,16 +9,17 @@ import android.support.v4.util.ArrayMap;
 import android.support.v4.util.AtomicFile;
 
 import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.utils.StringUtils;
 
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -32,17 +33,17 @@ public class TestDownloadContentCatalog 
      *
      * Verify that:
      *  * Catalog has not changed
      *  * Unchanged catalog will not be saved to disk
      */
     @Test
     public void testUntouchedCatalogHasNotChangedAndWillNotBePersisted() throws Exception {
         AtomicFile file = mock(AtomicFile.class);
-        doReturn("{content:[]}".getBytes("UTF-8")).when(file).readFully();
+        doReturn("{content:[]}".getBytes(StringUtils.UTF_8)).when(file).readFully();
 
         DownloadContentCatalog catalog = spy(new DownloadContentCatalog(file));
         catalog.loadFromDisk();
 
         Assert.assertFalse("Catalog has not changed", catalog.hasCatalogChanged());
 
         catalog.writeToDisk();
 
--- a/mobile/android/app/src/test/java/org/mozilla/gecko/push/TestPushState.java
+++ b/mobile/android/app/src/test/java/org/mozilla/gecko/push/TestPushState.java
@@ -2,16 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.push;
 
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.util.StringUtils;
 import org.robolectric.RuntimeEnvironment;
 
 import java.io.File;
 import java.io.FileOutputStream;
 
 @RunWith(TestRunner.class)
 public class TestPushState {
     @Test
@@ -52,17 +53,17 @@ public class TestPushState {
     @Test
     public void testCorruptedJSON() throws Exception {
         // Write some malformed JSON.
         // TODO: use mcomella's helpers!
         final File file = new File(RuntimeEnvironment.application.getApplicationInfo().dataDir, "testCorruptedJSON.json");
         FileOutputStream fos = null;
         try {
             fos = new FileOutputStream(file);
-            fos.write("}".getBytes("UTF-8"));
+            fos.write("}".getBytes(StringUtils.UTF_8));
         } finally {
             if (fos != null) {
                 fos.close();
             }
         }
 
         final PushState state = new PushState(RuntimeEnvironment.application, "testCorruptedJSON.json");
         Assert.assertTrue(state.getRegistrations().isEmpty());
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java
@@ -38,16 +38,17 @@ import org.mozilla.gecko.db.BrowserContr
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.reader.SavedReaderViewHelper;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.SynchronizerConfiguration;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.repositories.android.RepoUtils;
 import org.mozilla.gecko.util.FileUtils;
+import org.mozilla.gecko.util.StringUtils;
 
 import static org.mozilla.gecko.db.DBUtils.qualifyColumn;
 
 import android.accounts.Account;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.database.Cursor;
@@ -1777,28 +1778,23 @@ public class BrowserDatabaseHelper exten
     // Get the cache path for a URL, based on the storage format in place during the 27to28 transition.
     // This is a reimplementation of _toHashedPath from ReaderMode.jsm - given that we're likely
     // to migrate the SavedReaderViewHelper implementation at some point, it seems safest to have a local
     // implementation here - moreover this is probably faster than calling into JS.
     // This is public only to allow for testing.
     @RobocopTarget
     public static String getReaderCacheFileNameForURL(String url) {
         try {
-            // On KitKat and above we can use java.nio.charset.StandardCharsets.UTF_8 in place of "UTF8"
-            // which avoids having to handle UnsupportedCodingException
-            byte[] utf8 = url.getBytes("UTF8");
+            byte[] utf8 = url.getBytes(StringUtils.UTF_8);
 
             final MessageDigest digester = MessageDigest.getInstance("MD5");
             byte[] hash = digester.digest(utf8);
 
             final String hashString = new Base32().encodeAsString(hash);
             return hashString.substring(0, hashString.indexOf('=')) + ".json";
-        } catch (UnsupportedEncodingException e) {
-            // This should never happen
-            throw new IllegalStateException("UTF8 encoding not available - can't process readercache filename");
         } catch (NoSuchAlgorithmException e) {
             // This should also never happen
             throw new IllegalStateException("MD5 digester unavailable - can't process readercache filename");
         }
     }
 
     /*
      * Moves reading list items from the 'reading_list' table back into the 'bookmarks' table. This time the
--- a/mobile/android/base/java/org/mozilla/gecko/db/LoginsProvider.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/LoginsProvider.java
@@ -493,28 +493,28 @@ public class LoginsProvider extends Shar
         }
 
         return newCursor;
     }
 
     private String encrypt(@NonNull String initialValue) {
         try {
             final Cipher cipher = getCipher(Cipher.ENCRYPT_MODE);
-            return Base64.encodeToString(cipher.doFinal(initialValue.getBytes("UTF-8")), Base64.URL_SAFE);
+            return Base64.encodeToString(cipher.doFinal(initialValue.getBytes(StringUtils.UTF_8)), Base64.URL_SAFE);
         } catch (Exception e) {
             debug("encryption failed : " + e);
             throw new IllegalStateException("Logins encryption failed", e);
         }
     }
 
     private String decrypt(@NonNull String initialValue) {
         try {
             final Cipher cipher = getCipher(Cipher.DECRYPT_MODE);
             return new String(cipher.doFinal(Base64.decode(
-                    initialValue.getBytes("UTF-8"), Base64.URL_SAFE)), StringUtils.UTF_8);
+                    initialValue.getBytes(StringUtils.UTF_8), Base64.URL_SAFE)), StringUtils.UTF_8);
         } catch (Exception e) {
             debug("Decryption failed : " + e);
             throw new IllegalStateException("Logins decryption failed", e);
         }
     }
 
     private Cipher getCipher(int mode) throws UnsupportedEncodingException, GeneralSecurityException {
         return new NullCipher();
--- a/mobile/android/base/java/org/mozilla/gecko/dlc/catalog/DownloadContentCatalog.java
+++ b/mobile/android/base/java/org/mozilla/gecko/dlc/catalog/DownloadContentCatalog.java
@@ -9,22 +9,22 @@ import android.content.Context;
 import android.support.annotation.Nullable;
 import android.support.v4.util.ArrayMap;
 import android.support.v4.util.AtomicFile;
 import android.util.Log;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
+import org.mozilla.gecko.util.StringUtils;
 
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 import java.util.List;
 
 /**
  * Catalog of downloadable content (DLC).
  *
  * Changing elements returned by the catalog should be guarded by the catalog instance to guarantee visibility when
  * persisting changes.
@@ -213,35 +213,31 @@ public class DownloadContentCatalog {
         }
 
         ArrayMap<String, DownloadContent> loadedContent = new ArrayMap<>();
 
         try {
             JSONObject catalog;
 
             synchronized (file) {
-                catalog = new JSONObject(new String(file.readFully(), "UTF-8"));
+                catalog = new JSONObject(new String(file.readFully(), StringUtils.UTF_8));
             }
 
             JSONArray array = catalog.getJSONArray(JSON_KEY_CONTENT);
             for (int i = 0; i < array.length(); i++) {
                 DownloadContent currentContent = DownloadContentBuilder.fromJSON(array.getJSONObject(i));
                 loadedContent.put(currentContent.getId(), currentContent);
             }
         } catch (FileNotFoundException e) {
             Log.d(LOGTAG, "Catalog file does not exist: Starting with empty catalog.");
             loadedContent = new ArrayMap<>();
         } catch (JSONException | NullPointerException e) {
             Log.w(LOGTAG, "Unable to parse catalog JSON. Re-creating empty catalog.", e);
             loadedContent = new ArrayMap<>();
             hasCatalogChanged = true; // Indicate that we want to persist the new catalog
-        } catch (UnsupportedEncodingException e) {
-            AssertionError error = new AssertionError("Should not happen: This device does not speak UTF-8");
-            error.initCause(e);
-            throw error;
         } catch (IOException e) {
             Log.d(LOGTAG, "Can't read catalog due to IOException", e);
         }
 
         onCatalogLoaded(loadedContent);
 
         notifyAll();
 
@@ -270,25 +266,21 @@ public class DownloadContentCatalog {
                 JSONArray array = new JSONArray();
                 for (DownloadContent currentContent : content.values()) {
                     array.put(DownloadContentBuilder.toJSON(currentContent));
                 }
 
                 JSONObject catalog = new JSONObject();
                 catalog.put(JSON_KEY_CONTENT, array);
 
-                outputStream.write(catalog.toString().getBytes("UTF-8"));
+                outputStream.write(catalog.toString().getBytes(StringUtils.UTF_8));
 
                 file.finishWrite(outputStream);
 
                 hasCatalogChanged = false;
-            } catch (UnsupportedEncodingException e) {
-                AssertionError error = new AssertionError("Should not happen: This device does not speak UTF-8");
-                error.initCause(e);
-                throw error;
             } catch (IOException | JSONException e) {
                 Log.e(LOGTAG, "IOException during writing catalog", e);
 
                 if (outputStream != null) {
                     file.failWrite(outputStream);
                 }
             }
         }
--- a/mobile/android/base/java/org/mozilla/gecko/icons/storage/DiskStorage.java
+++ b/mobile/android/base/java/org/mozilla/gecko/icons/storage/DiskStorage.java
@@ -14,22 +14,22 @@ import android.util.Log;
 
 import com.jakewharton.disklrucache.DiskLruCache;
 
 import org.mozilla.gecko.background.nativecode.NativeCrypto;
 import org.mozilla.gecko.icons.IconRequest;
 import org.mozilla.gecko.icons.IconResponse;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.util.IOUtils;
+import org.mozilla.gecko.util.StringUtils;
 
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
 import java.security.MessageDigest;
 
 /**
  * Least Recently Used (LRU) disk cache for icons and the mappings from page URLs to icon URLs.
  */
 public class DiskStorage {
     private static final String LOGTAG = "Gecko/DiskStorage";
 
@@ -252,37 +252,35 @@ public class DiskStorage {
         try {
             // We use our own crypto implementation to avoid the penalty of loading the java crypto
             // framework.
             byte[] ctx = NativeCrypto.sha256init();
             if (ctx == null) {
                 return null;
             }
 
-            byte[] data = prefix.getBytes("UTF-8");
+            byte[] data = prefix.getBytes(StringUtils.UTF_8);
             NativeCrypto.sha256update(ctx, data, data.length);
 
-            data = url.getBytes("UTF-8");
+            data = url.getBytes(StringUtils.UTF_8);
             NativeCrypto.sha256update(ctx, data, data.length);
             return Utils.byte2Hex(NativeCrypto.sha256finalize(ctx));
         } catch (NoClassDefFoundError | ExceptionInInitializerError error) {
             // We could not load libmozglue.so. Let's use Java's MessageDigest as fallback. We do
             // this primarily for our unit tests that can't load native libraries. On an device
             // we will have a lot of other problems if we can't load libmozglue.so
             try {
                 MessageDigest md = MessageDigest.getInstance("SHA-256");
-                md.update(prefix.getBytes("UTF-8"));
-                md.update(url.getBytes("UTF-8"));
+                md.update(prefix.getBytes(StringUtils.UTF_8));
+                md.update(url.getBytes(StringUtils.UTF_8));
                 return Utils.byte2Hex(md.digest());
             } catch (Exception e) {
                 // Just give up. And let everyone know.
                 throw new RuntimeException(e);
             }
-        } catch (UnsupportedEncodingException e) {
-            throw new AssertionError("Should not happen: Device does not understand UTF-8");
         }
     }
 
     private void abortSilently(DiskLruCache.Editor editor) {
         if (editor != null) {
             try {
                 editor.abort();
             } catch (IOException e) {
--- a/mobile/android/base/java/org/mozilla/gecko/push/PushState.java
+++ b/mobile/android/base/java/org/mozilla/gecko/push/PushState.java
@@ -8,16 +8,17 @@ package org.mozilla.gecko.push;
 import android.content.Context;
 import android.support.annotation.NonNull;
 import android.support.annotation.WorkerThread;
 import android.support.v4.util.AtomicFile;
 import android.util.Log;
 
 import org.json.JSONException;
 import org.json.JSONObject;
+import org.mozilla.gecko.util.StringUtils;
 
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
@@ -41,17 +42,17 @@ public class PushState {
     protected final @NonNull Map<String, PushRegistration> registrations;
 
     public PushState(Context context, @NonNull String fileName) {
         this.registrations = new HashMap<>();
 
         file = new AtomicFile(new File(context.getApplicationInfo().dataDir, fileName));
         synchronized (file) {
             try {
-                final String s = new String(file.readFully(), "UTF-8");
+                final String s = new String(file.readFully(), StringUtils.UTF_8);
                 final JSONObject temp = new JSONObject(s);
                 if (temp.optLong("version", 0L) != VERSION) {
                     throw new JSONException("Unknown version!");
                 }
 
                 final JSONObject registrationsObject = temp.getJSONObject("registrations");
                 final Iterator<String> it = registrationsObject.keys();
                 while (it.hasNext()) {
@@ -86,17 +87,17 @@ public class PushState {
      * @return whether the cache was persisted successfully.
      */
     @WorkerThread
     public boolean checkpoint() {
         synchronized (file) {
             FileOutputStream fileOutputStream = null;
             try {
                 fileOutputStream = file.startWrite();
-                fileOutputStream.write(toJSONObject().toString().getBytes("UTF-8"));
+                fileOutputStream.write(toJSONObject().toString().getBytes(StringUtils.UTF_8));
                 file.finishWrite(fileOutputStream);
                 return true;
             } catch (JSONException | IOException e) {
                 Log.e(LOG_TAG, "Got exception writing JSON storage; ignoring.", e);
                 if (fileOutputStream != null) {
                     file.failWrite(fileOutputStream);
                 }
                 return false;
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccount20CreateDelegate.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccount20CreateDelegate.java
@@ -1,16 +1,17 @@
 /* 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 org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.util.StringUtils;
 
 import java.io.UnsupportedEncodingException;
 import java.security.GeneralSecurityException;
 
 public class FxAccount20CreateDelegate {
   protected final byte[] emailUTF8;
   protected final byte[] authPW;
   protected final boolean preVerified;
@@ -20,33 +21,28 @@ public class FxAccount20CreateDelegate {
    *
    * @param emailUTF8
    *          email as UTF-8 bytes.
    * @param quickStretchedPW
    *          quick stretched password as bytes.
    * @param preVerified
    *          true if account should be marked already verified; only effective
    *          for non-production auth servers.
-   * @throws UnsupportedEncodingException
    * @throws GeneralSecurityException
    */
   public FxAccount20CreateDelegate(byte[] emailUTF8, byte[] quickStretchedPW, boolean preVerified) throws UnsupportedEncodingException, GeneralSecurityException {
     this.emailUTF8 = emailUTF8;
     this.authPW = FxAccountUtils.generateAuthPW(quickStretchedPW);
     this.preVerified = preVerified;
   }
 
   public ExtendedJSONObject getCreateBody() throws FxAccountClientException {
     final ExtendedJSONObject body = new ExtendedJSONObject();
-    try {
-      body.put("email", new String(emailUTF8, "UTF-8"));
-      body.put("authPW", Utils.byte2Hex(authPW));
-      if (preVerified) {
-        // Production endpoints do not allow preVerified; this assumes we only
-        // set it when it's okay to send it.
-        body.put("preVerified", preVerified);
-      }
-      return body;
-    } catch (UnsupportedEncodingException e) {
-      throw new FxAccountClientException(e);
+    body.put("email", new String(emailUTF8, StringUtils.UTF_8));
+    body.put("authPW", Utils.byte2Hex(authPW));
+    if (preVerified) {
+      // Production endpoints do not allow preVerified; this assumes we only
+      // set it when it's okay to send it.
+      body.put("preVerified", preVerified);
     }
+    return body;
   }
 }
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccount20LoginDelegate.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccount20LoginDelegate.java
@@ -1,16 +1,17 @@
 /* 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 org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.util.StringUtils;
 
 import java.io.UnsupportedEncodingException;
 import java.security.GeneralSecurityException;
 
 /**
  * An abstraction around providing an email and authorization token to the auth
  * server.
  */
@@ -18,19 +19,15 @@ public class FxAccount20LoginDelegate {
   protected final byte[] emailUTF8;
   protected final byte[] authPW;
 
   public FxAccount20LoginDelegate(byte[] emailUTF8, byte[] quickStretchedPW) throws UnsupportedEncodingException, GeneralSecurityException {
     this.emailUTF8 = emailUTF8;
     this.authPW = FxAccountUtils.generateAuthPW(quickStretchedPW);
   }
 
-  public ExtendedJSONObject getCreateBody() throws FxAccountClientException {
+  public ExtendedJSONObject getCreateBody() {
     final ExtendedJSONObject body = new ExtendedJSONObject();
-    try {
-      body.put("email", new String(emailUTF8, "UTF-8"));
-      body.put("authPW", Utils.byte2Hex(authPW));
-      return body;
-    } catch (UnsupportedEncodingException e) {
-      throw new FxAccountClientException(e);
-    }
+    body.put("email", new String(emailUTF8, StringUtils.UTF_8));
+    body.put("authPW", Utils.byte2Hex(authPW));
+    return body;
   }
 }
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient20.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient20.java
@@ -19,16 +19,17 @@ import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.crypto.HKDF;
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.BaseResourceDelegate;
 import org.mozilla.gecko.sync.net.HawkAuthHeaderProvider;
 import org.mozilla.gecko.sync.net.Resource;
 import org.mozilla.gecko.sync.net.SyncResponse;
 import org.mozilla.gecko.sync.net.SyncStorageResponse;
+import org.mozilla.gecko.util.StringUtils;
 
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URLEncoder;
 import java.security.GeneralSecurityException;
 import java.security.InvalidKeyException;
@@ -642,17 +643,17 @@ public class FxAccountClient20 implement
 
         String uid = body.getString(JSON_KEY_UID);
         boolean verified = body.getBoolean(JSON_KEY_VERIFIED);
         byte[] sessionToken = Utils.hex2Byte(body.getString(JSON_KEY_SESSIONTOKEN));
         byte[] keyFetchToken = null;
         if (getKeys) {
           keyFetchToken = Utils.hex2Byte(body.getString(JSON_KEY_KEYFETCHTOKEN));
         }
-        LoginResponse loginResponse = new LoginResponse(new String(emailUTF8, "UTF-8"), uid, verified, sessionToken, keyFetchToken);
+        LoginResponse loginResponse = new LoginResponse(new String(emailUTF8, StringUtils.UTF_8), uid, verified, sessionToken, keyFetchToken);
 
         delegate.handleSuccess(loginResponse);
       }
     };
 
     post(resource, body);
   }
 
@@ -692,17 +693,17 @@ public class FxAccountClient20 implement
         if (tempVerified != null) {
           verified = tempVerified;
         }
         byte[] sessionToken = Utils.hex2Byte(body.getString(JSON_KEY_SESSIONTOKEN));
         byte[] keyFetchToken = null;
         if (getKeys) {
           keyFetchToken = Utils.hex2Byte(body.getString(JSON_KEY_KEYFETCHTOKEN));
         }
-        LoginResponse loginResponse = new LoginResponse(new String(emailUTF8, "UTF-8"), uid, verified, sessionToken, keyFetchToken);
+        LoginResponse loginResponse = new LoginResponse(new String(emailUTF8, StringUtils.UTF_8), uid, verified, sessionToken, keyFetchToken);
 
         delegate.handleSuccess(loginResponse);
       }
     };
 
     post(resource, body);
   }
 
@@ -731,17 +732,17 @@ public class FxAccountClient20 implement
    * @param delegate
    *          to invoke callbacks.
    */
   public void login(final byte[] emailUTF8, final PasswordStretcher stretcher, final boolean getKeys,
                     final Map<String, String> queryParameters,
                     final RequestDelegate<LoginResponse> delegate) {
     byte[] quickStretchedPW;
     try {
-      FxAccountUtils.pii(LOG_TAG, "Trying user provided email: '" + new String(emailUTF8, "UTF-8") + "'" );
+      FxAccountUtils.pii(LOG_TAG, "Trying user provided email: '" + new String(emailUTF8, StringUtils.UTF_8) + "'" );
       quickStretchedPW = stretcher.getQuickStretchedPW(emailUTF8);
     } catch (Exception e) {
       delegate.handleError(e);
       return;
     }
 
     this.login(emailUTF8, quickStretchedPW, getKeys, queryParameters, new RequestDelegate<LoginResponse>() {
       @Override
@@ -763,17 +764,17 @@ public class FxAccountClient20 implement
         };
 
         Logger.info(LOG_TAG, "Server returned alternate email; retrying login with provided email.");
         FxAccountUtils.pii(LOG_TAG, "Trying server provided email: '" + alternateEmail + "'" );
 
         try {
           // Nota bene: this is not recursive, since we call the fixed password
           // signature here, which invokes a non-retrying version.
-          byte[] alternateEmailUTF8 = alternateEmail.getBytes("UTF-8");
+          byte[] alternateEmailUTF8 = alternateEmail.getBytes(StringUtils.UTF_8);
           byte[] alternateQuickStretchedPW = stretcher.getQuickStretchedPW(alternateEmailUTF8);
           login(alternateEmailUTF8, alternateQuickStretchedPW, getKeys, queryParameters, delegate);
         } catch (Exception innerException) {
           delegate.handleError(innerException);
           return;
         }
       }
     });
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountUtils.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountUtils.java
@@ -15,16 +15,17 @@ import java.security.NoSuchAlgorithmExce
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.nativecode.NativeCrypto;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.crypto.HKDF;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.crypto.PBKDF2;
+import org.mozilla.gecko.util.StringUtils;
 
 import android.content.Context;
 
 public class FxAccountUtils {
   private static final String LOG_TAG = FxAccountUtils.class.getSimpleName();
 
   public static final int SALT_LENGTH_BYTES = 32;
   public static final int SALT_LENGTH_HEX = 2 * SALT_LENGTH_BYTES;
@@ -44,40 +45,40 @@ public class FxAccountUtils {
   public static boolean LOG_PERSONAL_INFORMATION = false;
 
   public static void pii(String tag, String message) {
     if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
       Logger.info(tag, "$$FxA PII$$: " + message);
     }
   }
 
-  public static String bytes(String string) throws UnsupportedEncodingException {
-    return Utils.byte2Hex(string.getBytes("UTF-8"));
+  public static String bytes(String string) {
+    return Utils.byte2Hex(string.getBytes(StringUtils.UTF_8));
   }
 
-  public static byte[] KW(String name) throws UnsupportedEncodingException {
+  public static byte[] KW(String name) {
     return Utils.concatAll(
-        KW_VERSION_STRING.getBytes("UTF-8"),
-        name.getBytes("UTF-8"));
+        KW_VERSION_STRING.getBytes(StringUtils.UTF_8),
+        name.getBytes(StringUtils.UTF_8));
   }
 
-  public static byte[] KWE(String name, byte[] emailUTF8) throws UnsupportedEncodingException {
+  public static byte[] KWE(String name, byte[] emailUTF8) {
     return Utils.concatAll(
-        KW_VERSION_STRING.getBytes("UTF-8"),
-        name.getBytes("UTF-8"),
-        ":".getBytes("UTF-8"),
+        KW_VERSION_STRING.getBytes(StringUtils.UTF_8),
+        name.getBytes(StringUtils.UTF_8),
+        ":".getBytes(StringUtils.UTF_8),
         emailUTF8);
   }
 
   /**
    * Calculate the SRP verifier <tt>x</tt> value.
    */
   public static BigInteger srpVerifierLowercaseX(byte[] emailUTF8, byte[] srpPWBytes, byte[] srpSaltBytes)
-      throws NoSuchAlgorithmException, UnsupportedEncodingException {
-    byte[] inner = Utils.sha256(Utils.concatAll(emailUTF8, ":".getBytes("UTF-8"), srpPWBytes));
+      throws NoSuchAlgorithmException {
+    byte[] inner = Utils.sha256(Utils.concatAll(emailUTF8, ":".getBytes(StringUtils.UTF_8), srpPWBytes));
     byte[] outer = Utils.sha256(Utils.concatAll(srpSaltBytes, inner));
     return new BigInteger(1, outer);
   }
 
   /**
    * Calculate the SRP verifier <tt>v</tt> value.
    */
   public static BigInteger srpVerifierLowercaseV(byte[] emailUTF8, byte[] srpPWBytes, byte[] srpSaltBytes, BigInteger g, BigInteger N)
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/QuickPasswordStretcher.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/QuickPasswordStretcher.java
@@ -5,31 +5,32 @@
 package org.mozilla.gecko.background.fxa;
 
 import java.io.UnsupportedEncodingException;
 import java.security.GeneralSecurityException;
 import java.util.HashMap;
 import java.util.Map;
 
 import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.util.StringUtils;
 
 public class QuickPasswordStretcher implements PasswordStretcher {
   protected final String password;
   protected final Map<String, String> cache = new HashMap<String, String>();
 
   public QuickPasswordStretcher(String password) {
     this.password = password;
   }
 
   @Override
   public synchronized byte[] getQuickStretchedPW(byte[] emailUTF8) throws UnsupportedEncodingException, GeneralSecurityException {
     if (emailUTF8 == null) {
       throw new IllegalArgumentException("emailUTF8 must not be null");
     }
     String key = Utils.byte2Hex(emailUTF8);
     if (!cache.containsKey(key)) {
-      byte[] value = FxAccountUtils.generateQuickStretchedPW(emailUTF8, password.getBytes("UTF-8"));
+      byte[] value = FxAccountUtils.generateQuickStretchedPW(emailUTF8, password.getBytes(StringUtils.UTF_8));
       cache.put(key, Utils.byte2Hex(value));
       return value;
     }
     return Utils.hex2Byte(cache.get(key));
   }
 }
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/JSONWebTokenUtils.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/JSONWebTokenUtils.java
@@ -1,23 +1,24 @@
 /* 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.browserid;
 
+import static org.mozilla.apache.commons.codec.binary.StringUtils.newStringUtf8;
+
 import org.json.simple.JSONObject;
 import org.mozilla.apache.commons.codec.binary.Base64;
-import org.mozilla.apache.commons.codec.binary.StringUtils;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.util.StringUtils;
 
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
 import java.security.GeneralSecurityException;
 import java.util.ArrayList;
 import java.util.TreeMap;
 
 /**
  * Encode and decode JSON Web Tokens.
  * <p>
  * Reverse-engineered from the Node.js jwcrypto library at
@@ -27,45 +28,45 @@ import java.util.TreeMap;
  */
 public class JSONWebTokenUtils {
   public static final long DEFAULT_CERTIFICATE_DURATION_IN_MILLISECONDS = 60 * 60 * 1000;
   public static final long DEFAULT_ASSERTION_DURATION_IN_MILLISECONDS = 60 * 60 * 1000;
   public static final long DEFAULT_FUTURE_EXPIRES_AT_IN_MILLISECONDS = 9999999999999L;
   public static final String DEFAULT_CERTIFICATE_ISSUER = "127.0.0.1";
   public static final String DEFAULT_ASSERTION_ISSUER = "127.0.0.1";
 
-  public static String encode(String payload, SigningPrivateKey privateKey) throws UnsupportedEncodingException, GeneralSecurityException  {
+  public static String encode(String payload, SigningPrivateKey privateKey) throws GeneralSecurityException  {
     final ExtendedJSONObject header = new ExtendedJSONObject();
     header.put("alg", privateKey.getAlgorithm());
-    String encodedHeader  = Base64.encodeBase64URLSafeString(header.toJSONString().getBytes("UTF-8"));
-    String encodedPayload = Base64.encodeBase64URLSafeString(payload.getBytes("UTF-8"));
+    String encodedHeader  = Base64.encodeBase64URLSafeString(header.toJSONString().getBytes(StringUtils.UTF_8));
+    String encodedPayload = Base64.encodeBase64URLSafeString(payload.getBytes(StringUtils.UTF_8));
     ArrayList<String> segments = new ArrayList<String>();
     segments.add(encodedHeader);
     segments.add(encodedPayload);
-    byte[] message = Utils.toDelimitedString(".", segments).getBytes("UTF-8");
+    byte[] message = Utils.toDelimitedString(".", segments).getBytes(StringUtils.UTF_8);
     byte[] signature = privateKey.signMessage(message);
     segments.add(Base64.encodeBase64URLSafeString(signature));
     return Utils.toDelimitedString(".", segments);
   }
 
-  public static String decode(String token, VerifyingPublicKey publicKey) throws GeneralSecurityException, UnsupportedEncodingException  {
+  public static String decode(String token, VerifyingPublicKey publicKey) throws GeneralSecurityException {
     if (token == null) {
       throw new IllegalArgumentException("token must not be null");
     }
     String[] segments = token.split("\\.");
     if (segments == null || segments.length != 3) {
       throw new GeneralSecurityException("malformed token");
     }
-    byte[] message = (segments[0] + "." + segments[1]).getBytes("UTF-8");
+    byte[] message = (segments[0] + "." + segments[1]).getBytes(StringUtils.UTF_8);
     byte[] signature = Base64.decodeBase64(segments[2]);
     boolean verifies = publicKey.verifyMessage(message, signature);
     if (!verifies) {
       throw new GeneralSecurityException("bad signature");
     }
-    String payload = StringUtils.newStringUtf8(Base64.decodeBase64(segments[1]));
+    String payload = newStringUtf8(Base64.decodeBase64(segments[1]));
     return payload;
   }
 
   /**
    * Public for testing.
    */
   @SuppressWarnings("unchecked")
   public static String getPayloadString(String payloadString, String audience, String issuer,
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AndroidFxAccount.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AndroidFxAccount.java
@@ -39,16 +39,17 @@ import org.mozilla.gecko.fxa.login.State
 import org.mozilla.gecko.fxa.login.State.StateLabel;
 import org.mozilla.gecko.fxa.login.StateFactory;
 import org.mozilla.gecko.fxa.sync.FxAccountProfileService;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.ThreadPool;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.setup.Constants;
+import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URISyntaxException;
 import java.security.GeneralSecurityException;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -476,21 +477,17 @@ public class AndroidFxAccount {
    * determine the user's Firefox Account status and yield access to whatever
    * user data the device has access to.
    *
    * @return JSON-object of Strings.
    */
   public ExtendedJSONObject toJSONObject() {
     ExtendedJSONObject o = unbundle();
     o.put("email", account.name);
-    try {
-      o.put("emailUTF8", Utils.byte2Hex(account.name.getBytes("UTF-8")));
-    } catch (UnsupportedEncodingException e) {
-      // Ignore.
-    }
+    o.put("emailUTF8", Utils.byte2Hex(account.name.getBytes(StringUtils.UTF_8)));
     o.put("fxaDeviceId", getDeviceId());
     o.put("fxaDeviceRegistrationVersion", getDeviceRegistrationVersion());
     o.put("fxaDeviceRegistrationTimestamp", getDeviceRegistrationTimestamp());
     return o;
   }
 
   public static AndroidFxAccount addAndroidAccount(
       @NonNull Context context,
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncAdapter.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncAdapter.java
@@ -49,16 +49,17 @@ import org.mozilla.gecko.sync.net.AuthHe
 import org.mozilla.gecko.sync.net.HawkAuthHeaderProvider;
 import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
 import org.mozilla.gecko.sync.telemetry.TelemetryCollector;
 import org.mozilla.gecko.sync.telemetry.TelemetryContract;
 import org.mozilla.gecko.tokenserver.TokenServerClient;
 import org.mozilla.gecko.tokenserver.TokenServerClientDelegate;
 import org.mozilla.gecko.tokenserver.TokenServerException;
 import org.mozilla.gecko.tokenserver.TokenServerToken;
+import org.mozilla.gecko.util.StringUtils;
 
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.BlockingQueue;
@@ -399,17 +400,17 @@ public class FxAccountSyncAdapter extend
           // global session.
           final SkewHandler storageServerSkewHandler = SkewHandler.getSkewHandlerForHostname(storageHostname);
           final long storageServerSkew = storageServerSkewHandler.getSkewInSeconds();
           // We expect Sync to upload large sets of records. Calculating the
           // payload verification hash for these record sets could be expensive,
           // so we explicitly do not send payload verification hashes to the
           // Sync storage endpoint.
           final boolean includePayloadVerificationHash = false;
-          final AuthHeaderProvider authHeaderProvider = new HawkAuthHeaderProvider(token.id, token.key.getBytes("UTF-8"), includePayloadVerificationHash, storageServerSkew);
+          final AuthHeaderProvider authHeaderProvider = new HawkAuthHeaderProvider(token.id, token.key.getBytes(StringUtils.UTF_8), includePayloadVerificationHash, storageServerSkew);
 
           final Context context = getContext();
           final SyncConfiguration syncConfig = new SyncConfiguration(token.uid, authHeaderProvider, sharedPrefs, syncKeyBundle);
 
           Collection<String> knownStageNames = SyncConfiguration.validEngineNames();
           syncConfig.stagesToSync = Utils.getStagesToSyncFromBundle(knownStageNames, extras);
           syncConfig.setClusterURL(storageServerURI);
 
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CryptoRecord.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CryptoRecord.java
@@ -1,16 +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.sync;
 
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
 
 import org.json.simple.JSONObject;
 import org.mozilla.apache.commons.codec.binary.Base64;
 import org.mozilla.gecko.sync.crypto.CryptoException;
 import org.mozilla.gecko.sync.crypto.CryptoInfo;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.crypto.MissingCryptoInputException;
 import org.mozilla.gecko.sync.crypto.NoKeyBundleException;
@@ -51,21 +50,20 @@ public class CryptoRecord extends Record
   private static final String KEY_IV         = "IV";
 
   /**
    * Helper method for doing actual decryption.
    *
    * Input: JSONObject containing a valid payload (cipherText, IV, HMAC),
    * KeyBundle with keys for decryption. Output: byte[] clearText
    * @throws CryptoException
-   * @throws UnsupportedEncodingException
    */
-  private static byte[] decryptPayload(ExtendedJSONObject payload, KeyBundle keybundle) throws CryptoException, UnsupportedEncodingException {
-    byte[] ciphertext = Base64.decodeBase64(((String) payload.get(KEY_CIPHERTEXT)).getBytes("UTF-8"));
-    byte[] iv         = Base64.decodeBase64(((String) payload.get(KEY_IV)).getBytes("UTF-8"));
+  private static byte[] decryptPayload(ExtendedJSONObject payload, KeyBundle keybundle) throws CryptoException {
+    byte[] ciphertext = Base64.decodeBase64(((String) payload.get(KEY_CIPHERTEXT)).getBytes(StringUtils.UTF_8));
+    byte[] iv         = Base64.decodeBase64(((String) payload.get(KEY_IV)).getBytes(StringUtils.UTF_8));
     byte[] hmac       = Utils.hex2Byte((String) payload.get(KEY_HMAC));
 
     return CryptoInfo.decrypt(ciphertext, iv, hmac, keybundle).getMessage();
   }
 
   // The encrypted JSON body object.
   // The decrypted JSON body object. Fields are copied from `body`.
 
@@ -126,17 +124,17 @@ public class CryptoRecord extends Record
    * @return
    *        A CryptoRecord that encapsulates the provided record.
    *
    * @throws NonObjectJSONException
    * @throws IOException
    */
   public static CryptoRecord fromJSONRecord(String jsonRecord)
       throws NonObjectJSONException, IOException, RecordParseException {
-    byte[] bytes = jsonRecord.getBytes("UTF-8");
+    byte[] bytes = jsonRecord.getBytes(StringUtils.UTF_8);
     ExtendedJSONObject object = ExtendedJSONObject.parseUTF8AsJSONObject(bytes);
 
     return CryptoRecord.fromJSONRecord(object);
   }
 
   // TODO: defensive programming.
   public static CryptoRecord fromJSONRecord(ExtendedJSONObject jsonRecord)
       throws IOException, NonObjectJSONException, RecordParseException {
@@ -196,17 +194,17 @@ public class CryptoRecord extends Record
 
     // There's no difference between handling the crypto/keys object and
     // anything else; we just get this.keyBundle from a different source.
     byte[] cleartext = decryptPayload(payload, keyBundle);
     payload = ExtendedJSONObject.parseUTF8AsJSONObject(cleartext);
     return this;
   }
 
-  public CryptoRecord encrypt() throws CryptoException, UnsupportedEncodingException {
+  public CryptoRecord encrypt() throws CryptoException {
     if (this.keyBundle == null) {
       throw new NoKeyBundleException();
     }
     String cleartext = payload.toJSONString();
     byte[] cleartextBytes = cleartext.getBytes(StringUtils.UTF_8);
     CryptoInfo info = CryptoInfo.encrypt(cleartextBytes, keyBundle);
     String message = new String(Base64.encodeBase64(info.getMessage()));
     String iv      = new String(Base64.encodeBase64(info.getIV()));
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/ExtendedJSONObject.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/ExtendedJSONObject.java
@@ -5,16 +5,17 @@
 package org.mozilla.gecko.sync;
 
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
 import org.json.simple.parser.JSONParser;
 import org.json.simple.parser.ParseException;
 import org.mozilla.apache.commons.codec.binary.Base64;
 import org.mozilla.gecko.sync.UnexpectedJSONException.BadRequiredFieldJSONException;
+import org.mozilla.gecko.util.StringUtils;
 
 import java.io.IOException;
 import java.io.Reader;
 import java.io.StringReader;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
@@ -133,17 +134,17 @@ public class ExtendedJSONObject implemen
    * Helper method to get a JSON object from a UTF-8 byte array.
    *
    * @param in UTF-8 bytes.
    * @throws NonObjectJSONException if the object is not valid JSON or not an object.
    * @throws IOException
    */
   public static ExtendedJSONObject parseUTF8AsJSONObject(byte[] in)
       throws NonObjectJSONException, IOException {
-    return new ExtendedJSONObject(new String(in, "UTF-8"));
+    return new ExtendedJSONObject(new String(in, StringUtils.UTF_8));
   }
 
   public ExtendedJSONObject() {
     this.object = new JSONObject();
   }
 
   public ExtendedJSONObject(JSONObject o) {
     this.object = o;
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Utils.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Utils.java
@@ -2,17 +2,16 @@
  * 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;
 
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
-import java.io.UnsupportedEncodingException;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.net.URLDecoder;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
 import java.text.DecimalFormat;
 import java.util.ArrayList;
@@ -122,21 +121,19 @@ public class Utils {
   /**
    * Utility for Base64 decoding. Should ensure that the correct
    * Apache Commons version is used.
    *
    * @param base64
    *        An input string. Will be decoded as UTF-8.
    * @return
    *        A byte array of decoded values.
-   * @throws UnsupportedEncodingException
-   *         Should not occur.
    */
-  public static byte[] decodeBase64(String base64) throws UnsupportedEncodingException {
-    return Base64.decodeBase64(base64.getBytes("UTF-8"));
+  public static byte[] decodeBase64(String base64) {
+    return Base64.decodeBase64(base64.getBytes(StringUtils.UTF_8));
   }
 
   public static byte[] decodeFriendlyBase32(String base32) {
     Base32 converter = new Base32();
     final String translated = base32.replace('8', 'l').replace('9', 'o');
     return converter.decode(translated.toUpperCase(Locale.US));
   }
 
@@ -197,47 +194,46 @@ public class Utils {
 
   public static byte[] sha256(byte[] in)
       throws NoSuchAlgorithmException {
     MessageDigest sha1 = MessageDigest.getInstance("SHA-256");
     return sha1.digest(in);
   }
 
   protected static byte[] sha1(final String utf8)
-      throws NoSuchAlgorithmException, UnsupportedEncodingException {
-    final byte[] bytes = utf8.getBytes("UTF-8");
+      throws NoSuchAlgorithmException {
+    final byte[] bytes = utf8.getBytes(StringUtils.UTF_8);
     try {
       return NativeCrypto.sha1(bytes);
     } catch (final LinkageError e) {
       // This will throw UnsatisifiedLinkError (missing mozglue) the first time it is called, and
       // ClassNotDefFoundError, for the uninitialized NativeCrypto class, each subsequent time this
       // is called; LinkageError is their common ancestor.
       Logger.warn(LOG_TAG, "Got throwable stretching password using native sha1 implementation; " +
           "ignoring and using Java implementation.", e);
       final MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
-      return sha1.digest(utf8.getBytes("UTF-8"));
+      return sha1.digest(utf8.getBytes(StringUtils.UTF_8));
     }
   }
 
   protected static String sha1Base32(final String utf8)
-      throws NoSuchAlgorithmException, UnsupportedEncodingException {
+      throws NoSuchAlgorithmException {
     return new Base32().encodeAsString(sha1(utf8)).toLowerCase(Locale.US);
   }
 
   /**
    * If we encounter characters not allowed by the API (as found for
    * instance in an email address), hash the value.
    * @param account
    *        An account string.
    * @return
    *        An acceptable string.
-   * @throws UnsupportedEncodingException
    * @throws NoSuchAlgorithmException
    */
-  public static String usernameFromAccount(final String account) throws NoSuchAlgorithmException, UnsupportedEncodingException {
+  public static String usernameFromAccount(final String account) throws NoSuchAlgorithmException {
     if (account == null || account.equals("")) {
       throw new IllegalArgumentException("No account name provided.");
     }
     if (account.matches("^[A-Za-z0-9._-]+$")) {
       return account.toLowerCase(Locale.US);
     }
     return sha1Base32(account.toLowerCase(Locale.US));
   }
@@ -247,20 +243,19 @@ public class Utils {
    *
    * @param product the Firefox Sync product package name (like "org.mozilla.firefox").
    * @param accountKey local Sync account identifier.
    * @param serverURL the Sync account server URL.
    * @param profile the Firefox profile name.
    * @param version the version of preferences to reference.
    * @return the path.
    * @throws NoSuchAlgorithmException
-   * @throws UnsupportedEncodingException
    */
   public static String getPrefsPath(final String product, final String accountKey, final String serverURL, final String profile, final long version)
-      throws NoSuchAlgorithmException, UnsupportedEncodingException {
+      throws NoSuchAlgorithmException {
     final String encodedAccount = sha1Base32(serverURL + ":" + usernameFromAccount(accountKey));
 
     if (version <= 0) {
       return "sync.prefs." + encodedAccount;
     } else {
       final String sanitizedProduct = product.replace('.', '!').replace(' ', '!');
       return "sync.prefs." + sanitizedProduct + "." + encodedAccount + "." + profile + "." + version;
     }
@@ -510,23 +505,23 @@ public class Utils {
 
   /**
    * This will take a string containing a UTF-8 representation of a UTF-8
    * byte array — e.g., "pïgéons1" — and return UTF-8 (e.g., "pïgéons1").
    *
    * This is the format produced by desktop Firefox when exchanging credentials
    * containing non-ASCII characters.
    */
-  public static String decodeUTF8(final String in) throws UnsupportedEncodingException {
+  public static String decodeUTF8(final String in) {
     final int length = in.length();
     final byte[] asciiBytes = new byte[length];
     for (int i = 0; i < length; ++i) {
       asciiBytes[i] = (byte) in.codePointAt(i);
     }
-    return new String(asciiBytes, "UTF-8");
+    return new String(asciiBytes, StringUtils.UTF_8);
   }
 
   /**
    * Replace "foo@bar.com" with "XXX@XXX.XXX".
    */
   public static String obfuscateEmail(final String in) {
     return in.replaceAll("[^@\\.]", "X");
   }
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/HKDF.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/HKDF.java
@@ -7,34 +7,31 @@ package org.mozilla.gecko.sync.crypto;
 import java.security.InvalidKeyException;
 import java.security.Key;
 import java.security.NoSuchAlgorithmException;
 
 import javax.crypto.Mac;
 import javax.crypto.spec.SecretKeySpec;
 
 import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.util.StringUtils;
 
 /*
  * A standards-compliant implementation of RFC 5869
  * for HMAC-based Key Derivation Function.
  * HMAC uses HMAC SHA256 standard.
  */
 public class HKDF {
   public static final String HMAC_ALGORITHM = "hmacSHA256";
 
   /**
    * Used for conversion in cases in which you *know* the encoding exists.
    */
   public static final byte[] bytes(String in) {
-    try {
-      return in.getBytes("UTF-8");
-    } catch (java.io.UnsupportedEncodingException e) {
-      return null;
-    }
+    return in.getBytes(StringUtils.UTF_8);
   }
 
   public static final int BLOCKSIZE     = 256 / 8;
   public static final byte[] HMAC_INPUT = bytes("Sync-AES_256_CBC-HMAC256");
 
   /*
    * Step 1 of RFC 5869
    * Get sha256HMAC Bytes
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/KeyBundle.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/KeyBundle.java
@@ -1,24 +1,24 @@
 /* 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.crypto;
 
-import java.io.UnsupportedEncodingException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
 
 import javax.crypto.KeyGenerator;
 import javax.crypto.Mac;
 
 import org.mozilla.apache.commons.codec.binary.Base64;
 import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.util.StringUtils;
 
 public class KeyBundle {
     private static final String KEY_ALGORITHM_SPEC = "AES";
     private static final int    KEY_SIZE           = 256;
 
     private byte[] encryptionKey;
     private byte[] hmacKey;
 
@@ -39,17 +39,17 @@ public class KeyBundle {
         throw new IllegalArgumentException("No sync key provided.");
       }
       if (username == null || username.equals("")) {
         throw new IllegalArgumentException("No username provided.");
       }
       // Hash appropriately.
       try {
         username = Utils.usernameFromAccount(username);
-      } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
+      } catch (NoSuchAlgorithmException e) {
         throw new IllegalArgumentException("Invalid username.");
       }
 
       byte[] syncKey = Utils.decodeFriendlyBase32(base32SyncKey);
       byte[] user    = username.getBytes();
 
       Mac hmacHasher;
       try {
@@ -72,19 +72,19 @@ public class KeyBundle {
        this.setHMACKey(hmacKey);
     }
 
     /**
      * Make a KeyBundle with the specified base64-encoded keys.
      *
      * @return A KeyBundle with the specified keys.
      */
-    public static KeyBundle fromBase64EncodedKeys(String base64EncryptionKey, String base64HmacKey) throws UnsupportedEncodingException {
-      return new KeyBundle(Base64.decodeBase64(base64EncryptionKey.getBytes("UTF-8")),
-                           Base64.decodeBase64(base64HmacKey.getBytes("UTF-8")));
+    public static KeyBundle fromBase64EncodedKeys(String base64EncryptionKey, String base64HmacKey) {
+      return new KeyBundle(Base64.decodeBase64(base64EncryptionKey.getBytes(StringUtils.UTF_8)),
+                           Base64.decodeBase64(base64HmacKey.getBytes(StringUtils.UTF_8)));
     }
 
     /**
      * Make a KeyBundle with two random 256 bit keys (encryption and HMAC).
      *
      * @return A KeyBundle with random keys.
      */
     public static KeyBundle withRandomKeys() throws CryptoException {
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/Crypto5MiddlewareRepositorySession.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/Crypto5MiddlewareRepositorySession.java
@@ -1,15 +1,14 @@
 /* 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.middleware;
 
-import java.io.UnsupportedEncodingException;
 import java.util.concurrent.ExecutorService;
 
 import org.mozilla.gecko.sync.CryptoRecord;
 import org.mozilla.gecko.sync.crypto.CryptoException;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
 import org.mozilla.gecko.sync.repositories.RecordFactory;
@@ -161,16 +160,16 @@ public class Crypto5MiddlewareRepository
   public void store(Record record) throws NoStoreDelegateException {
     if (storeDelegate == null) {
       throw new NoStoreDelegateException();
     }
     CryptoRecord rec = record.getEnvelope();
     rec.keyBundle = this.keyBundle;
     try {
       rec.encrypt();
-    } catch (UnsupportedEncodingException | CryptoException e) {
+    } catch (CryptoException e) {
       storeDelegate.onRecordStoreFailed(e, record.guid);
       return;
     }
     // Allow the inner session to do delegate handling.
     inner.store(rec);
   }
 }
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HMACAuthHeaderProvider.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HMACAuthHeaderProvider.java
@@ -11,16 +11,17 @@ import java.security.InvalidKeyException
 import java.security.NoSuchAlgorithmException;
 
 import javax.crypto.Mac;
 import javax.crypto.spec.SecretKeySpec;
 
 import org.mozilla.apache.commons.codec.binary.Base64;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.util.StringUtils;
 
 import ch.boye.httpclientandroidlib.Header;
 import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
 import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
 import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
 import ch.boye.httpclientandroidlib.message.BasicHeader;
 import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
 
@@ -192,21 +193,20 @@ public class HMACAuthHeaderProvider impl
   /**
    * Sign an HMAC request string.
    *
    * @param requestString to sign.
    * @param key as <code>String</code>.
    * @return signature as base-64 encoded string.
    * @throws InvalidKeyException
    * @throws NoSuchAlgorithmException
-   * @throws UnsupportedEncodingException
    */
   protected static String getSignature(String requestString, String key)
-      throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
-    String macString = Base64.encodeBase64String(sha1(requestString.getBytes("UTF-8"), key.getBytes("UTF-8")));
+      throws InvalidKeyException, NoSuchAlgorithmException {
+    String macString = Base64.encodeBase64String(sha1(requestString.getBytes(StringUtils.UTF_8), key.getBytes(StringUtils.UTF_8)));
 
     return macString;
   }
 
   /**
    * Generate an HMAC request string.
    * <p>
    * This method trusts its inputs to be valid as per the MAC Authentication spec.
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HawkAuthHeaderProvider.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HawkAuthHeaderProvider.java
@@ -15,16 +15,17 @@ import java.security.NoSuchAlgorithmExce
 import java.util.Locale;
 
 import javax.crypto.Mac;
 import javax.crypto.spec.SecretKeySpec;
 
 import org.mozilla.apache.commons.codec.binary.Base64;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.util.StringUtils;
 
 import ch.boye.httpclientandroidlib.Header;
 import ch.boye.httpclientandroidlib.HttpEntity;
 import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest;
 import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
 import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
 import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
 import ch.boye.httpclientandroidlib.message.BasicHeader;
@@ -146,17 +147,17 @@ public class HawkAuthHeaderProvider impl
       payloadHash = getPayloadHashString(request);
     } else {
       Logger.debug(LOG_TAG, "Configured to not include payload hash for this request.");
     }
 
     String app = null;
     String dlg = null;
     String requestString = getRequestString(request, "header", timestamp, nonce, payloadHash, extra, app, dlg);
-    String macString = getSignature(requestString.getBytes("UTF-8"), this.key);
+    String macString = getSignature(requestString.getBytes(StringUtils.UTF_8), this.key);
 
     StringBuilder sb = new StringBuilder();
     sb.append("Hawk id=\"");
     sb.append(this.id);
     sb.append("\", ");
     sb.append("ts=\"");
     sb.append(timestamp);
     sb.append("\", ");
@@ -186,22 +187,21 @@ public class HawkAuthHeaderProvider impl
    * Returns null if the request does not enclose an entity (is not an HTTP
    * PATCH, POST, or PUT). Throws if the payload verification hash cannot be
    * computed.
    *
    * @param request
    *          to compute hash for.
    * @return verification hash, or null if the request does not enclose an entity.
    * @throws IllegalArgumentException if the request does not enclose a valid non-null entity.
-   * @throws UnsupportedEncodingException
    * @throws NoSuchAlgorithmException
    * @throws IOException
    */
   protected static String getPayloadHashString(HttpRequestBase request)
-      throws UnsupportedEncodingException, NoSuchAlgorithmException, IOException, IllegalArgumentException {
+      throws NoSuchAlgorithmException, IOException, IllegalArgumentException {
     final boolean shouldComputePayloadHash = request instanceof HttpEntityEnclosingRequest;
     if (!shouldComputePayloadHash) {
       Logger.debug(LOG_TAG, "Not computing payload verification hash for non-enclosing request.");
       return null;
     }
     if (!(request instanceof HttpEntityEnclosingRequest)) {
       throw new IllegalArgumentException("Cannot compute payload verification hash for enclosing request without an entity");
     }
@@ -273,34 +273,34 @@ public class HawkAuthHeaderProvider impl
    * <p>
    * This is under-specified; the code here was reverse engineered from the code
    * at
    * <a href="https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/lib/crypto.js#L81">https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/lib/crypto.js#L81</a>.
    * @param entity to normalize and hash.
    * @return hash.
    * @throws IllegalArgumentException if entity is not repeatable.
    */
-  protected static byte[] getPayloadHash(HttpEntity entity) throws UnsupportedEncodingException, IOException, NoSuchAlgorithmException {
+  protected static byte[] getPayloadHash(HttpEntity entity) throws IOException, NoSuchAlgorithmException {
     if (!entity.isRepeatable()) {
       throw new IllegalArgumentException("entity must be repeatable");
     }
     final MessageDigest digest = MessageDigest.getInstance("SHA-256");
-    digest.update(("hawk." + HAWK_HEADER_VERSION + ".payload\n").getBytes("UTF-8"));
-    digest.update(getBaseContentType(entity.getContentType()).getBytes("UTF-8"));
-    digest.update("\n".getBytes("UTF-8"));
+    digest.update(("hawk." + HAWK_HEADER_VERSION + ".payload\n").getBytes(StringUtils.UTF_8));
+    digest.update(getBaseContentType(entity.getContentType()).getBytes(StringUtils.UTF_8));
+    digest.update("\n".getBytes(StringUtils.UTF_8));
     InputStream stream = entity.getContent();
     try {
       int numRead;
       byte[] buffer = new byte[4096];
       while (-1 != (numRead = stream.read(buffer))) {
         if (numRead > 0) {
           digest.update(buffer, 0, numRead);
         }
       }
-      digest.update("\n".getBytes("UTF-8")); // Trailing newline is specified by Hawk.
+      digest.update("\n".getBytes(StringUtils.UTF_8)); // Trailing newline is specified by Hawk.
       return digest.digest();
     } finally {
       stream.close();
     }
   }
 
   /**
    * Generate a normalized Hawk request string. This is under-specified; the
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/Record.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/Record.java
@@ -6,16 +6,17 @@ package org.mozilla.gecko.sync.repositor
 
 import android.support.annotation.Nullable;
 
 import java.io.UnsupportedEncodingException;
 
 import org.json.simple.JSONObject;
 import org.mozilla.gecko.sync.CryptoRecord;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
+import org.mozilla.gecko.util.StringUtils;
 
 /**
  * Record is the abstract base class for all entries that Sync processes:
  * bookmarks, passwords, history, and such.
  *
  * A Record can be initialized from or serialized to a CryptoRecord for
  * submission to an encrypted store.
  *
@@ -254,22 +255,17 @@ public abstract class Record {
   }
 
   @SuppressWarnings("static-method")
   public JSONObject toJSONObject() {
     throw new RuntimeException("Cannot JSONify non-CryptoRecord Records.");
   }
 
   public static byte[] stringToJSONBytes(String in) {
-    try {
-      return in.getBytes("UTF-8");
-    } catch (UnsupportedEncodingException e) {
-      // Can't happen.
-      return null;
-    }
+    return in.getBytes(StringUtils.UTF_8);
   }
 
   /**
    * Utility for safely populating an output CryptoRecord.
    *
    * @param rec
    * @param key
    * @param value
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SyncClientsEngineStage.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SyncClientsEngineStage.java
@@ -6,17 +6,16 @@ package org.mozilla.gecko.sync.stage;
 
 import android.accounts.Account;
 import android.content.Context;
 import android.os.SystemClock;
 import android.support.annotation.NonNull;
 import android.text.TextUtils;
 import android.util.Log;
 
-import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -667,18 +666,16 @@ public class SyncClientsEngineStage exte
     try {
       CryptoRecord cryptoRecord = recordToUpload.getEnvelope();
       cryptoRecord.keyBundle = clientUploadDelegate.keyBundle();
       if (cryptoRecord.keyBundle == null) {
         doAbort(new NoCollectionKeysSetException(), "No collection keys set.");
         return null;
       }
       return cryptoRecord.encrypt();
-    } catch (UnsupportedEncodingException e) {
-      doAbort(e, encryptionFailure + " Unsupported encoding.");
     } catch (CryptoException e) {
       doAbort(e, encryptionFailure);
     }
     return null;
   }
 
   public void clearRecordsToUpload() {
     try {
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/telemetry/TelemetryCollector.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/telemetry/TelemetryCollector.java
@@ -13,18 +13,18 @@ import org.mozilla.gecko.sync.Collection
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.HTTPFailureException;
 import org.mozilla.gecko.sync.SyncDeadlineReachedException;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.net.SyncStorageResponse;
 import org.mozilla.gecko.sync.repositories.FetchFailedException;
 import org.mozilla.gecko.sync.repositories.StoreFailedException;
 import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
+import org.mozilla.gecko.util.StringUtils;
 
-import java.io.UnsupportedEncodingException;
 import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.HashMap;
 
 /**
  * Gathers telemetry about a single run of sync.
  * In light of sync restarts, rarely a "single sync" will actually include more than one sync.
  * See {@link TelemetryStageCollector} for "stage telemetry".
@@ -72,21 +72,21 @@ public class TelemetryCollector {
         this.didRestart = true;
     }
 
     public void setIDs(@NonNull String uid, @NonNull String deviceID) {
         // We use hashed_fxa_uid from the token server as our UID.
         this.hashedUID = uid;
         try {
             this.hashedDeviceID = Utils.byte2Hex(Utils.sha256(
-                            deviceID.concat(uid).getBytes("UTF-8")
+                            deviceID.concat(uid).getBytes(StringUtils.UTF_8)
                     ));
-        } catch (UnsupportedEncodingException | NoSuchAlgorithmException e) {
+        } catch (NoSuchAlgorithmException e) {
             // Should not happen.
-            Log.e(LOG_TAG, "Either UTF-8 or SHA-256 are not supported", e);
+            Log.e(LOG_TAG, "SHA-256 is not supported", e);
         }
     }
 
     public void setError(@NonNull String name, @NonNull Exception e) {
         setError(name, e, null);
     }
 
     public void setError(@NonNull String name, @NonNull Exception e, @Nullable String details) {
@@ -123,21 +123,21 @@ public class TelemetryCollector {
         final Bundle device = new Bundle();
         device.putString(TelemetryContract.KEY_DEVICE_OS, client.os);
         device.putString(TelemetryContract.KEY_DEVICE_VERSION, client.version);
 
         final String clientAndUid = client.guid.concat(this.hashedUID);
         try {
             device.putString(
                     TelemetryContract.KEY_DEVICE_ID,
-                    Utils.byte2Hex(Utils.sha256(clientAndUid.getBytes("UTF-8")))
+                    Utils.byte2Hex(Utils.sha256(clientAndUid.getBytes(StringUtils.UTF_8)))
             );
-        } catch (UnsupportedEncodingException | NoSuchAlgorithmException e) {
+        } catch (NoSuchAlgorithmException e) {
             // Should not happen.
-            Log.e(LOG_TAG, "Either UTF-8 or SHA-256 are not supported", e);
+            Log.e(LOG_TAG, "SHA-256 is not supported", e);
         }
         devices.add(device);
     }
 
     public Bundle build() {
         if (this.started == null) {
             throw new IllegalStateException("Telemetry missing 'started' timestamp");
         }
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/util/PRNGFixes.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/util/PRNGFixes.java
@@ -17,17 +17,16 @@ import android.util.Log;
 import java.io.ByteArrayOutputStream;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
 import java.security.NoSuchAlgorithmException;
 import java.security.Provider;
 import java.security.SecureRandom;
 import java.security.SecureRandomSpi;
 import java.security.Security;
 
 /**
  * Fixes for the output of the default PRNG having low entropy.
@@ -325,15 +324,11 @@ public final class PRNGFixes {
         String fingerprint = Build.FINGERPRINT;
         if (fingerprint != null) {
             result.append(fingerprint);
         }
         String serial = getDeviceSerialNumber();
         if (serial != null) {
             result.append(serial);
         }
-        try {
-            return result.toString().getBytes("UTF-8");
-        } catch (UnsupportedEncodingException e) {
-            throw new RuntimeException("UTF-8 encoding not supported");
-        }
+        return result.toString().getBytes(StringUtils.UTF_8);
     }
 }
--- a/mobile/android/services/src/test/java/org/mozilla/android/sync/net/test/TestCredentialsEndToEnd.java
+++ b/mobile/android/services/src/test/java/org/mozilla/android/sync/net/test/TestCredentialsEndToEnd.java
@@ -5,16 +5,17 @@ package org.mozilla.android.sync.net.tes
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
+import org.mozilla.gecko.util.StringUtils;
 
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 
 import ch.boye.httpclientandroidlib.Header;
 
 import static org.junit.Assert.assertEquals;
 
@@ -48,17 +49,17 @@ public class TestCredentialsEndToEnd {
   @Test
   public void testAuthHeaderFromPassword() throws NonObjectJSONException, IOException {
     final ExtendedJSONObject parsed = new ExtendedJSONObject(DESKTOP_PASSWORD_JSON);
 
     final String password = parsed.getString("password");
     final String decoded = Utils.decodeUTF8(password);
 
     final byte[] expectedBytes = Utils.decodeBase64(BTOA_PASSWORD);
-    final String expected = new String(expectedBytes, "UTF-8");
+    final String expected = new String(expectedBytes, StringUtils.UTF_8);
 
     assertEquals(DESKTOP_ASSERTED_SIZE, password.length());
     assertEquals(expected, decoded);
 
     System.out.println("Retrieved password: " + password);
     System.out.println("Expected password:  " + expected);
     System.out.println("Rescued password:   " + decoded);
 
--- a/mobile/android/services/src/test/java/org/mozilla/android/sync/test/TestCollectionKeys.java
+++ b/mobile/android/services/src/test/java/org/mozilla/android/sync/test/TestCollectionKeys.java
@@ -75,17 +75,17 @@ public class TestCollectionKeys {
     CryptoRecord rec = new CryptoRecord(json);
 
     KeyBundle syncKeyBundle = new KeyBundle("slyjcrjednxd6rf4cr63vqilmkus6zbe", "6m8mv8ex2brqnrmsb9fjuvfg7y");
     rec.keyBundle = syncKeyBundle;
 
     rec.encrypt();
     CollectionKeys ck = new CollectionKeys();
     ck.setKeyPairsFromWBO(rec, syncKeyBundle);
-    byte[] input = "3fI6k1exImMgAKjilmMaAWxGqEIzFX/9K5EjEgH99vc=".getBytes("UTF-8");
+    byte[] input = "3fI6k1exImMgAKjilmMaAWxGqEIzFX/9K5EjEgH99vc=".getBytes(StringUtils.UTF_8);
     byte[] expected = Base64.decodeBase64(input);
     assertSame(expected, ck.defaultKeyBundle().getEncryptionKey());
   }
 
   @Test
   public void testCryptoRecordFromCollectionKeys() throws CryptoException, NoCollectionKeysSetException, IOException, NonObjectJSONException {
     CollectionKeys ck1 = CollectionKeys.generateCollectionKeys();
     assertNotNull(ck1.defaultKeyBundle());
--- a/mobile/android/services/src/test/java/org/mozilla/android/sync/test/TestCryptoRecord.java
+++ b/mobile/android/services/src/test/java/org/mozilla/android/sync/test/TestCryptoRecord.java
@@ -13,19 +13,19 @@ import org.mozilla.gecko.sync.CryptoReco
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.crypto.CryptoException;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
 import org.mozilla.gecko.sync.repositories.domain.HistoryRecord;
 import org.mozilla.gecko.sync.repositories.domain.Record;
+import org.mozilla.gecko.util.StringUtils;
 
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
 import java.util.Arrays;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 @RunWith(TestRunner.class)
@@ -104,40 +104,40 @@ public class TestCryptoRecord {
 
     ExtendedJSONObject body    = new ExtendedJSONObject();
     ExtendedJSONObject payload = new ExtendedJSONObject();
     payload.put("ciphertext", base64CipherText);
     payload.put("IV", base64IV);
     payload.put("hmac", base16Hmac);
     body.put("payload", payload.toJSONString());
     CryptoRecord record = CryptoRecord.fromJSONRecord(body);
-    byte[] decodedKey  = Base64.decodeBase64(base64EncryptionKey.getBytes("UTF-8"));
-    byte[] decodedHMAC = Base64.decodeBase64(base64HmacKey.getBytes("UTF-8")); 
+    byte[] decodedKey  = Base64.decodeBase64(base64EncryptionKey.getBytes(StringUtils.UTF_8));
+    byte[] decodedHMAC = Base64.decodeBase64(base64HmacKey.getBytes(StringUtils.UTF_8));
     record.keyBundle = new KeyBundle(decodedKey, decodedHMAC);
 
     record.decrypt();
     String id = (String) record.payload.get("id");
     assertTrue(id.equals("5qRsgXWRJZXr"));
   }
 
   @Test
-  public void testBaseCryptoRecordSyncKeyBundle() throws UnsupportedEncodingException, CryptoException {
+  public void testBaseCryptoRecordSyncKeyBundle() throws CryptoException {
     // These values pulled straight out of Firefox.
     String key  = "6m8mv8ex2brqnrmsb9fjuvfg7y";
     String user = "c6o7dvmr2c4ud2fyv6woz2u4zi22bcyd";
     
     // Check our friendly base32 decoding.
-    assertTrue(Arrays.equals(Utils.decodeFriendlyBase32(key), Base64.decodeBase64("8xbKrJfQYwbFkguKmlSm/g==".getBytes("UTF-8"))));
+    assertTrue(Arrays.equals(Utils.decodeFriendlyBase32(key), Base64.decodeBase64("8xbKrJfQYwbFkguKmlSm/g==".getBytes(StringUtils.UTF_8))));
     KeyBundle bundle = new KeyBundle(user, key);
     String expectedEncryptKeyBase64 = "/8RzbFT396htpZu5rwgIg2WKfyARgm7dLzsF5pwrVz8=";
     String expectedHMACKeyBase64    = "NChGjrqoXYyw8vIYP2334cvmMtsjAMUZNqFwV2LGNkM=";
     byte[] computedEncryptKey       = bundle.getEncryptionKey();
     byte[] computedHMACKey          = bundle.getHMACKey();
-    assertTrue(Arrays.equals(computedEncryptKey, Base64.decodeBase64(expectedEncryptKeyBase64.getBytes("UTF-8"))));
-    assertTrue(Arrays.equals(computedHMACKey,    Base64.decodeBase64(expectedHMACKeyBase64.getBytes("UTF-8"))));
+    assertTrue(Arrays.equals(computedEncryptKey, Base64.decodeBase64(expectedEncryptKeyBase64.getBytes(StringUtils.UTF_8))));
+    assertTrue(Arrays.equals(computedHMACKey,    Base64.decodeBase64(expectedHMACKeyBase64.getBytes(StringUtils.UTF_8))));
   }
 
   @Test
   public void testDecrypt() throws Exception {
     String jsonInput =              "{\"sortindex\": 90, \"payload\":" +
                                     "\"{\\\"ciphertext\\\":\\\"F4ukf0" +
                                     "LM+vhffiKyjaANXeUhfmOPPmQYX1XBoG" +
                                     "Rh1LiHeKHB5rqjhzd7yAoxqgmFnkIgQF" +
@@ -259,22 +259,22 @@ public class TestCryptoRecord {
     assertEquals(expectedJson.get("default"), decrypted.payload.get("default"));
     assertEquals(expectedJson.get("collection"), decrypted.payload.get("collection"));
     assertEquals(expectedJson.get("collections"), decrypted.payload.get("collections"));
 
     // Check that the extracted keys were as expected.
     JSONArray keys = new ExtendedJSONObject(decrypted.payload.toJSONString()).getArray("default");
     KeyBundle keyBundle = KeyBundle.fromBase64EncodedKeys((String)keys.get(0), (String)keys.get(1));
 
-    assertArrayEquals(Base64.decodeBase64(expectedBase64EncryptionKey.getBytes("UTF-8")), keyBundle.getEncryptionKey());
-    assertArrayEquals(Base64.decodeBase64(expectedBase64HmacKey.getBytes("UTF-8")), keyBundle.getHMACKey());
+    assertArrayEquals(Base64.decodeBase64(expectedBase64EncryptionKey.getBytes(StringUtils.UTF_8)), keyBundle.getEncryptionKey());
+    assertArrayEquals(Base64.decodeBase64(expectedBase64HmacKey.getBytes(StringUtils.UTF_8)), keyBundle.getHMACKey());
   }
 
   @Test
-  public void testTTL() throws UnsupportedEncodingException, CryptoException {
+  public void testTTL() throws CryptoException {
     Record historyRecord = new HistoryRecord();
     CryptoRecord cryptoRecord = historyRecord.getEnvelope();
     assertEquals(historyRecord.ttl, cryptoRecord.ttl);
 
     // Very important that ttls are set in outbound envelopes.
     JSONObject o = cryptoRecord.toJSONObject();
     assertEquals(cryptoRecord.ttl, o.get("ttl"));
 
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/fxa/login/TestFxAccountLoginStateMachine.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/fxa/login/TestFxAccountLoginStateMachine.java
@@ -12,16 +12,17 @@ import org.mozilla.gecko.background.fxa.
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.browserid.BrowserIDKeyPair;
 import org.mozilla.gecko.browserid.RSACryptoImplementation;
 import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.LoginStateMachineDelegate;
 import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.Transition;
 import org.mozilla.gecko.fxa.login.State.StateLabel;
 import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.util.StringUtils;
 
 import java.security.NoSuchAlgorithmException;
 import java.util.LinkedList;
 
 @RunWith(TestRunner.class)
 public class TestFxAccountLoginStateMachine {
   // private static final String TEST_AUDIENCE = "http://testAudience.com";
   private static final String TEST_EMAIL = "test@test.com";
@@ -34,20 +35,20 @@ public class TestFxAccountLoginStateMach
   private static final byte[] TEST_KEY_FETCH_TOKEN = Utils.generateRandomBytes(32);
 
   protected MockFxAccountClient client;
   protected FxAccountLoginStateMachine sm;
 
   @Before
   public void setUp() throws Exception {
     if (TEST_EMAIL_UTF8 == null) {
-      TEST_EMAIL_UTF8 = TEST_EMAIL.getBytes("UTF-8");
+      TEST_EMAIL_UTF8 = TEST_EMAIL.getBytes(StringUtils.UTF_8);
     }
     if (TEST_PASSWORD_UTF8 == null) {
-      TEST_PASSWORD_UTF8 = TEST_PASSWORD.getBytes("UTF-8");
+      TEST_PASSWORD_UTF8 = TEST_PASSWORD.getBytes(StringUtils.UTF_8);
     }
     if (TEST_QUICK_STRETCHED_PW == null) {
       TEST_QUICK_STRETCHED_PW = FxAccountUtils.generateQuickStretchedPW(TEST_EMAIL_UTF8, TEST_PASSWORD_UTF8);
     }
     if (TEST_UNWRAPKB == null) {
       TEST_UNWRAPKB = FxAccountUtils.generateUnwrapBKey(TEST_QUICK_STRETCHED_PW);
     }
     client = new MockFxAccountClient();
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/sync/crypto/test/TestCryptoInfo.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/sync/crypto/test/TestCryptoInfo.java
@@ -6,40 +6,40 @@ package org.mozilla.gecko.sync.crypto.te
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.apache.commons.codec.binary.Base64;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.crypto.CryptoException;
 import org.mozilla.gecko.sync.crypto.CryptoInfo;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
+import org.mozilla.gecko.util.StringUtils;
 
-import java.io.UnsupportedEncodingException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
 @RunWith(TestRunner.class)
 public class TestCryptoInfo {
 
   @Test
-  public void testEncryptedHMACIsSet() throws CryptoException, UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException {
+  public void testEncryptedHMACIsSet() throws CryptoException, InvalidKeyException, NoSuchAlgorithmException {
     KeyBundle kb = KeyBundle.withRandomKeys();
-    CryptoInfo encrypted = CryptoInfo.encrypt("plaintext".getBytes("UTF-8"), kb);
+    CryptoInfo encrypted = CryptoInfo.encrypt("plaintext".getBytes(StringUtils.UTF_8), kb);
     assertSame(kb, encrypted.getKeys());
     assertTrue(encrypted.generatedHMACIsHMAC());
   }
 
   @Test
-  public void testRandomEncryptedDecrypted() throws CryptoException, UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException {
+  public void testRandomEncryptedDecrypted() throws CryptoException, InvalidKeyException, NoSuchAlgorithmException {
     KeyBundle kb = KeyBundle.withRandomKeys();
-    byte[] plaintext = "plaintext".getBytes("UTF-8");
+    byte[] plaintext = "plaintext".getBytes(StringUtils.UTF_8);
     CryptoInfo info = CryptoInfo.encrypt(plaintext, kb);
     byte[] iv = info.getIV();
     info.decrypt();
     assertArrayEquals(plaintext, info.getMessage());
     assertSame(null, info.getHMAC());
     assertArrayEquals(iv, info.getIV());
     assertSame(kb, info.getKeys());
   }
@@ -136,9 +136,9 @@ public class TestCryptoInfo {
             new KeyBundle(
                 Base64.decodeBase64(base64EncryptionKey),
                 Base64.decodeBase64(base64HmacKey))
             );
 
     assertArrayEquals(Base64.decodeBase64(base64CipherText), encrypted.getMessage());
     assertArrayEquals(Utils.hex2Byte(base16Hmac), encrypted.getHMAC());
   }
-}
\ No newline at end of file
+}
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/sync/crypto/test/TestKeyBundle.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/sync/crypto/test/TestKeyBundle.java
@@ -4,39 +4,39 @@
 package org.mozilla.gecko.sync.crypto.test;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.apache.commons.codec.binary.Base64;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.crypto.CryptoException;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
+import org.mozilla.gecko.util.StringUtils;
 
-import java.io.UnsupportedEncodingException;
 import java.util.Arrays;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 @RunWith(TestRunner.class)
 public class TestKeyBundle {
   @Test
-  public void testCreateKeyBundle() throws UnsupportedEncodingException, CryptoException {
+  public void testCreateKeyBundle() throws CryptoException {
     String username              = "smqvooxj664hmrkrv6bw4r4vkegjhkns";
     String friendlyBase32SyncKey = "gbh7teqqcgyzd65svjgibd7tqy";
     String base64EncryptionKey   = "069EnS3EtDK4y1tZ1AyKX+U7WEsWRp9b" +
                                    "RIKLdW/7aoE=";
     String base64HmacKey         = "LF2YCS1QCgSNCf0BCQvQ06SGH8jqJDi9" +
                                    "dKj0O+b0fwI=";
 
     KeyBundle keys = new KeyBundle(username, friendlyBase32SyncKey);
-    assertArrayEquals(keys.getEncryptionKey(), Base64.decodeBase64(base64EncryptionKey.getBytes("UTF-8")));
-    assertArrayEquals(keys.getHMACKey(), Base64.decodeBase64(base64HmacKey.getBytes("UTF-8")));
+    assertArrayEquals(keys.getEncryptionKey(), Base64.decodeBase64(base64EncryptionKey.getBytes(StringUtils.UTF_8)));
+    assertArrayEquals(keys.getHMACKey(), Base64.decodeBase64(base64HmacKey.getBytes(StringUtils.UTF_8)));
   }
 
   /*
    * Basic sanity check to make sure length of keys is correct (32 bytes).
    * Also make sure that the two keys are different.
    */
   @Test
   public void testGenerateRandomKeys() throws CryptoException {
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/sync/net/test/TestHawkAuthHeaderProvider.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/sync/net/test/TestHawkAuthHeaderProvider.java
@@ -11,19 +11,19 @@ import ch.boye.httpclientandroidlib.clie
 import ch.boye.httpclientandroidlib.entity.StringEntity;
 import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
 import ch.boye.httpclientandroidlib.message.BasicHeader;
 import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.net.HawkAuthHeaderProvider;
+import org.mozilla.gecko.util.StringUtils;
 
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 
 import static org.junit.Assert.assertEquals;
 
 /**
  * These test vectors were taken from
@@ -40,18 +40,18 @@ public class TestHawkAuthHeaderProvider 
 
     // Public for testing.
     public static String getRequestString(HttpUriRequest request, String type, long timestamp, String nonce, String hash, String extra, String app, String dlg) {
       return HawkAuthHeaderProvider.getRequestString(request, type, timestamp, nonce, hash, extra, app, dlg);
     }
 
     // Public for testing.
     public static String getSignature(String requestString, String key)
-        throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
-      return HawkAuthHeaderProvider.getSignature(requestString.getBytes("UTF-8"), key.getBytes("UTF-8"));
+        throws InvalidKeyException, NoSuchAlgorithmException {
+      return HawkAuthHeaderProvider.getSignature(requestString.getBytes(StringUtils.UTF_8), key.getBytes(StringUtils.UTF_8));
     }
 
     // Public for testing.
     @Override
     public Header getAuthHeader(HttpRequestBase request, BasicHttpContext context, DefaultHttpClient client,
         long timestamp, String nonce, String extra, boolean includePayloadHash)
             throws InvalidKeyException, NoSuchAlgorithmException, IOException {
       return super.getAuthHeader(request, context, client, timestamp, nonce, extra, includePayloadHash);
@@ -101,30 +101,30 @@ public class TestHawkAuthHeaderProvider 
         "\n" +
         "some-app-ext-data\n";
 
     assertEquals("6R4rV5iE+NPoym+WwjeHzjAGXUtLNIxmo1vpMofpLAE=", LeakyHawkAuthHeaderProvider.getSignature(input, "werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn"));
   }
 
   @Test
   public void testSpecPayloadExample() throws Exception {
-    LeakyHawkAuthHeaderProvider provider = new LeakyHawkAuthHeaderProvider("dh37fgj492je", "werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn".getBytes("UTF-8"));
+    LeakyHawkAuthHeaderProvider provider = new LeakyHawkAuthHeaderProvider("dh37fgj492je", "werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn".getBytes(StringUtils.UTF_8));
     URI uri = new URI("http://example.com:8000/resource/1?b=1&a=2");
     HttpPost req = new HttpPost(uri);
     String body = "Thank you for flying Hawk";
     req.setEntity(new StringEntity(body));
     Header header = provider.getAuthHeader(req, null, null, 1353832234L, "j4h3g2", "some-app-ext-data", true);
     String expected = "Hawk id=\"dh37fgj492je\", ts=\"1353832234\", nonce=\"j4h3g2\", hash=\"Yi9LfIIFRtBEPt74PVmbTF/xVAwPn7ub15ePICfgnuY=\", ext=\"some-app-ext-data\", mac=\"aSe1DERmZuRl3pI36/9BdZmnErTw3sNzOOAUlfeKjVw=\"";
     assertEquals("Authorization", header.getName());
     assertEquals(expected, header.getValue());
   }
 
   @Test
   public void testSpecAuthorizationHeader() throws Exception {
-    LeakyHawkAuthHeaderProvider provider = new LeakyHawkAuthHeaderProvider("dh37fgj492je", "werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn".getBytes("UTF-8"));
+    LeakyHawkAuthHeaderProvider provider = new LeakyHawkAuthHeaderProvider("dh37fgj492je", "werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn".getBytes(StringUtils.UTF_8));
     URI uri = new URI("http://example.com:8000/resource/1?b=1&a=2");
     HttpGet req = new HttpGet(uri);
     Header header = provider.getAuthHeader(req, null, null, 1353832234L, "j4h3g2", "some-app-ext-data", false);
     String expected = "Hawk id=\"dh37fgj492je\", ts=\"1353832234\", nonce=\"j4h3g2\", ext=\"some-app-ext-data\", mac=\"6R4rV5iE+NPoym+WwjeHzjAGXUtLNIxmo1vpMofpLAE=\"";
     assertEquals("Authorization", header.getName());
     assertEquals(expected, header.getValue());
 
     // For a non-POST, non-PUT request, a request to include the payload verification hash is silently ignored.
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/sync/net/test/TestLiveHawkAuth.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/sync/net/test/TestLiveHawkAuth.java
@@ -14,16 +14,17 @@ import ch.boye.httpclientandroidlib.prot
 import org.junit.Assert;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.BaseResourceDelegate;
 import org.mozilla.gecko.sync.net.HawkAuthHeaderProvider;
 import org.mozilla.gecko.sync.net.Resource;
 import org.mozilla.gecko.sync.net.SyncResponse;
+import org.mozilla.gecko.util.StringUtils;
 
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.security.GeneralSecurityException;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -34,17 +35,17 @@ public class TestLiveHawkAuth {
    * Hawk comes with an example/usage.js server. Modify it to serve indefinitely,
    * un-comment the following line, and verify that the port and credentials
    * have not changed; then the following test should pass.
    */
   // @org.junit.Test
   public void testHawkUsage() throws Exception {
     // Id and credentials are hard-coded in example/usage.js.
     final String id = "dh37fgj492je";
-    final byte[] key = "werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn".getBytes("UTF-8");
+    final byte[] key = "werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn".getBytes(StringUtils.UTF_8);
     final BaseResource resource = new BaseResource("http://localhost:8000/", false);
 
     // Basic GET.
     resource.delegate = new TestBaseResourceDelegate(resource, new HawkAuthHeaderProvider(id, key, false, 0L));
     WaitHelper.getTestWaiter().performWait(new Runnable() {
       @Override
       public void run() {
         resource.get();
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/sync/telemetry/TelemetryCollectorTest.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/sync/telemetry/TelemetryCollectorTest.java
@@ -14,16 +14,17 @@ import org.mozilla.gecko.sync.Collection
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.HTTPFailureException;
 import org.mozilla.gecko.sync.SyncDeadlineReachedException;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.net.SyncStorageResponse;
 import org.mozilla.gecko.sync.repositories.FetchFailedException;
 import org.mozilla.gecko.sync.repositories.StoreFailedException;
 import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
+import org.mozilla.gecko.util.StringUtils;
 
 import java.util.ArrayList;
 import java.util.ConcurrentModificationException;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.doReturn;
 
 import static org.junit.Assert.*;
@@ -46,17 +47,17 @@ public class TelemetryCollectorTest {
         collector.setStarted(5L);
         collector.setFinished(10L);
         final Bundle bundle = collector.build();
 
         // Setting UID is a pass-through, since we're expecting it to be hashed already.
         assertEquals(uid, bundle.get("uid"));
         // Expect device ID to be hashed with the UID.
         assertEquals(
-                Utils.byte2Hex(Utils.sha256(deviceID.concat(uid).getBytes("UTF-8"))),
+                Utils.byte2Hex(Utils.sha256(deviceID.concat(uid).getBytes(StringUtils.UTF_8))),
                 bundle.get("deviceID")
         );
     }
 
     @Test(expected = IllegalStateException.class)
     public void testAddingDevicesBeforeSettingUID() throws Exception {
         collector.addDevice(new ClientRecord("client1"));
     }
@@ -74,17 +75,17 @@ public class TelemetryCollectorTest {
 
         // Test that client device ID is hashed together with UID
         collector.addDevice(client1);
 
         Bundle data = collector.build();
         ArrayList<Bundle> devices = data.getParcelableArrayList("devices");
         assertEquals(1, devices.size());
         assertEquals(
-                Utils.byte2Hex(Utils.sha256("client1-guid".concat("hashed-uid").getBytes("UTF-8"))),
+                Utils.byte2Hex(Utils.sha256("client1-guid".concat("hashed-uid").getBytes(StringUtils.UTF_8))),
                 devices.get(0).getString("id")
         );
         assertEquals("iOS", devices.get(0).getString("os"));
         assertEquals("1.33.7", devices.get(0).getString("version"));
 
         // Test that we can add more than just one device
         ClientRecord client2 = new ClientRecord("client2-guid");
         client2.os = "Android";
@@ -93,24 +94,24 @@ public class TelemetryCollectorTest {
 
         data = collector.build();
         devices = data.getParcelableArrayList("devices");
         assertEquals(2, devices.size());
 
         assertEquals("iOS", devices.get(0).getString("os"));
         assertEquals("1.33.7", devices.get(0).getString("version"));
         assertEquals(
-                Utils.byte2Hex(Utils.sha256("client1-guid".concat("hashed-uid").getBytes("UTF-8"))),
+                Utils.byte2Hex(Utils.sha256("client1-guid".concat("hashed-uid").getBytes(StringUtils.UTF_8))),
                 devices.get(0).getString("id")
         );
 
         assertEquals("Android", devices.get(1).getString("os"));
         assertEquals("55.0a1", devices.get(1).getString("version"));
         assertEquals(
-                Utils.byte2Hex(Utils.sha256("client2-guid".concat("hashed-uid").getBytes("UTF-8"))),
+                Utils.byte2Hex(Utils.sha256("client2-guid".concat("hashed-uid").getBytes(StringUtils.UTF_8))),
                 devices.get(1).getString("id")
         );
     }
 
     @Test
     public void testDuration() throws Exception {
         collector.setStarted(5L);
         collector.setFinished(11L);
@@ -341,9 +342,9 @@ public class TelemetryCollectorTest {
         builder.setLastException(mock(StoreFailedException.class))
                 .setFetchException(new java.net.SocketException())
                 .setStoreException(new IllegalStateException());
 
         error = builder.build();
         assertEquals("othererror", error.getString("name"));
         assertEquals("store:IllegalStateException", error.getString("error"));
     }
-}
\ No newline at end of file
+}
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/sync/test/TestExtendedJSONObject.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/sync/test/TestExtendedJSONObject.java
@@ -7,16 +7,17 @@ import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.NonArrayJSONException;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.UnexpectedJSONException.BadRequiredFieldJSONException;
+import org.mozilla.gecko.util.StringUtils;
 
 import java.io.IOException;
 
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -97,32 +98,32 @@ public class TestExtendedJSONObject {
       // Do nothing.
     }
   }
 
   @Test
   public void testParseUTF8AsJSONObject() throws Exception {
     String TEST = "{\"key\":\"value\"}";
 
-    ExtendedJSONObject o = ExtendedJSONObject.parseUTF8AsJSONObject(TEST.getBytes("UTF-8"));
+    ExtendedJSONObject o = ExtendedJSONObject.parseUTF8AsJSONObject(TEST.getBytes(StringUtils.UTF_8));
     assertNotNull(o);
     assertEquals("value", o.getString("key"));
   }
 
   @Test
   public void testBadParseUTF8AsJSONObject() throws Exception {
     try {
       ExtendedJSONObject.parseUTF8AsJSONObject("{}".getBytes("UTF-16"));
       fail();
     } catch (NonObjectJSONException e) {
       // Do nothing.
     }
 
     try {
-      ExtendedJSONObject.parseUTF8AsJSONObject("{".getBytes("UTF-8"));
+      ExtendedJSONObject.parseUTF8AsJSONObject("{".getBytes(StringUtils.UTF_8));
       fail();
     } catch (NonObjectJSONException e) {
       // Do nothing.
     }
   }
 
   @Test
   public void testHashCode() throws Exception {
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testNativeCrypto.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testNativeCrypto.java
@@ -14,16 +14,17 @@ import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.security.GeneralSecurityException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 
 import org.mozilla.gecko.background.nativecode.NativeCrypto;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.tests.helpers.GeckoHelper;
+import org.mozilla.gecko.util.StringUtils;
 
 import android.os.SystemClock;
 
 /**
  * Tests the Java wrapper over native implementations of crypto code. Test vectors from:
  *   * PBKDF2SHA256:
  *     - <https://github.com/ircmaxell/PHP-PasswordLib/blob/master/test/Data/Vectors/pbkdf2-draft-josefsson-sha256.test-vectors>
        - <https://gitorious.org/scrypt/nettle-scrypt/blobs/37c0d5288e991604fe33dba2f1724986a8dddf56/testsuite/pbkdf2-test.c>
@@ -233,19 +234,19 @@ public class testNativeCrypto extends UI
 
       final byte[] ctx = NativeCrypto.sha256init();
       NativeCrypto.sha256update(ctx, inputBytes, inputBytes.length);
       final byte[] ourBytes = NativeCrypto.sha256finalize(ctx);
       fAssertArrayEquals("MessageDigest hash is the same as NativeCrypto SHA-256 hash", mdBytes, ourBytes);
     }
   }
 
-  private void _testSHA256WithMultipleUpdatesFromStream() throws UnsupportedEncodingException {
+  private void _testSHA256WithMultipleUpdatesFromStream() {
     final String input = "HelloWorldThisIsASuperLongStringThatIsReadAsAStreamOfBytes";
-    final ByteArrayInputStream stream = new ByteArrayInputStream(input.getBytes("UTF-8"));
+    final ByteArrayInputStream stream = new ByteArrayInputStream(input.getBytes(StringUtils.UTF_8));
     final String expected = "8b5cb76b80f7eb6fb83ee138bfd31e2922e71dd245daa21a8d9876e8dee9eef5";
 
     byte[] buffer = new byte[10];
     final byte[] ctx = NativeCrypto.sha256init();
     int c;
 
     try {
       while ((c = stream.read(buffer)) != -1) {