Merge "Fix the build: Revert "Switching to the final PDF rendering library""
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index a233b1d..f36c36a 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -73,7 +73,7 @@
     }
 
     private static SharedElementListener getListener(Activity activity, boolean isReturning) {
-        return isReturning ? activity.mExitTransitionListener : activity.mEnterTransitionListener;
+        return isReturning ? activity.mEnterTransitionListener : activity.mExitTransitionListener;
     }
 
     @Override
diff --git a/core/java/android/net/LocalSocketImpl.java b/core/java/android/net/LocalSocketImpl.java
index 643e8c2..fa9f479 100644
--- a/core/java/android/net/LocalSocketImpl.java
+++ b/core/java/android/net/LocalSocketImpl.java
@@ -56,7 +56,10 @@
         /** {@inheritDoc} */
         @Override
         public int available() throws IOException {
-            return available_native(fd);
+            FileDescriptor myFd = fd;
+            if (myFd == null) throw new IOException("socket closed");
+
+            return available_native(myFd);
         }
 
         /** {@inheritDoc} */
diff --git a/core/res/res/drawable-hdpi/popup_background_qntm_mult.9.png b/core/res/res/drawable-hdpi/popup_background_qntm_mult.9.png
new file mode 100644
index 0000000..385734e
--- /dev/null
+++ b/core/res/res/drawable-hdpi/popup_background_qntm_mult.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/popup_background_qntm_mult.9.png b/core/res/res/drawable-mdpi/popup_background_qntm_mult.9.png
new file mode 100644
index 0000000..e920499
--- /dev/null
+++ b/core/res/res/drawable-mdpi/popup_background_qntm_mult.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/popup_background_qntm_mult.9.png b/core/res/res/drawable-xhdpi/popup_background_qntm_mult.9.png
new file mode 100644
index 0000000..a081ceb
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/popup_background_qntm_mult.9.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/popup_background_qntm_mult.9.png b/core/res/res/drawable-xxhdpi/popup_background_qntm_mult.9.png
new file mode 100644
index 0000000..fb7d715
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/popup_background_qntm_mult.9.png
Binary files differ
diff --git a/core/res/res/drawable/popup_background_quantum.xml b/core/res/res/drawable/popup_background_quantum.xml
index 7e5b003..a4d0291 100644
--- a/core/res/res/drawable/popup_background_quantum.xml
+++ b/core/res/res/drawable/popup_background_quantum.xml
@@ -14,12 +14,7 @@
      limitations under the License.
 -->
 
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-
-    <corners
-        android:radius="2dp" />
-    <solid
-        android:color="?attr/colorBackground" />
-
-</shape>
+<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/popup_background_qntm_mult"
+    android:tint="?attr/colorBackground"
+    android:tintMode="multiply" />
diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml
index 705143c..63df5be 100644
--- a/core/res/res/values-watch/themes_device_defaults.xml
+++ b/core/res/res/values-watch/themes_device_defaults.xml
@@ -17,12 +17,15 @@
     <style name="Theme.DeviceDefault" parent="Theme.Micro" />
     <style name="Theme.DeviceDefault.NoActionBar" parent="Theme.Micro" />
     <style name="Theme.DeviceDefault.Dialog" parent="Theme.Micro.Dialog" />
+    <style name="Theme.DeviceDefault.DialogWhenLarge" parent="Theme.Micro.Dialog" />
     <style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Micro.Dialog.Alert" />
     <style name="Theme.DeviceDefault.Light" parent="Theme.Micro.Light" />
     <style name="Theme.DeviceDefault.Light.NoActionBar" parent="Theme.Micro.Light" />
     <style name="Theme.DeviceDefault.Light.DarkActionBar" parent="Theme.Micro.Light" />
     <style name="Theme.DeviceDefault.Light.Dialog" parent="Theme.Micro.Dialog" />
+    <style name="Theme.DeviceDefault.Light.DialogWhenLarge" parent="Theme.Micro.Dialog" />
     <style name="Theme.DeviceDefault.Light.Dialog.Alert" parent="Theme.Micro.Dialog.Alert" />
+    <style name="Theme.DeviceDefault.Wallpaper" parent="Theme.Micro" />
 
 </resources>
 
