Merge change I36785a9d into eclair-mr2

* changes:
  Telephony: Make resetting radio on a radio technology change optional
diff --git a/api/current.xml b/api/current.xml
index 41714ef..af9c67d 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -23672,6 +23672,17 @@
 </implements>
 <implements name="android.content.DialogInterface.OnDismissListener">
 </implements>
+<method name="getSearchablesInGlobalSearch"
+ return="java.util.List&lt;android.app.SearchableInfo&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="onCancel"
  return="void"
  abstract="false"
@@ -24131,6 +24142,444 @@
 >
 </method>
 </interface>
+<class name="SearchableInfo"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.Parcelable">
+</implements>
+<method name="autoUrlDetect"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="findActionKey"
+ return="android.app.SearchableInfo.ActionKeyInfo"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="keyCode" type="int">
+</parameter>
+</method>
+<method name="getHintId"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getIconId"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getImeOptions"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getInputType"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getLabelId"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSearchActivity"
+ return="android.content.ComponentName"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSearchButtonText"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSettingsDescription"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSuggestAuthority"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSuggestIntentAction"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSuggestIntentData"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSuggestPackage"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSuggestPath"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSuggestSelection"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSuggestThreshold"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getVoiceLanguageId"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getVoiceLanguageModeId"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getVoiceMaxResults"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getVoicePromptTextId"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getVoiceSearchEnabled"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getVoiceSearchLaunchRecognizer"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getVoiceSearchLaunchWebSearch"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="queryAfterZeroResults"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="shouldIncludeInGlobalSearch"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="shouldRewriteQueryFromData"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="shouldRewriteQueryFromText"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="writeToParcel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="dest" type="android.os.Parcel">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+<field name="CREATOR"
+ type="android.os.Parcelable.Creator"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<class name="SearchableInfo.ActionKeyInfo"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.Parcelable">
+</implements>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getKeyCode"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getQueryActionMsg"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSuggestActionMsg"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSuggestActionMsgColumn"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="writeToParcel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="dest" type="android.os.Parcel">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+</class>
 <class name="Service"
  extends="android.content.ContextWrapper"
  abstract="true"
@@ -31903,6 +32352,17 @@
  visibility="public"
 >
 </field>
+<field name="BIND_NOT_FOREGROUND"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="CLIPBOARD_SERVICE"
  type="java.lang.String"
  transient="false"
@@ -83994,6 +84454,148 @@
 >
 </method>
 </class>
+<class name="TrafficStats"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="TrafficStats"
+ type="android.net.TrafficStats"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="getMobileRxBytes"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getMobileRxPkts"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getMobileTxBytes"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getMobileTxPkts"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getTotalRxBytes"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getTotalRxPkts"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getTotalTxBytes"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getTotalTxPkts"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getUidRxBytes"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uid" type="int">
+</parameter>
+</method>
+<method name="getUidTxBytes"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uid" type="int">
+</parameter>
+</method>
+<field name="UNSUPPORTED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
 <class name="Uri"
  extends="java.lang.Object"
  abstract="true"
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 467812e..1115f92 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -691,6 +691,20 @@
                     if (rd != null) {
                         rd.performReceive(intent, resultCode, data, extras,
                                 ordered, sticky);
+                    } else {
+                        // The activity manager dispatched a broadcast to a registered
+                        // receiver in this process, but before it could be delivered the
+                        // receiver was unregistered.  Acknowledge the broadcast on its
+                        // behalf so that the system's broadcast sequence can continue.
+                        if (DEBUG_BROADCAST) {
+                            Log.i(TAG, "Broadcast to unregistered receiver");
+                        }
+                        IActivityManager mgr = ActivityManagerNative.getDefault();
+                        try {
+                            mgr.finishReceiver(this, resultCode, data, extras, false);
+                        } catch (RemoteException e) {
+                            Log.w(TAG, "Couldn't finish broadcast to unregistered receiver");
+                        }
                     }
                 }
             }