diff --git a/core/res/res/values/colors_quantum.xml b/core/res/res/values/colors_quantum.xml
index 7171450..556463e 100644
--- a/core/res/res/values/colors_quantum.xml
+++ b/core/res/res/values/colors_quantum.xml
@@ -16,11 +16,11 @@
 
 <!-- Colors specific to Quantum themes. -->
 <resources>
-    <color name="background_quantum_dark">@color/black</color>
+    <color name="background_quantum_dark">#ff303030</color>
     <color name="background_quantum_light">@color/white</color>
 
-    <color name="bright_foreground_quantum_dark">@color/background_quantum_light</color>
-    <color name="bright_foreground_quantum_light">@color/background_quantum_dark</color>
+    <color name="bright_foreground_quantum_dark">@color/white</color>
+    <color name="bright_foreground_quantum_light">@color/black</color>
     <!-- TODO: This is 50% alpha black -->
     <color name="bright_foreground_disabled_quantum_dark">#80000000</color>
     <!-- TODO: This is 50% alpha white -->
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 1baaaa4..8eb83e4 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -791,11 +791,17 @@
 
    /**
     * Poll for a timestamp on demand.
-    *
-    * Use if you need to get the most recent timestamp outside of the event callback handler.
-    * Calling this method too often may be inefficient;
-    * if you need a high-resolution mapping between frame position and presentation time,
+    * <p>
+    * If you need to track timestamps during initial warmup or after a routing or mode change,
+    * you should request a new timestamp once per second until the reported timestamps
+    * show that the audio clock is stable.
+    * Thereafter, query for a new timestamp approximately once every 10 seconds to once per minute.
+    * Calling this method more often is inefficient.
+    * It is also counter-productive to call this method more often than recommended,
+    * because the short-term differences between successive timestamp reports are not meaningful.
+    * If you need a high-resolution mapping between frame position and presentation time,
     * consider implementing that at application level, based on low-resolution timestamps.
+    * <p>
     * The audio data at the returned position may either already have been
     * presented, or may have not yet been presented but is committed to be presented.
     * It is not possible to request the time corresponding to a particular position,
@@ -811,6 +817,8 @@
     *         be presented.
     *         In the case that no timestamp is available, any supplied instance is left unaltered.
     */