@@ -716,8 +730,8 @@
                     BroadcastReceiver receiver = mReceiver;
                     if (DEBUG_BROADCAST) {
                         int seq = mCurIntent.getIntExtra("seq", -1);
-                        Log.i(TAG, "Dispathing broadcast " + mCurIntent.getAction() + " seq=" + seq
-                                + " to " + mReceiver);
+                        Log.i(TAG, "Dispatching broadcast " + mCurIntent.getAction()
+                                + " seq=" + seq + " to " + mReceiver);
                     }
                     if (receiver == null) {
                         return;
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index ab5e102..1f17476 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -2038,10 +2038,8 @@
      * Returns a list of the searchable activities that can be included in global search.
      * 
      * @return a list containing searchable information for all searchable activities
-     *         that have the <code>exported</code> attribute set in their searchable
-     *         meta-data.
-     * 
-     * @hide because SearchableInfo is not part of the API.
+     *         that have the <code>android:includeInGlobalSearch</code> attribute set
+     *         in their searchable meta-data.
      */
     public List<SearchableInfo> getSearchablesInGlobalSearch() {
         try {
diff --git a/core/java/android/app/SearchableInfo.java b/core/java/android/app/SearchableInfo.java
index cbf7b3d..9897742 100644
--- a/core/java/android/app/SearchableInfo.java
+++ b/core/java/android/app/SearchableInfo.java
@@ -38,8 +38,9 @@
 import java.util.HashMap;
 
 /**
- * 
- * @hide Pending API council approval
+ * Searchability meta-data for an activity.
+ * See <a href="SearchManager.html#SearchabilityMetadata">Searchability meta-data</a>
+ * for more information.
  */
 public final class SearchableInfo implements Parcelable {
 
@@ -87,9 +88,9 @@
     private final String mSuggestProviderPackage;
     
     // Flag values for Searchable_voiceSearchMode
-    private static int VOICE_SEARCH_SHOW_BUTTON = 1;
-    private static int VOICE_SEARCH_LAUNCH_WEB_SEARCH = 2;
-    private static int VOICE_SEARCH_LAUNCH_RECOGNIZER = 4;
+    private static final int VOICE_SEARCH_SHOW_BUTTON = 1;
+    private static final int VOICE_SEARCH_LAUNCH_WEB_SEARCH = 2;
+    private static final int VOICE_SEARCH_LAUNCH_RECOGNIZER = 4;
     private final int mVoiceSearchMode;
     private final int mVoiceLanguageModeId;       // voiceLanguageModel
     private final int mVoicePromptTextId;         // voicePromptText
@@ -123,6 +124,8 @@
 
     /**
      * Checks whether the badge should be a text label.
+     *
+     * @hide This feature is deprecated, no need to add it to the API.
      */
     public boolean useBadgeLabel() {
         return 0 != (mSearchMode & SEARCH_MODE_BADGE_LABEL);
@@ -130,6 +133,8 @@
 
     /**
      * Checks whether the badge should be an icon.
+     *
+     * @hide This feature is deprecated, no need to add it to the API.
      */
     public boolean useBadgeIcon() {
         return (0 != (mSearchMode & SEARCH_MODE_BADGE_ICON)) && (mIconId != 0);
@@ -220,6 +225,7 @@
      * 
      * @param context You need to supply a context to start with
      * @return Returns a context related to the searchable activity
+     * @hide
      */
     public Context getActivityContext(Context context) {
         return createActivityContext(context, mSearchActivity);
@@ -251,6 +257,7 @@
      * @param activityContext If we can determine that the provider and the activity are the
      * same, we'll just return this one.
      * @return Returns a context related to the context provider
+     * @hide
      */
     public Context getProviderContext(Context context, Context activityContext) {
         Context theirContext = null;
@@ -351,7 +358,11 @@
     }
     
     /**
-     * Private class used to hold the "action key" configuration
+     * Information about an action key in searchability meta-data.
+     * See <a href="SearchManager.html#SearchabilityMetadata">Searchability meta-data</a>
+     * for more information.
+     *
+     * @see SearchableInfo#findActionKey(int)
      */
     public static class ActionKeyInfo implements Parcelable {
         
@@ -368,7 +379,7 @@
          * construct the object.
          * @throws IllegalArgumentException if the action key configuration is invalid
          */
-        public ActionKeyInfo(Context activityContext, AttributeSet attr) {
+        ActionKeyInfo(Context activityContext, AttributeSet attr) {
             TypedArray a = activityContext.obtainStyledAttributes(attr,
                     com.android.internal.R.styleable.SearchableActionKey);
 
@@ -399,7 +410,7 @@
          * @param in The Parcel containing the previously written ActionKeyInfo,
          * positioned at the location in the buffer where it was written.
          */
-        public ActionKeyInfo(Parcel in) {
+        private ActionKeyInfo(Parcel in) {
             mKeyCode = in.readInt();
             mQueryActionMsg = in.readString();
             mSuggestActionMsg = in.readString();
@@ -461,6 +472,8 @@
      * @param activityInfo Activity to get search information from.
      * @return Search information about the given activity, or {@code null} if
      *         the activity has no or invalid searchability meta-data.
+     *
+     * @hide For use by SearchManagerService.
      */
     public static SearchableInfo getActivityMetaData(Context context, ActivityInfo activityInfo) {
         // for each component, try to find metadata
@@ -720,7 +733,7 @@
      * @param in The Parcel containing the previously written SearchableInfo,
      * positioned at the location in the buffer where it was written.
      */
-    public SearchableInfo(Parcel in) {
+    SearchableInfo(Parcel in) {
         mLabelId = in.readInt();
         mSearchActivity = ComponentName.readFromParcel(in);
         mHintId = in.readInt();
diff --git a/core/java/android/content/AbstractThreadedSyncAdapter.java b/core/java/android/content/AbstractThreadedSyncAdapter.java
index 0db6155..091d44e 100644
--- a/core/java/android/content/AbstractThreadedSyncAdapter.java
+++ b/core/java/android/content/AbstractThreadedSyncAdapter.java
@@ -17,10 +17,10 @@
 package android.content;
 
 import android.accounts.Account;
+import android.net.TrafficStats;
 import android.os.Bundle;
-import android.os.Process;
-import android.os.NetStat;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.RemoteException;
 import android.util.EventLog;
 
@@ -157,8 +157,8 @@
 
             SyncResult syncResult = new SyncResult();
             int uid = Process.myUid();
-            mInitialTxBytes = NetStat.getUidTxBytes(uid);
-            mInitialRxBytes = NetStat.getUidRxBytes(uid);
+            mInitialTxBytes = TrafficStats.getUidTxBytes(uid);
+            mInitialRxBytes = TrafficStats.getUidRxBytes(uid);
             ContentProviderClient provider = null;
             try {
                 provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority);
@@ -175,8 +175,8 @@
                 if (!isCanceled()) {
                     mSyncContext.onFinished(syncResult);
                 }
-                onLogSyncDetails(NetStat.getUidTxBytes(uid) - mInitialTxBytes,
-                        NetStat.getUidRxBytes(uid) - mInitialRxBytes, syncResult);
+                onLogSyncDetails(TrafficStats.getUidTxBytes(uid) - mInitialTxBytes,
+                        TrafficStats.getUidRxBytes(uid) - mInitialRxBytes, syncResult);
                 // synchronize so that the assignment will be seen by other threads
                 // that also synchronize accesses to mSyncThread
                 synchronized (mSyncThreadLock) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 3344158..2ab5357 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -104,6 +104,18 @@
      */
     public static final int BIND_DEBUG_UNBIND = 0x0002;
 
+    /**
+     * Flag for {@link #bindService}: don't allow this binding to raise
+     * the target service's process to the foreground scheduling priority.
+     * It will still be raised to the at least the same memory priority
+     * as the client (so that its process will not be killable in any
+     * situation where the client is not killable), but for CPU scheduling
+     * purposes it may be left in the background.  This only has an impact
+     * in the situation where the binding client is a foreground process
+     * and the target service is in a background process.
+     */
+    public static final int BIND_NOT_FOREGROUND = 0x0004;
+
     /** Return an AssetManager instance for your application's package. */
     public abstract AssetManager getAssets();
 
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 0589ce6..d436365 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -557,7 +557,7 @@
         intent.setAction("android.content.SyncAdapter");
         intent.setComponent(syncAdapterInfo.componentName);
         mContext.bindService(intent, new InitializerServiceConnection(account, authority),
-                Context.BIND_AUTO_CREATE);
+                Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND);
     }
 
     private class InitializerServiceConnection implements ServiceConnection {
@@ -1145,7 +1145,8 @@
                     com.android.internal.R.string.sync_binding_label);
             intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
                     mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0));
-            return mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
+            return mContext.bindService(intent, this,
+                    Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND);
         }
 
         void unBindFromSyncAdapter() {
diff --git a/core/java/android/content/TempProviderSyncAdapter.java b/core/java/android/content/TempProviderSyncAdapter.java
index b46c545..5ccaa26 100644
--- a/core/java/android/content/TempProviderSyncAdapter.java
+++ b/core/java/android/content/TempProviderSyncAdapter.java
@@ -1,9 +1,12 @@
 package android.content;
 
+import android.accounts.Account;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
 import android.database.SQLException;
+import android.net.TrafficStats;
 import android.os.Bundle;
 import android.os.Debug;
-import android.os.NetStat;
 import android.os.Parcelable;
 import android.os.Process;
 import android.os.SystemProperties;
@@ -12,9 +15,6 @@
 import android.util.EventLog;
 import android.util.Log;
 import android.util.TimingLogger;
-import android.accounts.Account;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
 
 import java.io.IOException;
 
@@ -203,8 +203,8 @@
             if (mProviderSyncStarted) mProvider.onSyncCanceled();
             // We may lose the last few sync events when canceling.  Oh well.
             int uid = Process.myUid();
-            logSyncDetails(NetStat.getUidTxBytes(uid) - mInitialTxBytes,
-                    NetStat.getUidRxBytes(uid) - mInitialRxBytes, mResult);
+            logSyncDetails(TrafficStats.getUidTxBytes(uid) - mInitialTxBytes,
+                    TrafficStats.getUidRxBytes(uid) - mInitialRxBytes, mResult);
         }
         
         @Override
@@ -212,8 +212,8 @@
             Process.setThreadPriority(Process.myTid(),
                     Process.THREAD_PRIORITY_BACKGROUND);
             int uid = Process.myUid();
-            mInitialTxBytes = NetStat.getUidTxBytes(uid);
-            mInitialRxBytes = NetStat.getUidRxBytes(uid);
+            mInitialTxBytes = TrafficStats.getUidTxBytes(uid);
+            mInitialRxBytes = TrafficStats.getUidRxBytes(uid);
             try {
                 sync(mSyncContext, mAccount, mAuthority, mExtras);
             } catch (SQLException e) {
@@ -222,8 +222,8 @@
             } finally {
                 mSyncThread = null;
                 if (!mIsCanceled) {
-                    logSyncDetails(NetStat.getUidTxBytes(uid) - mInitialTxBytes,
-                    NetStat.getUidRxBytes(uid) - mInitialRxBytes, mResult);
+                    logSyncDetails(TrafficStats.getUidTxBytes(uid) - mInitialTxBytes,
+                    TrafficStats.getUidRxBytes(uid) - mInitialRxBytes, mResult);
                     mSyncContext.onFinished(mResult);
                 }
             }
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index 0f7ef22..76131fc 100755
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -1127,7 +1127,7 @@
         
         // Needs to be called after the gesture detector gets a turn, as it may have
         // displayed the mini keyboard
-        if (mMiniKeyboardOnScreen) {
+        if (mMiniKeyboardOnScreen && action != MotionEvent.ACTION_CANCEL) {
             return true;
         }
 
@@ -1222,6 +1222,7 @@
                 break;
             case MotionEvent.ACTION_CANCEL:
                 removeMessages();
+                dismissPopupKeyboard();
                 mAbortKey = true;
                 showPreview(NOT_A_KEY);
                 invalidateKey(mCurrentKey);
diff --git a/core/java/android/os/NetStat.java b/core/java/android/net/TrafficStats.java
similarity index 71%
rename from core/java/android/os/NetStat.java
rename to core/java/android/net/TrafficStats.java
index e294cdf..62e9f1f 100644
--- a/core/java/android/os/NetStat.java
+++ b/core/java/android/net/TrafficStats.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.os;
+package android.net;
 
 import android.util.Log;
 
@@ -22,11 +22,22 @@
 import java.io.RandomAccessFile;
 import java.io.IOException;
 
-/** @hide */
-public class NetStat {
+/**
+ * Class that provides network traffic statistics.  These statistics include bytes transmitted and
+ * received and network packets transmitted and received, over all interfaces, over the mobile
+ * interface, and on a per-UID basis.
+ * <p>
+ * These statistics may not be available on all platforms.  If the statistics are not supported
+ * by this device, {@link #UNSUPPORTED} will be returned.
+ */
+public class TrafficStats {
+    /**
+     * The return value to indicate that the device does not support the statistic.
+     */
+    public final static int UNSUPPORTED = -1;
 
     // Logging tag.
-    private final static String TAG = "netstat";
+    private final static String TAG = "trafficstats";
 
     // We pre-create all the File objects so we don't spend a lot of
     // CPU at runtime converting from Java Strings to byte[] for the
@@ -38,36 +49,40 @@
     private final static File SYS_CLASS_NET_DIR = new File("/sys/class/net");
 
     /**
-     * Get total number of tx packets sent through rmnet0 or ppp0
+     * Get the total number of packets transmitted through the mobile interface.
      *
-     * @return number of Tx packets through rmnet0 or ppp0
+     * @return number of packets.  If the statistics are not supported by this device,
+     * {@link #UNSUPPORTED} will be returned.
      */
     public static long getMobileTxPkts() {
         return getMobileStat(MOBILE_TX_PACKETS);
     }
 
     /**
-     *  Get total number of rx packets received through rmnet0 or ppp0
+     * Get the total number of packets received through the mobile interface.
      *
-     * @return number of Rx packets through rmnet0 or ppp0
+     * @return number of packets.  If the statistics are not supported by this device,
+     * {@link #UNSUPPORTED} will be returned.
      */
     public static long getMobileRxPkts() {
         return getMobileStat(MOBILE_RX_PACKETS);
     }
 
     /**
-     *  Get total number of tx bytes received through rmnet0 or ppp0
+     * Get the total number of bytes transmitted through the mobile interface.
      *
-     * @return number of Tx bytes through rmnet0 or ppp0
+     * @return number of bytes.  If the statistics are not supported by this device,
+     * {@link #UNSUPPORTED} will be returned.
      */
       public static long getMobileTxBytes() {
           return getMobileStat(MOBILE_TX_BYTES);
       }
 
     /**
-     *  Get total number of rx bytes received through rmnet0 or ppp0
+     * Get the total number of bytes received through the mobile interface.
      *
-     * @return number of Rx bytes through rmnet0 or ppp0
+     * @return number of bytes.  If the statistics are not supported by this device,
+     * {@link #UNSUPPORTED} will be returned.
      */
     public static long getMobileRxBytes() {
         return getMobileStat(MOBILE_RX_BYTES);
@@ -76,7 +91,8 @@
     /**
      * Get the total number of packets sent through all network interfaces.
      *
-     * @return the number of packets sent through all network interfaces
+     * @return the number of packets.  If the statistics are not supported by this device,
+     * {@link #UNSUPPORTED} will be returned.
      */
     public static long getTotalTxPkts() {
         return getTotalStat("tx_packets");
@@ -85,7 +101,8 @@
     /**
      * Get the total number of packets received through all network interfaces.
      *
-     * @return the number of packets received through all network interfaces
+     * @return number of packets.  If the statistics are not supported by this device,
+     * {@link #UNSUPPORTED} will be returned.
      */
     public static long getTotalRxPkts() {
         return getTotalStat("rx_packets");
@@ -94,7 +111,8 @@
     /**
      * Get the total number of bytes sent through all network interfaces.
      *
-     * @return the number of bytes sent through all network interfaces
+     * @return number of bytes.  If the statistics are not supported by this device,
+     * {@link #UNSUPPORTED} will be returned.
      */
     public static long getTotalTxBytes() {
         return getTotalStat("tx_bytes");
@@ -103,35 +121,35 @@
     /**
      * Get the total number of bytes received through all network interfaces.
      *
-     * @return the number of bytes received through all network interfaces
+     * @return number of bytes.  If the statistics are not supported by this device,
+     * {@link #UNSUPPORTED} will be returned.
      */
     public static long getTotalRxBytes() {
         return getTotalStat("rx_bytes");
     }
 
     /**
-     * Gets network bytes sent for this UID.
+     * Get the number of bytes sent through the network for this UID.
      * The statistics are across all interfaces.
-     * The statistics come from /proc/uid_stat.
      *
      * {@see android.os.Process#myUid()}.
      *
-     * @param uid
-     * @return byte count
+     * @param uid The UID of the process to examine.
+     * @return number of bytes.  If the statistics are not supported by this device,
+     * {@link #UNSUPPORTED} will be returned.
      */
     public static long getUidTxBytes(int uid) {
         return getNumberFromFilePath("/proc/uid_stat/" + uid + "/tcp_snd");
     }
 
     /**
-     * Gets network bytes received for this UID.
+     * Get the number of bytes received through the network for this UID.
      * The statistics are across all interfaces.
-     * The statistics come from /proc/uid_stat.
      *
      * {@see android.os.Process#myUid()}.
      *
-     * @param uid
-     * @return byte count
+     * @param uid The UID of the process to examine.
+     * @return number of bytes
      */
     public static long getUidRxBytes(int uid) {
         return getNumberFromFilePath("/proc/uid_stat/" + uid + "/tcp_rcv");
@@ -159,7 +177,7 @@
 
         File[] nets = SYS_CLASS_NET_DIR.listFiles();
         if (nets == null) {
-            return 0;
+            return UNSUPPORTED;
         }
         long total = 0;
         StringBuffer strbuf = new StringBuffer();
@@ -187,14 +205,14 @@
                       e);
             }
         }
-        return 0L;
+        return UNSUPPORTED;
     }
 
     // File will have format <number><newline>
     private static long getNumberFromFilePath(String filename) {
         RandomAccessFile raf = getFile(filename);
         if (raf == null) {
-            return 0L;
+            return UNSUPPORTED;
         }
         return getNumberFromFile(raf, filename);
     }
@@ -209,7 +227,7 @@
             raf.close();
         } catch (IOException e) {
             Log.w(TAG, "Exception getting TCP bytes from " + filename, e);
-            return 0L;
+            return UNSUPPORTED;
         } finally {
             if (raf != null) {
                 try {
diff --git a/core/java/android/provider/Gmail.java b/core/java/android/provider/Gmail.java
deleted file mode 100644
index 8bb7adf..0000000
--- a/core/java/android/provider/Gmail.java
+++ /dev/null
@@ -1,2468 +0,0 @@
-/*
- * Copyright (C) 2007 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.provider;
-
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-import com.google.android.collect.Sets;
-
-import android.content.AsyncQueryHandler;
-import android.content.ContentQueryMap;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.database.DataSetObserver;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.text.Html;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.TextUtils.SimpleStringSplitter;
-import android.text.style.CharacterStyle;
-import android.util.Log;
-
-import com.android.common.Patterns;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Observable;
-import java.util.Observer;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * A thin wrapper over the content resolver for accessing the gmail provider.
- *
- * @hide
- */
-public final class Gmail {
-    // Set to true to enable extra debugging.
-    private static final boolean DEBUG = false;
-
-    public static final String GMAIL_AUTH_SERVICE = "mail";
-    // These constants come from google3/java/com/google/caribou/backend/MailLabel.java.
-    public static final String LABEL_SENT = "^f";
-    public static final String LABEL_INBOX = "^i";
-    public static final String LABEL_DRAFT = "^r";
-    public static final String LABEL_UNREAD = "^u";
-    public static final String LABEL_TRASH = "^k";
-    public static final String LABEL_SPAM = "^s";
-    public static final String LABEL_STARRED = "^t";
-    public static final String LABEL_CHAT = "^b"; // 'b' for 'buzz'
-    public static final String LABEL_VOICEMAIL = "^vm";
-    public static final String LABEL_IGNORED = "^g";
-    public static final String LABEL_ALL = "^all";
-    // These constants (starting with "^^") are only used locally and are not understood by the
-    // server.
-    public static final String LABEL_VOICEMAIL_INBOX = "^^vmi";
-    public static final String LABEL_CACHED = "^^cached";
-    public static final String LABEL_OUTBOX = "^^out";
-
-    public static final String AUTHORITY = "gmail-ls";
-    private static final String TAG = "Gmail";
-    private static final String AUTHORITY_PLUS_CONVERSATIONS =
-            "content://" + AUTHORITY + "/conversations/";
-    private static final String AUTHORITY_PLUS_LABELS =
-            "content://" + AUTHORITY + "/labels/";
-    private static final String AUTHORITY_PLUS_MESSAGES =
-            "content://" + AUTHORITY + "/messages/";
-    private static final String AUTHORITY_PLUS_SETTINGS =
-            "content://" + AUTHORITY + "/settings/";
-
-    public static final Uri BASE_URI = Uri.parse(
-            "content://" + AUTHORITY);
-    private static final Uri LABELS_URI =
-            Uri.parse(AUTHORITY_PLUS_LABELS);
-    private static final Uri CONVERSATIONS_URI =
-            Uri.parse(AUTHORITY_PLUS_CONVERSATIONS);
-    private static final Uri SETTINGS_URI =
-            Uri.parse(AUTHORITY_PLUS_SETTINGS);
-
-    /** Separates email addresses in strings in the database. */
-    public static final String EMAIL_SEPARATOR = "\n";
-    public static final Pattern EMAIL_SEPARATOR_PATTERN = Pattern.compile(EMAIL_SEPARATOR);
-
-    /**
-     * Space-separated lists have separators only between items.
-     */
-    private static final char SPACE_SEPARATOR = ' ';
-    public static final Pattern SPACE_SEPARATOR_PATTERN = Pattern.compile(" ");
-
-    /**
-     * Comma-separated lists have separators between each item, before the first and after the last
-     * item. The empty list is <tt>,</tt>.
-     *
-     * <p>This makes them easier to modify with SQL since it is not a special case to add or
-     * remove the last item. Having a separator on each side of each value also makes it safe to use
-     * SQL's REPLACE to remove an item from a string by using REPLACE(',value,', ',').
-     *
-     * <p>We could use the same separator for both lists but this makes it easier to remember which
-     * kind of list one is dealing with.
-     */
-    private static final char COMMA_SEPARATOR = ',';
-    public static final Pattern COMMA_SEPARATOR_PATTERN = Pattern.compile(",");
-
-    /** Separates attachment info parts in strings in the database. */
-    public static final String ATTACHMENT_INFO_SEPARATOR = "\n";
-    public static final Pattern ATTACHMENT_INFO_SEPARATOR_PATTERN =
-            Pattern.compile(ATTACHMENT_INFO_SEPARATOR);
-
-    public static final Character SENDER_LIST_SEPARATOR = '\n';
-    public static final String SENDER_LIST_TOKEN_ELIDED = "e";
-    public static final String SENDER_LIST_TOKEN_NUM_MESSAGES = "n";
-    public static final String SENDER_LIST_TOKEN_NUM_DRAFTS = "d";
-    public static final String SENDER_LIST_TOKEN_LITERAL = "l";
-    public static final String SENDER_LIST_TOKEN_SENDING = "s";
-    public static final String SENDER_LIST_TOKEN_SEND_FAILED = "f";
-
-    /** Used for finding status in a cursor's extras. */
-    public static final String EXTRA_STATUS = "status";
-
-    public static final String RESPOND_INPUT_COMMAND = "command";
-    public static final String COMMAND_RETRY = "retry";
-    public static final String COMMAND_ACTIVATE = "activate";
-    public static final String COMMAND_SET_VISIBLE = "setVisible";
-    public static final String SET_VISIBLE_PARAM_VISIBLE = "visible";
-    public static final String RESPOND_OUTPUT_COMMAND_RESPONSE = "commandResponse";
-    public static final String COMMAND_RESPONSE_OK =  "ok";
-    public static final String COMMAND_RESPONSE_UNKNOWN =  "unknownCommand";
-
-    public static final String INSERT_PARAM_ATTACHMENT_ORIGIN = "origin";
-    public static final String INSERT_PARAM_ATTACHMENT_ORIGIN_EXTRAS = "originExtras";
-
-    private static final Pattern NAME_ADDRESS_PATTERN = Pattern.compile("\"(.*)\"");
-    private static final Pattern UNNAMED_ADDRESS_PATTERN = Pattern.compile("([^<]+)@");
-
-    private static final Map<Integer, Integer> sPriorityToLength = Maps.newHashMap();
-    public static final SimpleStringSplitter sSenderListSplitter = 
-            new SimpleStringSplitter(SENDER_LIST_SEPARATOR);
-    public static String[] sSenderFragments = new String[8];
-
-    /**
-     * Returns the name in an address string
-     * @param addressString such as &quot;bobby&quot; &lt;bob@example.com&gt;
-     * @return returns the quoted name in the addressString, otherwise the username from the email
-     *   address
-     */
-    public static String getNameFromAddressString(String addressString) {
-        Matcher namedAddressMatch = NAME_ADDRESS_PATTERN.matcher(addressString);
-        if (namedAddressMatch.find()) {
-            String name = namedAddressMatch.group(1);
-            if (name.length() > 0) return name;
-            addressString =
-                    addressString.substring(namedAddressMatch.end(), addressString.length());
-        }
-
-        Matcher unnamedAddressMatch = UNNAMED_ADDRESS_PATTERN.matcher(addressString);
-        if (unnamedAddressMatch.find()) {
-            return unnamedAddressMatch.group(1);
-        }
-
-        return addressString;
-    }
-
-    /**
-     * Returns the email address in an address string
-     * @param addressString such as &quot;bobby&quot; &lt;bob@example.com&gt;
-     * @return returns the email address, such as bob@example.com from the example above
-     */
-    public static String getEmailFromAddressString(String addressString) {
-        String result = addressString;
-        Matcher match = Patterns.EMAIL_ADDRESS.matcher(addressString);
-        if (match.find()) {
-            result = addressString.substring(match.start(), match.end());
-        }
-
-        return result;
-    }
-
-    /**
-     * Returns whether the label is user-defined (versus system-defined labels such as inbox, whose
-     * names start with "^").
-     */
-    public static boolean isLabelUserDefined(String label) {
-        // TODO: label should never be empty so we should be able to say [label.charAt(0) != '^'].
-        // However, it's a release week and I'm too scared to make that change.
-        return !label.startsWith("^");
-    }
-
-    private static final Set<String> USER_SETTABLE_BUILTIN_LABELS = Sets.newHashSet(
-            Gmail.LABEL_INBOX,
-            Gmail.LABEL_UNREAD,
-            Gmail.LABEL_TRASH,
-            Gmail.LABEL_SPAM,
-            Gmail.LABEL_STARRED,
-            Gmail.LABEL_IGNORED);
-
-    /**
-     * Returns whether the label is user-settable. For example, labels such as LABEL_DRAFT should
-     * only be set internally.
-     */
-    public static boolean isLabelUserSettable(String label) {
-        return USER_SETTABLE_BUILTIN_LABELS.contains(label) || isLabelUserDefined(label);
-    }
-
-    /**
-     * Returns the set of labels using the raw labels from a previous getRawLabels()
-     * as input.
-     * @return a copy of the set of labels. To add or remove labels call
-     * MessageCursor.addOrRemoveLabel on each message in the conversation.
-     */
-    public static Set<Long> getLabelIdsFromLabelIdsString(
-            TextUtils.StringSplitter splitter) {
-        Set<Long> labelIds = Sets.newHashSet();
-        for (String labelIdString : splitter) {
-            labelIds.add(Long.valueOf(labelIdString));
-        }
-        return labelIds;
-    }
-
-    /**
-     * @deprecated remove when the activities stop using canonical names to identify labels
-     */
-    public static Set<String> getCanonicalNamesFromLabelIdsString(
-            LabelMap labelMap, TextUtils.StringSplitter splitter) {
-        Set<String> canonicalNames = Sets.newHashSet();
-        for (long labelId : getLabelIdsFromLabelIdsString(splitter)) {
-            final String canonicalName = labelMap.getCanonicalName(labelId);
-            // We will sometimes see labels that the label map does not yet know about or that
-            // do not have names yet.
-            if (!TextUtils.isEmpty(canonicalName)) {
-                canonicalNames.add(canonicalName);
-            } else {
-                Log.w(TAG, "getCanonicalNamesFromLabelIdsString skipping label id: " + labelId);
-            }
-        }
-        return canonicalNames;
-    }
-
-    /**
-     * @return a StringSplitter that is configured to split message label id strings
-     */
-    public static TextUtils.StringSplitter newMessageLabelIdsSplitter() {
-        return new TextUtils.SimpleStringSplitter(SPACE_SEPARATOR);
-    }
-
-    /**
-     * @return a StringSplitter that is configured to split conversation label id strings
-     */
-    public static TextUtils.StringSplitter newConversationLabelIdsSplitter() {
-        return new CommaStringSplitter();
-    }
-
-    /**
-     * A splitter for strings of the form described in the docs for COMMA_SEPARATOR.
-     */
-    private static class CommaStringSplitter extends TextUtils.SimpleStringSplitter {
-
-        public CommaStringSplitter() {
-            super(COMMA_SEPARATOR);
-        }
-
-        @Override
-        public void setString(String string) {
-            // The string should always be at least a single comma.
-            super.setString(string.substring(1));
-        }
-    }
-
-    /**
-     * Creates a single string of the form that getLabelIdsFromLabelIdsString can split.
-     */
-    public static String getLabelIdsStringFromLabelIds(Set<Long> labelIds) {
-        StringBuilder sb = new StringBuilder();
-        sb.append(COMMA_SEPARATOR);
-        for (Long labelId : labelIds) {
-            sb.append(labelId);
-            sb.append(COMMA_SEPARATOR);
-        }
-        return sb.toString();
-    }
-
-    public static final class ConversationColumns {
-        public static final String ID = "_id";
-        public static final String SUBJECT = "subject";
-        public static final String SNIPPET = "snippet";
-        public static final String FROM = "fromAddress";
-        public static final String DATE = "date";
-        public static final String PERSONAL_LEVEL = "personalLevel";
-        /** A list of label names with a space after each one (including the last one). This makes
-         * it easier remove individual labels from this list using SQL. */
-        public static final String LABEL_IDS = "labelIds";
-        public static final String NUM_MESSAGES = "numMessages";
-        public static final String MAX_MESSAGE_ID = "maxMessageId";
-        public static final String HAS_ATTACHMENTS = "hasAttachments";
-        public static final String HAS_MESSAGES_WITH_ERRORS = "hasMessagesWithErrors";
-        public static final String FORCE_ALL_UNREAD = "forceAllUnread";
-
-        private ConversationColumns() {}
-    }
-
-    public static final class MessageColumns {
-
-        public static final String ID = "_id";
-        public static final String MESSAGE_ID = "messageId";
-        public static final String CONVERSATION_ID = "conversation";
-        public static final String SUBJECT = "subject";
-        public static final String SNIPPET = "snippet";
-        public static final String FROM = "fromAddress";
-        public static final String TO = "toAddresses";
-        public static final String CC = "ccAddresses";
-        public static final String BCC = "bccAddresses";
-        public static final String REPLY_TO = "replyToAddresses";
-        public static final String DATE_SENT_MS = "dateSentMs";
-        public static final String DATE_RECEIVED_MS = "dateReceivedMs";
-        public static final String LIST_INFO = "listInfo";
-        public static final String PERSONAL_LEVEL = "personalLevel";
-        public static final String BODY = "body";
-        public static final String EMBEDS_EXTERNAL_RESOURCES = "bodyEmbedsExternalResources";
-        public static final String LABEL_IDS = "labelIds";
-        public static final String JOINED_ATTACHMENT_INFOS = "joinedAttachmentInfos";
-        public static final String ERROR = "error";
-        // TODO: add a method for accessing this
-        public static final String REF_MESSAGE_ID = "refMessageId";
-
-        // Fake columns used only for saving or sending messages.
-        public static final String FAKE_SAVE = "save";
-        public static final String FAKE_REF_MESSAGE_ID = "refMessageId";
-
-        private MessageColumns() {}
-    }
-
-    public static final class LabelColumns {
-        public static final String CANONICAL_NAME = "canonicalName";
-        public static final String NAME = "name";
-        public static final String NUM_CONVERSATIONS = "numConversations";
-        public static final String NUM_UNREAD_CONVERSATIONS =
-                "numUnreadConversations";
-
-        private LabelColumns() {}
-    }
-
-    public static final class SettingsColumns {
-        public static final String LABELS_INCLUDED = "labelsIncluded";
-        public static final String LABELS_PARTIAL = "labelsPartial";
-        public static final String CONVERSATION_AGE_DAYS =
-                "conversationAgeDays";
-        public static final String MAX_ATTACHMENET_SIZE_MB =
-                "maxAttachmentSize";
-    }
-
-    /**
-     * These flags can be included as Selection Arguments when
-     * querying the provider.
-     */
-    public static class SelectionArguments {
-        private SelectionArguments() {
-            // forbid instantiation
-        }
-
-        /**
-         * Specifies that you do NOT wish the returned cursor to
-         * become the Active Network Cursor.  If you do not include
-         * this flag as a selectionArg, the new cursor will become the
-         * Active Network Cursor by default.
-         */
-        public static final String DO_NOT_BECOME_ACTIVE_NETWORK_CURSOR =
-                "SELECTION_ARGUMENT_DO_NOT_BECOME_ACTIVE_NETWORK_CURSOR";
-    }
-
-    // These are the projections that we need when getting cursors from the
-    // content provider.
-    private static String[] CONVERSATION_PROJECTION = {
-            ConversationColumns.ID,
-            ConversationColumns.SUBJECT,
-            ConversationColumns.SNIPPET,
-            ConversationColumns.FROM,
-            ConversationColumns.DATE,
-            ConversationColumns.PERSONAL_LEVEL,
-            ConversationColumns.LABEL_IDS,
-            ConversationColumns.NUM_MESSAGES,
-            ConversationColumns.MAX_MESSAGE_ID,
-            ConversationColumns.HAS_ATTACHMENTS,
-            ConversationColumns.HAS_MESSAGES_WITH_ERRORS,
-            ConversationColumns.FORCE_ALL_UNREAD};
-    private static String[] MESSAGE_PROJECTION = {
-            MessageColumns.ID,
-            MessageColumns.MESSAGE_ID,
-            MessageColumns.CONVERSATION_ID,
-            MessageColumns.SUBJECT,
-            MessageColumns.SNIPPET,
-            MessageColumns.FROM,
-            MessageColumns.TO,
-            MessageColumns.CC,
-            MessageColumns.BCC,
-            MessageColumns.REPLY_TO,
-            MessageColumns.DATE_SENT_MS,
-            MessageColumns.DATE_RECEIVED_MS,
-            MessageColumns.LIST_INFO,
-            MessageColumns.PERSONAL_LEVEL,
-            MessageColumns.BODY,
-            MessageColumns.EMBEDS_EXTERNAL_RESOURCES,
-            MessageColumns.LABEL_IDS,
-            MessageColumns.JOINED_ATTACHMENT_INFOS,
-            MessageColumns.ERROR};
-    private static String[] LABEL_PROJECTION = {
-            BaseColumns._ID,
-            LabelColumns.CANONICAL_NAME,
-            LabelColumns.NAME,
-            LabelColumns.NUM_CONVERSATIONS,
-            LabelColumns.NUM_UNREAD_CONVERSATIONS};
-    private static String[] SETTINGS_PROJECTION = {
-            SettingsColumns.LABELS_INCLUDED,
-            SettingsColumns.LABELS_PARTIAL,
-            SettingsColumns.CONVERSATION_AGE_DAYS,
-            SettingsColumns.MAX_ATTACHMENET_SIZE_MB,
-    };
-
-    private ContentResolver mContentResolver;
-
-    public Gmail(ContentResolver contentResolver) {
-        mContentResolver = contentResolver;
-    }
-
-    /**
-     * Returns source if source is non-null. Returns the empty string otherwise.
-     */
-    private static String toNonnullString(String source) {
-        if (source == null) {
-            return "";
-        } else {
-            return source;
-        }
-    }
-
-    /**
-     * Behavior for a new cursor: should it become the Active Network
-     * Cursor?  This could potentially lead to bad behavior if someone
-     * else is using the Active Network Cursor, since theirs will stop
-     * being the Active Network Cursor.
-     */
-    public static enum BecomeActiveNetworkCursor {
-        /**
-         * The new cursor should become the one and only Active
-         * Network Cursor.  Any other cursor that might already be the
-         * Active Network Cursor will cease to be so.
-         */
-        YES,
-
-        /**
-         * The new cursor should not become the Active Network
-         * Cursor. Any other cursor that might already be the Active
-         * Network Cursor will continue to be so.
-         */
-        NO
-    }
-
-    /**
-     * Wraps a Cursor in a ConversationCursor
-     *
-     * @param account the account the cursor is associated with
-     * @param cursor The Cursor to wrap
-     * @return a new ConversationCursor
-     */
-    public ConversationCursor getConversationCursorForCursor(String account, Cursor cursor) {
-        if (TextUtils.isEmpty(account)) {
-            throw new IllegalArgumentException("account is empty");
-        }
-        return new ConversationCursor(this, account, cursor);
-    }
-
-    /**
-     * Creates an array of SelectionArguments suitable for passing to the provider's query.
-     * Currently this only handles one flag, but it could be expanded in the future.
-     */
-    private static String[] getSelectionArguments(
-            BecomeActiveNetworkCursor becomeActiveNetworkCursor) {
-        if (BecomeActiveNetworkCursor.NO == becomeActiveNetworkCursor) {
-            return new String[] {SelectionArguments.DO_NOT_BECOME_ACTIVE_NETWORK_CURSOR};
-        } else {
-            // Default behavior; no args required.
-            return null;
-        }
-    }
-
-    /**
-     * Asynchronously gets a cursor over all conversations matching a query. The
-     * query is in Gmail's query syntax. When the operation is complete the handler's
-     * onQueryComplete() method is called with the resulting Cursor.
-     *
-     * @param account run the query on this account
-     * @param handler An AsyncQueryHanlder that will be used to run the query
-     * @param token The token to pass to startQuery, which will be passed back to onQueryComplete
-     * @param query a query in Gmail's query syntax
-     * @param becomeActiveNetworkCursor whether or not the returned
-     * cursor should become the Active Network Cursor
-     */
-    public void runQueryForConversations(String account, AsyncQueryHandler handler, int token,
-            String query, BecomeActiveNetworkCursor becomeActiveNetworkCursor) {
-        if (TextUtils.isEmpty(account)) {
-            throw new IllegalArgumentException("account is empty");
-        }
-        String[] selectionArgs = getSelectionArguments(becomeActiveNetworkCursor);
-        handler.startQuery(token, null, Uri.withAppendedPath(CONVERSATIONS_URI, account),
-                CONVERSATION_PROJECTION, query, selectionArgs, null);
-    }
-
-    /**
-     * Synchronously gets a cursor over all conversations matching a query. The
-     * query is in Gmail's query syntax.
-     *
-     * @param account run the query on this account
-     * @param query a query in Gmail's query syntax
-     * @param becomeActiveNetworkCursor whether or not the returned
-     * cursor should become the Active Network Cursor
-     */
-    public ConversationCursor getConversationCursorForQuery(
-            String account, String query, BecomeActiveNetworkCursor becomeActiveNetworkCursor) {
-        String[] selectionArgs = getSelectionArguments(becomeActiveNetworkCursor);
-        Cursor cursor = mContentResolver.query(
-                Uri.withAppendedPath(CONVERSATIONS_URI, account), CONVERSATION_PROJECTION,
-                query, selectionArgs, null);
-        return new ConversationCursor(this, account, cursor);
-    }
-
-    /**
-     * Gets a message cursor over the single message with the given id.
-     *
-     * @param account get the cursor for messages in this account
-     * @param messageId the id of the message
-     * @return a cursor over the message
-     */
-    public MessageCursor getMessageCursorForMessageId(String account, long messageId) {
-        if (TextUtils.isEmpty(account)) {
-            throw new IllegalArgumentException("account is empty");
-        }
-        Uri uri = Uri.parse(AUTHORITY_PLUS_MESSAGES + account + "/" + messageId);
-        Cursor cursor = mContentResolver.query(uri, MESSAGE_PROJECTION, null, null, null);
-        return new MessageCursor(this, mContentResolver, account, cursor);
-    }
-
-    /**
-     * Gets a message cursor over the messages that match the query. Note that
-     * this simply finds all of the messages that match and returns them. It
-     * does not return all messages in conversations where any message matches.
-     *
-     * @param account get the cursor for messages in this account
-     * @param query a query in GMail's query syntax. Currently only queries of
-     *     the form [label:<label>] are supported
-     * @return a cursor over the messages
-     */
-    public MessageCursor getLocalMessageCursorForQuery(String account, String query) {
-        if (TextUtils.isEmpty(account)) {
-            throw new IllegalArgumentException("account is empty");
-        }
-        Uri uri = Uri.parse(AUTHORITY_PLUS_MESSAGES + account + "/");
-        Cursor cursor = mContentResolver.query(uri, MESSAGE_PROJECTION, query, null, null);
-        return new MessageCursor(this, mContentResolver, account, cursor);
-    }
-
-    /**
-     * Gets a cursor over all of the messages in a conversation.
-     *
-     * @param account get the cursor for messages in this account
-     * @param conversationId the id of the converstion to fetch messages for
-     * @return a cursor over messages in the conversation
-     */
-    public MessageCursor getMessageCursorForConversationId(String account, long conversationId) {
-        if (TextUtils.isEmpty(account)) {
-            throw new IllegalArgumentException("account is empty");
-        }
-        Uri uri = Uri.parse(
-                AUTHORITY_PLUS_CONVERSATIONS + account + "/" + conversationId + "/messages");
-        Cursor cursor = mContentResolver.query(
-                uri, MESSAGE_PROJECTION, null, null, null);
-        return new MessageCursor(this, mContentResolver, account, cursor);
-    }
-
-    /**
-     * Expunge the indicated message. One use of this is to discard drafts.
-     *
-     * @param account the account of the message id
-     * @param messageId the id of the message to expunge
-     */
-    public void expungeMessage(String account, long messageId) {
-        if (TextUtils.isEmpty(account)) {
-            throw new IllegalArgumentException("account is empty");
-        }
-        Uri uri = Uri.parse(AUTHORITY_PLUS_MESSAGES + account + "/" + messageId);
-        mContentResolver.delete(uri, null, null);
-    }
-
-    /**
-     * Adds or removes the label on the conversation.
-     *
-     * @param account the account of the conversation
-     * @param conversationId the conversation
-     * @param maxServerMessageId the highest message id to whose labels should be changed. Note that
-     *   everywhere else in this file messageId means local message id but here you need to use a
-     *   server message id.
-     * @param label the label to add or remove
-     * @param add true to add the label, false to remove it
-     */
-    public void addOrRemoveLabelOnConversation(
-            String account, long conversationId, long maxServerMessageId, String label,
-            boolean add) {
-        if (TextUtils.isEmpty(account)) {
-            throw new IllegalArgumentException("account is empty");
-        }
-        if (add) {
-            Uri uri = Uri.parse(
-                    AUTHORITY_PLUS_CONVERSATIONS + account + "/" + conversationId + "/labels");
-            ContentValues values = new ContentValues();
-            values.put(LabelColumns.CANONICAL_NAME, label);
-            values.put(ConversationColumns.MAX_MESSAGE_ID, maxServerMessageId);
-            mContentResolver.insert(uri, values);
-        } else {
-            String encodedLabel;
-            try {
-                encodedLabel = URLEncoder.encode(label, "utf-8");
-            } catch (UnsupportedEncodingException e) {
-                throw new RuntimeException(e);
-            }
-            Uri uri = Uri.parse(
-                    AUTHORITY_PLUS_CONVERSATIONS + account + "/"
-                            + conversationId + "/labels/" + encodedLabel);
-            mContentResolver.delete(
-                    uri, ConversationColumns.MAX_MESSAGE_ID, new String[]{"" + maxServerMessageId});
-        }
-    }
-
-    /**
-     * Adds or removes the label on the message.
-     *
-     * @param contentResolver the content resolver.
-     * @param account the account of the message
-     * @param conversationId the conversation containing the message
-     * @param messageId the id of the message to whose labels should be changed
-     * @param label the label to add or remove
-     * @param add true to add the label, false to remove it
-     */
-    public static void addOrRemoveLabelOnMessage(ContentResolver contentResolver, String account,
-            long conversationId, long messageId, String label, boolean add) {
-
-        // conversationId is unused but we want to start passing it whereever we pass a message id.
-        if (add) {
-            Uri uri = Uri.parse(
-                    AUTHORITY_PLUS_MESSAGES + account + "/" + messageId + "/labels");
-            ContentValues values = new ContentValues();
-            values.put(LabelColumns.CANONICAL_NAME, label);
-            contentResolver.insert(uri, values);
-        } else {
-            String encodedLabel;
-            try {
-                encodedLabel = URLEncoder.encode(label, "utf-8");
-            } catch (UnsupportedEncodingException e) {
-                throw new RuntimeException(e);
-            }
-            Uri uri = Uri.parse(
-                    AUTHORITY_PLUS_MESSAGES + account + "/" + messageId
-                    + "/labels/" + encodedLabel);
-            contentResolver.delete(uri, null, null);
-        }
-    }
-
-    /**
-     * The mail provider will send an intent when certain changes happen in certain labels.
-     * Currently those labels are inbox and voicemail.
-     *
-     * <p>The intent will have the action ACTION_PROVIDER_CHANGED and the extras mentioned below.
-     * The data for the intent will be content://gmail-ls/unread/<name of label>.
-     *
-     * <p>The goal is to support the following user experience:<ul>
-     *   <li>When present the new mail indicator reports the number of unread conversations in the
-     *   inbox (or some other label).</li>
-     *   <li>When the user views the inbox the indicator is removed immediately. They do not have to
-     *   read all of the conversations.</li>
-     *   <li>If more mail arrives the indicator reappears and shows the total number of unread
-     *   conversations in the inbox.</li>
-     *   <li>If the user reads the new conversations on the web the indicator disappears on the
-     *   phone since there is no unread mail in the inbox that the user hasn't seen.</li>
-     *   <li>The phone should vibrate/etc when it transitions from having no unseen unread inbox
-     *   mail to having some.</li>
-     */
-
-    /** The account in which the change occurred. */
-    static public final String PROVIDER_CHANGED_EXTRA_ACCOUNT = "account";
-
-    /** The number of unread conversations matching the label. */
-    static public final String PROVIDER_CHANGED_EXTRA_COUNT = "count";
-
-    /** Whether to get the user's attention, perhaps by vibrating. */
-    static public final String PROVIDER_CHANGED_EXTRA_GET_ATTENTION = "getAttention";
-
-    /**
-     * A label that is attached to all of the conversations being notified about. This enables the
-     * receiver of a notification to get a list of matching conversations.
-     */
-    static public final String PROVIDER_CHANGED_EXTRA_TAG_LABEL = "tagLabel";
-
-    /**
-     * Settings for which conversations should be synced to the phone.
-     * Conversations are synced if any message matches any of the following
-     * criteria:
-     *
-     * <ul>
-     *   <li>the message has a label in the include set</li>
-     *   <li>the message is no older than conversationAgeDays and has a label in the partial set.
-     *   </li>
-     *   <li>also, pending changes on the server: the message has no user-controllable labels.</li>
-     * </ul>
-     *
-     * <p>A user-controllable label is a user-defined label or star, inbox,
-     * trash, spam, etc. LABEL_UNREAD is not considered user-controllable.
-     */
-    public static class Settings {
-        public long conversationAgeDays;
-        public long maxAttachmentSizeMb;
-        public String[] labelsIncluded;
-        public String[] labelsPartial;
-    }
-
-    /**
-     * Returns the settings.
-     * @param account the account whose setting should be retrieved
-     */
-    public Settings getSettings(String account) {
-        if (TextUtils.isEmpty(account)) {
-            throw new IllegalArgumentException("account is empty");
-        }
-        Settings settings = new Settings();
-        Cursor cursor = mContentResolver.query(
-                Uri.withAppendedPath(SETTINGS_URI, account), SETTINGS_PROJECTION, null, null, null);
-        cursor.moveToNext();
-        settings.labelsIncluded = TextUtils.split(cursor.getString(0), SPACE_SEPARATOR_PATTERN);
-        settings.labelsPartial = TextUtils.split(cursor.getString(1), SPACE_SEPARATOR_PATTERN);
-        settings.conversationAgeDays = Long.parseLong(cursor.getString(2));
-        settings.maxAttachmentSizeMb = Long.parseLong(cursor.getString(3));
-        cursor.close();
-        return settings;
-    }
-
-    /**
-     * Sets the settings. A sync will be scheduled automatically.
-     */
-    public void setSettings(String account, Settings settings) {
-        if (TextUtils.isEmpty(account)) {
-            throw new IllegalArgumentException("account is empty");
-        }
-        ContentValues values = new ContentValues();
-        values.put(
-                SettingsColumns.LABELS_INCLUDED,
-                TextUtils.join(" ", settings.labelsIncluded));
-        values.put(
-                SettingsColumns.LABELS_PARTIAL,
-                TextUtils.join(" ", settings.labelsPartial));
-        values.put(
-                SettingsColumns.CONVERSATION_AGE_DAYS,
-                settings.conversationAgeDays);
-        values.put(
-                SettingsColumns.MAX_ATTACHMENET_SIZE_MB,
-                settings.maxAttachmentSizeMb);
-        mContentResolver.update(Uri.withAppendedPath(SETTINGS_URI, account), values, null, null);
-    }
-
-    /**
-     * Uses sender instructions to build a formatted string.
-     *
-     * <p>Sender list instructions contain compact information about the sender list. Most work that
-     * can be done without knowing how much room will be availble for the sender list is done when
-     * creating the instructions.
-     *
-     * <p>The instructions string consists of tokens separated by SENDER_LIST_SEPARATOR. Here are
-     * the tokens, one per line:<ul>
-     * <li><tt>n</tt></li>
-     * <li><em>int</em>, the number of non-draft messages in the conversation</li>
-     * <li><tt>d</tt</li>
-     * <li><em>int</em>, the number of drafts in the conversation</li>
-     * <li><tt>l</tt></li>
-     * <li><em>literal html to be included in the output</em></li>
-     * <li><tt>s</tt> indicates that the message is sending (in the outbox without errors)</li>
-     * <li><tt>f</tt> indicates that the message failed to send (in the outbox with errors)</li>
-     * <li><em>for each message</em><ul>
-     *   <li><em>int</em>, 0 for read, 1 for unread</li>
-     *   <li><em>int</em>, the priority of the message. Zero is the most important</li>
-     *   <li><em>text</em>, the sender text or blank for messages from 'me'</li>
-     * </ul></li>
-     * <li><tt>e</tt> to indicate that one or more messages have been elided</li>
-     *
-     * <p>The instructions indicate how many messages and drafts are in the conversation and then
-     * describe the most important messages in order, indicating the priority of each message and
-     * whether the message is unread.
-     *
-     * @param instructions instructions as described above
-     * @param sb the SpannableStringBuilder to append to
-     * @param maxChars the number of characters available to display the text
-     * @param unreadStyle the CharacterStyle for unread messages, or null
-     * @param draftsStyle the CharacterStyle for draft messages, or null
-     * @param sendingString the string to use when there are messages scheduled to be sent
-     * @param sendFailedString the string to use when there are messages that mailed to send
-     * @param meString the string to use for messages sent by this user
-     * @param draftString the string to use for "Draft"
-     * @param draftPluralString the string to use for "Drafts" 
-     */
-    public static void getSenderSnippet(
-            String instructions, SpannableStringBuilder sb, int maxChars,
-            CharacterStyle unreadStyle,
-            CharacterStyle draftsStyle,
-            CharSequence meString, CharSequence draftString, CharSequence draftPluralString,
-            CharSequence sendingString, CharSequence sendFailedString,
-            boolean forceAllUnread, boolean forceAllRead) {
-        assert !(forceAllUnread && forceAllRead);
-        boolean unreadStatusIsForced = forceAllUnread || forceAllRead;
-        boolean forcedUnreadStatus = forceAllUnread;
-
-        // Measure each fragment. It's ok to iterate over the entire set of fragments because it is
-        // never a long list, even if there are many senders.
-        final Map<Integer, Integer> priorityToLength = sPriorityToLength;
-        priorityToLength.clear();
-
-        int maxFoundPriority = Integer.MIN_VALUE;
-        int numMessages = 0;
-        int numDrafts = 0;
-        CharSequence draftsFragment = "";
-        CharSequence sendingFragment = "";
-        CharSequence sendFailedFragment = "";
-        
-        sSenderListSplitter.setString(instructions);
-        int numFragments = 0;
-        String[] fragments = sSenderFragments;
-        int currentSize = fragments.length;
-        while (sSenderListSplitter.hasNext()) {
-            fragments[numFragments++] = sSenderListSplitter.next();
-            if (numFragments == currentSize) {
-                sSenderFragments = new String[2 * currentSize];
-                System.arraycopy(fragments, 0, sSenderFragments, 0, currentSize);
-                currentSize *= 2;
-                fragments = sSenderFragments;
-            }
-        }
-        
-        for (int i = 0; i < numFragments;) {
-            String fragment0 = fragments[i++];
-            if ("".equals(fragment0)) {
-                // This should be the final fragment.
-            } else if (Gmail.SENDER_LIST_TOKEN_ELIDED.equals(fragment0)) {
-                // ignore
-            } else if (Gmail.SENDER_LIST_TOKEN_NUM_MESSAGES.equals(fragment0)) {
-                numMessages = Integer.valueOf(fragments[i++]);
-            } else if (Gmail.SENDER_LIST_TOKEN_NUM_DRAFTS.equals(fragment0)) {
-                String numDraftsString = fragments[i++];
-                numDrafts = Integer.parseInt(numDraftsString);
-                draftsFragment = numDrafts == 1 ? draftString :
-                        draftPluralString + " (" + numDraftsString + ")";
-            } else if (Gmail.SENDER_LIST_TOKEN_LITERAL.equals(fragment0)) {
-                sb.append(Html.fromHtml(fragments[i++]));
-                return;
-            } else if (Gmail.SENDER_LIST_TOKEN_SENDING.equals(fragment0)) {
-                sendingFragment = sendingString;
-            } else if (Gmail.SENDER_LIST_TOKEN_SEND_FAILED.equals(fragment0)) {
-                sendFailedFragment = sendFailedString;
-            } else {
-                String priorityString = fragments[i++];
-                CharSequence nameString = fragments[i++];
-                if (nameString.length() == 0) nameString = meString;
-                int priority = Integer.parseInt(priorityString);
-                priorityToLength.put(priority, nameString.length());
-                maxFoundPriority = Math.max(maxFoundPriority, priority);
-            }
-        }
-        String numMessagesFragment =
-                (numMessages != 0) ? " (" + Integer.toString(numMessages + numDrafts) + ")" : "";
-
-        // Don't allocate fixedFragment unless we need it
-        SpannableStringBuilder fixedFragment = null;
-        int fixedFragmentLength = 0;
-        if (draftsFragment.length() != 0) {
-            if (fixedFragment == null) {
-                fixedFragment = new SpannableStringBuilder();
-            }
-            fixedFragment.append(draftsFragment);
-            if (draftsStyle != null) {
-                fixedFragment.setSpan(
-                        CharacterStyle.wrap(draftsStyle),
-                        0, fixedFragment.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-        }
-        if (sendingFragment.length() != 0) {
-            if (fixedFragment == null) {
-                fixedFragment = new SpannableStringBuilder();
-            }
-            if (fixedFragment.length() != 0) fixedFragment.append(", ");
-            fixedFragment.append(sendingFragment);
-        }
-        if (sendFailedFragment.length() != 0) {
-            if (fixedFragment == null) {
-                fixedFragment = new SpannableStringBuilder();
-            }
-            if (fixedFragment.length() != 0) fixedFragment.append(", ");
-            fixedFragment.append(sendFailedFragment);
-        }
-
-        if (fixedFragment != null) {
-            fixedFragmentLength = fixedFragment.length();
-        }
-
-        final boolean normalMessagesExist =
-                numMessagesFragment.length() != 0 || maxFoundPriority != Integer.MIN_VALUE;
-        String preFixedFragement = "";
-        if (normalMessagesExist && fixedFragmentLength != 0) {
-            preFixedFragement = ", ";
-        }
-        int maxPriorityToInclude = -1; // inclusive
-        int numCharsUsed =
-                numMessagesFragment.length() + preFixedFragement.length() + fixedFragmentLength;
-        int numSendersUsed = 0;
-        while (maxPriorityToInclude < maxFoundPriority) {
-            if (priorityToLength.containsKey(maxPriorityToInclude + 1)) {
-                int length = numCharsUsed + priorityToLength.get(maxPriorityToInclude + 1);
-                if (numCharsUsed > 0) length += 2;
-                // We must show at least two senders if they exist. If we don't have space for both
-                // then we will truncate names.
-                if (length > maxChars && numSendersUsed >= 2) {
-                    break;
-                }
-                numCharsUsed = length;
-                numSendersUsed++;
-            }
-            maxPriorityToInclude++;
-        }
-
-        int numCharsToRemovePerWord = 0;
-        if (numCharsUsed > maxChars) {
-            numCharsToRemovePerWord = (numCharsUsed - maxChars) / numSendersUsed;
-        }
-
-        boolean elided = false;
-        for (int i = 0; i < numFragments;) {
-            String fragment0 = fragments[i++];
-            if ("".equals(fragment0)) {
-                // This should be the final fragment.
-            } else if (SENDER_LIST_TOKEN_ELIDED.equals(fragment0)) {
-                elided = true;
-            } else if (SENDER_LIST_TOKEN_NUM_MESSAGES.equals(fragment0)) {
-                i++;
-            } else if (SENDER_LIST_TOKEN_NUM_DRAFTS.equals(fragment0)) {
-                i++;
-            } else if (SENDER_LIST_TOKEN_SENDING.equals(fragment0)) {
-            } else if (SENDER_LIST_TOKEN_SEND_FAILED.equals(fragment0)) {
-            } else {
-                final String unreadString = fragment0;
-                final String priorityString = fragments[i++];
-                String nameString = fragments[i++];
-                if (nameString.length() == 0) nameString = meString.toString();
-                if (numCharsToRemovePerWord != 0) {
-                    nameString = nameString.substring(
-                            0, Math.max(nameString.length() - numCharsToRemovePerWord, 0));
-                }
-                final boolean unread = unreadStatusIsForced
-                        ? forcedUnreadStatus : Integer.parseInt(unreadString) != 0;
-                final int priority = Integer.parseInt(priorityString);
-                if (priority <= maxPriorityToInclude) {
-                    if (sb.length() != 0) {
-                        sb.append(elided ? " .. " : ", ");
-                    }
-                    elided = false;
-                    int pos = sb.length();
-                    sb.append(nameString);
-                    if (unread && unreadStyle != null) {
-                        sb.setSpan(CharacterStyle.wrap(unreadStyle),
-                                pos, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                    }
-                } else {
-                    elided = true;
-                }
-            }
-        }
-        sb.append(numMessagesFragment);
-        if (fixedFragmentLength != 0) {
-            sb.append(preFixedFragement);
-            sb.append(fixedFragment);
-        }
-    }
-
-    /**
-     * This is a cursor that only defines methods to move throught the results
-     * and register to hear about changes. All access to the data is left to
-     * subinterfaces.
-     */
-    public static class MailCursor extends ContentObserver {
-
-        // A list of observers of this cursor.
-        private Set<MailCursorObserver> mObservers;
-
-        // Updated values are accumulated here before being written out if the
-        // cursor is asked to persist the changes.
-        private ContentValues mUpdateValues;
-
-        protected Cursor mCursor;
-        protected String mAccount;
-
-        public Cursor getCursor() {
-            return mCursor;
-        }
-
-        /**
-         * Constructs the MailCursor given a regular cursor, registering as a
-         * change observer of the cursor.
-         * @param account the account the cursor is associated with
-         * @param cursor the underlying cursor
-         */
-        protected MailCursor(String account, Cursor cursor) {
-            super(new Handler());
-            mObservers = new HashSet<MailCursorObserver>();
-            mCursor = cursor;
-            mAccount = account;
-            if (mCursor != null) mCursor.registerContentObserver(this);
-        }
-
-        /**
-         * Gets the account associated with this cursor.
-         * @return the account.
-         */
-        public String getAccount() {
-            return mAccount;
-        }
-
-        protected void checkThread() {
-            // Turn this on when activity code no longer runs in the sync thread
-            // after notifications of changes.
-//            Thread currentThread = Thread.currentThread();
-//            if (currentThread != mThread) {
-//                throw new RuntimeException("Accessed from the wrong thread");
-//            }
-        }
-
-        /**
-         * Lazily constructs a map of update values to apply to the database
-         * if requested. This map is cleared out when we move to a different
-         * item in the result set.
-         *
-         * @return a map of values to be applied by an update.
-         */
-        protected ContentValues getUpdateValues() {
-            if (mUpdateValues == null) {
-                mUpdateValues = new ContentValues();
-            }
-            return mUpdateValues;
-        }
-
-        /**
-         * Called whenever mCursor is changed to point to a different row.
-         * Subclasses should override this if they need to clear out state
-         * when this happens.
-         *
-         * Subclasses must call the inherited version if they override this.
-         */
-        protected void onCursorPositionChanged() {
-            mUpdateValues = null;
-        }
-
-        // ********* MailCursor
-
-        /**
-         * Returns the numbers of rows in the cursor.
-         *
-         * @return the number of rows in the cursor.
-         */
-        final public int count() {
-            if (mCursor != null) {
-                return mCursor.getCount();
-            } else {
-                return 0;
-            }
-        }
-
-        /**
-         * @return the current position of this cursor, or -1 if this cursor
-         * has not been initialized.
-         */
-        final public int position() {
-            if (mCursor != null) {
-                return mCursor.getPosition();
-            } else {
-                return -1;
-            }
-        }
-
-        /**
-         * Move the cursor to an absolute position. The valid
-         * range of vaues is -1 &lt;= position &lt;= count.
-         *
-         * <p>This method will return true if the request destination was
-         * reachable, otherwise it returns false.
-         *
-         * @param position the zero-based position to move to.
-         * @return whether the requested move fully succeeded.
-         */
-        final public boolean moveTo(int position) {
-            checkCursor();
-            checkThread();
-            boolean moved = mCursor.moveToPosition(position);
-            if (moved) onCursorPositionChanged();
-            return moved;
-        }
-
-        /**
-         * Move the cursor to the next row.
-         *
-         * <p>This method will return false if the cursor is already past the
-         * last entry in the result set.
-         *
-         * @return whether the move succeeded.
-         */
-        final public boolean next() {
-            checkCursor();
-            checkThread();
-            boolean moved = mCursor.moveToNext();
-            if (moved) onCursorPositionChanged();
-            return moved;
-        }
-
-        /**
-         * Release all resources and locks associated with the cursor. The
-         * cursor will not be valid after this function is called.
-         */
-        final public void release() {
-            if (mCursor != null) {
-                mCursor.unregisterContentObserver(this);
-                mCursor.deactivate();
-            }
-        }
-
-        final public void registerContentObserver(ContentObserver observer) {
-            mCursor.registerContentObserver(observer);
-        }
-
-        final public void unregisterContentObserver(ContentObserver observer) {
-            mCursor.unregisterContentObserver(observer);
-        }
-
-        final public void registerDataSetObserver(DataSetObserver observer) {
-            mCursor.registerDataSetObserver(observer);
-        }
-
-        final public void unregisterDataSetObserver(DataSetObserver observer) {
-            mCursor.unregisterDataSetObserver(observer);
-        }
-
-        /**
-         * Register an observer to hear about changes to the cursor.
-         *
-         * @param observer the observer to register
-         */
-        final public void registerObserver(MailCursorObserver observer) {
-            mObservers.add(observer);
-        }
-
-        /**
-         * Unregister an observer.
-         *
-         * @param observer the observer to unregister
-         */
-        final public void unregisterObserver(MailCursorObserver observer) {
-            mObservers.remove(observer);
-        }
-
-        // ********* ContentObserver
-
-        @Override
-        final public boolean deliverSelfNotifications() {
-            return false;
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            if (DEBUG) {
-                Log.d(TAG, "MailCursor is notifying " + mObservers.size() + " observers");
-            }
-            for (MailCursorObserver o: mObservers) {
-                o.onCursorChanged(this);
-            }
-        }
-
-        protected void checkCursor() {
-            if (mCursor == null) {
-                throw new IllegalStateException(
-                        "cannot read from an insertion cursor");
-            }
-        }
-
-        /**
-         * Returns the string value of the column, or "" if the value is null.
-         */
-        protected String getStringInColumn(int columnIndex) {
-            checkCursor();
-            return toNonnullString(mCursor.getString(columnIndex));
-        }
-    }
-
-    /**
-     * A MailCursor observer is notified of changes to the result set of a
-     * cursor.
-     */
-    public interface MailCursorObserver {
-
-        /**
-         * Called when the result set of a cursor has changed.
-         *
-         * @param cursor the cursor whose result set has changed.
-         */
-        void onCursorChanged(MailCursor cursor);
-    }
-
-    /**
-     * A cursor over labels.
-     */
-    public final class LabelCursor extends MailCursor {
-
-        private int mNameIndex;
-        private int mNumConversationsIndex;
-        private int mNumUnreadConversationsIndex;
-
-        private LabelCursor(String account, Cursor cursor) {
-            super(account, cursor);
-
-            mNameIndex = mCursor.getColumnIndexOrThrow(LabelColumns.CANONICAL_NAME);
-            mNumConversationsIndex =
-                    mCursor.getColumnIndexOrThrow(LabelColumns.NUM_CONVERSATIONS);
-            mNumUnreadConversationsIndex = mCursor.getColumnIndexOrThrow(
-                    LabelColumns.NUM_UNREAD_CONVERSATIONS);
-        }
-
-        /**
-         * Gets the canonical name of the current label.
-         *
-         * @return the current label's name.
-         */
-        public String getName() {
-            return getStringInColumn(mNameIndex);
-        }
-
-        /**
-         * Gets the number of conversations with this label.
-         *
-         * @return the number of conversations with this label.
-         */
-        public int getNumConversations() {
-            return mCursor.getInt(mNumConversationsIndex);
-        }
-
-        /**
-         * Gets the number of unread conversations with this label.
-         *
-         * @return the number of unread conversations with this label.
-         */
-        public int getNumUnreadConversations() {
-            return mCursor.getInt(mNumUnreadConversationsIndex);
-        }
-    }
-
-    /**
-     * This is a map of labels. TODO: make it observable.
-     */
-    public static final class LabelMap extends Observable {
-        private final static ContentValues EMPTY_CONTENT_VALUES = new ContentValues();
-
-        private ContentQueryMap mQueryMap;
-        private SortedSet<String> mSortedUserLabels;
-        private Map<String, Long> mCanonicalNameToId;
-
-        private long mLabelIdSent;
-        private long mLabelIdInbox;
-        private long mLabelIdDraft;
-        private long mLabelIdUnread;
-        private long mLabelIdTrash;
-        private long mLabelIdSpam;
-        private long mLabelIdStarred;
-        private long mLabelIdChat;
-        private long mLabelIdVoicemail;
-        private long mLabelIdIgnored;
-        private long mLabelIdVoicemailInbox;
-        private long mLabelIdCached;
-        private long mLabelIdOutbox;
-
-        private boolean mLabelsSynced = false;
-
-        public LabelMap(ContentResolver contentResolver, String account, boolean keepUpdated) {
-            if (TextUtils.isEmpty(account)) {
-                throw new IllegalArgumentException("account is empty");
-            }
-            Cursor cursor = contentResolver.query(
-                    Uri.withAppendedPath(LABELS_URI, account), LABEL_PROJECTION, null, null, null);
-            init(cursor, keepUpdated);
-        }
-
-        public LabelMap(Cursor cursor, boolean keepUpdated) {
-            init(cursor, keepUpdated);
-        }
-
-        private void init(Cursor cursor, boolean keepUpdated) {
-            mQueryMap = new ContentQueryMap(cursor, BaseColumns._ID, keepUpdated, null);
-            mSortedUserLabels = new TreeSet<String>(java.text.Collator.getInstance());
-            mCanonicalNameToId = Maps.newHashMap();
-            updateDataStructures();
-            mQueryMap.addObserver(new Observer() {
-                public void update(Observable observable, Object data) {
-                    updateDataStructures();
-                    setChanged();
-                    notifyObservers();
-                }
-            });
-        }
-
-        /**
-         * @return whether at least some labels have been synced.
-         */
-        public boolean labelsSynced() {
-            return mLabelsSynced;
-        }
-
-        /**
-         * Updates the data structures that are maintained separately from mQueryMap after the query
-         * map has changed.
-         */
-        private void updateDataStructures() {
-            mSortedUserLabels.clear();
-            mCanonicalNameToId.clear();
-            for (Map.Entry<String, ContentValues> row : mQueryMap.getRows().entrySet()) {
-                long labelId = Long.valueOf(row.getKey());
-                String canonicalName = row.getValue().getAsString(LabelColumns.CANONICAL_NAME);
-                if (isLabelUserDefined(canonicalName)) {
-                    mSortedUserLabels.add(canonicalName);
-                }
-                mCanonicalNameToId.put(canonicalName, labelId);
-
-                if (LABEL_SENT.equals(canonicalName)) {
-                    mLabelIdSent = labelId;
-                } else if (LABEL_INBOX.equals(canonicalName)) {
-                    mLabelIdInbox = labelId;
-                } else if (LABEL_DRAFT.equals(canonicalName)) {
-                    mLabelIdDraft = labelId;
-                } else if (LABEL_UNREAD.equals(canonicalName)) {
-                    mLabelIdUnread = labelId;
-                } else if (LABEL_TRASH.equals(canonicalName)) {
-                    mLabelIdTrash = labelId;
-                } else if (LABEL_SPAM.equals(canonicalName)) {
-                    mLabelIdSpam = labelId;
-                } else if (LABEL_STARRED.equals(canonicalName)) {
-                    mLabelIdStarred = labelId;
-                } else if (LABEL_CHAT.equals(canonicalName)) {
-                    mLabelIdChat = labelId;
-                } else if (LABEL_IGNORED.equals(canonicalName)) {
-                    mLabelIdIgnored = labelId;
-                } else if (LABEL_VOICEMAIL.equals(canonicalName)) {
-                    mLabelIdVoicemail = labelId;
-                } else if (LABEL_VOICEMAIL_INBOX.equals(canonicalName)) {
-                    mLabelIdVoicemailInbox = labelId;
-                } else if (LABEL_CACHED.equals(canonicalName)) {
-                    mLabelIdCached = labelId;
-                } else if (LABEL_OUTBOX.equals(canonicalName)) {
-                    mLabelIdOutbox = labelId;
-                }
-                mLabelsSynced = mLabelIdSent != 0
-                    && mLabelIdInbox != 0
-                    && mLabelIdDraft != 0
-                    && mLabelIdUnread != 0
-                    && mLabelIdTrash != 0
-                    && mLabelIdSpam != 0
-                    && mLabelIdStarred != 0
-                    && mLabelIdChat != 0
-                    && mLabelIdIgnored != 0
-                    && mLabelIdVoicemail != 0;
-            }
-        }
-
-        public long getLabelIdSent() {
-            checkLabelsSynced();
-            return mLabelIdSent;
-        }
-
-        public long getLabelIdInbox() {
-            checkLabelsSynced();
-            return mLabelIdInbox;
-        }
-
-        public long getLabelIdDraft() {
-            checkLabelsSynced();
-            return mLabelIdDraft;
-        }
-
-        public long getLabelIdUnread() {
-            checkLabelsSynced();
-            return mLabelIdUnread;
-        }
-
-        public long getLabelIdTrash() {
-            checkLabelsSynced();
-            return mLabelIdTrash;
-        }
-
-        public long getLabelIdSpam() {
-            checkLabelsSynced();
-            return mLabelIdSpam;
-        }
-
-        public long getLabelIdStarred() {
-            checkLabelsSynced();
-            return mLabelIdStarred;
-        }
-
-        public long getLabelIdChat() {
-            checkLabelsSynced();
-            return mLabelIdChat;
-        }
-
-        public long getLabelIdIgnored() {
-            checkLabelsSynced();
-            return mLabelIdIgnored;
-        }
-
-        public long getLabelIdVoicemail() {
-            checkLabelsSynced();
-            return mLabelIdVoicemail;
-        }
-
-        public long getLabelIdVoicemailInbox() {
-            checkLabelsSynced();
-            return mLabelIdVoicemailInbox;
-        }
-
-        public long getLabelIdCached() {
-            checkLabelsSynced();
-            return mLabelIdCached;
-        }
-
-        public long getLabelIdOutbox() {
-            checkLabelsSynced();
-            return mLabelIdOutbox;
-        }
-
-        private void checkLabelsSynced() {
-            if (!labelsSynced()) {
-                throw new IllegalStateException("LabelMap not initalized");
-            }
-        }
-
-        /** Returns the list of user-defined labels in alphabetical order. */
-        public SortedSet<String> getSortedUserLabels() {
-            return mSortedUserLabels;
-        }
-
-        private static final List<String> SORTED_USER_MEANINGFUL_SYSTEM_LABELS =
-                Lists.newArrayList(
-                        LABEL_INBOX, LABEL_STARRED, LABEL_CHAT, LABEL_SENT,
-                        LABEL_OUTBOX, LABEL_DRAFT, LABEL_ALL,
-                        LABEL_SPAM, LABEL_TRASH);
-
-
-        private static final Set<String> USER_MEANINGFUL_SYSTEM_LABELS_SET =
-                Sets.newHashSet(
-                        SORTED_USER_MEANINGFUL_SYSTEM_LABELS.toArray(
-                                new String[]{}));
-
-        public static List<String> getSortedUserMeaningfulSystemLabels() {
-            return SORTED_USER_MEANINGFUL_SYSTEM_LABELS;
-        }
-
-        public static Set<String> getUserMeaningfulSystemLabelsSet() {
-            return USER_MEANINGFUL_SYSTEM_LABELS_SET;
-        }
-
-        /**
-         * If you are ever tempted to remove outbox or draft from this set make sure you have a
-         * way to stop draft and outbox messages from getting purged before they are sent to the
-         * server.
-         */
-        private static final Set<String> FORCED_INCLUDED_LABELS =
-                Sets.newHashSet(LABEL_OUTBOX, LABEL_DRAFT);
-
-        public static Set<String> getForcedIncludedLabels() {
-            return FORCED_INCLUDED_LABELS;
-        }
-
-        private static final Set<String> FORCED_INCLUDED_OR_PARTIAL_LABELS =
-                Sets.newHashSet(LABEL_INBOX);
-
-        public static Set<String> getForcedIncludedOrPartialLabels() {
-            return FORCED_INCLUDED_OR_PARTIAL_LABELS;
-        }
-
-        private static final Set<String> FORCED_UNSYNCED_LABELS =
-                Sets.newHashSet(LABEL_ALL, LABEL_CHAT, LABEL_SPAM, LABEL_TRASH);
-
-        public static Set<String> getForcedUnsyncedLabels() {
-            return FORCED_UNSYNCED_LABELS;
-        }
-
-        /**
-         * Returns the number of conversation with a given label.
-         * @deprecated Use {@link #getLabelId} instead.
-         */
-        @Deprecated
-        public int getNumConversations(String label) {
-            return getNumConversations(getLabelId(label));
-        }
-
-        /** Returns the number of conversation with a given label. */
-        public int getNumConversations(long labelId) {
-            return getLabelIdValues(labelId).getAsInteger(LabelColumns.NUM_CONVERSATIONS);
-        }
-
-        /**
-         * Returns the number of unread conversation with a given label.
-         * @deprecated Use {@link #getLabelId} instead.
-         */
-        @Deprecated
-        public int getNumUnreadConversations(String label) {
-            return getNumUnreadConversations(getLabelId(label));
-        }
-
-        /** Returns the number of unread conversation with a given label. */
-        public int getNumUnreadConversations(long labelId) {
-            Integer unreadConversations =
-                    getLabelIdValues(labelId).getAsInteger(LabelColumns.NUM_UNREAD_CONVERSATIONS);
-            // There seems to be a race condition here that can get the label maps into a bad
-            // state and lose state on a particular label.
-            int result = 0;
-            if (unreadConversations != null) {
-                result = unreadConversations < 0 ? 0 : unreadConversations;
-            }
-
-            return result;
-        }
-
-        /**
-         * @return the canonical name for a label
-         */
-        public String getCanonicalName(long labelId) {
-            return getLabelIdValues(labelId).getAsString(LabelColumns.CANONICAL_NAME);
-        }
-
-        /**
-         * @return the human name for a label
-         */
-        public String getName(long labelId) {
-            return getLabelIdValues(labelId).getAsString(LabelColumns.NAME);
-        }
-
-        /**
-         * @return whether a given label is known
-         */
-        public boolean hasLabel(long labelId) {
-            return mQueryMap.getRows().containsKey(Long.toString(labelId));
-        }
-
-        /**
-         * @return returns the id of a label given the canonical name
-         * @deprecated this is only needed because most of the UI uses label names instead of ids
-         */
-        public long getLabelId(String canonicalName) {
-            if (mCanonicalNameToId.containsKey(canonicalName)) {
-                return mCanonicalNameToId.get(canonicalName);
-            } else {
-                throw new IllegalArgumentException("Unknown canonical name: " + canonicalName);
-            }
-        }
-
-        private ContentValues getLabelIdValues(long labelId) {
-            final ContentValues values = mQueryMap.getValues(Long.toString(labelId));
-            if (values != null) {
-                return values;
-            } else {
-                return EMPTY_CONTENT_VALUES;
-            }
-        }
-
-        /** Force the map to requery. This should not be necessary outside tests. */
-        public void requery() {
-            mQueryMap.requery();
-        }
-
-        public void close() {
-            mQueryMap.close();
-        }
-    }
-
-    private Map<String, Gmail.LabelMap> mLabelMaps = Maps.newHashMap();
-
-    public LabelMap getLabelMap(String account) {
-        Gmail.LabelMap labelMap = mLabelMaps.get(account);
-        if (labelMap == null) {
-            labelMap = new Gmail.LabelMap(mContentResolver, account, true /* keepUpdated */);
-            mLabelMaps.put(account, labelMap);
-        }
-        return labelMap;
-    }
-
-    public enum PersonalLevel {
-        NOT_TO_ME(0),
-        TO_ME_AND_OTHERS(1),
-        ONLY_TO_ME(2);
-
-        private int mLevel;
-
-        PersonalLevel(int level) {
-            mLevel = level;
-        }
-
-        public int toInt() {
-            return mLevel;
-        }
-
-        public static PersonalLevel fromInt(int level) {
-            switch (level) {
-                case 0: return NOT_TO_ME;
-                case 1: return TO_ME_AND_OTHERS;
-                case 2: return ONLY_TO_ME;
-                default:
-                    throw new IllegalArgumentException(
-                            level + " is not a personal level");
-            }
-        }
-    }
-
-    /**
-     * Indicates a version of an attachment.
-     */
-    public enum AttachmentRendition {
-        /**
-         * The full version of an attachment if it can be handled on the device, otherwise the
-         * preview.
-         */
-        BEST,
-
-        /** A smaller or simpler version of the attachment, such as a scaled-down image or an HTML
-         * version of a document. Not always available.
-         */
-        SIMPLE,
-    }
-
-    /**
-     * The columns that can be requested when querying an attachment's download URI. See
-     * getAttachmentDownloadUri.
-     */
-    public static final class AttachmentColumns implements BaseColumns {
-
-        /** Contains a STATUS value from {@link android.provider.Downloads} */
-        public static final String STATUS = "status";
-
-        /**
-         * The name of the file to open (with ContentProvider.open). If this is empty then continue
-         * to use the attachment's URI.
-         *
-         * TODO: I'm not sure that we need this. See the note in CL 66853-p9.
-         */
-        public static final String FILENAME = "filename";
-    }
-
-    /**
-     * We track where an attachment came from so that we know how to download it and include it
-     * in new messages.
-     */
-    public enum AttachmentOrigin {
-        /** Extras are "<conversationId>-<messageId>-<partId>". */
-        SERVER_ATTACHMENT,
-        /** Extras are "<path>". */
-        LOCAL_FILE;
-
-        private static final String SERVER_EXTRAS_SEPARATOR = "_";
-
-        public static String serverExtras(
-                long conversationId, long messageId, String partId) {
-            return conversationId + SERVER_EXTRAS_SEPARATOR
-                    + messageId + SERVER_EXTRAS_SEPARATOR + partId;
-        }
-
-        /**
-         * @param extras extras as returned by serverExtras
-         * @return an array of conversationId, messageId, partId (all as strings)
-         */
-        public static String[] splitServerExtras(String extras) {
-            return TextUtils.split(extras, SERVER_EXTRAS_SEPARATOR);
-        }
-
-        public static String localFileExtras(Uri path) {
-            return path.toString();
-        }
-    }
-
-    public static final class Attachment {
-        /** Identifies the attachment uniquely when combined wih a message id.*/
-        public String partId;
-
-        /** The intended filename of the attachment.*/
-        public String name;
-
-        /** The native content type.*/
-        public String contentType;
-
-        /** The size of the attachment in its native form.*/
-        public int size;
-
-        /**
-         * The content type of the simple version of the attachment. Blank if no simple version is
-         * available.
-         */
-        public String simpleContentType;
-
-        public AttachmentOrigin origin;
-
-        public String originExtras;
-
-        public String toJoinedString() {
-            return TextUtils.join(
-                "|", Lists.newArrayList(partId == null ? "" : partId,
-                                        name.replace("|", ""), contentType,
-                                        size, simpleContentType,
-                                        origin.toString(), originExtras));
-        }
-
-        public static Attachment parseJoinedString(String joinedString) {
-            String[] fragments = TextUtils.split(joinedString, "\\|");
-            int i = 0;
-            Attachment attachment = new Attachment();
-            attachment.partId = fragments[i++];
-            if (TextUtils.isEmpty(attachment.partId)) {
-                attachment.partId = null;
-            }
-            attachment.name = fragments[i++];
-            attachment.contentType = fragments[i++];
-            attachment.size = Integer.parseInt(fragments[i++]);
-            attachment.simpleContentType = fragments[i++];
-            attachment.origin = AttachmentOrigin.valueOf(fragments[i++]);
-            attachment.originExtras = fragments[i++];
-            return attachment;
-        }
-    }
-
-    /**
-     * Any given attachment can come in two different renditions (see
-     * {@link android.provider.Gmail.AttachmentRendition}) and can be saved to the sd card or to a
-     * cache. The gmail provider automatically syncs some attachments to the cache. Other
-     * attachments can be downloaded on demand. Attachments in the cache will be purged as needed to
-     * save space. Attachments on the SD card must be managed by the user or other software.
-     *
-     * @param account which account to use
-     * @param messageId the id of the mesage with the attachment
-     * @param attachment the attachment
-     * @param rendition the desired rendition
-     * @param saveToSd whether the attachment should be saved to (or loaded from) the sd card or
-     * @return the URI to ask the content provider to open in order to open an attachment.
-     */
-    public static Uri getAttachmentUri(
-            String account, long messageId, Attachment attachment,
-            AttachmentRendition rendition, boolean saveToSd) {
-        if (TextUtils.isEmpty(account)) {
-            throw new IllegalArgumentException("account is empty");
-        }
-        if (attachment.origin == AttachmentOrigin.LOCAL_FILE) {
-            return Uri.parse(attachment.originExtras);
-        } else {
-            return Uri.parse(
-                    AUTHORITY_PLUS_MESSAGES).buildUpon()
-                    .appendPath(account).appendPath(Long.toString(messageId))
-                    .appendPath("attachments").appendPath(attachment.partId)
-                    .appendPath(rendition.toString())
-                    .appendPath(Boolean.toString(saveToSd))
-                    .build();
-        }
-    }
-
-    /**
-     * Return the URI to query in order to find out whether an attachment is downloaded.
-     *
-     * <p>Querying this will also start a download if necessary. The cursor returned by querying
-     * this URI can contain the columns in {@link android.provider.Gmail.AttachmentColumns}.
-     *
-     * <p>Deleting this URI will cancel the download if it was not started automatically by the
-     * provider. It will also remove bookkeeping for saveToSd downloads.
-     *
-     * @param attachmentUri the attachment URI as returned by getAttachmentUri. The URI's authority
-     *   Gmail.AUTHORITY. If it is not then you should open the file directly.
-     */
-    public static Uri getAttachmentDownloadUri(Uri attachmentUri) {
-        if (!"content".equals(attachmentUri.getScheme())) {
-            throw new IllegalArgumentException("Uri's scheme must be 'content': " + attachmentUri);
-        }
-        return attachmentUri.buildUpon().appendPath("download").build();
-    }
-
-    public enum CursorStatus {
-        LOADED,
-        LOADING,
-        ERROR, // A network error occurred.
-    }
-
-    /**
-     * A cursor over messages.
-     */
-    public static final class MessageCursor extends MailCursor {
-
-        private LabelMap mLabelMap;
-
-        private ContentResolver mContentResolver;
-
-        /**
-         * Only valid if mCursor == null, in which case we are inserting a new
-         * message.
-         */
-        long mInReplyToLocalMessageId;
-        boolean mPreserveAttachments;
-
-        private int mIdIndex;
-        private int mConversationIdIndex;
-        private int mSubjectIndex;
-        private int mSnippetIndex;
-        private int mFromIndex;
-        private int mToIndex;
-        private int mCcIndex;
-        private int mBccIndex;
-        private int mReplyToIndex;
-        private int mDateSentMsIndex;
-        private int mDateReceivedMsIndex;
-        private int mListInfoIndex;
-        private int mPersonalLevelIndex;
-        private int mBodyIndex;
-        private int mBodyEmbedsExternalResourcesIndex;
-        private int mLabelIdsIndex;
-        private int mJoinedAttachmentInfosIndex;
-        private int mErrorIndex;
-
-        private TextUtils.StringSplitter mLabelIdsSplitter = newMessageLabelIdsSplitter();
-
-        public MessageCursor(Gmail gmail, ContentResolver cr, String account, Cursor cursor) {
-            super(account, cursor);
-            mLabelMap = gmail.getLabelMap(account);
-            if (cursor == null) {
-                throw new IllegalArgumentException(
-                        "null cursor passed to MessageCursor()");
-            }
-
-            mContentResolver = cr;
-
-            mIdIndex = mCursor.getColumnIndexOrThrow(MessageColumns.ID);
-            mConversationIdIndex =
-                    mCursor.getColumnIndexOrThrow(MessageColumns.CONVERSATION_ID);
-            mSubjectIndex = mCursor.getColumnIndexOrThrow(MessageColumns.SUBJECT);
-            mSnippetIndex = mCursor.getColumnIndexOrThrow(MessageColumns.SNIPPET);
-            mFromIndex = mCursor.getColumnIndexOrThrow(MessageColumns.FROM);
-            mToIndex = mCursor.getColumnIndexOrThrow(MessageColumns.TO);
-            mCcIndex = mCursor.getColumnIndexOrThrow(MessageColumns.CC);
-            mBccIndex = mCursor.getColumnIndexOrThrow(MessageColumns.BCC);
-            mReplyToIndex = mCursor.getColumnIndexOrThrow(MessageColumns.REPLY_TO);
-            mDateSentMsIndex =
-                    mCursor.getColumnIndexOrThrow(MessageColumns.DATE_SENT_MS);
-            mDateReceivedMsIndex =
-                    mCursor.getColumnIndexOrThrow(MessageColumns.DATE_RECEIVED_MS);
-            mListInfoIndex = mCursor.getColumnIndexOrThrow(MessageColumns.LIST_INFO);
-            mPersonalLevelIndex =
-                    mCursor.getColumnIndexOrThrow(MessageColumns.PERSONAL_LEVEL);
-            mBodyIndex = mCursor.getColumnIndexOrThrow(MessageColumns.BODY);
-            mBodyEmbedsExternalResourcesIndex =
-                    mCursor.getColumnIndexOrThrow(MessageColumns.EMBEDS_EXTERNAL_RESOURCES);
-            mLabelIdsIndex = mCursor.getColumnIndexOrThrow(MessageColumns.LABEL_IDS);
-            mJoinedAttachmentInfosIndex =
-                    mCursor.getColumnIndexOrThrow(MessageColumns.JOINED_ATTACHMENT_INFOS);
-            mErrorIndex = mCursor.getColumnIndexOrThrow(MessageColumns.ERROR);
-
-            mInReplyToLocalMessageId = 0;
-            mPreserveAttachments = false;
-        }
-
-        protected MessageCursor(ContentResolver cr, String account, long inReplyToMessageId,
-                boolean preserveAttachments) {
-            super(account, null);
-            mContentResolver = cr;
-            mInReplyToLocalMessageId = inReplyToMessageId;
-            mPreserveAttachments = preserveAttachments;
-        }
-
-        @Override
-        protected void onCursorPositionChanged() {
-            super.onCursorPositionChanged();
-        }
-
-        public CursorStatus getStatus() {
-            Bundle extras = mCursor.getExtras();
-            String stringStatus = extras.getString(EXTRA_STATUS);
-            return CursorStatus.valueOf(stringStatus);
-        }
-
-        /** Retry a network request after errors. */
-        public void retry() {
-            Bundle input = new Bundle();
-            input.putString(RESPOND_INPUT_COMMAND, COMMAND_RETRY);
-            Bundle output = mCursor.respond(input);
-            String response = output.getString(RESPOND_OUTPUT_COMMAND_RESPONSE);
-            assert COMMAND_RESPONSE_OK.equals(response);
-        }
-
-        /**
-         * Gets the message id of the current message. Note that this is an
-         * immutable local message (not, for example, GMail's message id, which
-         * is immutable).
-         *
-         * @return the message's id
-         */
-        public long getMessageId() {
-            checkCursor();
-            return mCursor.getLong(mIdIndex);
-        }
-
-        /**
-         * Gets the message's conversation id. This must be immutable. (For
-         * example, with GMail this should be the original conversation id
-         * rather than the default notion of converation id.)
-         *
-         * @return the message's conversation id
-         */
-        public long getConversationId() {
-            checkCursor();
-            return mCursor.getLong(mConversationIdIndex);
-        }
-
-        /**
-         * Gets the message's subject.
-         *
-         * @return the message's subject
-         */
-        public String getSubject() {
-            return getStringInColumn(mSubjectIndex);
-        }
-
-        /**
-         * Gets the message's snippet (the short piece of the body). The snippet
-         * is generated from the body and cannot be set directly.
-         *
-         * @return the message's snippet
-         */
-        public String getSnippet() {
-            return getStringInColumn(mSnippetIndex);
-        }
-
-        /**
-         * Gets the message's from address.
-         *
-         * @return the message's from address
-         */
-        public String getFromAddress() {
-            return getStringInColumn(mFromIndex);
-        }
-
-        /**
-         * Returns the addresses for the key, if it has been updated, or index otherwise.
-         */
-        private String[] getAddresses(String key, int index) {
-            ContentValues updated = getUpdateValues();
-            String addresses;
-            if (updated.containsKey(key)) {
-                addresses = (String)getUpdateValues().get(key);
-            } else {
-                addresses = getStringInColumn(index);
-            }
-
-            return TextUtils.split(addresses, EMAIL_SEPARATOR_PATTERN);
-        }
-
-        /**
-         * Gets the message's to addresses.
-         * @return the message's to addresses
-         */
-        public String[] getToAddresses() {
-           return getAddresses(MessageColumns.TO, mToIndex);
-        }
-
-        /**
-         * Gets the message's cc addresses.
-         * @return the message's cc addresses
-         */
-        public String[] getCcAddresses() {
-            return getAddresses(MessageColumns.CC, mCcIndex);
-        }
-
-        /**
-         * Gets the message's bcc addresses.
-         * @return the message's bcc addresses
-         */
-        public String[] getBccAddresses() {
-            return getAddresses(MessageColumns.BCC, mBccIndex);
-        }
-
-        /**
-         * Gets the message's replyTo address.
-         *
-         * @return the message's replyTo address
-         */
-        public String[] getReplyToAddress() {
-            return TextUtils.split(getStringInColumn(mReplyToIndex), EMAIL_SEPARATOR_PATTERN);
-        }
-
-        public long getDateSentMs() {
-            checkCursor();
-            return mCursor.getLong(mDateSentMsIndex);
-        }
-
-        public long getDateReceivedMs() {
-            checkCursor();
-            return mCursor.getLong(mDateReceivedMsIndex);
-        }
-
-        public String getListInfo() {
-            return getStringInColumn(mListInfoIndex);
-        }
-
-        public PersonalLevel getPersonalLevel() {
-            checkCursor();
-            int personalLevelInt = mCursor.getInt(mPersonalLevelIndex);
-            return PersonalLevel.fromInt(personalLevelInt);
-        }
-
-        /**
-         * @deprecated Always returns true.
-         */
-        @Deprecated
-        public boolean getExpanded() {
-            return true;
-        }
-
-        /**
-         * Gets the message's body.
-         *
-         * @return the message's body
-         */
-        public String getBody() {
-            return getStringInColumn(mBodyIndex);
-        }
-
-        /**
-         * @return whether the message's body contains embedded references to external resources. In
-         * that case the resources should only be displayed if the user explicitly asks for them to
-         * be
-         */
-        public boolean getBodyEmbedsExternalResources() {
-            checkCursor();
-            return mCursor.getInt(mBodyEmbedsExternalResourcesIndex) != 0;
-        }
-
-        /**
-         * @return a copy of the set of label ids
-         */
-        public Set<Long> getLabelIds() {
-            String labelNames = mCursor.getString(mLabelIdsIndex);
-            mLabelIdsSplitter.setString(labelNames);
-            return getLabelIdsFromLabelIdsString(mLabelIdsSplitter);
-        }
-
-        /**
-         * @return a joined string of labels separated by spaces.
-         */
-        public String getRawLabelIds() {
-            return mCursor.getString(mLabelIdsIndex);
-        }
-
-        /**
-         * Adds a label to a message (if add is true) or removes it (if add is
-         * false).
-         *
-         * @param label the label to add or remove
-         * @param add whether to add or remove the label
-         */
-        public void addOrRemoveLabel(String label, boolean add) {
-            addOrRemoveLabelOnMessage(mContentResolver, mAccount, getConversationId(),
-                    getMessageId(), label, add);
-        }
-
-        public ArrayList<Attachment> getAttachmentInfos() {
-            ArrayList<Attachment> attachments = Lists.newArrayList();
-
-            String joinedAttachmentInfos = mCursor.getString(mJoinedAttachmentInfosIndex);
-            if (joinedAttachmentInfos != null) {
-                for (String joinedAttachmentInfo :
-                        TextUtils.split(joinedAttachmentInfos, ATTACHMENT_INFO_SEPARATOR_PATTERN)) {
-
-                    Attachment attachment = Attachment.parseJoinedString(joinedAttachmentInfo);
-                    attachments.add(attachment);
-                }
-            }
-            return attachments;
-        }
-
-        /**
-         * @return the error text for the message. Error text gets set if the server rejects a
-         * message that we try to save or send. If there is error text then the message is no longer
-         * scheduled to be saved or sent. Calling save() or send() will clear any error as well as
-         * scheduling another atempt to save or send the message.
-         */
-        public String getErrorText() {
-            return mCursor.getString(mErrorIndex);
-        }
-    }
-
-    /**
-     * A helper class for creating or updating messags. Use the putXxx methods to provide initial or
-     * new values for the message. Then save or send the message. To save or send an existing
-     * message without making other changes to it simply provide an emty ContentValues.
-     */
-    public static class MessageModification {
-
-        /**
-         * Sets the message's subject. Only valid for drafts.
-         *
-         * @param values the ContentValues that will be used to create or update the message
-         * @param subject the new subject
-         */
-        public static void putSubject(ContentValues values, String subject) {
-            values.put(MessageColumns.SUBJECT, subject);
-        }
-
-        /**
-         * Sets the message's to address. Only valid for drafts.
-         *
-         * @param values the ContentValues that will be used to create or update the message
-         * @param toAddresses the new to addresses
-         */
-        public static void putToAddresses(ContentValues values, String[] toAddresses) {
-            values.put(MessageColumns.TO, TextUtils.join(EMAIL_SEPARATOR, toAddresses));
-        }
-
-        /**
-         * Sets the message's cc address. Only valid for drafts.
-         *
-         * @param values the ContentValues that will be used to create or update the message
-         * @param ccAddresses the new cc addresses
-         */
-        public static void putCcAddresses(ContentValues values, String[] ccAddresses) {
-            values.put(MessageColumns.CC, TextUtils.join(EMAIL_SEPARATOR, ccAddresses));
-        }
-
-        /**
-         * Sets the message's bcc address. Only valid for drafts.
-         *
-         * @param values the ContentValues that will be used to create or update the message
-         * @param bccAddresses the new bcc addresses
-         */
-        public static void putBccAddresses(ContentValues values, String[] bccAddresses) {
-            values.put(MessageColumns.BCC, TextUtils.join(EMAIL_SEPARATOR, bccAddresses));
-        }
-
-        /**
-         * Saves a new body for the message. Only valid for drafts.
-         *
-         * @param values the ContentValues that will be used to create or update the message
-         * @param body the new body of the message
-         */
-        public static void putBody(ContentValues values, String body) {
-            values.put(MessageColumns.BODY, body);
-        }
-
-        /**
-         * Sets the attachments on a message. Only valid for drafts.
-         *
-         * @param values the ContentValues that will be used to create or update the message
-         * @param attachments
-         */
-        public static void putAttachments(ContentValues values, List<Attachment> attachments) {
-            values.put(
-                    MessageColumns.JOINED_ATTACHMENT_INFOS, joinedAttachmentsString(attachments));
-        }
-
-        /**
-         * Create a new message and save it as a draft or send it.
-         *
-         * @param contentResolver the content resolver to use
-         * @param account the account to use
-         * @param values the values for the new message
-         * @param refMessageId the message that is being replied to or forwarded
-         * @param save whether to save or send the message
-         * @return the id of the new message
-         */
-        public static long sendOrSaveNewMessage(
-                ContentResolver contentResolver, String account,
-                ContentValues values, long refMessageId, boolean save) {
-            values.put(MessageColumns.FAKE_SAVE, save);
-            values.put(MessageColumns.FAKE_REF_MESSAGE_ID, refMessageId);
-            Uri uri = Uri.parse(AUTHORITY_PLUS_MESSAGES + account + "/");
-            Uri result = contentResolver.insert(uri, values);
-            return ContentUris.parseId(result);
-        }
-
-        /**
-         * Update an existing draft and save it as a new draft or send it.
-         *
-         * @param contentResolver the content resolver to use
-         * @param account the account to use
-         * @param messageId the id of the message to update
-         * @param updateValues the values to change. Unspecified fields will not be altered
-         * @param save whether to resave the message as a draft or send it
-         */
-        public static void sendOrSaveExistingMessage(
-                ContentResolver contentResolver, String account, long messageId,
-                ContentValues updateValues, boolean save) {
-            updateValues.put(MessageColumns.FAKE_SAVE, save);
-            updateValues.put(MessageColumns.FAKE_REF_MESSAGE_ID, 0);
-            Uri uri = Uri.parse(
-                    AUTHORITY_PLUS_MESSAGES + account + "/" + messageId);
-            contentResolver.update(uri, updateValues, null, null);
-        }
-
-        /**
-         * The string produced here is parsed by Gmail.MessageCursor#getAttachmentInfos.
-         */
-        public static String joinedAttachmentsString(List<Gmail.Attachment> attachments) {
-            StringBuilder attachmentsSb = new StringBuilder();
-            for (Gmail.Attachment attachment : attachments) {
-                if (attachmentsSb.length() != 0) {
-                    attachmentsSb.append(Gmail.ATTACHMENT_INFO_SEPARATOR);
-                }
-                attachmentsSb.append(attachment.toJoinedString());
-            }
-            return attachmentsSb.toString();
-        }
-
-    }
-
-    /**
-     * A cursor over conversations.
-     *
-     * "Conversation" refers to the information needed to populate a list of
-     * conversations, not all of the messages in a conversation.
-     */
-    public static final class ConversationCursor extends MailCursor {
-
-        private LabelMap mLabelMap;
-
-        private int mConversationIdIndex;
-        private int mSubjectIndex;
-        private int mSnippetIndex;
-        private int mFromIndex;
-        private int mDateIndex;
-        private int mPersonalLevelIndex;
-        private int mLabelIdsIndex;
-        private int mNumMessagesIndex;
-        private int mMaxMessageIdIndex;
-        private int mHasAttachmentsIndex;
-        private int mHasMessagesWithErrorsIndex;
-        private int mForceAllUnreadIndex;
-
-        private TextUtils.StringSplitter mLabelIdsSplitter = newConversationLabelIdsSplitter();
-
-        private ConversationCursor(Gmail gmail, String account, Cursor cursor) {
-            super(account, cursor);
-            mLabelMap = gmail.getLabelMap(account);
-
-            mConversationIdIndex =
-                    mCursor.getColumnIndexOrThrow(ConversationColumns.ID);
-            mSubjectIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.SUBJECT);
-            mSnippetIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.SNIPPET);
-            mFromIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.FROM);
-            mDateIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.DATE);
-            mPersonalLevelIndex =
-                    mCursor.getColumnIndexOrThrow(ConversationColumns.PERSONAL_LEVEL);
-            mLabelIdsIndex =
-                    mCursor.getColumnIndexOrThrow(ConversationColumns.LABEL_IDS);
-            mNumMessagesIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.NUM_MESSAGES);
-            mMaxMessageIdIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.MAX_MESSAGE_ID);
-            mHasAttachmentsIndex =
-                    mCursor.getColumnIndexOrThrow(ConversationColumns.HAS_ATTACHMENTS);
-            mHasMessagesWithErrorsIndex =
-                    mCursor.getColumnIndexOrThrow(ConversationColumns.HAS_MESSAGES_WITH_ERRORS);
-            mForceAllUnreadIndex =
-                    mCursor.getColumnIndexOrThrow(ConversationColumns.FORCE_ALL_UNREAD);
-        }
-
-        @Override
-        protected void onCursorPositionChanged() {
-            super.onCursorPositionChanged();
-        }
-
-        public CursorStatus getStatus() {
-            Bundle extras = mCursor.getExtras();
-            String stringStatus = extras.getString(EXTRA_STATUS);
-            return CursorStatus.valueOf(stringStatus);
-        }
-
-        /** Retry a network request after errors. */
-        public void retry() {
-            Bundle input = new Bundle();
-            input.putString(RESPOND_INPUT_COMMAND, COMMAND_RETRY);
-            Bundle output = mCursor.respond(input);
-            String response = output.getString(RESPOND_OUTPUT_COMMAND_RESPONSE);
-            assert COMMAND_RESPONSE_OK.equals(response);
-        }
-
-        /**
-         * When a conversation cursor is created it becomes the active network cursor, which means
-         * that it will fetch results from the network if it needs to in order to show all mail that
-         * matches its query. If you later want to requery an older cursor and would like that
-         * cursor to be the active cursor you need to call this method before requerying.
-         */
-        public void becomeActiveNetworkCursor() {
-            Bundle input = new Bundle();
-            input.putString(RESPOND_INPUT_COMMAND, COMMAND_ACTIVATE);
-            Bundle output = mCursor.respond(input);
-            String response = output.getString(RESPOND_OUTPUT_COMMAND_RESPONSE);
-            assert COMMAND_RESPONSE_OK.equals(response);
-        }
-
-        /**
-         * Tells the cursor whether its contents are visible to the user. The cursor will
-         * automatically broadcast intents to remove any matching new-mail notifications when this
-         * cursor's results become visible and, if they are visible, when the cursor is requeried.
-         *
-         * Note that contents shown in an activity that is resumed but not focused
-         * (onWindowFocusChanged/hasWindowFocus) then results shown in that activity do not count
-         * as visible. (This happens when the activity is behind the lock screen or a dialog.)
-         *
-         * @param visible whether the contents of this cursor are visible to the user.
-         */
-        public void setContentsVisibleToUser(boolean visible) {
-            Bundle input = new Bundle();
-            input.putString(RESPOND_INPUT_COMMAND, COMMAND_SET_VISIBLE);
-            input.putBoolean(SET_VISIBLE_PARAM_VISIBLE, visible);
-            Bundle output = mCursor.respond(input);
-            String response = output.getString(RESPOND_OUTPUT_COMMAND_RESPONSE);
-            assert COMMAND_RESPONSE_OK.equals(response);
-        }
-
-        /**
-         * Gets the conversation id. This is immutable. (The server calls it the original
-         * conversation id.)
-         *
-         * @return the conversation id
-         */
-        public long getConversationId() {
-            return mCursor.getLong(mConversationIdIndex);
-        }
-
-        /**
-         * Returns the instructions for building from snippets. Pass this to getFromSnippetHtml
-         * in order to actually build the snippets.
-         * @return snippet instructions for use by getFromSnippetHtml()
-         */
-        public String getFromSnippetInstructions() {
-            return getStringInColumn(mFromIndex);
-        }
-
-        /**
-         * Gets the conversation's subject.
-         *
-         * @return the subject
-         */
-        public String getSubject() {
-            return getStringInColumn(mSubjectIndex);
-        }
-
-        /**
-         * Gets the conversation's snippet.
-         *
-         * @return the snippet
-         */
-        public String getSnippet() {
-            return getStringInColumn(mSnippetIndex);
-        }
-
-        /**
-         * Get's the conversation's personal level.
-         *
-         * @return the personal level.
-         */
-        public PersonalLevel getPersonalLevel() {
-            int personalLevelInt = mCursor.getInt(mPersonalLevelIndex);
-            return PersonalLevel.fromInt(personalLevelInt);
-        }
-
-        /**
-         * @return a copy of the set of labels. To add or remove labels call
-         *         MessageCursor.addOrRemoveLabel on each message in the conversation.
-         * @deprecated use getLabelIds
-         */
-        public Set<String> getLabels() {
-            return getLabels(getRawLabelIds(), mLabelMap);
-        }
-
-        /**
-         * @return a copy of the set of labels. To add or remove labels call
-         *         MessageCursor.addOrRemoveLabel on each message in the conversation.
-         */
-        public Set<Long> getLabelIds() {
-            mLabelIdsSplitter.setString(getRawLabelIds());
-            return getLabelIdsFromLabelIdsString(mLabelIdsSplitter);
-        }
-
-        /**
-         * Returns the set of labels using the raw labels from a previous getRawLabels()
-         * as input.
-         * @return a copy of the set of labels. To add or remove labels call
-         * MessageCursor.addOrRemoveLabel on each message in the conversation.
-         */
-        public Set<String> getLabels(String rawLabelIds, LabelMap labelMap) {
-            mLabelIdsSplitter.setString(rawLabelIds);
-            return getCanonicalNamesFromLabelIdsString(labelMap, mLabelIdsSplitter);
-        }
-
-        /**
-         * @return a joined string of labels separated by spaces. Use
-         * getLabels(rawLabels) to convert this to a Set of labels.
-         */
-        public String getRawLabelIds() {
-            return mCursor.getString(mLabelIdsIndex);
-        }
-
-        /**
-         * @return the number of messages in the conversation
-         */
-        public int getNumMessages() {
-            return mCursor.getInt(mNumMessagesIndex);
-        }
-
-        /**
-         * @return the max message id in the conversation
-         */
-        public long getMaxServerMessageId() {
-            return mCursor.getLong(mMaxMessageIdIndex);
-        }
-
-        public long getDateMs() {
-            return mCursor.getLong(mDateIndex);
-        }
-
-        public boolean hasAttachments() {
-            return mCursor.getInt(mHasAttachmentsIndex) != 0;
-        }
-
-        public boolean hasMessagesWithErrors() {
-            return mCursor.getInt(mHasMessagesWithErrorsIndex) != 0;
-        }
-
-        public boolean getForceAllUnread() {
-            return !mCursor.isNull(mForceAllUnreadIndex)
-                    && mCursor.getInt(mForceAllUnreadIndex) != 0;
-        }
-    }
-}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1fc3678..d3f46b8 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2589,8 +2589,8 @@
      * @param gainFocus True if the View has focus; false otherwise.
      * @param direction The direction focus has moved when requestFocus()
      *                  is called to give this view focus. Values are
-     *                  View.FOCUS_UP, View.FOCUS_DOWN, View.FOCUS_LEFT or
-     *                  View.FOCUS_RIGHT. It may not always apply, in which
+     *                  {@link #FOCUS_UP}, {@link #FOCUS_DOWN}, {@link #FOCUS_LEFT} or
+     *                  {@link #FOCUS_RIGHT}. It may not always apply, in which
      *                  case use the default.
      * @param previouslyFocusedRect The rectangle, in this view's coordinate
      *        system, of the previously focused view.  If applicable, this will be
@@ -5961,7 +5961,11 @@
     protected void onRestoreInstanceState(Parcelable state) {
         mPrivateFlags |= SAVE_STATE_CALLED;
         if (state != BaseSavedState.EMPTY_STATE && state != null) {
-            throw new IllegalArgumentException("Wrong state class -- expecting View State");
+            throw new IllegalArgumentException("Wrong state class, expecting View State but "
+                    + "received " + state.getClass().toString() + " instead. This usually happens "
+                    + "when two views of different type have the same id in the same hierarchy. " 
+                    + "This view's id is " + ViewDebug.resolveId(mContext, getId()) + ". Make sure " 
+                    + "other views do not use the same id.");
         }
     }
 
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 4baf612..2fd974e 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -1300,7 +1300,7 @@
         }
     }
 
-    private static Object resolveId(Context context, int id) {
+    static Object resolveId(Context context, int id) {
         Object fieldValue;
         final Resources resources = context.getResources();
         if (id >= 0) {
diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java
index 924398e..608c8a24 100644
--- a/core/java/android/webkit/WebTextView.java
+++ b/core/java/android/webkit/WebTextView.java
@@ -789,7 +789,6 @@
         // Set up a measure spec so a layout can always be recreated.
         mWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
         mHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
-        requestFocus();
     }
 
     /**
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 3be6a3f..7c79fd9 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -3205,6 +3205,8 @@
         // Note that sendOurVisibleRect calls viewToContent, so the coordinates
         // should be in content coordinates.
         Rect bounds = nativeFocusCandidateNodeBounds();
+        Rect vBox = contentToViewRect(bounds);
+        mWebTextView.setRect(vBox.left, vBox.top, vBox.width(), vBox.height());
         if (!Rect.intersects(bounds, visibleRect)) {
             mWebTextView.bringIntoView();
         }
@@ -3215,25 +3217,14 @@
             // i.e. In the case of opening/closing the screen.
             // In that case, we need to set the dimensions, but not the other
             // aspects.
-            // We also need to restore the selection, which gets wrecked by
-            // calling setTextEntryRect.
-            Spannable spannable = (Spannable) mWebTextView.getText();
-            int start = Selection.getSelectionStart(spannable);
-            int end = Selection.getSelectionEnd(spannable);
             // If the text has been changed by webkit, update it.  However, if
             // there has been more UI text input, ignore it.  We will receive
             // another update when that text is recognized.
-            if (text != null && !text.equals(spannable.toString())
+            if (text != null && !text.equals(mWebTextView.getText().toString())
                     && nativeTextGeneration() == mTextGeneration) {
                 mWebTextView.setTextAndKeepSelection(text);
-            } else {
-                // FIXME: Determine whether this is necessary.
-                Selection.setSelection(spannable, start, end);
             }
         } else {
-            Rect vBox = contentToViewRect(bounds);
-            mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
-                    vBox.height());
             mWebTextView.setGravity(nativeFocusCandidateIsRtlText() ?
                     Gravity.RIGHT : Gravity.NO_GRAVITY);
             // This needs to be called before setType, which may call
@@ -3247,8 +3238,8 @@
                 text = "";
             }
             mWebTextView.setTextAndKeepSelection(text);
-            mWebTextView.requestFocus();
         }
+        mWebTextView.requestFocus();
     }
 
     /**
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 6079773..5ded240 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -1592,9 +1592,6 @@
     // Used to avoid posting more than one split picture message.
     private boolean mSplitPictureIsScheduled;
 
-    // Used to suspend drawing.
-    private boolean mDrawIsPaused;
-
     // mRestoreState is set in didFirstLayout(), and reset in the next
     // webkitDraw after passing it to the UI thread.
     private RestoreState mRestoreState = null;
@@ -1713,17 +1710,6 @@
         sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY);
         sWebCoreHandler.sendMessageAtFrontOfQueue(sWebCoreHandler
                 .obtainMessage(WebCoreThread.REDUCE_PRIORITY));
-        // Note: there is one possible failure mode. If pauseUpdate() is called
-        // from UI thread while in webcore thread WEBKIT_DRAW is just pulled out
-        // of the queue and about to be executed. mDrawIsScheduled may be set to
-        // false in webkitDraw(). So update won't be blocked. But at least the
-        // webcore thread priority is still lowered.
-        if (core != null) {
-            synchronized (core) {
-                core.mDrawIsPaused = true;
-                core.mEventHub.removeMessages(EventHub.WEBKIT_DRAW);
-            }
-        }
     }
 
     static void resumeUpdate(WebViewCore core) {
@@ -1732,14 +1718,6 @@
         sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY);
         sWebCoreHandler.sendMessageAtFrontOfQueue(sWebCoreHandler
                 .obtainMessage(WebCoreThread.RESUME_PRIORITY));
-        if (core != null) {
-            synchronized (core) {
-                core.mDrawIsScheduled = false;
-                core.mDrawIsPaused = false;
-                if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "resumeUpdate");
-                core.contentDraw();
-            }
-        }
     }
 
     static void startCacheTransaction() {
@@ -1778,9 +1756,7 @@
         }
         // only fire an event if this is our first request
         synchronized (this) {
-            if (mDrawIsPaused || mDrawIsScheduled) {
-                return;
-            }
+            if (mDrawIsScheduled) return;
             mDrawIsScheduled = true;
             mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW));
         }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 5199ada..e964a8f 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -17,8 +17,8 @@
 package com.android.internal.os;
 
 import android.bluetooth.BluetoothHeadset;
+import android.net.TrafficStats;
 import android.os.BatteryStats;
-import android.os.NetStat;
 import android.os.Parcel;
 import android.os.ParcelFormatException;
 import android.os.Parcelable;
@@ -1022,8 +1022,8 @@
     public void doUnplug(long batteryUptime, long batteryRealtime) {
         for (int iu = mUidStats.size() - 1; iu >= 0; iu--) {
             Uid u = mUidStats.valueAt(iu);
-            u.mStartedTcpBytesReceived = NetStat.getUidRxBytes(u.mUid);
-            u.mStartedTcpBytesSent = NetStat.getUidTxBytes(u.mUid);
+            u.mStartedTcpBytesReceived = TrafficStats.getUidRxBytes(u.mUid);
+            u.mStartedTcpBytesSent = TrafficStats.getUidTxBytes(u.mUid);
             u.mTcpBytesReceivedAtLastUnplug = u.mCurrentTcpBytesReceived;
             u.mTcpBytesSentAtLastUnplug = u.mCurrentTcpBytesSent;
         }
@@ -1031,10 +1031,10 @@
             mUnpluggables.get(i).unplug(batteryUptime, batteryRealtime);
         }
         // Track total mobile data
-        doDataUnplug(mMobileDataRx, NetStat.getMobileRxBytes());
-        doDataUnplug(mMobileDataTx, NetStat.getMobileTxBytes());
-        doDataUnplug(mTotalDataRx, NetStat.getTotalRxBytes());
-        doDataUnplug(mTotalDataTx, NetStat.getTotalTxBytes());
+        doDataUnplug(mMobileDataRx, TrafficStats.getMobileRxBytes());
+        doDataUnplug(mMobileDataTx, TrafficStats.getMobileTxBytes());
+        doDataUnplug(mTotalDataRx, TrafficStats.getTotalRxBytes());
+        doDataUnplug(mTotalDataTx, TrafficStats.getTotalTxBytes());
         // Track radio awake time
         mRadioDataStart = getCurrentRadioDataUptime();
         mRadioDataUptime = 0;
@@ -1058,10 +1058,10 @@
         for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
             mUnpluggables.get(i).plug(batteryUptime, batteryRealtime);
         }
-        doDataPlug(mMobileDataRx, NetStat.getMobileRxBytes());
-        doDataPlug(mMobileDataTx, NetStat.getMobileTxBytes());
-        doDataPlug(mTotalDataRx, NetStat.getTotalRxBytes());
-        doDataPlug(mTotalDataTx, NetStat.getTotalTxBytes());
+        doDataPlug(mMobileDataRx, TrafficStats.getMobileRxBytes());
+        doDataPlug(mMobileDataTx, TrafficStats.getMobileTxBytes());
+        doDataPlug(mTotalDataRx, TrafficStats.getTotalRxBytes());
+        doDataPlug(mTotalDataTx, TrafficStats.getTotalTxBytes());
         // Track radio awake time
         mRadioDataUptime = getRadioDataUptime();
         mRadioDataStart = -1;
@@ -1519,7 +1519,7 @@
         
         public long computeCurrentTcpBytesReceived() {
             return mCurrentTcpBytesReceived + (mStartedTcpBytesReceived >= 0
-                    ? (NetStat.getUidRxBytes(mUid) - mStartedTcpBytesReceived) : 0);
+                    ? (TrafficStats.getUidRxBytes(mUid) - mStartedTcpBytesReceived) : 0);
         }
 
         @Override
@@ -1696,7 +1696,7 @@
         
         public long computeCurrentTcpBytesSent() {
             return mCurrentTcpBytesSent + (mStartedTcpBytesSent >= 0
-                    ? (NetStat.getUidTxBytes(mUid) - mStartedTcpBytesSent) : 0);
+                    ? (TrafficStats.getUidTxBytes(mUid) - mStartedTcpBytesSent) : 0);
         }
 
         void writeToParcelLocked(Parcel out, long batteryRealtime) {
@@ -2919,22 +2919,22 @@
 
     /** Only STATS_UNPLUGGED works properly */
     public long getMobileTcpBytesSent(int which) {
-        return getTcpBytes(NetStat.getMobileTxBytes(), mMobileDataTx, which);
+        return getTcpBytes(TrafficStats.getMobileTxBytes(), mMobileDataTx, which);
     }
 
     /** Only STATS_UNPLUGGED works properly */
     public long getMobileTcpBytesReceived(int which) {
-        return getTcpBytes(NetStat.getMobileRxBytes(), mMobileDataRx, which);
+        return getTcpBytes(TrafficStats.getMobileRxBytes(), mMobileDataRx, which);
     }
 
     /** Only STATS_UNPLUGGED works properly */
     public long getTotalTcpBytesSent(int which) {
-        return getTcpBytes(NetStat.getTotalTxBytes(), mTotalDataTx, which);
+        return getTcpBytes(TrafficStats.getTotalTxBytes(), mTotalDataTx, which);
     }
 
     /** Only STATS_UNPLUGGED works properly */
     public long getTotalTcpBytesReceived(int which) {
-        return getTcpBytes(NetStat.getTotalRxBytes(), mTotalDataRx, which);
+        return getTcpBytes(TrafficStats.getTotalRxBytes(), mTotalDataRx, which);
     }
 
     @Override
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
index eacf0ce..ba0bf0d 100644
--- a/core/java/com/android/internal/os/BinderInternal.java
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -76,6 +76,13 @@
      */
     public static final native IBinder getContextObject();
     
+    /**
+     * Special for system process to not allow incoming calls to run at
+     * background scheduling priority.
+     * @hide
+     */
+    public static final native void disableBackgroundScheduling(boolean disable);
+    
     static native final void handleGc();
     
     public static void forceGc(String reason) {
diff --git a/core/java/com/google/android/net/GoogleHttpClient.java b/core/java/com/google/android/net/GoogleHttpClient.java
index 8a1298f..7bdc7f8 100644
--- a/core/java/com/google/android/net/GoogleHttpClient.java
+++ b/core/java/com/google/android/net/GoogleHttpClient.java
@@ -19,13 +19,14 @@
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
+import android.net.TrafficStats;
 import android.net.http.AndroidHttpClient;
 import android.os.Build;
-import android.os.NetStat;
 import android.os.SystemClock;
 import android.provider.Checkin;
 import android.util.Config;
 import android.util.Log;
+import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpEntityEnclosingRequest;
 import org.apache.http.HttpHost;
@@ -45,7 +46,6 @@
 import org.apache.http.impl.client.RequestWrapper;
 import org.apache.http.params.HttpParams;
 import org.apache.http.protocol.HttpContext;
-import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
 
 import java.io.IOException;
 import java.net.InetAddress;
@@ -205,8 +205,8 @@
                 // to follow redirects, count each redirect as an additional round trip.
 
                 int uid = android.os.Process.myUid();
-                long startTx = NetStat.getUidTxBytes(uid);
-                long startRx = NetStat.getUidRxBytes(uid);
+                long startTx = TrafficStats.getUidTxBytes(uid);
+                long startRx = TrafficStats.getUidRxBytes(uid);
 
                 response = mClient.execute(request, context);
                 HttpEntity origEntity = response == null ? null : response.getEntity();
diff --git a/core/java/com/google/android/net/NetworkStatsEntity.java b/core/java/com/google/android/net/NetworkStatsEntity.java
index f5d2349..a22fa1e 100644
--- a/core/java/com/google/android/net/NetworkStatsEntity.java
+++ b/core/java/com/google/android/net/NetworkStatsEntity.java
@@ -16,11 +16,10 @@
 
 package com.google.android.net;
 
-import android.os.NetStat;
+import android.net.TrafficStats;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.EventLog;
-
 import org.apache.http.HttpEntity;
 import org.apache.http.entity.HttpEntityWrapper;
 
@@ -45,8 +44,8 @@
                 super.close();
             } finally {
                 long processingTime = SystemClock.elapsedRealtime() - mProcessingStartTime;
-                long tx = NetStat.getUidTxBytes(mUid);
-                long rx = NetStat.getUidRxBytes(mUid);
+                long tx = TrafficStats.getUidTxBytes(mUid);
+                long rx = TrafficStats.getUidRxBytes(mUid);
 
                 EventLog.writeEvent(HTTP_STATS_EVENT, mUa, mResponseLatency, processingTime,
                         tx - mStartTx, rx - mStartRx);
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index f0885fd..627fcbf 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -670,6 +670,12 @@
     android::IPCThreadState::self()->joinThreadPool();
 }
 
+static void android_os_BinderInternal_disableBackgroundScheduling(JNIEnv* env,
+        jobject clazz, jboolean disable)
+{
+    IPCThreadState::disableBackgroundScheduling(disable ? true : false);
+}
+
 static void android_os_BinderInternal_handleGc(JNIEnv* env, jobject clazz)
 {
     LOGV("Gc has executed, clearing binder ops");
@@ -682,6 +688,7 @@
      /* name, signature, funcPtr */
     { "getContextObject", "()Landroid/os/IBinder;", (void*)android_os_BinderInternal_getContextObject },
     { "joinThreadPool", "()V", (void*)android_os_BinderInternal_joinThreadPool },
+    { "disableBackgroundScheduling", "(Z)V", (void*)android_os_BinderInternal_disableBackgroundScheduling },
     { "handleGc", "()V", (void*)android_os_BinderInternal_handleGc }
 };
 
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 094b02d..e84f2e5 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -120,11 +120,7 @@
 
 jint android_os_Process_myTid(JNIEnv* env, jobject clazz)
 {
-#ifdef HAVE_GETTID
-    return gettid();
-#else
-    return getpid();
-#endif
+    return androidGetTid();
 }
 
 jint android_os_Process_getUidForName(JNIEnv* env, jobject clazz, jstring name)
@@ -191,15 +187,11 @@
 
 void android_os_Process_setThreadGroup(JNIEnv* env, jobject clazz, int pid, jint grp)
 {
-    if (grp > ANDROID_TGROUP_MAX || grp < 0) { 
-        signalExceptionForGroupError(env, clazz, EINVAL);
+    int res = androidSetThreadSchedulingGroup(pid, grp);
+    if (res != NO_ERROR) {
+        signalExceptionForGroupError(env, clazz, res == BAD_VALUE ? EINVAL : errno);
         return;
     }
-
-    if (set_sched_policy(pid, (grp == ANDROID_TGROUP_BG_NONINTERACT) ?
-                                      SP_BACKGROUND : SP_FOREGROUND)) {
-        signalExceptionForGroupError(env, clazz, errno);
-    }
 }
 
 void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jint grp) 
@@ -275,22 +267,15 @@
 void android_os_Process_setThreadPriority(JNIEnv* env, jobject clazz,
                                               jint pid, jint pri)
 {
-    int rc = 0;
-
-    if (pri >= ANDROID_PRIORITY_BACKGROUND) {
-        rc = set_sched_policy(pid, SP_BACKGROUND);
-    } else if (getpriority(PRIO_PROCESS, pid) >= ANDROID_PRIORITY_BACKGROUND) {
-        rc = set_sched_policy(pid, SP_FOREGROUND);
+    int rc = androidSetThreadPriority(pid, pri);
+    if (rc != 0) {
+        if (rc == INVALID_OPERATION) {
+            signalExceptionForPriorityError(env, clazz, errno);
+        } else {
+            signalExceptionForGroupError(env, clazz, errno);
+        }
     }
-
-    if (rc) {
-        signalExceptionForGroupError(env, clazz, errno);
-        return;
-    }
-
-    if (setpriority(PRIO_PROCESS, pid, pri) < 0) {
-        signalExceptionForPriorityError(env, clazz, errno);
-    }
+    
     //LOGI("Setting priority of %d: %d, getpriority returns %d\n",
     //     pid, pri, getpriority(PRIO_PROCESS, pid));
 }
diff --git a/include/binder/IBinder.h b/include/binder/IBinder.h
index 884b5c1..749a977 100644
--- a/include/binder/IBinder.h
+++ b/include/binder/IBinder.h
@@ -52,7 +52,7 @@
         DUMP_TRANSACTION        = B_PACK_CHARS('_','D','M','P'),
         INTERFACE_TRANSACTION   = B_PACK_CHARS('_', 'N', 'T', 'F'),
 
-        // Corresponds to tfOneWay -- an asynchronous call.
+        // Corresponds to TF_ONE_WAY -- an asynchronous call.
         FLAG_ONEWAY             = 0x00000001
     };
 
diff --git a/include/binder/IPCThreadState.h b/include/binder/IPCThreadState.h
index 78306b2b..3ab985d 100644
--- a/include/binder/IPCThreadState.h
+++ b/include/binder/IPCThreadState.h
@@ -68,6 +68,13 @@
 
     static  void                shutdown();
     
+    // Call this to disable switching threads to background scheduling when
+    // receiving incoming IPC calls.  This is specifically here for the
+    // Android system process, since it expects to have background apps calling
+    // in to it but doesn't want to acquire locks in its services while in
+    // the background.
+    static  void                disableBackgroundScheduling(bool disable);
+    
 private:
                                 IPCThreadState();
                                 ~IPCThreadState();
@@ -93,9 +100,10 @@
                                            void* cookie);
     
     const   sp<ProcessState>    mProcess;
+    const   pid_t               mMyThreadId;
             Vector<BBinder*>    mPendingStrongDerefs;
             Vector<RefBase::weakref_type*> mPendingWeakDerefs;
-                                
+            
             Parcel              mIn;
             Parcel              mOut;
             status_t            mLastError;
diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h
index 49145e8..a845908 100644
--- a/include/utils/ResourceTypes.h
+++ b/include/utils/ResourceTypes.h
@@ -393,7 +393,10 @@
     enum {
         // If set, the string index is sorted by the string values (based
         // on strcmp16()).
-        SORTED_FLAG = 1<<0
+        SORTED_FLAG = 1<<0,
+
+        // String pool is encoded in UTF-8
+        UTF8_FLAG = 1<<8
     };
     uint32_t flags;
 
@@ -456,9 +459,11 @@
     void*                       mOwnedData;
     const ResStringPool_header* mHeader;
     size_t                      mSize;
+    mutable Mutex               mDecodeLock;
     const uint32_t*             mEntries;
     const uint32_t*             mEntryStyles;
-    const char16_t*             mStrings;
+    const void*                 mStrings;
+    char16_t**                  mCache;
     uint32_t                    mStringPoolSize;    // number of uint16_t
     const uint32_t*             mStyles;
     uint32_t                    mStylePoolSize;    // number of uint32_t
diff --git a/include/utils/String16.h b/include/utils/String16.h
index a2d22ee..07a0c11 100644
--- a/include/utils/String16.h
+++ b/include/utils/String16.h
@@ -49,12 +49,17 @@
 // Version of strzcmp16 for comparing strings in different endianness.
 int strzcmp16_h_n(const char16_t *s1H, size_t n1, const char16_t *s2N, size_t n2);
 
+// Convert UTF-8 to UTF-16 including surrogate pairs
+void utf8_to_utf16(const uint8_t *src, size_t srcLen, char16_t* dst, const size_t dstLen);
+
 }
 
 // ---------------------------------------------------------------------------
 
 namespace android {
 
+// ---------------------------------------------------------------------------
+
 class String8;
 class TextOutput;
 
diff --git a/include/utils/String8.h b/include/utils/String8.h
index ecc5774..c4b18a4 100644
--- a/include/utils/String8.h
+++ b/include/utils/String8.h
@@ -60,6 +60,11 @@
 /*
  * Returns the UTF-8 length of "src".
  */
+size_t utf8_length_from_utf16(const char16_t *src, size_t src_len);
+
+/*
+ * Returns the UTF-8 length of "src".
+ */
 size_t utf8_length_from_utf32(const char32_t *src, size_t src_len);
 
 /*
@@ -120,6 +125,9 @@
 size_t utf32_to_utf8(const char32_t* src, size_t src_len,
                      char* dst, size_t dst_len);
 
+size_t utf16_to_utf8(const char16_t* src, size_t src_len,
+                     char* dst, size_t dst_len);
+
 }
 
 // ---------------------------------------------------------------------------
diff --git a/include/utils/threads.h b/include/utils/threads.h
index 0fc533f..130d83c 100644
--- a/include/utils/threads.h
+++ b/include/utils/threads.h
@@ -124,6 +124,24 @@
 
 extern void androidSetCreateThreadFunc(android_create_thread_fn func);
 
+// ------------------------------------------------------------------
+// Extra functions working with raw pids.
+
+// Get pid for the current thread.
+extern pid_t androidGetTid();
+
+// Change the scheduling group of a particular thread.  The group
+// should be one of the ANDROID_TGROUP constants.  Returns BAD_VALUE if
+// grp is out of range, else another non-zero value with errno set if
+// the operation failed.
+extern int androidSetThreadSchedulingGroup(pid_t tid, int grp);
+
+// Change the priority AND scheduling group of a particular thread.  The priority
+// should be one of the ANDROID_PRIORITY constants.  Returns INVALID_OPERATION
+// if the priority set failed, else another value if just the group set failed;
+// in either case errno is set.
+extern int androidSetThreadPriority(pid_t tid, int prio);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index b2a7db8..473f580 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -292,6 +292,7 @@
 static bool gHaveTLS = false;
 static pthread_key_t gTLS = 0;
 static bool gShutdown = false;
+static bool gDisableBackgroundScheduling = false;
 
 IPCThreadState* IPCThreadState::self()
 {
@@ -332,6 +333,11 @@
     }
 }
 
+void IPCThreadState::disableBackgroundScheduling(bool disable)
+{
+    gDisableBackgroundScheduling = disable;
+}
+
 sp<ProcessState> IPCThreadState::process()
 {
     return mProcess;
@@ -386,6 +392,11 @@
 
     mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
     
+    // This thread may have been spawned by a thread that was in the background
+    // scheduling group, so first we will make sure it is in the default/foreground
+    // one to avoid performing an initial transaction in the background.
+    androidSetThreadSchedulingGroup(mMyThreadId, ANDROID_TGROUP_DEFAULT);
+        
     status_t result;
     do {
         int32_t cmd;
@@ -427,19 +438,13 @@
         }
         
         // After executing the command, ensure that the thread is returned to the
-        // default cgroup and priority before rejoining the pool.  This is a failsafe
-        // in case the command implementation failed to properly restore the thread's
-        // scheduling parameters upon completion.
-        int my_id;
-#ifdef HAVE_GETTID
-        my_id = gettid();
-#else
-        my_id = getpid();
-#endif
-        if (!set_sched_policy(my_id, SP_FOREGROUND)) {
-            // success; reset the priority as well
-            setpriority(PRIO_PROCESS, my_id, ANDROID_PRIORITY_NORMAL);
-        }
+        // default cgroup before rejoining the pool.  The driver takes care of
+        // restoring the priority, but doesn't do anything with cgroups so we
+        // need to take care of that here in userspace.  Note that we do make
+        // sure to go in the foreground after executing a transaction, but
+        // there are other callbacks into user code that could have changed
+        // our group so we want to make absolutely sure it is put back.
+        androidSetThreadSchedulingGroup(mMyThreadId, ANDROID_TGROUP_DEFAULT);
 
         // Let this thread exit the thread pool if it is no longer
         // needed and it is not the main process thread.
@@ -583,10 +588,10 @@
 }
 
 IPCThreadState::IPCThreadState()
-    : mProcess(ProcessState::self())
+    : mProcess(ProcessState::self()), mMyThreadId(androidGetTid())
 {
     pthread_setspecific(gTLS, this);
-        clearCaller();
+    clearCaller();
     mIn.setDataCapacity(256);
     mOut.setDataCapacity(256);
 }
@@ -930,6 +935,17 @@
             mCallingPid = tr.sender_pid;
             mCallingUid = tr.sender_euid;
             
+            bool doBackground = !gDisableBackgroundScheduling &&
+                    getpriority(PRIO_PROCESS, mMyThreadId)
+                            >= ANDROID_PRIORITY_BACKGROUND;
+            if (doBackground) {
+                // We have inherited a background priority from the caller.
+                // Ensure this thread is in the background scheduling class,
+                // since the driver won't modify scheduling classes for us.
+                androidSetThreadSchedulingGroup(mMyThreadId,
+                        ANDROID_TGROUP_BG_NONINTERACT);
+            }
+            
             //LOGI(">>>> TRANSACT from pid %d uid %d\n", mCallingPid, mCallingUid);
             
             Parcel reply;
@@ -967,6 +983,13 @@
             mCallingPid = origPid;
             mCallingUid = origUid;
             
+            if (doBackground) {
+                // We moved to the background scheduling group to execute
+                // this transaction, so now that we are done go back in the
+                // foreground.
+                androidSetThreadSchedulingGroup(mMyThreadId, ANDROID_TGROUP_DEFAULT);
+            }
+            
             IF_LOG_TRANSACTIONS() {
                 TextOutput::Bundle _b(alog);
                 alog << "BC_REPLY thr " << (void*)pthread_self() << " / obj "
diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp
index 450af8d..afca814 100644
--- a/libs/utils/ResourceTypes.cpp
+++ b/libs/utils/ResourceTypes.cpp
@@ -229,12 +229,12 @@
 // --------------------------------------------------------------------
 
 ResStringPool::ResStringPool()
-    : mError(NO_INIT), mOwnedData(NULL)
+    : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL)
 {
 }
 
 ResStringPool::ResStringPool(const void* data, size_t size, bool copyData)
-    : mError(NO_INIT), mOwnedData(NULL)
+    : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL)
 {
     setTo(data, size, copyData);
 }
@@ -296,7 +296,17 @@
                     (int)size);
             return (mError=BAD_TYPE);
         }
-        mStrings = (const char16_t*)
+
+        size_t charSize;
+        if (mHeader->flags&ResStringPool_header::UTF8_FLAG) {
+            charSize = sizeof(uint8_t);
+            mCache = (char16_t**)malloc(sizeof(char16_t**)*mHeader->stringCount);
+            memset(mCache, 0, sizeof(char16_t**)*mHeader->stringCount);
+        } else {
+            charSize = sizeof(char16_t);
+        }
+
+        mStrings = (const void*)
             (((const uint8_t*)data)+mHeader->stringsStart);
         if (mHeader->stringsStart >= (mHeader->header.size-sizeof(uint16_t))) {
             LOGW("Bad string block: string pool starts at %d, after total size %d\n",
@@ -305,7 +315,7 @@
         }
         if (mHeader->styleCount == 0) {
             mStringPoolSize =
-                (mHeader->header.size-mHeader->stringsStart)/sizeof(uint16_t);
+                (mHeader->header.size-mHeader->stringsStart)/charSize;
         } else {
             // check invariant: styles follow the strings
             if (mHeader->stylesStart <= mHeader->stringsStart) {
@@ -314,7 +324,7 @@
                 return (mError=BAD_TYPE);
             }
             mStringPoolSize =
-                (mHeader->stylesStart-mHeader->stringsStart)/sizeof(uint16_t);
+                (mHeader->stylesStart-mHeader->stringsStart)/charSize;
         }
 
         // check invariant: stringCount > 0 requires a string pool to exist
@@ -329,13 +339,19 @@
             for (i=0; i<mHeader->stringCount; i++) {
                 e[i] = dtohl(mEntries[i]);
             }
-            char16_t* s = const_cast<char16_t*>(mStrings);
-            for (i=0; i<mStringPoolSize; i++) {
-                s[i] = dtohs(mStrings[i]);
+            if (!(mHeader->flags&ResStringPool_header::UTF8_FLAG)) {
+                const char16_t* strings = (const char16_t*)mStrings;
+                char16_t* s = const_cast<char16_t*>(strings);
+                for (i=0; i<mStringPoolSize; i++) {
+                    s[i] = dtohs(strings[i]);
+                }
             }
         }
 
-        if (mStrings[mStringPoolSize-1] != 0) {
+        if ((mHeader->flags&ResStringPool_header::UTF8_FLAG &&
+                ((uint8_t*)mStrings)[mStringPoolSize-1] != 0) ||
+                (!mHeader->flags&ResStringPool_header::UTF8_FLAG &&
+                ((char16_t*)mStrings)[mStringPoolSize-1] != 0)) {
             LOGW("Bad string block: last string is not 0-terminated\n");
             return (mError=BAD_TYPE);
         }
@@ -410,24 +426,67 @@
         free(mOwnedData);
         mOwnedData = NULL;
     }
+    if (mHeader != NULL && mCache != NULL) {
+        for (size_t x = 0; x < mHeader->stringCount; x++) {
+            if (mCache[x] != NULL) {
+                free(mCache[x]);
+                mCache[x] = NULL;
+            }
+        }
+        free(mCache);
+        mCache = NULL;
+    }
 }
 
+#define DECODE_LENGTH(str, chrsz, len) \
+    len = *(str); \
+    if (*(str)&(1<<(chrsz*8-1))) { \
+        (str)++; \
+        len = (((len)&((1<<(chrsz*8-1))-1))<<(chrsz*8)) + *(str); \
+    } \
+    (str)++;
+
 const uint16_t* ResStringPool::stringAt(size_t idx, size_t* outLen) const
 {
     if (mError == NO_ERROR && idx < mHeader->stringCount) {
-        const uint32_t off = (mEntries[idx]/sizeof(uint16_t));
+        const bool isUTF8 = (mHeader->flags&ResStringPool_header::UTF8_FLAG) != 0;
+        const uint32_t off = mEntries[idx]/(isUTF8?sizeof(char):sizeof(char16_t));
         if (off < (mStringPoolSize-1)) {
-            const char16_t* str = mStrings+off;
-            *outLen = *str;
-            if ((*str)&0x8000) {
-                str++;
-                *outLen = (((*outLen)&0x7fff)<<16) + *str;
-            }
-            if ((uint32_t)(str+1+*outLen-mStrings) < mStringPoolSize) {
-                return str+1;
+            if (!isUTF8) {
+                const char16_t* strings = (char16_t*)mStrings;
+                const char16_t* str = strings+off;
+                DECODE_LENGTH(str, sizeof(char16_t), *outLen)
+                if ((uint32_t)(str+*outLen-strings) < mStringPoolSize) {
+                    return str;
+                } else {
+                    LOGW("Bad string block: string #%d extends to %d, past end at %d\n",
+                            (int)idx, (int)(str+*outLen-strings), (int)mStringPoolSize);
+                }
             } else {
-                LOGW("Bad string block: string #%d extends to %d, past end at %d\n",
-                        (int)idx, (int)(str+1+*outLen-mStrings), (int)mStringPoolSize);
+                const uint8_t* strings = (uint8_t*)mStrings;
+                const uint8_t* str = strings+off;
+                DECODE_LENGTH(str, sizeof(uint8_t), *outLen)
+                size_t encLen;
+                DECODE_LENGTH(str, sizeof(uint8_t), encLen)
+                if ((uint32_t)(str+encLen-strings) < mStringPoolSize) {
+                    AutoMutex lock(mDecodeLock);
+                    if (mCache[idx] != NULL) {
+                        return mCache[idx];
+                    }
+                    char16_t *u16str = (char16_t *)calloc(*outLen+1, sizeof(char16_t));
+                    if (!u16str) {
+                        LOGW("No memory when trying to allocate decode cache for string #%d\n",
+                                (int)idx);
+                        return NULL;
+                    }
+                    const unsigned char *u8src = reinterpret_cast<const unsigned char *>(str);
+                    utf8_to_utf16(u8src, encLen, u16str, *outLen);
+                    mCache[idx] = u16str;
+                    return u16str;
+                } else {
+                    LOGW("Bad string block: string #%d extends to %d, past end at %d\n",
+                            (int)idx, (int)(str+encLen-strings), (int)mStringPoolSize);
+                }
             }
         } else {
             LOGW("Bad string block: string #%d entry is at %d, past end at %d\n",
@@ -466,6 +525,10 @@
 
     size_t len;
 
+    // TODO optimize searching for UTF-8 strings taking into account
+    // the cache fill to determine when to convert the searched-for
+    // string key to UTF-8.
+
     if (mHeader->flags&ResStringPool_header::SORTED_FLAG) {
         // Do a binary search for the string...
         ssize_t l = 0;
@@ -1043,6 +1106,7 @@
 void ResXMLTree::uninit()
 {
     mError = NO_INIT;
+    mStrings.uninit();
     if (mOwnedData) {
         free(mOwnedData);
         mOwnedData = NULL;
diff --git a/libs/utils/String16.cpp b/libs/utils/String16.cpp
index aef67f2..eab7b2b 100644
--- a/libs/utils/String16.cpp
+++ b/libs/utils/String16.cpp
@@ -172,10 +172,6 @@
            : 0);
 }
 
-// ---------------------------------------------------------------------------
-
-namespace android {
-
 static inline size_t
 utf8_char_len(uint8_t ch)
 {
@@ -215,8 +211,38 @@
     //printf("Char at %p: len=%d, utf-16=%p\n", src, length, (void*)result);
 }
 
+void
+utf8_to_utf16(const uint8_t *src, size_t srcLen,
+        char16_t* dst, const size_t dstLen)
+{
+    const uint8_t* const end = src + srcLen;
+    const char16_t* const dstEnd = dst + dstLen;
+    while (src < end && dst < dstEnd) {
+        size_t len = utf8_char_len(*src);
+        uint32_t codepoint = utf8_to_utf32((const uint8_t*)src, len);
+
+        // Convert the UTF32 codepoint to one or more UTF16 codepoints
+        if (codepoint <= 0xFFFF) {
+            // Single UTF16 character
+            *dst++ = (char16_t) codepoint;
+        } else {
+            // Multiple UTF16 characters with surrogates
+            codepoint = codepoint - 0x10000;
+            *dst++ = (char16_t) ((codepoint >> 10) + 0xD800);
+            *dst++ = (char16_t) ((codepoint & 0x3FF) + 0xDC00);
+        }
+
+        src += len;
+    }
+    if (dst < dstEnd) {
+        *dst = 0;
+    }
+}
+
 // ---------------------------------------------------------------------------
 
+namespace android {
+
 static SharedBuffer* gEmptyStringBuf = NULL;
 static char16_t* gEmptyString = NULL;
 
@@ -260,30 +286,14 @@
         p += utf8len;
     }
     
-    SharedBuffer* buf = SharedBuffer::alloc((chars+1)*sizeof(char16_t));
+    size_t bufSize = (chars+1)*sizeof(char16_t);
+    SharedBuffer* buf = SharedBuffer::alloc(bufSize);
     if (buf) {
         p = in;
         char16_t* str = (char16_t*)buf->data();
-        char16_t* d = str;
-        while (p < end) {
-            size_t len = utf8_char_len(*p);
-            uint32_t codepoint = utf8_to_utf32((const uint8_t*)p, len);
-
-            // Convert the UTF32 codepoint to one or more UTF16 codepoints
-            if (codepoint <= 0xFFFF) {
-                // Single UTF16 character
-                *d++ = (char16_t) codepoint;
-            } else {
-                // Multiple UTF16 characters with surrogates
-                codepoint = codepoint - 0x10000;
-                *d++ = (char16_t) ((codepoint >> 10) + 0xD800);
-                *d++ = (char16_t) ((codepoint & 0x3FF) + 0xDC00);
-            }
-
-            p += len;
-        }
-        *d = 0;
         
+        utf8_to_utf16((const uint8_t*)p, len, str, bufSize);
+
         //printf("Created UTF-16 string from UTF-8 \"%s\":", in);
         //printHexData(1, str, buf->size(), 16, 1);
         //printf("\n");
diff --git a/libs/utils/String8.cpp b/libs/utils/String8.cpp
index e908ec1..3a34838 100644
--- a/libs/utils/String8.cpp
+++ b/libs/utils/String8.cpp
@@ -208,10 +208,23 @@
     return getEmptyString();
 }
 
-// Note: not dealing with expanding surrogate pairs.
 static char* allocFromUTF16(const char16_t* in, size_t len)
 {
-    return allocFromUTF16OrUTF32<char16_t, size_t>(in, len);
+    if (len == 0) return getEmptyString();
+
+    const size_t bytes = utf8_length_from_utf16(in, len);
+
+    SharedBuffer* buf = SharedBuffer::alloc(bytes+1);
+    LOG_ASSERT(buf, "Unable to allocate shared buffer");
+    if (buf) {
+        char* str = (char*)buf->data();
+
+        utf16_to_utf8(in, len, str, bytes+1);
+
+        return str;
+    }
+
+    return getEmptyString();
 }
 
 static char* allocFromUTF32(const char32_t* in, size_t len)
@@ -762,6 +775,26 @@
     return ret;
 }
 
+size_t utf8_length_from_utf16(const char16_t *src, size_t src_len)
+{
+    if (src == NULL || src_len == 0) {
+        return 0;
+    }
+    size_t ret = 0;
+    const char16_t* const end = src + src_len;
+    while (src < end) {
+        if ((*src & 0xFC00) == 0xD800 && (src + 1) < end
+                && (*++src & 0xFC00) == 0xDC00) {
+            // surrogate pairs are always 4 bytes.
+            ret += 4;
+            src++;
+        } else {
+            ret += android::utf32_to_utf8_bytes((char32_t) *src++);
+        }
+    }
+    return ret;
+}
+
 static int32_t utf32_at_internal(const char* cur, size_t *num_read)
 {
     const char first_char = *cur;
@@ -848,3 +881,33 @@
     }
     return cur - dst;
 }
+
+size_t utf16_to_utf8(const char16_t* src, size_t src_len,
+                     char* dst, size_t dst_len)
+{
+    if (src == NULL || src_len == 0 || dst == NULL || dst_len == 0) {
+        return 0;
+    }
+    const char16_t* cur_utf16 = src;
+    const char16_t* const end_utf16 = src + src_len;
+    char *cur = dst;
+    const char* const end = dst + dst_len;
+    while (cur_utf16 < end_utf16 && cur < end) {
+        char32_t utf32;
+        // surrogate pairs
+        if ((*cur_utf16 & 0xFC00) == 0xD800 && (cur_utf16 + 1) < end_utf16) {
+            utf32 = (*cur_utf16++ - 0xD800) << 10;
+            utf32 |= *cur_utf16++ - 0xDC00;
+            utf32 += 0x10000;
+        } else {
+            utf32 = (char32_t) *cur_utf16++;
+        }
+        size_t len = android::utf32_to_utf8_bytes(utf32);
+        android::utf32_to_utf8((uint8_t*)cur, utf32, len);
+        cur += len;
+    }
+    if (cur < end) {
+        *cur = '\0';
+    }
+    return cur - dst;
+}
diff --git a/libs/utils/Threads.cpp b/libs/utils/Threads.cpp
index ec3db09..6ca2603 100644
--- a/libs/utils/Threads.cpp
+++ b/libs/utils/Threads.cpp
@@ -20,6 +20,8 @@
 #include <utils/threads.h>
 #include <utils/Log.h>
 
+#include <cutils/sched_policy.h>
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <memory.h>
@@ -269,6 +271,53 @@
     gCreateThreadFn = func;
 }
 
+pid_t androidGetTid()
+{
+#ifdef HAVE_GETTID
+    return gettid();
+#else
+    return getpid();
+#endif
+}
+
+int androidSetThreadSchedulingGroup(pid_t tid, int grp)
+{
+    if (grp > ANDROID_TGROUP_MAX || grp < 0) { 
+        return BAD_VALUE;
+    }
+
+    if (set_sched_policy(tid, (grp == ANDROID_TGROUP_BG_NONINTERACT) ?
+                                      SP_BACKGROUND : SP_FOREGROUND)) {
+        return PERMISSION_DENIED;
+    }
+    
+    return NO_ERROR;
+}
+
+int androidSetThreadPriority(pid_t tid, int pri)
+{
+    int rc = 0;
+    int lasterr = 0;
+
+    if (pri >= ANDROID_PRIORITY_BACKGROUND) {
+        rc = set_sched_policy(tid, SP_BACKGROUND);
+    } else if (getpriority(PRIO_PROCESS, tid) >= ANDROID_PRIORITY_BACKGROUND) {
+        rc = set_sched_policy(tid, SP_FOREGROUND);
+    }
+
+    if (rc) {
+        lasterr = errno;
+    }
+
+    if (setpriority(PRIO_PROCESS, tid, pri) < 0) {
+        rc = INVALID_OPERATION;
+    } else {
+        errno = lasterr;
+    }
+    
+    return rc;
+}
+
 namespace android {
 
 /*
diff --git a/preloaded-classes b/preloaded-classes
index af74b33..f86555c 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -435,6 +435,7 @@
 android.net.NetworkInfo$DetailedState
 android.net.NetworkInfo$State
 android.net.NetworkUtils
+android.net.TrafficStats
 android.net.Uri
 android.net.Uri$HierarchicalUri
 android.net.Uri$OpaqueUri
@@ -515,7 +516,6 @@
 android.os.Looper
 android.os.MemoryFile
 android.os.Message
-android.os.NetStat
 android.os.Parcel
 android.os.Parcel$1
 android.os.ParcelFileDescriptor
@@ -600,16 +600,6 @@
 android.provider.ContactsContract$RawContacts
 android.provider.Downloads
 android.provider.DrmStore$Audio
-android.provider.Gmail
-android.provider.Gmail$AttachmentOrigin
-android.provider.Gmail$AttachmentRendition
-android.provider.Gmail$BecomeActiveNetworkCursor
-android.provider.Gmail$ConversationCursor
-android.provider.Gmail$CursorStatus
-android.provider.Gmail$LabelMap
-android.provider.Gmail$MailCursor
-android.provider.Gmail$MessageCursor
-android.provider.Gmail$PersonalLevel
 android.provider.Im$Account
 android.provider.Im$Avatars
 android.provider.Im$Chats
diff --git a/services/java/com/android/server/NetStatService.java b/services/java/com/android/server/NetStatService.java
index 1ea0bac..0834405 100644
--- a/services/java/com/android/server/NetStatService.java
+++ b/services/java/com/android/server/NetStatService.java
@@ -17,8 +17,8 @@
 package com.android.server;
 
 import android.content.Context;
+import android.net.TrafficStats;
 import android.os.INetStatService;
-import android.os.NetStat;
 
 public class NetStatService extends INetStatService.Stub {
 
@@ -27,34 +27,34 @@
     }
 
     public long getMobileTxPackets() {
-        return NetStat.getMobileTxPkts();
+        return TrafficStats.getMobileTxPkts();
     }
 
     public long getMobileRxPackets() {
-        return NetStat.getMobileRxPkts();
+        return TrafficStats.getMobileRxPkts();
     }
 
     public long getMobileTxBytes() {
-        return NetStat.getMobileTxBytes();
+        return TrafficStats.getMobileTxBytes();
     }
 
     public long getMobileRxBytes() {
-        return NetStat.getMobileRxBytes();
+        return TrafficStats.getMobileRxBytes();
     }
 
     public long getTotalTxPackets() {
-        return NetStat.getTotalTxPkts();
+        return TrafficStats.getTotalTxPkts();
     }
 
     public long getTotalRxPackets() {
-        return NetStat.getTotalRxPkts();
+        return TrafficStats.getTotalRxPkts();
     }
 
     public long getTotalTxBytes() {
-        return NetStat.getTotalTxBytes();
+        return TrafficStats.getTotalTxBytes();
     }
 
     public long getTotalRxBytes() {
-        return NetStat.getTotalRxBytes();
+        return TrafficStats.getTotalRxBytes();
     }
 }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 73f930d..214ecc1 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -18,6 +18,7 @@
 
 import com.android.server.am.ActivityManagerService;
 import com.android.server.status.StatusBarService;
+import com.android.internal.os.BinderInternal;
 import com.android.internal.os.SamplingProfilerIntegration;
 
 import dalvik.system.VMRuntime;
@@ -80,6 +81,8 @@
         android.os.Process.setThreadPriority(
                 android.os.Process.THREAD_PRIORITY_FOREGROUND);
 
+        BinderInternal.disableBackgroundScheduling(true);
+        
         String factoryTestStr = SystemProperties.get("ro.factorytest");
         int factoryTest = "".equals(factoryTestStr) ? SystemServer.FACTORY_TEST_OFF
                 : Integer.parseInt(factoryTestStr);
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 65070dd..6c5c52f 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -5258,7 +5258,8 @@
         
         app.thread = thread;
         app.curAdj = app.setAdj = -100;
-        app.curSchedGroup = app.setSchedGroup = Process.THREAD_GROUP_DEFAULT;
+        app.curSchedGroup = Process.THREAD_GROUP_DEFAULT;
+        app.setSchedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
         app.forcingToForeground = null;
         app.foregroundServices = false;
         app.debugging = false;
@@ -9133,7 +9134,7 @@
                 if (needSep) pw.println(" ");
                 needSep = true;
                 pw.println("  Running processes (most recent first):");
-                dumpProcessList(pw, mLRUProcesses, "    ",
+                dumpProcessList(pw, this, mLRUProcesses, "    ",
                         "App ", "PERS", true);
                 needSep = true;
             }
@@ -9164,7 +9165,7 @@
                 if (needSep) pw.println(" ");
                 needSep = true;
                 pw.println("  Persisent processes that are starting:");
-                dumpProcessList(pw, mPersistentStartingProcesses, "    ",
+                dumpProcessList(pw, this, mPersistentStartingProcesses, "    ",
                         "Starting Norm", "Restarting PERS", false);
             }
 
@@ -9172,7 +9173,7 @@
                 if (needSep) pw.println(" ");
                 needSep = true;
                 pw.println("  Processes that are starting:");
-                dumpProcessList(pw, mStartingProcesses, "    ",
+                dumpProcessList(pw, this, mStartingProcesses, "    ",
                         "Starting Norm", "Starting PERS", false);
             }
 
@@ -9180,7 +9181,7 @@
                 if (needSep) pw.println(" ");
                 needSep = true;
                 pw.println("  Processes that are being removed:");
-                dumpProcessList(pw, mRemovedProcesses, "    ",
+                dumpProcessList(pw, this, mRemovedProcesses, "    ",
                         "Removed Norm", "Removed PERS", false);
             }
             
@@ -9188,7 +9189,7 @@
                 if (needSep) pw.println(" ");
                 needSep = true;
                 pw.println("  Processes that are on old until the system is ready:");
-                dumpProcessList(pw, mProcessesOnHold, "    ",
+                dumpProcessList(pw, this, mProcessesOnHold, "    ",
                         "OnHold Norm", "OnHold PERS", false);
             }
 
@@ -9617,7 +9618,16 @@
         }
     }
 
-    private static final int dumpProcessList(PrintWriter pw, List list,
+    private static String buildOomTag(String prefix, String space, int val, int base) {
+        if (val == base) {
+            if (space == null) return prefix;
+            return prefix + "  ";
+        }
+        return prefix + "+" + Integer.toString(val-base);
+    }
+    
+    private static final int dumpProcessList(PrintWriter pw,
+            ActivityManagerService service, List list,
             String prefix, String normalLabel, String persistentLabel,
             boolean inclOomAdj) {
         int numPers = 0;
@@ -9628,9 +9638,55 @@
                       + " #" + i + ":");
                 r.dump(pw, prefix + "  ");
             } else if (inclOomAdj) {
-                pw.println(String.format("%s%s #%2d: adj=%4d/%d %s (%s)",
+                String oomAdj;
+                if (r.setAdj >= EMPTY_APP_ADJ) {
+                    oomAdj = buildOomTag("empty", null, r.setAdj,
+                            EMPTY_APP_ADJ);
+                } else if (r.setAdj >= CONTENT_PROVIDER_ADJ) {
+                    oomAdj = buildOomTag("cprov", null, r.setAdj,
+                            CONTENT_PROVIDER_ADJ);
+                } else if (r.setAdj >= HIDDEN_APP_MIN_ADJ) {
+                    oomAdj = buildOomTag("hid", "  ", r.setAdj,
+                            HIDDEN_APP_MIN_ADJ);
+                } else if (r.setAdj >= service.HOME_APP_ADJ) {
+                    oomAdj = buildOomTag("home ", null, r.setAdj,
+                            service.HOME_APP_ADJ);
+                } else if (r.setAdj >= service.SECONDARY_SERVER_ADJ) {
+                    oomAdj = buildOomTag("svc", "  ", r.setAdj,
+                            service.SECONDARY_SERVER_ADJ);
+                } else if (r.setAdj >= service.BACKUP_APP_ADJ) {
+                    oomAdj = buildOomTag("bckup", null, r.setAdj,
+                            service.BACKUP_APP_ADJ);
+                } else if (r.setAdj >= service.VISIBLE_APP_ADJ) {
+                    oomAdj = buildOomTag("vis  ", null, r.setAdj,
+                            service.VISIBLE_APP_ADJ);
+                } else if (r.setAdj >= service.FOREGROUND_APP_ADJ) {
+                    oomAdj = buildOomTag("fore ", null, r.setAdj,
+                            service.FOREGROUND_APP_ADJ);
+                } else if (r.setAdj >= CORE_SERVER_ADJ) {
+                    oomAdj = buildOomTag("core ", null, r.setAdj,
+                            CORE_SERVER_ADJ);
+                } else if (r.setAdj >= SYSTEM_ADJ) {
+                    oomAdj = buildOomTag("sys  ", null, r.setAdj,
+                            SYSTEM_ADJ);
+                } else {
+                    oomAdj = Integer.toString(r.setAdj);
+                }
+                String schedGroup;
+                switch (r.setSchedGroup) {
+                    case Process.THREAD_GROUP_BG_NONINTERACTIVE:
+                        schedGroup = "B";
+                        break;
+                    case Process.THREAD_GROUP_DEFAULT:
+                        schedGroup = "F";
+                        break;
+                    default:
+                        schedGroup = Integer.toString(r.setSchedGroup);
+                        break;
+                }
+                pw.println(String.format("%s%s #%2d: adj=%s/%s %s (%s)",
                         prefix, (r.persistent ? persistentLabel : normalLabel),
-                        i, r.setAdj, r.setSchedGroup, r.toString(), r.adjType));
+                        i, oomAdj, schedGroup, r.toString(), r.adjType));
                 if (r.adjSource != null || r.adjTarget != null) {
                     pw.println(prefix + "          " + r.adjTarget
                             + " used by " + r.adjSource);
@@ -13038,6 +13094,7 @@
 
         if (app.thread == null) {
             app.adjSeq = mAdjSeq;
+            app.curSchedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
             return (app.curAdj=EMPTY_APP_ADJ);
         }
 
@@ -13058,52 +13115,63 @@
         // Determine the importance of the process, starting with most
         // important to least, and assign an appropriate OOM adjustment.
         int adj;
+        int schedGroup;
         int N;
         if (app == TOP_APP) {
             // The last app on the list is the foreground app.
             adj = FOREGROUND_APP_ADJ;
+            schedGroup = Process.THREAD_GROUP_DEFAULT;
             app.adjType = "top-activity";
         } else if (app.instrumentationClass != null) {
             // Don't want to kill running instrumentation.
             adj = FOREGROUND_APP_ADJ;
+            schedGroup = Process.THREAD_GROUP_DEFAULT;
             app.adjType = "instrumentation";
         } else if (app.persistentActivities > 0) {
             // Special persistent activities...  shouldn't be used these days.
             adj = FOREGROUND_APP_ADJ;
+            schedGroup = Process.THREAD_GROUP_DEFAULT;
             app.adjType = "persistent";
         } else if (app.curReceiver != null ||
                 (mPendingBroadcast != null && mPendingBroadcast.curApp == app)) {
             // An app that is currently receiving a broadcast also
             // counts as being in the foreground.
             adj = FOREGROUND_APP_ADJ;
+            schedGroup = Process.THREAD_GROUP_DEFAULT;
             app.adjType = "broadcast";
         } else if (app.executingServices.size() > 0) {
             // An app that is currently executing a service callback also
             // counts as being in the foreground.
             adj = FOREGROUND_APP_ADJ;
+            schedGroup = Process.THREAD_GROUP_DEFAULT;
             app.adjType = "exec-service";
         } else if (app.foregroundServices) {
             // The user is aware of this app, so make it visible.
             adj = VISIBLE_APP_ADJ;
+            schedGroup = Process.THREAD_GROUP_DEFAULT;
             app.adjType = "foreground-service";
         } else if (app.forcingToForeground != null) {
             // The user is aware of this app, so make it visible.
             adj = VISIBLE_APP_ADJ;
+            schedGroup = Process.THREAD_GROUP_DEFAULT;
             app.adjType = "force-foreground";
             app.adjSource = app.forcingToForeground;
         } else if (app == mHomeProcess) {
             // This process is hosting what we currently consider to be the
             // home app, so we don't want to let it go into the background.
             adj = HOME_APP_ADJ;
+            schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
             app.adjType = "home";
         } else if ((N=app.activities.size()) != 0) {
             // This app is in the background with paused activities.
             adj = hiddenAdj;
+            schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
             app.adjType = "bg-activities";
             for (int j=0; j<N; j++) {
                 if (((HistoryRecord)app.activities.get(j)).visible) {
                     // This app has a visible activity!
                     adj = VISIBLE_APP_ADJ;
+                    schedGroup = Process.THREAD_GROUP_DEFAULT;
                     app.adjType = "visible";
                     break;
                 }
@@ -13111,6 +13179,7 @@
         } else {
             // A very not-needed process.
             adj = EMPTY_APP_ADJ;
+            schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
             app.adjType = "empty";
         }
 
@@ -13120,7 +13189,6 @@
         // infinite recursion.
         app.adjSeq = mAdjSeq;
         app.curRawAdj = adj;
-        app.curAdj = adj <= app.maxAdj ? adj : app.maxAdj;
 
         if (mBackupTarget != null && app == mBackupTarget.app) {
             // If possible we want to avoid killing apps while they're being backed up
@@ -13131,7 +13199,8 @@
             }
         }
 
-        if (app.services.size() != 0 && adj > FOREGROUND_APP_ADJ) {
+        if (app.services.size() != 0 && (adj > FOREGROUND_APP_ADJ
+                || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) {
             final long now = SystemClock.uptimeMillis();
             // This process is more important if the top activity is
             // bound to the service.
@@ -13149,7 +13218,8 @@
                         }
                     }
                 }
-                if (s.connections.size() > 0 && adj > FOREGROUND_APP_ADJ) {
+                if (s.connections.size() > 0 && (adj > FOREGROUND_APP_ADJ
+                        || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) {
                     Iterator<ConnectionRecord> kt
                             = s.connections.values().iterator();
                     while (kt.hasNext() && adj > FOREGROUND_APP_ADJ) {
@@ -13181,6 +13251,11 @@
                                 app.adjSource = cr.binding.client;
                                 app.adjTarget = s.serviceInfo.name;
                             }
+                            if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) {
+                                if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) {
+                                    schedGroup = Process.THREAD_GROUP_DEFAULT;
+                                }
+                            }
                         }
                         HistoryRecord a = cr.activity;
                         //if (a != null) {
@@ -13190,6 +13265,7 @@
                                 (a.state == ActivityState.RESUMED
                                  || a.state == ActivityState.PAUSING)) {
                             adj = FOREGROUND_APP_ADJ;
+                            schedGroup = Process.THREAD_GROUP_DEFAULT;
                             app.adjType = "service";
                             app.adjTypeCode = ActivityManager.RunningAppProcessInfo
                                     .REASON_SERVICE_IN_USE;
@@ -13211,9 +13287,11 @@
             }
         }
 
-        if (app.pubProviders.size() != 0 && adj > FOREGROUND_APP_ADJ) {
+        if (app.pubProviders.size() != 0 && (adj > FOREGROUND_APP_ADJ
+                || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) {
             Iterator jt = app.pubProviders.values().iterator();
-            while (jt.hasNext() && adj > FOREGROUND_APP_ADJ) {
+            while (jt.hasNext() && (adj > FOREGROUND_APP_ADJ
+                    || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) {
                 ContentProviderRecord cpr = (ContentProviderRecord)jt.next();
                 if (cpr.clients.size() != 0) {
                     Iterator<ProcessRecord> kt = cpr.clients.iterator();
@@ -13242,6 +13320,9 @@
                             app.adjSource = client;
                             app.adjTarget = cpr.info.name;
                         }
+                        if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) {
+                            schedGroup = Process.THREAD_GROUP_DEFAULT;
+                        }
                     }
                 }
                 // If the provider has external (non-framework) process
@@ -13250,6 +13331,7 @@
                 if (cpr.externals != 0) {
                     if (adj > FOREGROUND_APP_ADJ) {
                         adj = FOREGROUND_APP_ADJ;
+                        schedGroup = Process.THREAD_GROUP_DEFAULT;
                         app.adjType = "provider";
                         app.adjTarget = cpr.info.name;
                     }
@@ -13272,12 +13354,13 @@
         //      " adj=" + adj + " curAdj=" + app.curAdj + " maxAdj=" + app.maxAdj);
         if (adj > app.maxAdj) {
             adj = app.maxAdj;
+            if (app.maxAdj <= VISIBLE_APP_ADJ) {
+                schedGroup = Process.THREAD_GROUP_DEFAULT;
+            }
         }
 
         app.curAdj = adj;
-        app.curSchedGroup = adj > VISIBLE_APP_ADJ
-                ? Process.THREAD_GROUP_BG_NONINTERACTIVE
-                : Process.THREAD_GROUP_DEFAULT;
+        app.curSchedGroup = schedGroup;
         
         return adj;
     }
@@ -13422,7 +13505,7 @@
 
         int adj = computeOomAdjLocked(app, hiddenAdj, TOP_APP);
 
-        if (app.pid != 0 && app.pid != MY_PID) {
+        if ((app.pid != 0 && app.pid != MY_PID) || Process.supportsProcesses()) {
             if (app.curRawAdj != app.setRawAdj) {
                 if (app.curRawAdj > FOREGROUND_APP_ADJ
                         && app.setRawAdj <= FOREGROUND_APP_ADJ) {
diff --git a/tests/LotsOfApps/Android.mk b/tests/LotsOfApps/Android.mk
index 3019f5c..8d0cfa3 100644
--- a/tests/LotsOfApps/Android.mk
+++ b/tests/LotsOfApps/Android.mk
@@ -1,7 +1,7 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE_TAGS := optional
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h
index 1ac13f2..cf70121 100644
--- a/tools/aapt/Bundle.h
+++ b/tools/aapt/Bundle.h
@@ -37,7 +37,7 @@
           mForce(false), mGrayscaleTolerance(0), mMakePackageDirs(false),
           mUpdate(false), mExtending(false),
           mRequireLocalization(false), mPseudolocalize(false),
-          mValues(false),
+          mUTF8(false), mValues(false),
           mCompressionMethod(0), mOutputAPKFile(NULL),
           mAssetSourceDir(NULL), mProguardFile(NULL),
           mAndroidManifestFile(NULL), mPublicOutputFile(NULL),
@@ -76,6 +76,8 @@
     void setRequireLocalization(bool val) { mRequireLocalization = val; }
     bool getPseudolocalize(void) const { return mPseudolocalize; }
     void setPseudolocalize(bool val) { mPseudolocalize = val; }
+    bool getUTF8(void) const { return mUTF8; }
+    void setUTF8(bool val) { mUTF8 = val; }
     bool getValues(void) const { return mValues; }
     void setValues(bool val) { mValues = val; }
     int getCompressionMethod(void) const { return mCompressionMethod; }
@@ -161,6 +163,7 @@
     bool        mExtending;
     bool        mRequireLocalization;
     bool        mPseudolocalize;
+    bool        mUTF8;
     bool        mValues;
     int         mCompressionMethod;
     bool        mJunkPath;
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 1a536d6..ff9cc11 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -412,6 +412,7 @@
             }
             tree.restart();
             printXMLBlock(&tree);
+            tree.uninit();
             delete asset;
             asset = NULL;
         }
diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp
index 98286c0..bd03b74 100644
--- a/tools/aapt/Main.cpp
+++ b/tools/aapt/Main.cpp
@@ -118,6 +118,7 @@
         "   -P  specify where to output public resource definitions\n"
         "   -S  directory in which to find resources.  Multiple directories will be scanned"
         "       and the first match found (left to right) will take precedence."
+        "   -8  Encode string resources in UTF-8.\n"
         "   -0  specifies an additional extension for which such files will not\n"
         "       be stored compressed in the .apk.  An empty string means to not\n"
         "       compress any files at all.\n"
@@ -370,6 +371,9 @@
                     bundle.setCompressionMethod(ZipEntry::kCompressStored);
                 }
                 break;
+            case '8':
+                bundle.setUTF8(true);
+                break;
             case '-':
                 if (strcmp(cp, "-min-sdk-version") == 0) {
                     argc--;
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index fdcada4..d04a873 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -613,6 +613,12 @@
 
     NOISY(printf("Found %d included resource packages\n", (int)table.size()));
 
+    // Standard flags for compiled XML and optional UTF-8 encoding
+    int xmlFlags = XML_COMPILE_STANDARD_RESOURCE;
+    if (bundle->getUTF8()) {
+        xmlFlags |= XML_COMPILE_UTF8;
+    }
+
     // --------------------------------------------------------------
     // First, gather all resource information.
     // --------------------------------------------------------------
@@ -763,7 +769,7 @@
         ResourceDirIterator it(layouts, String8("layout"));
         while ((err=it.next()) == NO_ERROR) {
             String8 src = it.getFile()->getPrintableSource();
-            err = compileXmlFile(assets, it.getFile(), &table);
+            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
             if (err == NO_ERROR) {
                 ResXMLTree block;
                 block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
@@ -782,7 +788,7 @@
     if (anims != NULL) {
         ResourceDirIterator it(anims, String8("anim"));
         while ((err=it.next()) == NO_ERROR) {
-            err = compileXmlFile(assets, it.getFile(), &table);
+            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
             if (err != NO_ERROR) {
                 hasErrors = true;
             }
@@ -797,7 +803,7 @@
     if (xmls != NULL) {
         ResourceDirIterator it(xmls, String8("xml"));
         while ((err=it.next()) == NO_ERROR) {
-            err = compileXmlFile(assets, it.getFile(), &table);
+            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
             if (err != NO_ERROR) {
                 hasErrors = true;
             }
@@ -819,7 +825,7 @@
     if (colors != NULL) {
         ResourceDirIterator it(colors, String8("color"));
         while ((err=it.next()) == NO_ERROR) {
-          err = compileXmlFile(assets, it.getFile(), &table);
+          err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
             if (err != NO_ERROR) {
                 hasErrors = true;
             }
@@ -835,7 +841,7 @@
         ResourceDirIterator it(menus, String8("menu"));
         while ((err=it.next()) == NO_ERROR) {
             String8 src = it.getFile()->getPrintableSource();
-            err = compileXmlFile(assets, it.getFile(), &table);
+            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
             if (err != NO_ERROR) {
                 hasErrors = true;
             }
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 19b9b01..a9cbd11 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -39,6 +39,10 @@
         root->removeWhitespace(false, NULL);
     }
 
+    if ((options&XML_COMPILE_UTF8) != 0) {
+        root->setUTF8(true);
+    }
+
     bool hasErrors = false;
     
     if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) {
@@ -2505,7 +2509,7 @@
 
     // Iterate through all data, collecting all values (strings,
     // references, etc).
-    StringPool valueStrings;
+    StringPool valueStrings = StringPool(false, bundle->getUTF8());
     for (pi=0; pi<N; pi++) {
         sp<Package> p = mOrderedPackages.itemAt(pi);
         if (p->getTypes().size() == 0) {
@@ -2513,8 +2517,8 @@
             continue;
         }
 
-        StringPool typeStrings;
-        StringPool keyStrings;
+        StringPool typeStrings = StringPool(false, bundle->getUTF8());
+        StringPool keyStrings = StringPool(false, bundle->getUTF8());
 
         const size_t N = p->getOrderedTypes().size();
         for (size_t ti=0; ti<N; ti++) {
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index caa01b3..cfa75a71 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -24,6 +24,7 @@
     XML_COMPILE_COMPACT_WHITESPACE = 1<<2,
     XML_COMPILE_STRIP_WHITESPACE = 1<<3,
     XML_COMPILE_STRIP_RAW_VALUES = 1<<4,
+    XML_COMPILE_UTF8 = 1<<5,
     
     XML_COMPILE_STANDARD_RESOURCE =
             XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp
index 715170a..ec58591 100644
--- a/tools/aapt/StringPool.cpp
+++ b/tools/aapt/StringPool.cpp
@@ -30,8 +30,8 @@
     }
 }
 
-StringPool::StringPool(bool sorted)
-    : mSorted(sorted), mValues(-1), mIdents(-1)
+StringPool::StringPool(bool sorted, bool utf8)
+    : mSorted(sorted), mUTF8(utf8), mValues(-1), mIdents(-1)
 {
 }
 
@@ -165,6 +165,16 @@
     return err == NO_ERROR ? pool : NULL;
 }
 
+#define ENCODE_LENGTH(str, chrsz, strSize) \
+{ \
+    size_t maxMask = 1 << ((chrsz*8)-1); \
+    size_t maxSize = maxMask-1; \
+    if (strSize > maxSize) { \
+        *str++ = maxMask | ((strSize>>(chrsz*8))&maxSize); \
+    } \
+    *str++ = strSize; \
+}
+
 status_t StringPool::writeStringBlock(const sp<AaptFile>& pool)
 {
     // Allow appending.  Sorry this is a little wacky.
@@ -213,28 +223,53 @@
         return NO_MEMORY;
     }
 
+    const size_t charSize = mUTF8 ? sizeof(uint8_t) : sizeof(char16_t);
+
     size_t strPos = 0;
     for (i=0; i<STRINGS; i++) {
         entry& ent = mEntries.editItemAt(i);
         const size_t strSize = (ent.value.size());
-        const size_t lenSize = strSize > 0x7fff ? sizeof(uint32_t) : sizeof(uint16_t);
-        const size_t totalSize = lenSize + ((strSize+1)*sizeof(uint16_t));
+        const size_t lenSize = strSize > (size_t)(1<<((charSize*8)-1))-1 ?
+            charSize*2 : charSize;
+
+        String8 encStr;
+        if (mUTF8) {
+            encStr = String8(ent.value);
+        }
+
+        const size_t encSize = mUTF8 ? encStr.size() : 0;
+        const size_t encLenSize = mUTF8 ?
+            (encSize > (size_t)(1<<((charSize*8)-1))-1 ?
+                charSize*2 : charSize) : 0;
 
         ent.offset = strPos;
-        uint16_t* dat = (uint16_t*)pool->editData(preSize + strPos + totalSize);
+
+        const size_t totalSize = lenSize + encLenSize +
+            ((mUTF8 ? encSize : strSize)+1)*charSize;
+
+        void* dat = (void*)pool->editData(preSize + strPos + totalSize);
         if (dat == NULL) {
             fprintf(stderr, "ERROR: Out of memory for string pool\n");
             return NO_MEMORY;
         }
-        dat += (preSize+strPos)/sizeof(uint16_t);
-        if (lenSize > sizeof(uint16_t)) {
-            *dat = htods(0x8000 | ((strSize>>16)&0x7fff));
-            dat++;
-        }
-        *dat++ = htods(strSize);
-        strcpy16_htod(dat, ent.value);
+        dat = (uint8_t*)dat + preSize + strPos;
+        if (mUTF8) {
+            uint8_t* strings = (uint8_t*)dat;
 
-        strPos += lenSize + (strSize+1)*sizeof(uint16_t);
+            ENCODE_LENGTH(strings, sizeof(uint8_t), strSize)
+
+            ENCODE_LENGTH(strings, sizeof(uint8_t), encSize)
+
+            strncpy((char*)strings, encStr, encSize+1);
+        } else {
+            uint16_t* strings = (uint16_t*)dat;
+
+            ENCODE_LENGTH(strings, sizeof(uint16_t), strSize)
+
+            strcpy16_htod(strings, ent.value);
+        }
+
+        strPos += totalSize;
     }
 
     // Pad ending string position up to a uint32_t boundary.
@@ -312,6 +347,9 @@
     if (mSorted) {
         header->flags |= htodl(ResStringPool_header::SORTED_FLAG);
     }
+    if (mUTF8) {
+        header->flags |= htodl(ResStringPool_header::UTF8_FLAG);
+    }
     header->stringsStart = htodl(preSize);
     header->stylesStart = htodl(STYLES > 0 ? (preSize+strPos) : 0);
 
diff --git a/tools/aapt/StringPool.h b/tools/aapt/StringPool.h
index 9082b37..7275259 100644
--- a/tools/aapt/StringPool.h
+++ b/tools/aapt/StringPool.h
@@ -68,8 +68,11 @@
      * lookup with ResStringPool::indexOfString() (O(log n)), at the expense
      * of support for styled string entries (which requires the same string
      * be included multiple times in the pool).
+     *
+     * If 'utf8' is true, strings will be encoded with UTF-8 instead of
+     * left in Java's native UTF-16.
      */
-    explicit StringPool(bool sorted = false);
+    explicit StringPool(bool sorted = false, bool utf8 = false);
 
     /**
      * Add a new string to the pool.  If mergeDuplicates is true, thenif
@@ -123,6 +126,7 @@
 
 private:
     const bool                              mSorted;
+    const bool                              mUTF8;
     // Raw array of unique strings, in some arbitrary order.
     Vector<entry>                           mEntries;
     // Array of indices into mEntries, in the order they were
diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp
index d4d2a45c..036dde4 100644
--- a/tools/aapt/XMLNode.cpp
+++ b/tools/aapt/XMLNode.cpp
@@ -478,6 +478,7 @@
     , mFilename(filename)
     , mStartLineNumber(0)
     , mEndLineNumber(0)
+    , mUTF8(false)
 {
     if (isNamespace) {
         mNamespacePrefix = s1;
@@ -837,7 +838,7 @@
 status_t XMLNode::flatten(const sp<AaptFile>& dest,
         bool stripComments, bool stripRawValues) const
 {
-    StringPool strings;
+    StringPool strings = StringPool(false, mUTF8);
     Vector<uint32_t> resids;
     
     // First collect just the strings for attribute names that have a
diff --git a/tools/aapt/XMLNode.h b/tools/aapt/XMLNode.h
index a9bea43..dc92fa7 100644
--- a/tools/aapt/XMLNode.h
+++ b/tools/aapt/XMLNode.h
@@ -124,6 +124,8 @@
 
     void removeWhitespace(bool stripAll=true, const char** cDataTags=NULL);
 
+    void setUTF8(bool val) { mUTF8 = val; }
+
     status_t parseValues(const sp<AaptAssets>& assets, ResourceTable* table);
 
     status_t assignResourceIds(const sp<AaptAssets>& assets,
@@ -189,6 +191,9 @@
     String8 mFilename;
     int32_t mStartLineNumber;
     int32_t mEndLineNumber;
+
+    // Encode compiled XML with UTF-8 StringPools?
+    bool mUTF8;
 };
 
 #endif
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index 66552e4..0b9c69c 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -1162,7 +1162,7 @@
                     }
                 }
                 break;
-                
+
             case EVENT_DRIVER_STATE_CHANGED:
                 // Wi-Fi driver state changed:
                 // 0 STARTED