+    // Add this text when the "on new timestamp" API is added:
+    //   Use if you need to get the most recent timestamp outside of the event callback handler.
     public boolean getTimestamp(AudioTimestamp timestamp)
     {
         if (timestamp == null) {
diff --git a/media/lib/signer/Android.mk b/media/lib/signer/Android.mk
index 4c3772f..bca643a 100644
--- a/media/lib/signer/Android.mk
+++ b/media/lib/signer/Android.mk
@@ -25,7 +25,7 @@
 LOCAL_SRC_FILES := \
             $(call all-java-files-under, java)
 
-include $(BUILD_STATIC_JAVA_LIBRARY)
+include $(BUILD_JAVA_LIBRARY)
 
 
 # ====  com.android.mediadrm.signer.xml lib def  ========================
diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java
index 6b7463c..88de17a 100644
--- a/telecomm/java/android/telecomm/Connection.java
+++ b/telecomm/java/android/telecomm/Connection.java
@@ -18,7 +18,6 @@
 
 import android.net.Uri;
 import android.os.Bundle;
-import android.util.Log;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -28,8 +27,6 @@
  */
 public abstract class Connection {
 
-    private static String TAG = Connection.class.getSimpleName();
-
     public interface Listener {
         void onStateChanged(Connection c, int state);
         void onAudioStateChanged(Connection c, CallAudioState state);
@@ -146,7 +143,7 @@
      * @hide
      */
     public final void playDtmfTone(char c) {
-        Log.d(TAG, "playDtmfTone " + c);
+        Log.d(this, "playDtmfTone %c", c);
         onPlayDtmfTone(c);
     }
 
@@ -156,7 +153,7 @@
      * @hide
      */
     public final void stopDtmfTone() {
-        Log.d(TAG, "stopDtmfTone");
+        Log.d(this, "stopDtmfTone");
         onStopDtmfTone();
     }
 
@@ -168,7 +165,7 @@
      * @hide
      */
     public final void disconnect() {
-        Log.d(TAG, "disconnect");
+        Log.d(this, "disconnect");
         onDisconnect();
     }
 
@@ -180,7 +177,7 @@
      * @hide
      */
     public final void abort() {
-        Log.d(TAG, "abort");
+        Log.d(this, "abort");
         onAbort();
     }
 
@@ -192,7 +189,7 @@
      * @hide
      */
     public final void hold() {
-        Log.d(TAG, "hold");
+        Log.d(this, "hold");
         onHold();
     }
 
@@ -204,7 +201,7 @@
      * @hide
      */
     public final void unhold() {
-        Log.d(TAG, "unhold");
+        Log.d(this, "unhold");
         onUnhold();
     }
 
@@ -216,7 +213,7 @@
      * @hide
      */
     public final void answer() {
-        Log.d(TAG, "answer");
+        Log.d(this, "answer");
         if (mState == State.RINGING) {
             onAnswer();
         }
@@ -230,7 +227,7 @@
      * @hide
      */
     public final void reject() {
-        Log.d(TAG, "reject");
+        Log.d(this, "reject");
         if (mState == State.RINGING) {
             onReject();
         }
@@ -242,7 +239,7 @@
      * @param state The new audio state.
      */
     public void setAudioState(CallAudioState state) {
-        Log.d(TAG, "setAudioState " + state);
+        Log.d(this, "setAudioState %s", state);
         onSetAudioState(state);
     }
 
@@ -265,7 +262,7 @@
             case State.DISCONNECTED:
                 return "DISCONNECTED";
             default:
-                Log.wtf(TAG, "Unknown state " + state);
+                Log.wtf(Connection.class, "Unknown state %d", state);
                 return "UNKNOWN";
         }
     }
@@ -276,7 +273,7 @@
      * @param handle The new handle.
      */
     protected void setHandle(Uri handle) {
-        Log.d(TAG, "setHandle " + handle);
+        Log.d(this, "setHandle %s", handle);
         // TODO: Enforce super called
         mHandle = handle;
         for (Listener l : mListeners) {
@@ -325,7 +322,7 @@
      */
     protected void setDisconnected(int cause, String message) {
         setState(State.DISCONNECTED);
-        Log.d(TAG, "Disconnected with cause " + cause + " message " + message);
+        Log.d(this, "Disconnected with cause %d message %s", cause, message);
         for (Listener l : mListeners) {
             l.onDisconnected(this, cause, message);
         }
@@ -403,7 +400,7 @@
     protected void onReject() {}
 
     private void setState(int state) {
-        Log.d(TAG, "setState: " + stateToString(state));
+        Log.d(this, "setState: %s", stateToString(state));
         this.mState = state;
         for (Listener l : mListeners) {
             l.onStateChanged(this, state);
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index aba4579..9ace36f 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -18,7 +18,6 @@
 
 import android.net.Uri;
 import android.os.Bundle;
-import android.util.Log;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -28,13 +27,8 @@
  * processes running on an Android device.
  */
 public abstract class ConnectionService extends CallService {
-    private static final String TAG = ConnectionService.class.getSimpleName();
-
-    // STOPSHIP: Debug Logging should be conditional on a debug flag or use a set of
-    // logging functions that make it automaticaly so.
-
     // Flag controlling whether PII is emitted into the logs
-    private static final boolean PII_DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG);
 
     private static final Connection NULL_CONNECTION = new Connection() {};
 
@@ -46,7 +40,7 @@
         @Override
         public void onStateChanged(Connection c, int state) {
             String id = mIdByConnection.get(c);
-            Log.d(TAG, "Adapter set state " + id + " " + Connection.stateToString(state));
+            Log.d(this, "Adapter set state %d %s", id, Connection.stateToString(state));
             switch (state) {
                 case Connection.State.ACTIVE:
                     getAdapter().setActive(id);
@@ -72,7 +66,7 @@
         @Override
         public void onDisconnected(Connection c, int cause, String message) {
             String id = mIdByConnection.get(c);
-            Log.d(TAG, "Adapter set disconnected " + cause + " " + message);
+            Log.d(this, "Adapter set disconnected %d %s", cause, message);
             getAdapter().setDisconnected(id, cause, message);
         }
 
@@ -99,21 +93,21 @@
 
     @Override
     public final void isCompatibleWith(final CallInfo callInfo) {
-        Log.d(TAG, "isCompatibleWith " + callInfo);
+        Log.d(this, "isCompatibleWith %s", callInfo);
         onFindSubscriptions(
                 callInfo.getHandle(),
                 new Response<Uri, Subscription>() {
                     @Override
                     public void onResult(Uri handle, Subscription... result) {
                         boolean isCompatible = result.length > 0;
-                        Log.d(TAG, "adapter setIsCompatibleWith "
+                        Log.d(this, "adapter setIsCompatibleWith "
                                 + callInfo.getId() + " " + isCompatible);
                         getAdapter().setIsCompatibleWith(callInfo.getId(), isCompatible);
                     }
 
                     @Override
                     public void onError(Uri handle, String reason) {
-                        Log.wtf(TAG, "Error in onFindSubscriptions " + callInfo.getHandle()
+                        Log.w(this, "Error in onFindSubscriptions " + callInfo.getHandle()
                                 + " error: " + reason);
                         getAdapter().setIsCompatibleWith(callInfo.getId(), false);
                     }
@@ -123,7 +117,7 @@
 
     @Override
     public final void call(final CallInfo callInfo) {
-        Log.d(TAG, "call " + callInfo);
+        Log.d(this, "call %s", callInfo);
         onCreateConnections(
                 new ConnectionRequest(
                         callInfo.getHandle(),
@@ -132,7 +126,7 @@
                     @Override
                     public void onResult(ConnectionRequest request, Connection... result) {
                         if (result.length != 1) {
-                            Log.d(TAG, "adapter handleFailedOutgoingCall " + callInfo);
+                            Log.d(this, "adapter handleFailedOutgoingCall %s", callInfo);
                             getAdapter().handleFailedOutgoingCall(
                                     callInfo.getId(),
                                     "Created " + result.length + " Connections, expected 1");
@@ -141,8 +135,7 @@
                             }
                         } else {
                             addConnection(callInfo.getId(), result[0]);
-                            Log.d(TAG, "adapter handleSuccessfulOutgoingCall "
-                                    + callInfo.getId());
+                            Log.d(this, "adapter handleSuccessfulOutgoingCall %s", callInfo.getId());
                             getAdapter().handleSuccessfulOutgoingCall(callInfo.getId());
                         }
                     }
@@ -157,13 +150,13 @@
 
     @Override
     public final void abort(String callId) {
-        Log.d(TAG, "abort " + callId);
+        Log.d(this, "abort %s", callId);
         findConnectionForAction(callId, "abort").abort();
     }
 
     @Override
     public final void setIncomingCallId(final String callId, Bundle extras) {
-        Log.d(TAG, "setIncomingCallId " + callId + " " + extras);
+        Log.d(this, "setIncomingCallId %s %s", callId, extras);
         onCreateIncomingConnection(
                 new ConnectionRequest(
                         null,  // TODO: Can we obtain this from "extras"?
@@ -172,7 +165,7 @@
                     @Override
                     public void onResult(ConnectionRequest request, Connection... result) {
                         if (result.length != 1) {
-                            Log.d(TAG, "adapter handleFailedOutgoingCall " + callId);
+                            Log.d(this, "adapter handleFailedOutgoingCall %s", callId);
                             getAdapter().handleFailedOutgoingCall(
                                     callId,
                                     "Created " + result.length + " Connections, expected 1");
@@ -181,7 +174,7 @@
                             }
                         } else {
                             addConnection(callId, result[0]);
-                            Log.d(TAG, "adapter notifyIncomingCall " + callId);
+                            Log.d(this, "adapter notifyIncomingCall %s", callId);
                             // TODO: Uri.EMPTY is because CallInfo crashes when Parceled with a
                             // null URI ... need to fix that at its cause!
                             getAdapter().notifyIncomingCall(new CallInfo(
@@ -194,7 +187,7 @@
 
                     @Override
                     public void onError(ConnectionRequest request, String reason) {
-                        Log.d(TAG, "adapter failed setIncomingCallId " + request + " " + reason);
+                        Log.d(this, "adapter failed setIncomingCallId %s %s", request, reason);
                     }
                 }
         );
@@ -202,49 +195,49 @@
 
     @Override
     public final void answer(String callId) {
-        Log.d(TAG, "answer " + callId);
+        Log.d(this, "answer %s", callId);
         findConnectionForAction(callId, "answer").answer();
     }
 
     @Override
     public final void reject(String callId) {
-        Log.d(TAG, "reject " + callId);
+        Log.d(this, "reject %s", callId);
         findConnectionForAction(callId, "reject").reject();
     }
 
     @Override
     public final void disconnect(String callId) {
-        Log.d(TAG, "disconnect " + callId);
+        Log.d(this, "disconnect %s", callId);
         findConnectionForAction(callId, "disconnect").disconnect();
     }
 
     @Override
     public final void hold(String callId) {
-        Log.d(TAG, "hold " + callId);
+        Log.d(this, "hold %s", callId);
         findConnectionForAction(callId, "hold").hold();
     }
 
     @Override
     public final void unhold(String callId) {
-        Log.d(TAG, "unhold " + callId);
+        Log.d(this, "unhold %s", callId);
         findConnectionForAction(callId, "unhold").unhold();
     }
 
     @Override
     public final void playDtmfTone(String callId, char digit) {
-        Log.d(TAG, "playDtmfTone " + callId + " " + Character.toString(digit));
+        Log.d(this, "playDtmfTone %s %c", callId, digit);
         findConnectionForAction(callId, "playDtmfTone").playDtmfTone(digit);
     }
 
     @Override
     public final void stopDtmfTone(String callId) {
-        Log.d(TAG, "stopDtmfTone " + callId);
+        Log.d(this, "stopDtmfTone %s", callId);
         findConnectionForAction(callId, "stopDtmfTone").stopDtmfTone();
     }
 
     @Override
     public final void onAudioStateChanged(String callId, CallAudioState audioState) {
-        Log.d(TAG, "onAudioStateChanged " + callId + " " + audioState);
+        Log.d(this, "onAudioStateChanged %s %s", callId, audioState);
         findConnectionForAction(callId, "onAudioStateChanged").setAudioState(audioState);
     }
 
@@ -318,7 +311,7 @@
             case Connection.State.DISCONNECTED:
                 return CallState.DISCONNECTED;
             default:
-                Log.wtf(TAG, "Unknown Connection.State " + connectionState);
+                Log.wtf(this, "Unknown Connection.State %d", connectionState);
                 return CallState.NEW;
         }
     }
@@ -339,7 +332,7 @@
         if (mConnectionById.containsKey(callId)) {
             return mConnectionById.get(callId);
         }
-        Log.wtf(TAG, action + " - Cannot find Connection \"" + callId + "\"");
+        Log.w(this, "%s - Cannot find Connection %s", action, callId);
         return NULL_CONNECTION;
     }
 }
\ No newline at end of file
diff --git a/telecomm/java/android/telecomm/Log.java b/telecomm/java/android/telecomm/Log.java
new file mode 100644
index 0000000..b8dfb11
--- /dev/null
+++ b/telecomm/java/android/telecomm/Log.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecomm;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.IllegalFormatException;
+import java.util.Locale;
+
+/**
+ * Manages logging for the entire module.
+ *
+ * @hide
+ */
+final public class Log {
+
+    // Generic tag for all Telecomm Framework logging
+    private static final String TAG = "TelecommFramework";
+
+    public static final boolean FORCE_LOGGING = true; /* STOP SHIP if true */
+    public static final boolean DEBUG = isLoggable(android.util.Log.DEBUG);
+    public static final boolean INFO = isLoggable(android.util.Log.INFO);
+    public static final boolean VERBOSE = isLoggable(android.util.Log.VERBOSE);
+    public static final boolean WARN = isLoggable(android.util.Log.WARN);
+    public static final boolean ERROR = isLoggable(android.util.Log.ERROR);
+
+    private Log() {}
+
+    public static boolean isLoggable(int level) {
+        return FORCE_LOGGING || android.util.Log.isLoggable(TAG, level);
+    }
+
+    public static void d(String prefix, String format, Object... args) {
+        if (DEBUG) {
+            android.util.Log.d(TAG, buildMessage(prefix, format, args));
+        }
+    }
+
+    public static void d(Object objectPrefix, String format, Object... args) {
+        if (DEBUG) {
+            android.util.Log.d(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+        }
+    }
+
+    public static void i(String prefix, String format, Object... args) {
+        if (INFO) {
+            android.util.Log.i(TAG, buildMessage(prefix, format, args));
+        }
+    }
+
+    public static void i(Object objectPrefix, String format, Object... args) {
+        if (INFO) {
+            android.util.Log.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+        }
+    }
+
+    public static void v(String prefix, String format, Object... args) {
+        if (VERBOSE) {
+            android.util.Log.v(TAG, buildMessage(prefix, format, args));
+        }
+    }
+
+    public static void v(Object objectPrefix, String format, Object... args) {
+        if (VERBOSE) {
+            android.util.Log.v(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+        }
+    }
+
+    public static void w(String prefix, String format, Object... args) {
+        if (WARN) {
+            android.util.Log.w(TAG, buildMessage(prefix, format, args));
+        }
+    }
+
+    public static void w(Object objectPrefix, String format, Object... args) {
+        if (WARN) {
+            android.util.Log.w(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+        }
+    }
+
+    public static void e(String prefix, Throwable tr, String format, Object... args) {
+        if (ERROR) {
+            android.util.Log.e(TAG, buildMessage(prefix, format, args), tr);
+        }
+    }
+
+    public static void e(Object objectPrefix, Throwable tr, String format, Object... args) {
+        if (ERROR) {
+            android.util.Log.e(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args),
+                    tr);
+        }
+    }
+
+    public static void wtf(String prefix, Throwable tr, String format, Object... args) {
+        android.util.Log.wtf(TAG, buildMessage(prefix, format, args), tr);
+    }
+
+    public static void wtf(Object objectPrefix, Throwable tr, String format, Object... args) {
+        android.util.Log.wtf(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args),
+                tr);
+    }
+
+    public static void wtf(String prefix, String format, Object... args) {
+        String msg = buildMessage(prefix, format, args);
+        android.util.Log.wtf(TAG, msg, new IllegalStateException(msg));
+    }
+
+    public static void wtf(Object objectPrefix, String format, Object... args) {
+        String msg = buildMessage(getPrefixFromObject(objectPrefix), format, args);
+        android.util.Log.wtf(TAG, msg, new IllegalStateException(msg));
+    }
+
+    /**
+     * Redact personally identifiable information for production users.
+     * If we are running in verbose mode, return the original string, otherwise
+     * return a SHA-1 hash of the input string.
+     */
+    public static String pii(Object pii) {
+        if (pii == null || VERBOSE) {
+            return String.valueOf(pii);
+        }
+        return "[" + secureHash(String.valueOf(pii).getBytes()) + "]";
+    }
+
+    private static String secureHash(byte[] input) {
+        MessageDigest messageDigest;
+        try {
+            messageDigest = MessageDigest.getInstance("SHA-1");
+        } catch (NoSuchAlgorithmException e) {
+            return null;
+        }
+        messageDigest.update(input);
+        byte[] result = messageDigest.digest();
+        return encodeHex(result);
+    }
+
+    private static String encodeHex(byte[] bytes) {
+        StringBuffer hex = new StringBuffer(bytes.length * 2);
+
+        for (int i = 0; i < bytes.length; i++) {
+            int byteIntValue = bytes[i] & 0xff;
+            if (byteIntValue < 0x10) {
+                hex.append("0");
+            }
+            hex.append(Integer.toString(byteIntValue, 16));
+        }
+
+        return hex.toString();
+    }
+
+    private static String getPrefixFromObject(Object obj) {
+        return obj == null ? "<null>" : obj.getClass().getSimpleName();
+    }
+
+    private static String buildMessage(String prefix, String format, Object... args) {
+        String msg;
+        try {
+            msg = (args == null || args.length == 0) ? format
+                    : String.format(Locale.US, format, args);
+        } catch (IllegalFormatException ife) {
+            wtf("Log", ife, "IllegalFormatException: formatString='%s' numArgs=%d", format,
+                    args.length);
+            msg = format + " (An error occurred while formatting the message.)";
+        }
+        return String.format(Locale.US, "%s: %s", prefix, msg);
+    }
+}