Merge "Work on more low memory reporting to apps."
diff --git a/api/current.txt b/api/current.txt
index 261d9ab..1c4190e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6909,7 +6909,7 @@
     method public boolean onMove(int, int);
   }
 
-  public abstract interface Cursor {
+  public abstract interface Cursor implements java.io.Closeable {
     method public abstract void close();
     method public abstract void copyStringToBuffer(int, android.database.CharArrayBuffer);
     method public abstract deprecated void deactivate();
@@ -6982,7 +6982,6 @@
     ctor public deprecated CursorWindow(boolean);
     method public boolean allocRow();
     method public void clear();
-    method public void close();
     method public void copyStringToBuffer(int, int, android.database.CharArrayBuffer);
     method public int describeContents();
     method public void freeLastRow();
@@ -7241,13 +7240,14 @@
     ctor public SQLiteCantOpenDatabaseException(java.lang.String);
   }
 
-  public abstract class SQLiteClosable {
+  public abstract class SQLiteClosable implements java.io.Closeable {
     ctor public SQLiteClosable();
     method public void acquireReference();
+    method public void close();
     method protected abstract void onAllReferencesReleased();
-    method protected void onAllReferencesReleasedFromContainer();
+    method protected deprecated void onAllReferencesReleasedFromContainer();
     method public void releaseReference();
-    method public void releaseReferenceFromContainer();
+    method public deprecated void releaseReferenceFromContainer();
   }
 
   public class SQLiteConstraintException extends android.database.sqlite.SQLiteException {
@@ -7277,7 +7277,6 @@
     method public void beginTransactionNonExclusive();
     method public void beginTransactionWithListener(android.database.sqlite.SQLiteTransactionListener);
     method public void beginTransactionWithListenerNonExclusive(android.database.sqlite.SQLiteTransactionListener);
-    method public void close();
     method public android.database.sqlite.SQLiteStatement compileStatement(java.lang.String) throws android.database.SQLException;
     method public static android.database.sqlite.SQLiteDatabase create(android.database.sqlite.SQLiteDatabase.CursorFactory);
     method public int delete(java.lang.String, java.lang.String, java.lang.String[]);
@@ -7420,7 +7419,6 @@
     method public void bindNull(int);
     method public void bindString(int, java.lang.String);
     method public void clearBindings();
-    method public void close();
     method public final deprecated int getUniqueId();
     method protected void onAllReferencesReleased();
   }
diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java
index 59ec89d..907833d 100644
--- a/core/java/android/database/Cursor.java
+++ b/core/java/android/database/Cursor.java
@@ -20,6 +20,8 @@
 import android.net.Uri;
 import android.os.Bundle;
 
+import java.io.Closeable;
+
 /**
  * This interface provides random read-write access to the result set returned
  * by a database query.
@@ -27,7 +29,7 @@
  * Cursor implementations are not required to be synchronized so code using a Cursor from multiple
  * threads should perform its own synchronization when using the Cursor.
  */
-public interface Cursor {
+public interface Cursor extends Closeable {
     /*
      * Values returned by {@link #getType(int)}.
      * These should be consistent with the corresponding types defined in CursorWindow.h
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index 85f570c1..f1f3017 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -169,14 +169,6 @@
     }
 
     /**
-     * Closes the cursor window and frees its underlying resources when all other
-     * remaining references have been released.
-     */
-    public void close() {
-        releaseReference();
-    }
-
-    /**
      * Clears out the existing contents of the window, making it safe to reuse
      * for new data.
      * <p>
@@ -703,8 +695,13 @@
     }
 
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(mStartPos);
-        nativeWriteToParcel(mWindowPtr, dest);
+        acquireReference();
+        try {
+            dest.writeInt(mStartPos);
+            nativeWriteToParcel(mWindowPtr, dest);
+        } finally {
+            releaseReference();
+        }
 
         if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) {
             releaseReference();
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index 0022118..99d260e 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -269,63 +269,56 @@
         if (position < 0 || position >= cursor.getCount()) {
             return;
         }
-        window.acquireReference();
-        try {
-            final int oldPos = cursor.getPosition();
-            final int numColumns = cursor.getColumnCount();
-            window.clear();
-            window.setStartPosition(position);
-            window.setNumColumns(numColumns);
-            if (cursor.moveToPosition(position)) {
-                do {
-                    if (!window.allocRow()) {
-                        break;
-                    }
-                    for (int i = 0; i < numColumns; i++) {
-                        final int type = cursor.getType(i);
-                        final boolean success;
-                        switch (type) {
-                            case Cursor.FIELD_TYPE_NULL:
-                                success = window.putNull(position, i);
-                                break;
+        final int oldPos = cursor.getPosition();
+        final int numColumns = cursor.getColumnCount();
+        window.clear();
+        window.setStartPosition(position);
+        window.setNumColumns(numColumns);
+        if (cursor.moveToPosition(position)) {
+            do {
+                if (!window.allocRow()) {
+                    break;
+                }
+                for (int i = 0; i < numColumns; i++) {
+                    final int type = cursor.getType(i);
+                    final boolean success;
+                    switch (type) {
+                        case Cursor.FIELD_TYPE_NULL:
+                            success = window.putNull(position, i);
+                            break;
 
-                            case Cursor.FIELD_TYPE_INTEGER:
-                                success = window.putLong(cursor.getLong(i), position, i);
-                                break;
+                        case Cursor.FIELD_TYPE_INTEGER:
+                            success = window.putLong(cursor.getLong(i), position, i);
+                            break;
 
-                            case Cursor.FIELD_TYPE_FLOAT:
-                                success = window.putDouble(cursor.getDouble(i), position, i);
-                                break;
+                        case Cursor.FIELD_TYPE_FLOAT:
+                            success = window.putDouble(cursor.getDouble(i), position, i);
+                            break;
 
-                            case Cursor.FIELD_TYPE_BLOB: {
-                                final byte[] value = cursor.getBlob(i);
-                                success = value != null ? window.putBlob(value, position, i)
-                                        : window.putNull(position, i);
-                                break;
-                            }
-
-                            default: // assume value is convertible to String
-                            case Cursor.FIELD_TYPE_STRING: {
-                                final String value = cursor.getString(i);
-                                success = value != null ? window.putString(value, position, i)
-                                        : window.putNull(position, i);
-                                break;
-                            }
+                        case Cursor.FIELD_TYPE_BLOB: {
+                            final byte[] value = cursor.getBlob(i);
+                            success = value != null ? window.putBlob(value, position, i)
+                                    : window.putNull(position, i);
+                            break;
                         }
-                        if (!success) {
-                            window.freeLastRow();
+
+                        default: // assume value is convertible to String
+                        case Cursor.FIELD_TYPE_STRING: {
+                            final String value = cursor.getString(i);
+                            success = value != null ? window.putString(value, position, i)
+                                    : window.putNull(position, i);
                             break;
                         }
                     }
-                    position += 1;
-                } while (cursor.moveToNext());
-            }
-            cursor.moveToPosition(oldPos);
-        } catch (IllegalStateException e){
-            // simply ignore it
-        } finally {
-            window.releaseReference();
+                    if (!success) {
+                        window.freeLastRow();
+                        break;
+                    }
+                }
+                position += 1;
+            } while (cursor.moveToNext());
         }
+        cursor.moveToPosition(oldPos);
     }
 
     /**
diff --git a/core/java/android/database/sqlite/SQLiteClosable.java b/core/java/android/database/sqlite/SQLiteClosable.java
index 7e91a7b..adfbc6e 100644
--- a/core/java/android/database/sqlite/SQLiteClosable.java
+++ b/core/java/android/database/sqlite/SQLiteClosable.java
@@ -16,15 +16,39 @@
 
 package android.database.sqlite;
 
+import java.io.Closeable;
+
 /**
  * An object created from a SQLiteDatabase that can be closed.
+ *
+ * This class implements a primitive reference counting scheme for database objects.
  */
-public abstract class SQLiteClosable {
+public abstract class SQLiteClosable implements Closeable {
     private int mReferenceCount = 1;
 
+    /**
+     * Called when the last reference to the object was released by
+     * a call to {@link #releaseReference()} or {@link #close()}.
+     */
     protected abstract void onAllReferencesReleased();
-    protected void onAllReferencesReleasedFromContainer() {}
 
+    /**
+     * Called when the last reference to the object was released by
+     * a call to {@link #releaseReferenceFromContainer()}.
+     *
+     * @deprecated Do not use.
+     */
+    @Deprecated
+    protected void onAllReferencesReleasedFromContainer() {
+        onAllReferencesReleased();
+    }
+
+    /**
+     * Acquires a reference to the object.
+     *
+     * @throws IllegalStateException if the last reference to the object has already
+     * been released.
+     */
     public void acquireReference() {
         synchronized(this) {
             if (mReferenceCount <= 0) {
@@ -35,6 +59,12 @@
         }
     }
 
+    /**
+     * Releases a reference to the object, closing the object if the last reference
+     * was released.
+     *
+     * @see #onAllReferencesReleased()
+     */
     public void releaseReference() {
         boolean refCountIsZero = false;
         synchronized(this) {
@@ -45,6 +75,14 @@
         }
     }
 
+    /**
+     * Releases a reference to the object that was owned by the container of the object,
+     * closing the object if the last reference was released.
+     *
+     * @see #onAllReferencesReleasedFromContainer()
+     * @deprecated Do not use.
+     */
+    @Deprecated
     public void releaseReferenceFromContainer() {
         boolean refCountIsZero = false;
         synchronized(this) {
@@ -54,4 +92,17 @@
             onAllReferencesReleasedFromContainer();
         }
     }
+
+    /**
+     * Releases a reference to the object, closing the object if the last reference
+     * was released.
+     *
+     * Calling this method is equivalent to calling {@link #releaseReference}.
+     *
+     * @see #releaseReference()
+     * @see #onAllReferencesReleased()
+     */
+    public void close() {
+        releaseReference();
+    }
 }
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index d16f29f..0db3e4f 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -704,44 +704,49 @@
             throw new IllegalArgumentException("window must not be null.");
         }
 
-        int actualPos = -1;
-        int countedRows = -1;
-        int filledRows = -1;
-        final int cookie = mRecentOperations.beginOperation("executeForCursorWindow",
-                sql, bindArgs);
+        window.acquireReference();
         try {
-            final PreparedStatement statement = acquirePreparedStatement(sql);
+            int actualPos = -1;
+            int countedRows = -1;
+            int filledRows = -1;
+            final int cookie = mRecentOperations.beginOperation("executeForCursorWindow",
+                    sql, bindArgs);
             try {
-                throwIfStatementForbidden(statement);
-                bindArguments(statement, bindArgs);
-                applyBlockGuardPolicy(statement);
-                attachCancellationSignal(cancellationSignal);
+                final PreparedStatement statement = acquirePreparedStatement(sql);
                 try {
-                    final long result = nativeExecuteForCursorWindow(
-                            mConnectionPtr, statement.mStatementPtr, window.mWindowPtr,
-                            startPos, requiredPos, countAllRows);
-                    actualPos = (int)(result >> 32);
-                    countedRows = (int)result;
-                    filledRows = window.getNumRows();
-                    window.setStartPosition(actualPos);
-                    return countedRows;
+                    throwIfStatementForbidden(statement);
+                    bindArguments(statement, bindArgs);
+                    applyBlockGuardPolicy(statement);
+                    attachCancellationSignal(cancellationSignal);
+                    try {
+                        final long result = nativeExecuteForCursorWindow(
+                                mConnectionPtr, statement.mStatementPtr, window.mWindowPtr,
+                                startPos, requiredPos, countAllRows);
+                        actualPos = (int)(result >> 32);
+                        countedRows = (int)result;
+                        filledRows = window.getNumRows();
+                        window.setStartPosition(actualPos);
+                        return countedRows;
+                    } finally {
+                        detachCancellationSignal(cancellationSignal);
+                    }
                 } finally {
-                    detachCancellationSignal(cancellationSignal);
+                    releasePreparedStatement(statement);
                 }
+            } catch (RuntimeException ex) {
+                mRecentOperations.failOperation(cookie, ex);
+                throw ex;
             } finally {
-                releasePreparedStatement(statement);
+                if (mRecentOperations.endOperationDeferLog(cookie)) {
+                    mRecentOperations.logOperation(cookie, "window='" + window
+                            + "', startPos=" + startPos
+                            + ", actualPos=" + actualPos
+                            + ", filledRows=" + filledRows
+                            + ", countedRows=" + countedRows);
+                }
             }
-        } catch (RuntimeException ex) {
-            mRecentOperations.failOperation(cookie, ex);
-            throw ex;
         } finally {
-            if (mRecentOperations.endOperationDeferLog(cookie)) {
-                mRecentOperations.logOperation(cookie, "window='" + window
-                        + "', startPos=" + startPos
-                        + ", actualPos=" + actualPos
-                        + ", filledRows=" + filledRows
-                        + ", countedRows=" + countedRows);
-            }
+            window.releaseReference();
         }
     }
 
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 604247e..d41b484 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -492,9 +492,16 @@
 
     private void beginTransaction(SQLiteTransactionListener transactionListener,
             boolean exclusive) {
-        getThreadSession().beginTransaction(exclusive ? SQLiteSession.TRANSACTION_MODE_EXCLUSIVE :
-                SQLiteSession.TRANSACTION_MODE_IMMEDIATE, transactionListener,
-                getThreadDefaultConnectionFlags(false /*readOnly*/), null);
+        acquireReference();
+        try {
+            getThreadSession().beginTransaction(
+                    exclusive ? SQLiteSession.TRANSACTION_MODE_EXCLUSIVE :
+                            SQLiteSession.TRANSACTION_MODE_IMMEDIATE,
+                    transactionListener,
+                    getThreadDefaultConnectionFlags(false /*readOnly*/), null);
+        } finally {
+            releaseReference();
+        }
     }
 
     /**
@@ -502,7 +509,12 @@
      * are committed and rolled back.
      */
     public void endTransaction() {
-        getThreadSession().endTransaction(null);
+        acquireReference();
+        try {
+            getThreadSession().endTransaction(null);
+        } finally {
+            releaseReference();
+        }
     }
 
     /**
@@ -515,7 +527,12 @@
      * transaction is already marked as successful.
      */
     public void setTransactionSuccessful() {
-        getThreadSession().setTransactionSuccessful();
+        acquireReference();
+        try {
+            getThreadSession().setTransactionSuccessful();
+        } finally {
+            releaseReference();
+        }
     }
 
     /**
@@ -524,7 +541,12 @@
      * @return True if the current thread is in a transaction.
      */
     public boolean inTransaction() {
-        return getThreadSession().hasTransaction();
+        acquireReference();
+        try {
+            return getThreadSession().hasTransaction();
+        } finally {
+            releaseReference();
+        }
     }
 
     /**
@@ -540,7 +562,12 @@
      * @return True if the current thread is holding an active connection to the database.
      */
     public boolean isDbLockedByCurrentThread() {
-        return getThreadSession().hasConnection();
+        acquireReference();
+        try {
+            return getThreadSession().hasConnection();
+        } finally {
+            releaseReference();
+        }
     }
 
     /**
@@ -599,7 +626,12 @@
     }
 
     private boolean yieldIfContendedHelper(boolean throwIfUnsafe, long sleepAfterYieldDelay) {
-        return getThreadSession().yieldTransaction(sleepAfterYieldDelay, throwIfUnsafe, null);
+        acquireReference();
+        try {
+            return getThreadSession().yieldTransaction(sleepAfterYieldDelay, throwIfUnsafe, null);
+        } finally {
+            releaseReference();
+        }
     }
 
     /**
@@ -788,13 +820,6 @@
     }
 
     /**
-     * Close the database.
-     */
-    public void close() {
-        dispose(false);
-    }
-
-    /**
      * Registers a CustomFunction callback as a function that can be called from
      * SQLite database triggers.
      *
@@ -948,8 +973,12 @@
      * {@link SQLiteStatement}s are not synchronized, see the documentation for more details.
      */
     public SQLiteStatement compileStatement(String sql) throws SQLException {
-        throwIfNotOpen(); // fail fast
-        return new SQLiteStatement(this, sql, null);
+        acquireReference();
+        try {
+            return new SQLiteStatement(this, sql, null);
+        } finally {
+            releaseReference();
+        }
     }
 
     /**
@@ -1110,12 +1139,16 @@
             boolean distinct, String table, String[] columns,
             String selection, String[] selectionArgs, String groupBy,
             String having, String orderBy, String limit, CancellationSignal cancellationSignal) {
-        throwIfNotOpen(); // fail fast
-        String sql = SQLiteQueryBuilder.buildQueryString(
-                distinct, table, columns, selection, groupBy, having, orderBy, limit);
+        acquireReference();
+        try {
+            String sql = SQLiteQueryBuilder.buildQueryString(
+                    distinct, table, columns, selection, groupBy, having, orderBy, limit);
 
-        return rawQueryWithFactory(cursorFactory, sql, selectionArgs,
-                findEditTable(table), cancellationSignal);
+            return rawQueryWithFactory(cursorFactory, sql, selectionArgs,
+                    findEditTable(table), cancellationSignal);
+        } finally {
+            releaseReference();
+        }
     }
 
     /**
@@ -1260,12 +1293,15 @@
     public Cursor rawQueryWithFactory(
             CursorFactory cursorFactory, String sql, String[] selectionArgs,
             String editTable, CancellationSignal cancellationSignal) {
-        throwIfNotOpen(); // fail fast
-
-        SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable,
-                cancellationSignal);
-        return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory,
-                selectionArgs);
+        acquireReference();
+        try {
+            SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable,
+                    cancellationSignal);
+            return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory,
+                    selectionArgs);
+        } finally {
+            releaseReference();
+        }
     }
 
     /**
@@ -1384,38 +1420,44 @@
      */
     public long insertWithOnConflict(String table, String nullColumnHack,
             ContentValues initialValues, int conflictAlgorithm) {
-        StringBuilder sql = new StringBuilder();
-        sql.append("INSERT");
-        sql.append(CONFLICT_VALUES[conflictAlgorithm]);
-        sql.append(" INTO ");
-        sql.append(table);
-        sql.append('(');
+        acquireReference();
+        try {
+            StringBuilder sql = new StringBuilder();
+            sql.append("INSERT");
+            sql.append(CONFLICT_VALUES[conflictAlgorithm]);
+            sql.append(" INTO ");
+            sql.append(table);
+            sql.append('(');
 
-        Object[] bindArgs = null;
-        int size = (initialValues != null && initialValues.size() > 0) ? initialValues.size() : 0;
-        if (size > 0) {
-            bindArgs = new Object[size];
-            int i = 0;
-            for (String colName : initialValues.keySet()) {
-                sql.append((i > 0) ? "," : "");
-                sql.append(colName);
-                bindArgs[i++] = initialValues.get(colName);
+            Object[] bindArgs = null;
+            int size = (initialValues != null && initialValues.size() > 0)
+                    ? initialValues.size() : 0;
+            if (size > 0) {
+                bindArgs = new Object[size];
+                int i = 0;
+                for (String colName : initialValues.keySet()) {
+                    sql.append((i > 0) ? "," : "");
+                    sql.append(colName);
+                    bindArgs[i++] = initialValues.get(colName);
+                }
+                sql.append(')');
+                sql.append(" VALUES (");
+                for (i = 0; i < size; i++) {
+                    sql.append((i > 0) ? ",?" : "?");
+                }
+            } else {
+                sql.append(nullColumnHack + ") VALUES (NULL");
             }
             sql.append(')');
-            sql.append(" VALUES (");
-            for (i = 0; i < size; i++) {
-                sql.append((i > 0) ? ",?" : "?");
-            }
-        } else {
-            sql.append(nullColumnHack + ") VALUES (NULL");
-        }
-        sql.append(')');
 
-        SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
-        try {
-            return statement.executeInsert();
+            SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
+            try {
+                return statement.executeInsert();
+            } finally {
+                statement.close();
+            }
         } finally {
-            statement.close();
+            releaseReference();
         }
     }
 
@@ -1430,12 +1472,17 @@
      *         whereClause.
      */
     public int delete(String table, String whereClause, String[] whereArgs) {
-        SQLiteStatement statement =  new SQLiteStatement(this, "DELETE FROM " + table +
-                (!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs);
+        acquireReference();
         try {
-            return statement.executeUpdateDelete();
+            SQLiteStatement statement =  new SQLiteStatement(this, "DELETE FROM " + table +
+                    (!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs);
+            try {
+                return statement.executeUpdateDelete();
+            } finally {
+                statement.close();
+            }
         } finally {
-            statement.close();
+            releaseReference();
         }
     }
 
@@ -1470,38 +1517,43 @@
             throw new IllegalArgumentException("Empty values");
         }
 
-        StringBuilder sql = new StringBuilder(120);
-        sql.append("UPDATE ");
-        sql.append(CONFLICT_VALUES[conflictAlgorithm]);
-        sql.append(table);
-        sql.append(" SET ");
-
-        // move all bind args to one array
-        int setValuesSize = values.size();
-        int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length);
-        Object[] bindArgs = new Object[bindArgsSize];
-        int i = 0;
-        for (String colName : values.keySet()) {
-            sql.append((i > 0) ? "," : "");
-            sql.append(colName);
-            bindArgs[i++] = values.get(colName);
-            sql.append("=?");
-        }
-        if (whereArgs != null) {
-            for (i = setValuesSize; i < bindArgsSize; i++) {
-                bindArgs[i] = whereArgs[i - setValuesSize];
-            }
-        }
-        if (!TextUtils.isEmpty(whereClause)) {
-            sql.append(" WHERE ");
-            sql.append(whereClause);
-        }
-
-        SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
+        acquireReference();
         try {
-            return statement.executeUpdateDelete();
+            StringBuilder sql = new StringBuilder(120);
+            sql.append("UPDATE ");
+            sql.append(CONFLICT_VALUES[conflictAlgorithm]);
+            sql.append(table);
+            sql.append(" SET ");
+
+            // move all bind args to one array
+            int setValuesSize = values.size();
+            int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length);
+            Object[] bindArgs = new Object[bindArgsSize];
+            int i = 0;
+            for (String colName : values.keySet()) {
+                sql.append((i > 0) ? "," : "");
+                sql.append(colName);
+                bindArgs[i++] = values.get(colName);
+                sql.append("=?");
+            }
+            if (whereArgs != null) {
+                for (i = setValuesSize; i < bindArgsSize; i++) {
+                    bindArgs[i] = whereArgs[i - setValuesSize];
+                }
+            }
+            if (!TextUtils.isEmpty(whereClause)) {
+                sql.append(" WHERE ");
+                sql.append(whereClause);
+            }
+
+            SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
+            try {
+                return statement.executeUpdateDelete();
+            } finally {
+                statement.close();
+            }
         } finally {
-            statement.close();
+            releaseReference();
         }
     }
 
@@ -1579,24 +1631,29 @@
     }
 
     private int executeSql(String sql, Object[] bindArgs) throws SQLException {
-        if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) {
-            boolean disableWal = false;
-            synchronized (mLock) {
-                if (!mHasAttachedDbsLocked) {
-                    mHasAttachedDbsLocked = true;
-                    disableWal = true;
+        acquireReference();
+        try {
+            if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) {
+                boolean disableWal = false;
+                synchronized (mLock) {
+                    if (!mHasAttachedDbsLocked) {
+                        mHasAttachedDbsLocked = true;
+                        disableWal = true;
+                    }
+                }
+                if (disableWal) {
+                    disableWriteAheadLogging();
                 }
             }
-            if (disableWal) {
-                disableWriteAheadLogging();
-            }
-        }
 
-        SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs);
-        try {
-            return statement.executeUpdateDelete();
+            SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs);
+            try {
+                return statement.executeUpdateDelete();
+            } finally {
+                statement.close();
+            }
         } finally {
-            statement.close();
+            releaseReference();
         }
     }
 
@@ -1881,26 +1938,32 @@
                 attachedDbs.add(new Pair<String, String>("main", mConfigurationLocked.path));
                 return attachedDbs;
             }
+
+            acquireReference();
         }
 
-        // has attached databases. query sqlite to get the list of attached databases.
-        Cursor c = null;
         try {
-            c = rawQuery("pragma database_list;", null);
-            while (c.moveToNext()) {
-                // sqlite returns a row for each database in the returned list of databases.
-                //   in each row,
-                //       1st column is the database name such as main, or the database
-                //                              name specified on the "ATTACH" command
-                //       2nd column is the database file path.
-                attachedDbs.add(new Pair<String, String>(c.getString(1), c.getString(2)));
+            // has attached databases. query sqlite to get the list of attached databases.
+            Cursor c = null;
+            try {
+                c = rawQuery("pragma database_list;", null);
+                while (c.moveToNext()) {
+                    // sqlite returns a row for each database in the returned list of databases.
+                    //   in each row,
+                    //       1st column is the database name such as main, or the database
+                    //                              name specified on the "ATTACH" command
+                    //       2nd column is the database file path.
+                    attachedDbs.add(new Pair<String, String>(c.getString(1), c.getString(2)));
+                }
+            } finally {
+                if (c != null) {
+                    c.close();
+                }
             }
+            return attachedDbs;
         } finally {
-            if (c != null) {
-                c.close();
-            }
+            releaseReference();
         }
-        return attachedDbs;
     }
 
     /**
@@ -1917,35 +1980,38 @@
      * false otherwise.
      */
     public boolean isDatabaseIntegrityOk() {
-        throwIfNotOpen(); // fail fast
-
-        List<Pair<String, String>> attachedDbs = null;
+        acquireReference();
         try {
-            attachedDbs = getAttachedDbs();
-            if (attachedDbs == null) {
-                throw new IllegalStateException("databaselist for: " + getPath() + " couldn't " +
-                        "be retrieved. probably because the database is closed");
-            }
-        } catch (SQLiteException e) {
-            // can't get attachedDb list. do integrity check on the main database
-            attachedDbs = new ArrayList<Pair<String, String>>();
-            attachedDbs.add(new Pair<String, String>("main", getPath()));
-        }
-
-        for (int i = 0; i < attachedDbs.size(); i++) {
-            Pair<String, String> p = attachedDbs.get(i);
-            SQLiteStatement prog = null;
+            List<Pair<String, String>> attachedDbs = null;
             try {
-                prog = compileStatement("PRAGMA " + p.first + ".integrity_check(1);");
-                String rslt = prog.simpleQueryForString();
-                if (!rslt.equalsIgnoreCase("ok")) {
-                    // integrity_checker failed on main or attached databases
-                    Log.e(TAG, "PRAGMA integrity_check on " + p.second + " returned: " + rslt);
-                    return false;
+                attachedDbs = getAttachedDbs();
+                if (attachedDbs == null) {
+                    throw new IllegalStateException("databaselist for: " + getPath() + " couldn't " +
+                            "be retrieved. probably because the database is closed");
                 }
-            } finally {
-                if (prog != null) prog.close();
+            } catch (SQLiteException e) {
+                // can't get attachedDb list. do integrity check on the main database
+                attachedDbs = new ArrayList<Pair<String, String>>();
+                attachedDbs.add(new Pair<String, String>("main", getPath()));
             }
+
+            for (int i = 0; i < attachedDbs.size(); i++) {
+                Pair<String, String> p = attachedDbs.get(i);
+                SQLiteStatement prog = null;
+                try {
+                    prog = compileStatement("PRAGMA " + p.first + ".integrity_check(1);");
+                    String rslt = prog.simpleQueryForString();
+                    if (!rslt.equalsIgnoreCase("ok")) {
+                        // integrity_checker failed on main or attached databases
+                        Log.e(TAG, "PRAGMA integrity_check on " + p.second + " returned: " + rslt);
+                        return false;
+                    }
+                } finally {
+                    if (prog != null) prog.close();
+                }
+            }
+        } finally {
+            releaseReference();
         }
         return true;
     }
@@ -1955,12 +2021,6 @@
         return "SQLiteDatabase: " + getPath();
     }
 
-    private void throwIfNotOpen() {
-        synchronized (mConnectionPoolLocked) {
-            throwIfNotOpenLocked();
-        }
-    }
-
     private void throwIfNotOpenLocked() {
         if (mConnectionPoolLocked == null) {
             throw new IllegalStateException("The database '" + mConfigurationLocked.label
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index 9f0edfb..94a23cb 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -190,13 +190,6 @@
     }
 
     /**
-     * Release this program's resources, making it invalid.
-     */
-    public void close() {
-        releaseReference();
-    }
-
-    /**
      * Given an array of String bindArgs, this method binds all of them in one single call.
      *
      * @param bindArgs the String array of bind args, none of which must be null.
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index a52e2ba..715d1f2 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -222,23 +222,27 @@
         return wid;
     }
 
-    int breakText(int start, int limit, boolean forwards, float width) {
+    int breakText(int limit, boolean forwards, float width) {
         float[] w = mWidths;
         if (forwards) {
-            for (int i = start; i < limit; ++i) {
-                if ((width -= w[i]) < 0) {
-                    return i - start;
-                }
+            int i = 0;
+            while (i < limit) {
+                width -= w[i];
+                if (width < 0.0f) break;
+                i++;
             }
+            while (i > 0 && mChars[i - 1] == ' ') i--;
+            return i;
         } else {
-            for (int i = limit; --i >= start;) {
-                if ((width -= w[i]) < 0) {
-                    return limit - i -1;
-                }
+            int i = limit - 1;
+            while (i >= 0) {
+                width -= w[i];
+                if (width < 0.0f) break;
+                i--;
             }
+            while (i < limit - 1 && mChars[i + 1] == ' ') i++;
+            return limit - i - 1;
         }
-
-        return limit - start;
     }
 
     float measure(int start, int limit) {
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index afae5bb2..270624c 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -1091,13 +1091,13 @@
             if (avail < 0) {
                 // it all goes
             } else if (where == TruncateAt.START) {
-                right = len - mt.breakText(0, len, false, avail);
+                right = len - mt.breakText(len, false, avail);
             } else if (where == TruncateAt.END || where == TruncateAt.END_SMALL) {
-                left = mt.breakText(0, len, true, avail);
+                left = mt.breakText(len, true, avail);
             } else {
-                right = len - mt.breakText(0, len, false, avail / 2);
+                right = len - mt.breakText(len, false, avail / 2);
                 avail -= mt.measure(right, len);
-                left = mt.breakText(0, right, true, avail);
+                left = mt.breakText(right, true, avail);
             }
 
             if (callback != null) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ecfca74..c982d7a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5247,6 +5247,7 @@
                 if (mNextFocusForwardId == View.NO_ID) return null;
                 return findViewInsideOutShouldExist(root, mNextFocusForwardId);
             case FOCUS_BACKWARD: {
+                if (mID == View.NO_ID) return null;
                 final int id = mID;
                 return root.findViewByPredicateInsideOut(this, new Predicate<View>() {
                     @Override
diff --git a/core/java/android/webkit/ViewStateSerializer.java b/core/java/android/webkit/ViewStateSerializer.java
index a22fc26..e672b62 100644
--- a/core/java/android/webkit/ViewStateSerializer.java
+++ b/core/java/android/webkit/ViewStateSerializer.java
@@ -52,12 +52,12 @@
             throws IOException {
         DataInputStream dis = new DataInputStream(stream);
         int version = dis.readInt();
-        if (version != VERSION) {
+        if (version > VERSION) {
             throw new IOException("Unexpected version: " + version);
         }
         int contentWidth = dis.readInt();
         int contentHeight = dis.readInt();
-        int baseLayer = nativeDeserializeViewState(dis,
+        int baseLayer = nativeDeserializeViewState(version, dis,
                 new byte[WORKING_STREAM_STORAGE]);
 
         final WebViewCore.DrawData draw = new WebViewCore.DrawData();
@@ -76,7 +76,7 @@
             OutputStream stream, byte[] storage);
 
     // Returns a pointer to the BaseLayer
-    private static native int nativeDeserializeViewState(
+    private static native int nativeDeserializeViewState(int version,
             InputStream stream, byte[] storage);
 
     private ViewStateSerializer() {}
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index c9a3ff1..04a698f 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -1121,7 +1121,7 @@
     static final int WEBCORE_INITIALIZED_MSG_ID         = 107;
     static final int UPDATE_TEXTFIELD_TEXT_MSG_ID       = 108;
     static final int UPDATE_ZOOM_RANGE                  = 109;
-    static final int UNHANDLED_NAV_KEY                  = 110;
+    static final int TAKE_FOCUS                         = 110;
     static final int CLEAR_TEXT_ENTRY                   = 111;
     static final int UPDATE_TEXT_SELECTION_MSG_ID       = 112;
     static final int SHOW_RECT_MSG_ID                   = 113;
@@ -5309,8 +5309,6 @@
         if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
                 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
             switchOutDrawHistory();
-            letPageHandleNavKey(keyCode, event.getEventTime(), true, event.getMetaState());
-            return true;
         }
 
         if (isEnterActionKey(keyCode)) {
@@ -5342,7 +5340,7 @@
         }
 
         // pass the key to DOM
-        mWebViewCore.sendMessage(EventHub.KEY_DOWN, event);
+        sendKeyEvent(event);
         // return true as DOM handles the key
         return true;
     }
@@ -5405,12 +5403,6 @@
             }
         }
 
-        if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
-                && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
-            letPageHandleNavKey(keyCode, event.getEventTime(), false, event.getMetaState());
-            return true;
-        }
-
         if (isEnterActionKey(keyCode)) {
             // remove the long press message first
             mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
@@ -5424,7 +5416,7 @@
         }
 
         // pass the key to DOM
-        mWebViewCore.sendMessage(EventHub.KEY_UP, event);
+        sendKeyEvent(event);
         // return true as DOM handles the key
         return true;
     }
@@ -6956,9 +6948,7 @@
             case KeyEvent.KEYCODE_DPAD_LEFT:
                 return SoundEffectConstants.NAVIGATION_LEFT;
         }
-        throw new IllegalArgumentException("keyCode must be one of " +
-                "{KEYCODE_DPAD_UP, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_DOWN, " +
-                "KEYCODE_DPAD_LEFT}.");
+        return 0;
     }
 
     private void doTrackball(long time, int metaState) {
@@ -8232,8 +8222,12 @@
                 case FORM_DID_BLUR:
                     // TODO: Figure out if this is needed for something (b/6111763)
                     break;
-                case UNHANDLED_NAV_KEY:
-                    // TODO: Support this (b/6109044)
+                case TAKE_FOCUS:
+                    int direction = msg.arg1;
+                    View focusSearch = mWebView.focusSearch(direction);
+                    if (focusSearch != null && focusSearch != mWebView) {
+                        focusSearch.requestFocus();
+                    }
                     break;
                 case CLEAR_TEXT_ENTRY:
                     hideSoftKeyboard();
@@ -8529,7 +8523,7 @@
         if (mFocusedNode.mHasFocus && !mWebView.isInTouchMode()) {
             return !mFocusedNode.mEditable;
         }
-        if (mInitialHitTestResult.getType() == HitTestResult.UNKNOWN_TYPE) {
+        if (mFocusedNode.mHasFocus && mFocusedNode.mEditable) {
             return false;
         }
         long delay = System.currentTimeMillis() - mTouchHighlightRequested;
@@ -9129,14 +9123,10 @@
      */
     private void letPageHandleNavKey(int keyCode, long time, boolean down, int metaState) {
         int keyEventAction;
-        int eventHubAction;
         if (down) {
             keyEventAction = KeyEvent.ACTION_DOWN;
-            eventHubAction = EventHub.KEY_DOWN;
-            mWebView.playSoundEffect(keyCodeToSoundsEffect(keyCode));
         } else {
             keyEventAction = KeyEvent.ACTION_UP;
-            eventHubAction = EventHub.KEY_UP;
         }
 
         KeyEvent event = new KeyEvent(time, time, keyEventAction, keyCode,
@@ -9144,7 +9134,41 @@
                 | (metaState & KeyEvent.META_ALT_ON)
                 | (metaState & KeyEvent.META_SYM_ON)
                 , KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0);
-        mWebViewCore.sendMessage(eventHubAction, event);
+        sendKeyEvent(event);
+    }
+
+    private void sendKeyEvent(KeyEvent event) {
+        int direction = 0;
+        switch (event.getKeyCode()) {
+        case KeyEvent.KEYCODE_DPAD_DOWN:
+            direction = View.FOCUS_DOWN;
+            break;
+        case KeyEvent.KEYCODE_DPAD_UP:
+            direction = View.FOCUS_UP;
+            break;
+        case KeyEvent.KEYCODE_DPAD_LEFT:
+            direction = View.FOCUS_LEFT;
+            break;
+        case KeyEvent.KEYCODE_DPAD_RIGHT:
+            direction = View.FOCUS_RIGHT;
+            break;
+        case KeyEvent.KEYCODE_TAB:
+            direction = event.isShiftPressed() ? View.FOCUS_BACKWARD : View.FOCUS_FORWARD;
+            break;
+        }
+        if (direction != 0 && mWebView.focusSearch(direction) == null) {
+            // Can't take focus in that direction
+            direction = 0;
+        }
+        int eventHubAction = EventHub.KEY_UP;
+        if (event.getAction() == KeyEvent.ACTION_DOWN) {
+            eventHubAction = EventHub.KEY_DOWN;
+            int sound = keyCodeToSoundsEffect(event.getKeyCode());
+            if (sound != 0) {
+                mWebView.playSoundEffect(sound);
+            }
+        }
+        mWebViewCore.sendMessage(eventHubAction, direction, event);
     }
 
     /**
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 65356f5..09aa286c 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -139,6 +139,8 @@
     private int mHighMemoryUsageThresholdMb;
     private int mHighUsageDeltaMb;
 
+    private int mChromeCanFocusDirection;
+
     // The thread name used to identify the WebCore thread and for use in
     // debugging other classes that require operation within the WebCore thread.
     /* package */ static final String THREAD_NAME = "WebViewCoreThread";
@@ -344,6 +346,58 @@
     }
 
     /**
+     * Called by JNI to advance focus to the next view.
+     */
+    private void chromeTakeFocus(int webkitDirection) {
+        if (mWebView == null) return;
+        Message m = mWebView.mPrivateHandler.obtainMessage(
+                WebViewClassic.TAKE_FOCUS);
+        m.arg1 = mapDirection(webkitDirection);
+        m.sendToTarget();
+    }
+
+    /**
+     * Called by JNI to see if we can take focus in the given direction.
+     */
+    private boolean chromeCanTakeFocus(int webkitDirection) {
+        int direction = mapDirection(webkitDirection);
+        return direction == mChromeCanFocusDirection && direction != 0;
+    }
+
+    /**
+     * Maps a Webkit focus direction to a framework one
+     */
+    private int mapDirection(int webkitDirection) {
+        /*
+         * This is WebKit's FocusDirection enum (from FocusDirection.h)
+        enum FocusDirection {
+            FocusDirectionNone = 0,
+            FocusDirectionForward,
+            FocusDirectionBackward,
+            FocusDirectionUp,
+            FocusDirectionDown,
+            FocusDirectionLeft,
+            FocusDirectionRight
+        };
+         */
+        switch (webkitDirection) {
+        case 1:
+            return View.FOCUS_FORWARD;
+        case 2:
+            return View.FOCUS_BACKWARD;
+        case 3:
+            return View.FOCUS_UP;
+        case 4:
+            return View.FOCUS_DOWN;
+        case 5:
+            return View.FOCUS_LEFT;
+        case 6:
+            return View.FOCUS_RIGHT;
+        }
+        return 0;
+    }
+
+    /**
      * Called by JNI.  Open a file chooser to upload a file.
      * @param acceptType The value of the 'accept' attribute of the
      *         input tag associated with this file picker.
@@ -1311,11 +1365,11 @@
                             break;
 
                         case KEY_DOWN:
-                            key((KeyEvent) msg.obj, true);
+                            key((KeyEvent) msg.obj, msg.arg1, true);
                             break;
 
                         case KEY_UP:
-                            key((KeyEvent) msg.obj, false);
+                            key((KeyEvent) msg.obj, msg.arg1, false);
                             break;
 
                         case KEY_PRESS:
@@ -1950,11 +2004,12 @@
         return mBrowserFrame.saveWebArchive(filename, autoname);
     }
 
-    private void key(KeyEvent evt, boolean isDown) {
+    private void key(KeyEvent evt, int canTakeFocusDirection, boolean isDown) {
         if (DebugFlags.WEB_VIEW_CORE) {
             Log.v(LOGTAG, "CORE key at " + System.currentTimeMillis() + ", "
                     + evt);
         }
+        mChromeCanFocusDirection = canTakeFocusDirection;
         int keyCode = evt.getKeyCode();
         int unicodeChar = evt.getUnicodeChar();
 
@@ -1964,18 +2019,18 @@
             unicodeChar = evt.getCharacters().codePointAt(0);
         }
 
-        if (!nativeKey(mNativeClass, keyCode, unicodeChar, evt.getRepeatCount(),
+        boolean handled = nativeKey(mNativeClass, keyCode, unicodeChar, evt.getRepeatCount(),
                 evt.isShiftPressed(), evt.isAltPressed(),
-                evt.isSymPressed(), isDown) && keyCode != KeyEvent.KEYCODE_ENTER) {
+                evt.isSymPressed(), isDown);
+        mChromeCanFocusDirection = 0;
+        if (!handled && keyCode != KeyEvent.KEYCODE_ENTER) {
             if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
                     && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
-                if (DebugFlags.WEB_VIEW_CORE) {
-                    Log.v(LOGTAG, "key: arrow unused by page: " + keyCode);
-                }
-                if (mWebView != null && evt.isDown()) {
-                    Message.obtain(mWebView.mPrivateHandler,
-                            WebViewClassic.UNHANDLED_NAV_KEY, keyCode,
-                            0).sendToTarget();
+                if (canTakeFocusDirection != 0 && isDown) {
+                    Message m = mWebView.mPrivateHandler.obtainMessage(
+                            WebViewClassic.TAKE_FOCUS);
+                    m.arg1 = canTakeFocusDirection;
+                    m.sendToTarget();
                 }
                 return;
             }
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index a10d241..d5c2018 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -27,28 +27,26 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
-import android.widget.Button;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
+import android.widget.TextView;
 import android.widget.Toast;
 
 /**
  * @hide
  */
-public class ActionMenuItemView extends LinearLayout
+public class ActionMenuItemView extends TextView
         implements MenuView.ItemView, View.OnClickListener, View.OnLongClickListener,
         ActionMenuView.ActionMenuChildView {
     private static final String TAG = "ActionMenuItemView";
 
     private MenuItemImpl mItemData;
     private CharSequence mTitle;
+    private Drawable mIcon;
     private MenuBuilder.ItemInvoker mItemInvoker;
 
-    private ImageButton mImageButton;
-    private Button mTextButton;
     private boolean mAllowTextWithIcon;
     private boolean mExpandedFormat;
     private int mMinWidth;
+    private int mSavedPaddingLeft;
 
     public ActionMenuItemView(Context context) {
         this(context, null);
@@ -68,17 +66,12 @@
         mMinWidth = a.getDimensionPixelSize(
                 com.android.internal.R.styleable.ActionMenuItemView_minWidth, 0);
         a.recycle();
-    }
 
-    @Override
-    public void onFinishInflate() {
-        mImageButton = (ImageButton) findViewById(com.android.internal.R.id.imageButton);
-        mTextButton = (Button) findViewById(com.android.internal.R.id.textButton);
-        mImageButton.setOnClickListener(this);
-        mTextButton.setOnClickListener(this);
-        mImageButton.setOnLongClickListener(this);
         setOnClickListener(this);
         setOnLongClickListener(this);
+
+        // Save the inflated padding for later, we'll need it.
+        mSavedPaddingLeft = getPaddingLeft();
     }
 
     public MenuItemImpl getItemData() {
@@ -96,13 +89,6 @@
         setEnabled(itemData.isEnabled());
     }
 
-    @Override
-    public void setEnabled(boolean enabled) {
-        super.setEnabled(enabled);
-        mImageButton.setEnabled(enabled);
-        mTextButton.setEnabled(enabled);
-    }
-
     public void onClick(View v) {
         if (mItemInvoker != null) {
             mItemInvoker.invokeItem(mItemData);
@@ -135,26 +121,22 @@
     }
 
     private void updateTextButtonVisibility() {
-        boolean visible = !TextUtils.isEmpty(mTextButton.getText());
-        visible &= mImageButton.getDrawable() == null ||
+        boolean visible = !TextUtils.isEmpty(mTitle);
+        visible &= mIcon == null ||
                 (mItemData.showsTextAsAction() && (mAllowTextWithIcon || mExpandedFormat));
 
-        mTextButton.setVisibility(visible ? VISIBLE : GONE);
+        setText(visible ? mTitle : null);
     }
 
     public void setIcon(Drawable icon) {
-        mImageButton.setImageDrawable(icon);
-        if (icon != null) {
-            mImageButton.setVisibility(VISIBLE);
-        } else {
-            mImageButton.setVisibility(GONE);
-        }
+        mIcon = icon;
+        setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
 
         updateTextButtonVisibility();
     }
 
     public boolean hasText() {
-        return mTextButton.getVisibility() != GONE;
+        return !TextUtils.isEmpty(getText());
     }
 
     public void setShortcut(boolean showShortcut, char shortcutKey) {
@@ -164,8 +146,6 @@
     public void setTitle(CharSequence title) {
         mTitle = title;
 
-        mTextButton.setText(mTitle);
-
         setContentDescription(mTitle);
         updateTextButtonVisibility();
     }
@@ -236,12 +216,17 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final boolean textVisible = hasText();
+        if (textVisible) {
+            setPadding(mSavedPaddingLeft, getPaddingTop(), getPaddingRight(), getPaddingBottom());
+        }
+
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
         final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
-        final int specSize = MeasureSpec.getSize(widthMeasureSpec);
+        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
         final int oldMeasuredWidth = getMeasuredWidth();
-        final int targetWidth = widthMode == MeasureSpec.AT_MOST ? Math.min(specSize, mMinWidth)
+        final int targetWidth = widthMode == MeasureSpec.AT_MOST ? Math.min(widthSize, mMinWidth)
                 : mMinWidth;
 
         if (widthMode != MeasureSpec.EXACTLY && mMinWidth > 0 && oldMeasuredWidth < targetWidth) {
@@ -249,5 +234,13 @@
             super.onMeasure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
                     heightMeasureSpec);
         }
+
+        if (!textVisible && mIcon != null) {
+            // TextView won't center compound drawables in both dimensions without
+            // a little coercion. Pad in to center the icon after we've measured.
+            final int w = getMeasuredWidth();
+            final int dw = mIcon.getIntrinsicWidth();
+            setPadding((w - dw) / 2, getPaddingTop(), getPaddingRight(), getPaddingBottom());
+        }
     }
 }
diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
index 530809b..dca45a9 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
@@ -116,9 +116,9 @@
         if (!mMaxItemsSet) {
             mMaxItems = mContext.getResources().getInteger(
                     com.android.internal.R.integer.max_action_buttons);
-            if (mMenu != null) {
-                mMenu.onItemsChanged(true);
-            }
+        }
+        if (mMenu != null) {
+            mMenu.onItemsChanged(true);
         }
     }
 
diff --git a/core/java/com/android/internal/view/menu/ActionMenuView.java b/core/java/com/android/internal/view/menu/ActionMenuView.java
index 8d8c72c..e00fe9f 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuView.java
@@ -96,6 +96,13 @@
         if (mFormatItems) {
             onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec);
         } else {
+            // Previous measurement at exact format may have set margins - reset them.
+            final int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                final View child = getChildAt(i);
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                lp.leftMargin = lp.rightMargin = 0;
+            }
             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         }
     }
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 2f325bf..8c05459 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -324,13 +324,31 @@
                     if (mSplitView != null) {
                         mSplitView.addView(mMenuView);
                     }
+                    mMenuView.getLayoutParams().width = LayoutParams.MATCH_PARENT;
                 } else {
                     addView(mMenuView);
+                    mMenuView.getLayoutParams().width = LayoutParams.WRAP_CONTENT;
                 }
+                mMenuView.requestLayout();
             }
             if (mSplitView != null) {
                 mSplitView.setVisibility(splitActionBar ? VISIBLE : GONE);
             }
+
+            if (mActionMenuPresenter != null) {
+                if (!splitActionBar) {
+                    mActionMenuPresenter.setExpandedActionViewsExclusive(
+                            getResources().getBoolean(
+                                    com.android.internal.R.bool.action_bar_expanded_action_views_exclusive));
+                } else {
+                    mActionMenuPresenter.setExpandedActionViewsExclusive(false);
+                    // Allow full screen width in split mode.
+                    mActionMenuPresenter.setWidthLimit(
+                            getContext().getResources().getDisplayMetrics().widthPixels, true);
+                    // No limit to the item count; use whatever will fit.
+                    mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
+                }
+            }
             super.setSplitActionBar(splitActionBar);
         }
     }
diff --git a/core/res/res/layout/action_menu_item_layout.xml b/core/res/res/layout/action_menu_item_layout.xml
index dca6c52..ba7cf3b 100644
--- a/core/res/res/layout/action_menu_item_layout.xml
+++ b/core/res/res/layout/action_menu_item_layout.xml
@@ -18,39 +18,12 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_gravity="center"
-    android:addStatesFromChildren="true"
     android:gravity="center"
     android:focusable="true"
-    android:paddingLeft="4dip"
-    android:paddingRight="4dip"
-    style="?android:attr/actionButtonStyle">
-    <ImageButton android:id="@+id/imageButton"
-                 android:layout_width="wrap_content"
-                 android:layout_height="wrap_content"
-                 android:layout_gravity="center"
-                 android:visibility="gone"
-                 android:layout_marginTop="4dip"
-                 android:layout_marginBottom="4dip"
-                 android:layout_marginLeft="4dip"
-                 android:layout_marginRight="4dip"
-                 android:scaleType="fitCenter"
-                 android:adjustViewBounds="true"
-                 android:background="@null"
-                 android:focusable="false" />
-    <Button android:id="@+id/textButton"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center"
-            android:visibility="gone"
-            android:textAppearance="?attr/actionMenuTextAppearance"
-            style="?attr/buttonStyleSmall"
-            android:textColor="?attr/actionMenuTextColor"
-            android:singleLine="true"
-            android:ellipsize="none"
-            android:background="@null"
-            android:paddingTop="4dip"
-            android:paddingBottom="4dip"
-            android:paddingLeft="4dip"
-            android:paddingRight="4dip"
-            android:focusable="false" />
-</com.android.internal.view.menu.ActionMenuItemView>
+    android:paddingTop="4dip"
+    android:paddingBottom="4dip"
+    android:paddingLeft="8dip"
+    android:paddingRight="8dip"
+    android:textAppearance="?attr/actionMenuTextAppearance"
+    android:textColor="?attr/actionMenuTextColor"
+    style="?android:attr/actionButtonStyle" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 519ab87..a089021 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -88,7 +88,6 @@
   <java-symbol type="id" name="hour" />
   <java-symbol type="id" name="icon" />
   <java-symbol type="id" name="image" />
-  <java-symbol type="id" name="imageButton" />
   <java-symbol type="id" name="increment" />
   <java-symbol type="id" name="internalEmpty" />
   <java-symbol type="id" name="info" />
@@ -173,7 +172,6 @@
   <java-symbol type="id" name="switch_old" />
   <java-symbol type="id" name="switchWidget" />
   <java-symbol type="id" name="text" />
-  <java-symbol type="id" name="textButton" />
   <java-symbol type="id" name="time" />
   <java-symbol type="id" name="time_current" />
   <java-symbol type="id" name="timeDisplayBackground" />
diff --git a/core/tests/coretests/res/layout/textview_test.xml b/core/tests/coretests/res/layout/textview_test.xml
deleted file mode 100644
index f0c7b9e..0000000
--- a/core/tests/coretests/res/layout/textview_test.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:id="@+id/textviewtest_layout"
-              android:layout_width="fill_parent"
-              android:layout_height="fill_parent">
-
-    <TextView android:id="@+id/textviewtest_textview"
-              android:layout_height="wrap_content"
-              android:layout_width="wrap_content"
-              android:text="@string/textview_hebrew_text"/>
-
-</LinearLayout>
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java
index d4dbced..af6df1a 100644
--- a/core/tests/coretests/src/android/widget/TextViewTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewTest.java
@@ -16,25 +16,18 @@
 
 package android.widget;
 
-import android.test.ActivityInstrumentationTestCase2;
+import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.text.GetChars;
-import android.view.View;
-
-import com.android.frameworks.coretests.R;
 
 /**
  * TextViewTest tests {@link TextView}.
  */
-public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewTestActivity> {
-
-    public TextViewTest() {
-        super(TextViewTestActivity.class);
-    }
+public class TextViewTest extends AndroidTestCase {
 
     @SmallTest
     public void testArray() throws Exception {
-        TextView tv = new TextView(getActivity());
+        TextView tv = new TextView(mContext);
 
         char[] c = new char[] { 'H', 'e', 'l', 'l', 'o', ' ',
                                 'W', 'o', 'r', 'l', 'd', '!' };
@@ -61,181 +54,4 @@
         assertEquals('o', c2[4]);
         assertEquals('\0', c2[5]);
     }
-
-    @SmallTest
-    public void testTextDirectionDefault() {
-        TextView tv = new TextView(getActivity());
-        assertEquals(View.TEXT_DIRECTION_INHERIT, tv.getTextDirection());
-    }
-
-    @SmallTest
-    public void testSetGetTextDirection() {
-        TextView tv = new TextView(getActivity());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG);
-        assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_ANY_RTL);
-        assertEquals(View.TEXT_DIRECTION_ANY_RTL, tv.getTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_INHERIT);
-        assertEquals(View.TEXT_DIRECTION_INHERIT, tv.getTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_LTR);
-        assertEquals(View.TEXT_DIRECTION_LTR, tv.getTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_RTL);
-        assertEquals(View.TEXT_DIRECTION_RTL, tv.getTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_LOCALE);
-        assertEquals(View.TEXT_DIRECTION_LOCALE, tv.getTextDirection());
-    }
-
-    @SmallTest
-    public void testGetResolvedTextDirectionLtr() {
-        TextView tv = new TextView(getActivity());
-        tv.setText("this is a test");
-
-        assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG);
-        assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_ANY_RTL);
-        assertEquals(View.TEXT_DIRECTION_ANY_RTL, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_INHERIT);
-        assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_LTR);
-        assertEquals(View.TEXT_DIRECTION_LTR, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_RTL);
-        assertEquals(View.TEXT_DIRECTION_RTL, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_LOCALE);
-        assertEquals(View.TEXT_DIRECTION_LOCALE, tv.getResolvedTextDirection());
-    }
-
-    @SmallTest
-    public void testGetResolvedTextDirectionLtrWithInheritance() {
-        LinearLayout ll = new LinearLayout(getActivity());
-        ll.setTextDirection(View.TEXT_DIRECTION_ANY_RTL);
-
-        TextView tv = new TextView(getActivity());
-        tv.setText("this is a test");
-        ll.addView(tv);
-
-        tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG);
-        assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_ANY_RTL);
-        assertEquals(View.TEXT_DIRECTION_ANY_RTL, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_INHERIT);
-        assertEquals(View.TEXT_DIRECTION_ANY_RTL, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_LTR);
-        assertEquals(View.TEXT_DIRECTION_LTR, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_RTL);
-        assertEquals(View.TEXT_DIRECTION_RTL, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_LOCALE);
-        assertEquals(View.TEXT_DIRECTION_LOCALE, tv.getResolvedTextDirection());
-    }
-
-    @SmallTest
-    public void testGetResolvedTextDirectionRtl() {
-        TextView tv = new TextView(getActivity());
-        tv.setText("\u05DD\u05DE"); // hebrew
-
-        assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG);
-        assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_ANY_RTL);
-        assertEquals(View.TEXT_DIRECTION_ANY_RTL, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_INHERIT);
-        assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_LTR);
-        assertEquals(View.TEXT_DIRECTION_LTR, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_RTL);
-        assertEquals(View.TEXT_DIRECTION_RTL, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_LOCALE);
-        assertEquals(View.TEXT_DIRECTION_LOCALE, tv.getResolvedTextDirection());
-    }
-
-    @SmallTest
-    public void testGetResolvedTextDirectionRtlWithInheritance() {
-        LinearLayout ll = new LinearLayout(getActivity());
-        ll.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG);
-
-        TextView tv = new TextView(getActivity());
-        tv.setText("\u05DD\u05DE"); // hebrew
-        ll.addView(tv);
-
-        tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG);
-        assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_ANY_RTL);
-        assertEquals(View.TEXT_DIRECTION_ANY_RTL, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_INHERIT);
-        assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_LTR);
-        assertEquals(View.TEXT_DIRECTION_LTR, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_RTL);
-        assertEquals(View.TEXT_DIRECTION_RTL, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_LOCALE);
-        assertEquals(View.TEXT_DIRECTION_LOCALE, tv.getResolvedTextDirection());
-
-        // Force to RTL text direction on the layout
-        ll.setTextDirection(View.TEXT_DIRECTION_RTL);
-
-        tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG);
-        assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_ANY_RTL);
-        assertEquals(View.TEXT_DIRECTION_ANY_RTL, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_INHERIT);
-        assertEquals(View.TEXT_DIRECTION_RTL, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_LTR);
-        assertEquals(View.TEXT_DIRECTION_LTR, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_RTL);
-        assertEquals(View.TEXT_DIRECTION_RTL, tv.getResolvedTextDirection());
-
-        tv.setTextDirection(View.TEXT_DIRECTION_LOCALE);
-        assertEquals(View.TEXT_DIRECTION_LOCALE, tv.getResolvedTextDirection());
-    }
-
-    @SmallTest
-    public void testResetTextDirection() {
-        final TextViewTestActivity activity = getActivity();
-
-        final LinearLayout ll = (LinearLayout) activity.findViewById(R.id.textviewtest_layout);
-        final TextView tv = (TextView) activity.findViewById(R.id.textviewtest_textview);
-
-        getActivity().runOnUiThread(new Runnable() {
-            public void run() {
-                ll.setTextDirection(View.TEXT_DIRECTION_RTL);
-                tv.setTextDirection(View.TEXT_DIRECTION_INHERIT);
-                assertEquals(View.TEXT_DIRECTION_RTL, tv.getResolvedTextDirection());
-
-                ll.removeView(tv);
-                assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getResolvedTextDirection());
-            }
-        });
-    }
 }
diff --git a/core/tests/coretests/src/android/widget/TextViewTestActivity.java b/core/tests/coretests/src/android/widget/TextViewTestActivity.java
deleted file mode 100644
index 1bb4d24..0000000
--- a/core/tests/coretests/src/android/widget/TextViewTestActivity.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2011 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.widget;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-import com.android.frameworks.coretests.R;
-
-public class TextViewTestActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.textview_test);
-    }
-}
diff --git a/include/media/AudioTrack.h b/include/media/AudioTrack.h
index 9f2bd3a..95b9d86 100644
--- a/include/media/AudioTrack.h
+++ b/include/media/AudioTrack.h
@@ -135,8 +135,10 @@
      * format:             Audio format (e.g AUDIO_FORMAT_PCM_16_BIT for signed
      *                     16 bits per sample).
      * channelMask:        Channel mask: see audio_channels_t.
-     * frameCount:         Total size of track PCM buffer in frames. This defines the
-     *                     latency of the track.
+     * frameCount:         Minimum size of track PCM buffer in frames. This defines the
+     *                     latency of the track. The actual size selected by the AudioTrack could be
+     *                     larger if the requested size is not compatible with current audio HAL
+     *                     latency.
      * flags:              Reserved for future use.
      * cbf:                Callback function. If not null, this function is called periodically
      *                     to request new PCM data.
diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp
index 4890f05..a1c99e5 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -784,12 +784,9 @@
                 mNotificationFramesAct = frameCount/2;
             }
             if (frameCount < minFrameCount) {
-                if (enforceFrameCount) {
-                    ALOGE("Invalid buffer size: minFrameCount %d, frameCount %d", minFrameCount, frameCount);
-                    return BAD_VALUE;
-                } else {
-                    frameCount = minFrameCount;
-                }
+                ALOGW_IF(enforceFrameCount, "Minimum buffer size corrected from %d to %d",
+                         frameCount, minFrameCount);
+                frameCount = minFrameCount;
             }
         } else {
             // Ensure that buffer alignment matches channelCount
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index bd3e07a..1a85c9c 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -325,7 +325,7 @@
             mStreamType, mLeftVolume, mRightVolume);
     result.append(buffer);
     snprintf(buffer, 255, "  msec per frame(%f), latency (%d)\n",
-            mMsecsPerFrame, mLatency);
+            mMsecsPerFrame, (mTrack != 0) ? mTrack->latency() : -1);
     result.append(buffer);
     snprintf(buffer, 255, "  aux effect id(%d), send level (%f)\n",
             mAuxEffectId, mSendLevel);
@@ -1384,7 +1384,6 @@
     mRightVolume = 1.0;
     mPlaybackRatePermille = 1000;
     mSampleRateHz = 0;
-    mLatency = 0;
     mMsecsPerFrame = 0;
     mAuxEffectId = 0;
     mSendLevel = 0.0;
@@ -1443,7 +1442,8 @@
 
 uint32_t MediaPlayerService::AudioOutput::latency () const
 {
-    return mLatency;
+    if (mTrack == 0) return 0;
+    return mTrack->latency();
 }
 
 float MediaPlayerService::AudioOutput::msecsPerFrame() const
@@ -1533,7 +1533,6 @@
 
     mSampleRateHz = sampleRate;
     mMsecsPerFrame = mPlaybackRatePermille / (float) sampleRate;
-    mLatency = t->latency();
     mTrack = t;
 
     status_t res = t->setSampleRate(mPlaybackRatePermille * mSampleRateHz / 1000);
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index 681ecab..85cec22 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -118,7 +118,6 @@
         int32_t                 mPlaybackRatePermille;
         uint32_t                mSampleRateHz; // sample rate of the content, as set in open()
         float                   mMsecsPerFrame;
-        uint32_t                mLatency;
         int                     mSessionId;
         float                   mSendLevel;
         int                     mAuxEffectId;
diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp
index 9427ef7..650b6c4 100644
--- a/media/libstagefright/AudioPlayer.cpp
+++ b/media/libstagefright/AudioPlayer.cpp
@@ -427,6 +427,12 @@
                 break;
             }
 
+            if (mAudioSink != NULL) {
+                mLatencyUs = (int64_t)mAudioSink->latency() * 1000;
+            } else {
+                mLatencyUs = (int64_t)mAudioTrack->latency() * 1000;
+            }
+
             CHECK(mInputBuffer->meta_data()->findInt64(
                         kKeyTime, &mPositionTimeMediaUs));
 
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index b972548..d8a5d99 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -310,10 +310,10 @@
     }
 
     result.append("Global session refs:\n");
-    result.append(" session pid cnt\n");
+    result.append(" session pid count\n");
     for (size_t i = 0; i < mAudioSessionRefs.size(); i++) {
         AudioSessionRef *r = mAudioSessionRefs[i];
-        snprintf(buffer, SIZE, " %7d %3d %3d\n", r->sessionid, r->pid, r->cnt);
+        snprintf(buffer, SIZE, " %7d %3d %3d\n", r->mSessionid, r->mPid, r->mCnt);
         result.append(buffer);
     }
     write(fd, result.string(), result.size());
@@ -1036,9 +1036,9 @@
     bool removed = false;
     for (size_t i = 0; i< num; ) {
         AudioSessionRef *ref = mAudioSessionRefs.itemAt(i);
-        ALOGV(" pid %d @ %d", ref->pid, i);
-        if (ref->pid == pid) {
-            ALOGV(" removing entry for pid %d session %d", pid, ref->sessionid);
+        ALOGV(" pid %d @ %d", ref->mPid, i);
+        if (ref->mPid == pid) {
+            ALOGV(" removing entry for pid %d session %d", pid, ref->mSessionid);
             mAudioSessionRefs.removeAt(i);
             delete ref;
             removed = true;
@@ -2067,9 +2067,7 @@
                 maxPeriod = seconds(mFrameCount) / mSampleRate * 15;
 }
 
-if (mType == DUPLICATING) {
-                updateWaitTime();
-}
+                updateWaitTime_l();
 
                 activeSleepTime = activeSleepTimeUs();
                 idleSleepTime = idleSleepTimeUs();
@@ -2267,79 +2265,79 @@
 // shared by MIXER and DIRECT, overridden by DUPLICATING
 void AudioFlinger::PlaybackThread::threadLoop_write()
 {
-            // FIXME rewrite to reduce number of system calls
-            mLastWriteTime = systemTime();
-            mInWrite = true;
-            mBytesWritten += mixBufferSize;
-            int bytesWritten = (int)mOutput->stream->write(mOutput->stream, mMixBuffer, mixBufferSize);
-            if (bytesWritten < 0) mBytesWritten -= mixBufferSize;
-            mNumWrites++;
-            mInWrite = false;
+    // FIXME rewrite to reduce number of system calls
+    mLastWriteTime = systemTime();
+    mInWrite = true;
+    mBytesWritten += mixBufferSize;
+    int bytesWritten = (int)mOutput->stream->write(mOutput->stream, mMixBuffer, mixBufferSize);
+    if (bytesWritten < 0) mBytesWritten -= mixBufferSize;
+    mNumWrites++;
+    mInWrite = false;
 }
 
 // shared by MIXER and DIRECT, overridden by DUPLICATING
 void AudioFlinger::PlaybackThread::threadLoop_standby()
 {
-                    ALOGV("Audio hardware entering standby, mixer %p, suspend count %u", this, mSuspended);
-                    mOutput->stream->common.standby(&mOutput->stream->common);
+    ALOGV("Audio hardware entering standby, mixer %p, suspend count %u", this, mSuspended);
+    mOutput->stream->common.standby(&mOutput->stream->common);
 }
 
 void AudioFlinger::MixerThread::threadLoop_mix()
 {
-            // obtain the presentation timestamp of the next output buffer
-            int64_t pts;
-            status_t status = INVALID_OPERATION;
+    // obtain the presentation timestamp of the next output buffer
+    int64_t pts;
+    status_t status = INVALID_OPERATION;
 
-            if (NULL != mOutput->stream->get_next_write_timestamp) {
-                status = mOutput->stream->get_next_write_timestamp(
-                        mOutput->stream, &pts);
-            }
+    if (NULL != mOutput->stream->get_next_write_timestamp) {
+        status = mOutput->stream->get_next_write_timestamp(
+                mOutput->stream, &pts);
+    }
 
-            if (status != NO_ERROR) {
-                pts = AudioBufferProvider::kInvalidPTS;
-            }
+    if (status != NO_ERROR) {
+        pts = AudioBufferProvider::kInvalidPTS;
+    }
 
-            // mix buffers...
-            mAudioMixer->process(pts);
-            // increase sleep time progressively when application underrun condition clears.
-            // Only increase sleep time if the mixer is ready for two consecutive times to avoid
-            // that a steady state of alternating ready/not ready conditions keeps the sleep time
-            // such that we would underrun the audio HAL.
-            if ((sleepTime == 0) && (sleepTimeShift > 0)) {
-                sleepTimeShift--;
-            }
-            sleepTime = 0;
-            standbyTime = systemTime() + mStandbyTimeInNsecs;
-            //TODO: delay standby when effects have a tail
+    // mix buffers...
+    mAudioMixer->process(pts);
+    // increase sleep time progressively when application underrun condition clears.
+    // Only increase sleep time if the mixer is ready for two consecutive times to avoid
+    // that a steady state of alternating ready/not ready conditions keeps the sleep time
+    // such that we would underrun the audio HAL.
+    if ((sleepTime == 0) && (sleepTimeShift > 0)) {
+        sleepTimeShift--;
+    }
+    sleepTime = 0;
+    standbyTime = systemTime() + mStandbyTimeInNsecs;
+    //TODO: delay standby when effects have a tail
 }
 
 void AudioFlinger::MixerThread::threadLoop_sleepTime()
 {
-            // If no tracks are ready, sleep once for the duration of an output
-            // buffer size, then write 0s to the output
-            if (sleepTime == 0) {
-                if (mixerStatus == MIXER_TRACKS_ENABLED) {
-                    sleepTime = activeSleepTime >> sleepTimeShift;
-                    if (sleepTime < kMinThreadSleepTimeUs) {
-                        sleepTime = kMinThreadSleepTimeUs;
-                    }
-                    // reduce sleep time in case of consecutive application underruns to avoid
-                    // starving the audio HAL. As activeSleepTimeUs() is larger than a buffer
-                    // duration we would end up writing less data than needed by the audio HAL if
-                    // the condition persists.
-                    if (sleepTimeShift < kMaxThreadSleepTimeShift) {
-                        sleepTimeShift++;
-                    }
-                } else {
-                    sleepTime = idleSleepTime;
-                }
-            } else if (mBytesWritten != 0 ||
-                       (mixerStatus == MIXER_TRACKS_ENABLED && longStandbyExit)) {
-                memset (mMixBuffer, 0, mixBufferSize);
-                sleepTime = 0;
-                ALOGV_IF((mBytesWritten == 0 && (mixerStatus == MIXER_TRACKS_ENABLED && longStandbyExit)), "anticipated start");
+    // If no tracks are ready, sleep once for the duration of an output
+    // buffer size, then write 0s to the output
+    if (sleepTime == 0) {
+        if (mixerStatus == MIXER_TRACKS_ENABLED) {
+            sleepTime = activeSleepTime >> sleepTimeShift;
+            if (sleepTime < kMinThreadSleepTimeUs) {
+                sleepTime = kMinThreadSleepTimeUs;
             }
-            // TODO add standby time extension fct of effect tail
+            // reduce sleep time in case of consecutive application underruns to avoid
+            // starving the audio HAL. As activeSleepTimeUs() is larger than a buffer
+            // duration we would end up writing less data than needed by the audio HAL if
+            // the condition persists.
+            if (sleepTimeShift < kMaxThreadSleepTimeShift) {
+                sleepTimeShift++;
+            }
+        } else {
+            sleepTime = idleSleepTime;
+        }
+    } else if (mBytesWritten != 0 ||
+               (mixerStatus == MIXER_TRACKS_ENABLED && longStandbyExit)) {
+        memset (mMixBuffer, 0, mixBufferSize);
+        sleepTime = 0;
+        ALOGV_IF((mBytesWritten == 0 && (mixerStatus == MIXER_TRACKS_ENABLED && longStandbyExit)), "anticipated start");
+    }
+    // TODO add standby time extension fct of effect tail
 }
 
 // prepareTracks_l() must be called with ThreadBase::mLock held
@@ -2858,173 +2856,173 @@
     sp<Track>& trackToRemove
 )
 {
-// FIXME Temporarily renamed to avoid confusion with the member "mixerStatus"
-mixer_state mixerStatus_ = MIXER_IDLE;
+    // FIXME Temporarily renamed to avoid confusion with the member "mixerStatus"
+    mixer_state mixerStatus_ = MIXER_IDLE;
 
-            // find out which tracks need to be processed
-            if (mActiveTracks.size() != 0) {
-                sp<Track> t = mActiveTracks[0].promote();
-                // see FIXME in AudioFlinger.h, return MIXER_IDLE might also work
-                if (t == 0) return MIXER_CONTINUE;
-                //if (t == 0) continue;
+    // find out which tracks need to be processed
+    if (mActiveTracks.size() != 0) {
+        sp<Track> t = mActiveTracks[0].promote();
+        // see FIXME in AudioFlinger.h, return MIXER_IDLE might also work
+        if (t == 0) return MIXER_CONTINUE;
+        //if (t == 0) continue;
 
-                Track* const track = t.get();
-                audio_track_cblk_t* cblk = track->cblk();
+        Track* const track = t.get();
+        audio_track_cblk_t* cblk = track->cblk();
 
-                // The first time a track is added we wait
-                // for all its buffers to be filled before processing it
-                if (cblk->framesReady() && track->isReady() &&
-                        !track->isPaused() && !track->isTerminated())
-                {
-                    //ALOGV("track %d u=%08x, s=%08x [OK]", track->name(), cblk->user, cblk->server);
+        // The first time a track is added we wait
+        // for all its buffers to be filled before processing it
+        if (cblk->framesReady() && track->isReady() &&
+                !track->isPaused() && !track->isTerminated())
+        {
+            //ALOGV("track %d u=%08x, s=%08x [OK]", track->name(), cblk->user, cblk->server);
 
-                    if (track->mFillingUpStatus == Track::FS_FILLED) {
-                        track->mFillingUpStatus = Track::FS_ACTIVE;
-                        mLeftVolFloat = mRightVolFloat = 0;
-                        mLeftVolShort = mRightVolShort = 0;
-                        if (track->mState == TrackBase::RESUMING) {
-                            track->mState = TrackBase::ACTIVE;
-                            rampVolume = true;
-                        }
-                    } else if (cblk->server != 0) {
-                        // If the track is stopped before the first frame was mixed,
-                        // do not apply ramp
-                        rampVolume = true;
-                    }
-                    // compute volume for this track
-                    float left, right;
-                    if (track->isMuted() || mMasterMute || track->isPausing() ||
-                        mStreamTypes[track->streamType()].mute) {
-                        left = right = 0;
-                        if (track->isPausing()) {
-                            track->setPaused();
-                        }
-                    } else {
-                        float typeVolume = mStreamTypes[track->streamType()].volume;
-                        float v = mMasterVolume * typeVolume;
-                        uint32_t vlr = cblk->getVolumeLR();
-                        float v_clamped = v * (vlr & 0xFFFF);
-                        if (v_clamped > MAX_GAIN) v_clamped = MAX_GAIN;
-                        left = v_clamped/MAX_GAIN;
-                        v_clamped = v * (vlr >> 16);
-                        if (v_clamped > MAX_GAIN) v_clamped = MAX_GAIN;
-                        right = v_clamped/MAX_GAIN;
-                    }
+            if (track->mFillingUpStatus == Track::FS_FILLED) {
+                track->mFillingUpStatus = Track::FS_ACTIVE;
+                mLeftVolFloat = mRightVolFloat = 0;
+                mLeftVolShort = mRightVolShort = 0;
+                if (track->mState == TrackBase::RESUMING) {
+                    track->mState = TrackBase::ACTIVE;
+                    rampVolume = true;
+                }
+            } else if (cblk->server != 0) {
+                // If the track is stopped before the first frame was mixed,
+                // do not apply ramp
+                rampVolume = true;
+            }
+            // compute volume for this track
+            float left, right;
+            if (track->isMuted() || mMasterMute || track->isPausing() ||
+                mStreamTypes[track->streamType()].mute) {
+                left = right = 0;
+                if (track->isPausing()) {
+                    track->setPaused();
+                }
+            } else {
+                float typeVolume = mStreamTypes[track->streamType()].volume;
+                float v = mMasterVolume * typeVolume;
+                uint32_t vlr = cblk->getVolumeLR();
+                float v_clamped = v * (vlr & 0xFFFF);
+                if (v_clamped > MAX_GAIN) v_clamped = MAX_GAIN;
+                left = v_clamped/MAX_GAIN;
+                v_clamped = v * (vlr >> 16);
+                if (v_clamped > MAX_GAIN) v_clamped = MAX_GAIN;
+                right = v_clamped/MAX_GAIN;
+            }
 
-                    if (left != mLeftVolFloat || right != mRightVolFloat) {
-                        mLeftVolFloat = left;
-                        mRightVolFloat = right;
+            if (left != mLeftVolFloat || right != mRightVolFloat) {
+                mLeftVolFloat = left;
+                mRightVolFloat = right;
 
-                        // If audio HAL implements volume control,
-                        // force software volume to nominal value
-                        if (mOutput->stream->set_volume(mOutput->stream, left, right) == NO_ERROR) {
-                            left = 1.0f;
-                            right = 1.0f;
-                        }
+                // If audio HAL implements volume control,
+                // force software volume to nominal value
+                if (mOutput->stream->set_volume(mOutput->stream, left, right) == NO_ERROR) {
+                    left = 1.0f;
+                    right = 1.0f;
+                }
 
-                        // Convert volumes from float to 8.24
-                        uint32_t vl = (uint32_t)(left * (1 << 24));
-                        uint32_t vr = (uint32_t)(right * (1 << 24));
+                // Convert volumes from float to 8.24
+                uint32_t vl = (uint32_t)(left * (1 << 24));
+                uint32_t vr = (uint32_t)(right * (1 << 24));
 
-                        // Delegate volume control to effect in track effect chain if needed
-                        // only one effect chain can be present on DirectOutputThread, so if
-                        // there is one, the track is connected to it
-                        if (!mEffectChains.isEmpty()) {
-                            // Do not ramp volume if volume is controlled by effect
-                            if (mEffectChains[0]->setVolume_l(&vl, &vr)) {
-                                rampVolume = false;
-                            }
-                        }
-
-                        // Convert volumes from 8.24 to 4.12 format
-                        uint32_t v_clamped = (vl + (1 << 11)) >> 12;
-                        if (v_clamped > MAX_GAIN_INT) v_clamped = MAX_GAIN_INT;
-                        leftVol = (uint16_t)v_clamped;
-                        v_clamped = (vr + (1 << 11)) >> 12;
-                        if (v_clamped > MAX_GAIN_INT) v_clamped = MAX_GAIN_INT;
-                        rightVol = (uint16_t)v_clamped;
-                    } else {
-                        leftVol = mLeftVolShort;
-                        rightVol = mRightVolShort;
+                // Delegate volume control to effect in track effect chain if needed
+                // only one effect chain can be present on DirectOutputThread, so if
+                // there is one, the track is connected to it
+                if (!mEffectChains.isEmpty()) {
+                    // Do not ramp volume if volume is controlled by effect
+                    if (mEffectChains[0]->setVolume_l(&vl, &vr)) {
                         rampVolume = false;
                     }
+                }
 
-                    // reset retry count
-                    track->mRetryCount = kMaxTrackRetriesDirect;
-                    activeTrack = t;
-                    mixerStatus_ = MIXER_TRACKS_READY;
+                // Convert volumes from 8.24 to 4.12 format
+                uint32_t v_clamped = (vl + (1 << 11)) >> 12;
+                if (v_clamped > MAX_GAIN_INT) v_clamped = MAX_GAIN_INT;
+                leftVol = (uint16_t)v_clamped;
+                v_clamped = (vr + (1 << 11)) >> 12;
+                if (v_clamped > MAX_GAIN_INT) v_clamped = MAX_GAIN_INT;
+                rightVol = (uint16_t)v_clamped;
+            } else {
+                leftVol = mLeftVolShort;
+                rightVol = mRightVolShort;
+                rampVolume = false;
+            }
+
+            // reset retry count
+            track->mRetryCount = kMaxTrackRetriesDirect;
+            activeTrack = t;
+            mixerStatus_ = MIXER_TRACKS_READY;
+        } else {
+            //ALOGV("track %d u=%08x, s=%08x [NOT READY]", track->name(), cblk->user, cblk->server);
+            if (track->isStopped()) {
+                track->reset();
+            }
+            if (track->isTerminated() || track->isStopped() || track->isPaused()) {
+                // We have consumed all the buffers of this track.
+                // Remove it from the list of active tracks.
+                trackToRemove = track;
+            } else {
+                // No buffers for this track. Give it a few chances to
+                // fill a buffer, then remove it from active list.
+                if (--(track->mRetryCount) <= 0) {
+                    ALOGV("BUFFER TIMEOUT: remove(%d) from active list", track->name());
+                    trackToRemove = track;
                 } else {
-                    //ALOGV("track %d u=%08x, s=%08x [NOT READY]", track->name(), cblk->user, cblk->server);
-                    if (track->isStopped()) {
-                        track->reset();
-                    }
-                    if (track->isTerminated() || track->isStopped() || track->isPaused()) {
-                        // We have consumed all the buffers of this track.
-                        // Remove it from the list of active tracks.
-                        trackToRemove = track;
-                    } else {
-                        // No buffers for this track. Give it a few chances to
-                        // fill a buffer, then remove it from active list.
-                        if (--(track->mRetryCount) <= 0) {
-                            ALOGV("BUFFER TIMEOUT: remove(%d) from active list", track->name());
-                            trackToRemove = track;
-                        } else {
-                            mixerStatus_ = MIXER_TRACKS_ENABLED;
-                        }
-                    }
+                    mixerStatus_ = MIXER_TRACKS_ENABLED;
                 }
             }
+        }
+    }
 
-            // remove all the tracks that need to be...
-            if (CC_UNLIKELY(trackToRemove != 0)) {
-                mActiveTracks.remove(trackToRemove);
-                if (!mEffectChains.isEmpty()) {
-                    ALOGV("stopping track on chain %p for session Id: %d", effectChains[0].get(),
-                            trackToRemove->sessionId());
-                    mEffectChains[0]->decActiveTrackCnt();
-                }
-                if (trackToRemove->isTerminated()) {
-                    removeTrack_l(trackToRemove);
-                }
-            }
+    // remove all the tracks that need to be...
+    if (CC_UNLIKELY(trackToRemove != 0)) {
+        mActiveTracks.remove(trackToRemove);
+        if (!mEffectChains.isEmpty()) {
+            ALOGV("stopping track on chain %p for session Id: %d", effectChains[0].get(),
+                    trackToRemove->sessionId());
+            mEffectChains[0]->decActiveTrackCnt();
+        }
+        if (trackToRemove->isTerminated()) {
+            removeTrack_l(trackToRemove);
+        }
+    }
 
-return mixerStatus_;
+    return mixerStatus_;
 }
 
 void AudioFlinger::DirectOutputThread::threadLoop_mix()
 {
-            AudioBufferProvider::Buffer buffer;
-            size_t frameCount = mFrameCount;
-            int8_t *curBuf = (int8_t *)mMixBuffer;
-            // output audio to hardware
-            while (frameCount) {
-                buffer.frameCount = frameCount;
-                activeTrack->getNextBuffer(&buffer);
-                if (CC_UNLIKELY(buffer.raw == NULL)) {
-                    memset(curBuf, 0, frameCount * mFrameSize);
-                    break;
-                }
-                memcpy(curBuf, buffer.raw, buffer.frameCount * mFrameSize);
-                frameCount -= buffer.frameCount;
-                curBuf += buffer.frameCount * mFrameSize;
-                activeTrack->releaseBuffer(&buffer);
-            }
-            sleepTime = 0;
-            standbyTime = systemTime() + standbyDelay;
+    AudioBufferProvider::Buffer buffer;
+    size_t frameCount = mFrameCount;
+    int8_t *curBuf = (int8_t *)mMixBuffer;
+    // output audio to hardware
+    while (frameCount) {
+        buffer.frameCount = frameCount;
+        activeTrack->getNextBuffer(&buffer);
+        if (CC_UNLIKELY(buffer.raw == NULL)) {
+            memset(curBuf, 0, frameCount * mFrameSize);
+            break;
+        }
+        memcpy(curBuf, buffer.raw, buffer.frameCount * mFrameSize);
+        frameCount -= buffer.frameCount;
+        curBuf += buffer.frameCount * mFrameSize;
+        activeTrack->releaseBuffer(&buffer);
+    }
+    sleepTime = 0;
+    standbyTime = systemTime() + standbyDelay;
 }
 
 void AudioFlinger::DirectOutputThread::threadLoop_sleepTime()
 {
-            if (sleepTime == 0) {
-                if (mixerStatus == MIXER_TRACKS_ENABLED) {
-                    sleepTime = activeSleepTime;
-                } else {
-                    sleepTime = idleSleepTime;
-                }
-            } else if (mBytesWritten != 0 && audio_is_linear_pcm(mFormat)) {
-                memset (mMixBuffer, 0, mFrameCount * mFrameSize);
-                sleepTime = 0;
-            }
+    if (sleepTime == 0) {
+        if (mixerStatus == MIXER_TRACKS_ENABLED) {
+            sleepTime = activeSleepTime;
+        } else {
+            sleepTime = idleSleepTime;
+        }
+    } else if (mBytesWritten != 0 && audio_is_linear_pcm(mFormat)) {
+        memset (mMixBuffer, 0, mFrameCount * mFrameSize);
+        sleepTime = 0;
+    }
 }
 
 // getTrackName_l() must be called with ThreadBase::mLock held
@@ -3139,52 +3137,52 @@
 
 void AudioFlinger::DuplicatingThread::threadLoop_mix()
 {
-            // mix buffers...
-            if (outputsReady(outputTracks)) {
-                mAudioMixer->process(AudioBufferProvider::kInvalidPTS);
-            } else {
-                memset(mMixBuffer, 0, mixBufferSize);
-            }
-            sleepTime = 0;
-            writeFrames = mFrameCount;
+    // mix buffers...
+    if (outputsReady(outputTracks)) {
+        mAudioMixer->process(AudioBufferProvider::kInvalidPTS);
+    } else {
+        memset(mMixBuffer, 0, mixBufferSize);
+    }
+    sleepTime = 0;
+    writeFrames = mFrameCount;
 }
 
 void AudioFlinger::DuplicatingThread::threadLoop_sleepTime()
 {
-            if (sleepTime == 0) {
-                if (mixerStatus == MIXER_TRACKS_ENABLED) {
-                    sleepTime = activeSleepTime;
-                } else {
-                    sleepTime = idleSleepTime;
-                }
-            } else if (mBytesWritten != 0) {
-                // flush remaining overflow buffers in output tracks
-                for (size_t i = 0; i < outputTracks.size(); i++) {
-                    if (outputTracks[i]->isActive()) {
-                        sleepTime = 0;
-                        writeFrames = 0;
-                        memset(mMixBuffer, 0, mixBufferSize);
-                        break;
-                    }
-                }
+    if (sleepTime == 0) {
+        if (mixerStatus == MIXER_TRACKS_ENABLED) {
+            sleepTime = activeSleepTime;
+        } else {
+            sleepTime = idleSleepTime;
+        }
+    } else if (mBytesWritten != 0) {
+        // flush remaining overflow buffers in output tracks
+        for (size_t i = 0; i < outputTracks.size(); i++) {
+            if (outputTracks[i]->isActive()) {
+                sleepTime = 0;
+                writeFrames = 0;
+                memset(mMixBuffer, 0, mixBufferSize);
+                break;
             }
+        }
+    }
 }
 
 void AudioFlinger::DuplicatingThread::threadLoop_write()
 {
-            standbyTime = systemTime() + mStandbyTimeInNsecs;
-            for (size_t i = 0; i < outputTracks.size(); i++) {
-                outputTracks[i]->write(mMixBuffer, writeFrames);
-            }
-            mBytesWritten += mixBufferSize;
+    standbyTime = systemTime() + mStandbyTimeInNsecs;
+    for (size_t i = 0; i < outputTracks.size(); i++) {
+        outputTracks[i]->write(mMixBuffer, writeFrames);
+    }
+    mBytesWritten += mixBufferSize;
 }
 
 void AudioFlinger::DuplicatingThread::threadLoop_standby()
 {
-                    // DuplicatingThread implements standby by stopping all tracks
-                    for (size_t i = 0; i < outputTracks.size(); i++) {
-                        outputTracks[i]->stop();
-                    }
+    // DuplicatingThread implements standby by stopping all tracks
+    for (size_t i = 0; i < outputTracks.size(); i++) {
+        outputTracks[i]->stop();
+    }
 }
 
 void AudioFlinger::DuplicatingThread::addOutputTrack(MixerThread *thread)
@@ -3202,7 +3200,7 @@
         thread->setStreamVolume(AUDIO_STREAM_CNT, 1.0f);
         mOutputTracks.add(outputTrack);
         ALOGV("addOutputTrack() track %p, on thread %p", outputTrack, thread);
-        updateWaitTime();
+        updateWaitTime_l();
     }
 }
 
@@ -3213,14 +3211,15 @@
         if (mOutputTracks[i]->thread() == thread) {
             mOutputTracks[i]->destroy();
             mOutputTracks.removeAt(i);
-            updateWaitTime();
+            updateWaitTime_l();
             return;
         }
     }
     ALOGV("removeOutputTrack(): unkonwn thread: %p", thread);
 }
 
-void AudioFlinger::DuplicatingThread::updateWaitTime()
+// caller must hold mLock
+void AudioFlinger::DuplicatingThread::updateWaitTime_l()
 {
     mWaitTimeMs = UINT_MAX;
     for (size_t i = 0; i < mOutputTracks.size(); i++) {
@@ -5700,9 +5699,9 @@
     size_t num = mAudioSessionRefs.size();
     for (size_t i = 0; i< num; i++) {
         AudioSessionRef *ref = mAudioSessionRefs.editItemAt(i);
-        if (ref->sessionid == audioSession && ref->pid == caller) {
-            ref->cnt++;
-            ALOGV(" incremented refcount to %d", ref->cnt);
+        if (ref->mSessionid == audioSession && ref->mPid == caller) {
+            ref->mCnt++;
+            ALOGV(" incremented refcount to %d", ref->mCnt);
             return;
         }
     }
@@ -5718,10 +5717,10 @@
     size_t num = mAudioSessionRefs.size();
     for (size_t i = 0; i< num; i++) {
         AudioSessionRef *ref = mAudioSessionRefs.itemAt(i);
-        if (ref->sessionid == audioSession && ref->pid == caller) {
-            ref->cnt--;
-            ALOGV(" decremented refcount to %d", ref->cnt);
-            if (ref->cnt == 0) {
+        if (ref->mSessionid == audioSession && ref->mPid == caller) {
+            ref->mCnt--;
+            ALOGV(" decremented refcount to %d", ref->mCnt);
+            if (ref->mCnt == 0) {
                 mAudioSessionRefs.removeAt(i);
                 delete ref;
                 purgeStaleEffects_l();
@@ -5766,9 +5765,9 @@
         bool found = false;
         for (size_t k = 0; k < numsessionrefs; k++) {
             AudioSessionRef *ref = mAudioSessionRefs.itemAt(k);
-            if (ref->sessionid == sessionid) {
+            if (ref->mSessionid == sessionid) {
                 ALOGV(" session %d still exists for %d with %d refs",
-                     sessionid, ref->pid, ref->cnt);
+                     sessionid, ref->mPid, ref->mCnt);
                 found = true;
                 break;
             }
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index c7ac0a8..336c777 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -812,7 +812,7 @@
         virtual     void        threadLoop_standby();
 
         // Non-trivial for DUPLICATING only
-        virtual     void        updateWaitTime() { }
+        virtual     void        updateWaitTime_l() { }
 
         // Non-trivial for DIRECT only
         virtual     void        applyVolume() { }
@@ -1046,7 +1046,9 @@
         virtual     void        threadLoop_sleepTime();
         virtual     void        threadLoop_write();
         virtual     void        threadLoop_standby();
-        virtual     void        updateWaitTime();
+
+        // called from threadLoop, addOutputTrack, removeOutputTrack
+        virtual     void        updateWaitTime_l();
     private:
 
                     uint32_t    mWaitTimeMs;
@@ -1570,12 +1572,11 @@
 
     // for mAudioSessionRefs only
     struct AudioSessionRef {
-        // FIXME rename parameter names when fields get "m" prefix
-        AudioSessionRef(int sessionid_, pid_t pid_) :
-            sessionid(sessionid_), pid(pid_), cnt(1) {}
-        const int sessionid;
-        const pid_t pid;
-        int cnt;
+        AudioSessionRef(int sessionid, pid_t pid) :
+            mSessionid(sessionid), mPid(pid), mCnt(1) {}
+        const int   mSessionid;
+        const pid_t mPid;
+        int         mCnt;
     };
 
     friend class RecordThread;
diff --git a/services/java/com/android/server/DropBoxManagerService.java b/services/java/com/android/server/DropBoxManagerService.java
index d37c9ab..932cba1 100644
--- a/services/java/com/android/server/DropBoxManagerService.java
+++ b/services/java/com/android/server/DropBoxManagerService.java
@@ -28,6 +28,7 @@
 import android.os.DropBoxManager;
 import android.os.FileUtils;
 import android.os.Handler;
+import android.os.Message;
 import android.os.StatFs;
 import android.os.SystemClock;
 import android.provider.Settings;
@@ -64,6 +65,9 @@
     private static final int DEFAULT_RESERVE_PERCENT = 10;
     private static final int QUOTA_RESCAN_MILLIS = 5000;
 
+    // mHandler 'what' value.
+    private static final int MSG_SEND_BROADCAST = 1;
+
     private static final boolean PROFILE_DUMP = false;
 
     // TODO: This implementation currently uses one file per entry, which is
@@ -88,11 +92,11 @@
     private int mCachedQuotaBlocks = 0;  // Space we can use: computed from free space, etc.
     private long mCachedQuotaUptimeMillis = 0;
 
-    // Ensure that all log entries have a unique timestamp
-    private long mLastTimestamp = 0;
-
     private volatile boolean mBooted = false;
 
+    // Provide a way to perform sendBroadcast asynchronously to avoid deadlocks.
+    private final Handler mHandler;
+
     /** Receives events that might indicate a need to clean up files. */
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
@@ -143,11 +147,21 @@
         mContentResolver.registerContentObserver(
             Settings.Secure.CONTENT_URI, true,
             new ContentObserver(new Handler()) {
+                @Override
                 public void onChange(boolean selfChange) {
                     mReceiver.onReceive(context, (Intent) null);
                 }
             });
 
+        mHandler = new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                if (msg.what == MSG_SEND_BROADCAST) {
+                    mContext.sendBroadcast((Intent)msg.obj, android.Manifest.permission.READ_LOGS);
+                }
+            }
+        };
+
         // The real work gets done lazily in init() -- that way service creation always
         // succeeds, and things like disk problems cause individual method failures.
     }
@@ -157,6 +171,7 @@
         mContext.unregisterReceiver(mReceiver);
     }
 
+    @Override
     public void add(DropBoxManager.Entry entry) {
         File temp = null;
         OutputStream output = null;
@@ -227,14 +242,17 @@
             long time = createEntry(temp, tag, flags);
             temp = null;
 
-            Intent dropboxIntent = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED);
+            final Intent dropboxIntent = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED);
             dropboxIntent.putExtra(DropBoxManager.EXTRA_TAG, tag);
             dropboxIntent.putExtra(DropBoxManager.EXTRA_TIME, time);
             if (!mBooted) {
                 dropboxIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
             }
-            mContext.sendBroadcast(dropboxIntent, android.Manifest.permission.READ_LOGS);
-
+            // Call sendBroadcast after returning from this call to avoid deadlock. In particular
+            // the caller may be holding the WindowManagerService lock but sendBroadcast requires a
+            // lock in ActivityManagerService. ActivityManagerService has been caught holding that
+            // very lock while waiting for the WindowManagerService lock.
+            mHandler.sendMessage(mHandler.obtainMessage(MSG_SEND_BROADCAST, dropboxIntent));
         } catch (IOException e) {
             Slog.e(TAG, "Can't write: " + tag, e);
         } finally {
diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java
index 308661f..6593a0f 100644
--- a/services/java/com/android/server/NativeDaemonConnector.java
+++ b/services/java/com/android/server/NativeDaemonConnector.java
@@ -34,8 +34,8 @@
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.LinkedList;
 
 /**
  * Generic connector class for interfacing with a native daemon which uses the
@@ -50,11 +50,15 @@
     private OutputStream mOutputStream;
     private LocalLog mLocalLog;
 
-    private final BlockingQueue<NativeDaemonEvent> mResponseQueue;
+    private final ResponseQueue mResponseQueue;
 
     private INativeDaemonConnectorCallbacks mCallbacks;
     private Handler mCallbackHandler;
 
+    private AtomicInteger mSequenceNumber;
+
+    private static final int DEFAULT_TIMEOUT = 1 * 60 * 1000; /* 1 minute */
+
     /** Lock held whenever communicating with native daemon. */
     private final Object mDaemonLock = new Object();
 
@@ -64,7 +68,8 @@
             int responseQueueSize, String logTag, int maxLogSize) {
         mCallbacks = callbacks;
         mSocket = socket;
-        mResponseQueue = new LinkedBlockingQueue<NativeDaemonEvent>(responseQueueSize);
+        mResponseQueue = new ResponseQueue(responseQueueSize);
+        mSequenceNumber = new AtomicInteger(0);
         TAG = logTag != null ? logTag : "NativeDaemonConnector";
         mLocalLog = new LocalLog(maxLogSize);
     }
@@ -79,7 +84,7 @@
             try {
                 listenToSocket();
             } catch (Exception e) {
-                Slog.e(TAG, "Error in NativeDaemonConnector", e);
+                loge("Error in NativeDaemonConnector: " + e);
                 SystemClock.sleep(5000);
             }
         }
@@ -90,12 +95,10 @@
         String event = (String) msg.obj;
         try {
             if (!mCallbacks.onEvent(msg.what, event, event.split(" "))) {
-                Slog.w(TAG, String.format(
-                        "Unhandled event '%s'", event));
+                log(String.format("Unhandled event '%s'", event));
             }
         } catch (Exception e) {
-            Slog.e(TAG, String.format(
-                    "Error handling '%s'", event), e);
+            loge("Error handling '" + event + "': " + e);
         }
         return true;
     }
@@ -111,7 +114,9 @@
             socket.connect(address);
 
             InputStream inputStream = socket.getInputStream();
-            mOutputStream = socket.getOutputStream();
+            synchronized (mDaemonLock) {
+                mOutputStream = socket.getOutputStream();
+            }
 
             mCallbacks.onDaemonConnected();
 
@@ -120,7 +125,10 @@
 
             while (true) {
                 int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
-                if (count < 0) break;
+                if (count < 0) {
+                    loge("got " + count + " reading with start = " + start);
+                    break;
+                }
 
                 // Add our starting point to the count and reset the start.
                 count += start;
@@ -140,14 +148,10 @@
                                 mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
                                         event.getCode(), event.getRawEvent()));
                             } else {
-                                try {
-                                    mResponseQueue.put(event);
-                                } catch (InterruptedException ex) {
-                                    Slog.e(TAG, "Failed to put response onto queue: " + ex);
-                                }
+                                mResponseQueue.add(event.getCmdNumber(), event);
                             }
                         } catch (IllegalArgumentException e) {
-                            Slog.w(TAG, "Problem parsing message: " + rawEvent, e);
+                            log("Problem parsing message: " + rawEvent + " - " + e);
                         }
 
                         start = i + 1;
@@ -169,15 +173,16 @@
                 }
             }
         } catch (IOException ex) {
-            Slog.e(TAG, "Communications error", ex);
+            loge("Communications error: " + ex);
             throw ex;
         } finally {
             synchronized (mDaemonLock) {
                 if (mOutputStream != null) {
                     try {
+                        loge("closing stream for " + mSocket);
                         mOutputStream.close();
                     } catch (IOException e) {
-                        Slog.w(TAG, "Failed closing output stream", e);
+                        loge("Failed closing output stream: " + e);
                     }
                     mOutputStream = null;
                 }
@@ -188,17 +193,17 @@
                     socket.close();
                 }
             } catch (IOException ex) {
-                Slog.w(TAG, "Failed closing socket", ex);
+                loge("Failed closing socket: " + ex);
             }
         }
     }
 
     /**
-     * Send command to daemon, escaping arguments as needed.
+     * Make command for daemon, escaping arguments as needed.
      *
-     * @return the final command issued.
+     * @return the final command.
      */
-    private String sendCommandLocked(String cmd, Object... args)
+    private StringBuilder makeCommand(String cmd, Object... args)
             throws NativeDaemonConnectorException {
         // TODO: eventually enforce that cmd doesn't contain arguments
         if (cmd.indexOf('\0') >= 0) {
@@ -216,22 +221,33 @@
             appendEscaped(builder, argString);
         }
 
-        final String unterminated = builder.toString();
-        log("SND -> {" + unterminated + "}");
+        return builder;
+    }
+
+    private int sendCommand(StringBuilder builder)
+            throws NativeDaemonConnectorException {
+
+        int sequenceNumber = mSequenceNumber.incrementAndGet();
+
+        builder.insert(0, Integer.toString(sequenceNumber) + " ");
+
+        log("SND -> {" + builder.toString() + "}");
 
         builder.append('\0');
 
-        if (mOutputStream == null) {
-            throw new NativeDaemonConnectorException("missing output stream");
-        } else {
-            try {
-                mOutputStream.write(builder.toString().getBytes(Charsets.UTF_8));
-            } catch (IOException e) {
-                throw new NativeDaemonConnectorException("problem sending command", e);
+        synchronized (mDaemonLock) {
+            if (mOutputStream == null) {
+                throw new NativeDaemonConnectorException("missing output stream");
+            } else {
+                try {
+                    mOutputStream.write(builder.toString().getBytes(Charsets.UTF_8));
+                } catch (IOException e) {
+                    throw new NativeDaemonConnectorException("problem sending command", e);
+                }
             }
         }
 
-        return unterminated;
+        return sequenceNumber;
     }
 
     /**
@@ -292,39 +308,42 @@
      */
     public NativeDaemonEvent[] executeForList(String cmd, Object... args)
             throws NativeDaemonConnectorException {
-        synchronized (mDaemonLock) {
-            return executeLocked(cmd, args);
-        }
+            return execute(DEFAULT_TIMEOUT, cmd, args);
     }
 
-    private NativeDaemonEvent[] executeLocked(String cmd, Object... args)
+    /**
+     * Issue the given command to the native daemon and return any
+     * {@linke NativeDaemonEvent@isClassContinue()} responses, including the
+     * final terminal response.  Note that the timeout does not count time in
+     * deep sleep.
+     *
+     * @throws NativeDaemonConnectorException when problem communicating with
+     *             native daemon, or if the response matches
+     *             {@link NativeDaemonEvent#isClassClientError()} or
+     *             {@link NativeDaemonEvent#isClassServerError()}.
+     */
+    public NativeDaemonEvent[] execute(int timeout, String cmd, Object... args)
             throws NativeDaemonConnectorException {
         final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
-
-        while (mResponseQueue.size() > 0) {
-            try {
-                log("ignoring {" + mResponseQueue.take() + "}");
-            } catch (Exception e) {}
-        }
-
-        final String sentCommand = sendCommandLocked(cmd, args);
+        final StringBuilder sentCommand = makeCommand(cmd, args);
+        final int cmdNumber = sendCommand(sentCommand);
 
         NativeDaemonEvent event = null;
+        cmd = sentCommand.toString();
         do {
-            try {
-                event = mResponseQueue.take();
-            } catch (InterruptedException e) {
-                Slog.w(TAG, "interrupted waiting for event line");
-                continue;
+            event = mResponseQueue.remove(cmdNumber, timeout, cmd);
+            if (event == null) {
+                loge("timed-out waiting for response to " + cmdNumber + " " + cmd);
+                throw new NativeDaemonFailureException(cmd, event);
             }
             events.add(event);
         } while (event.isClassContinue());
 
         if (event.isClassClientError()) {
-            throw new NativeDaemonArgumentException(sentCommand, event);
+            throw new NativeDaemonArgumentException(cmd, event);
         }
         if (event.isClassServerError()) {
-            throw new NativeDaemonFailureException(sentCommand, event);
+            throw new NativeDaemonFailureException(cmd, event);
         }
 
         return events.toArray(new NativeDaemonEvent[events.size()]);
@@ -448,10 +467,120 @@
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         mLocalLog.dump(fd, pw, args);
+        pw.println();
+        mResponseQueue.dump(fd, pw, args);
     }
 
     private void log(String logstring) {
         if (LOGD) Slog.d(TAG, logstring);
         mLocalLog.log(logstring);
     }
+
+    private void loge(String logstring) {
+        Slog.e(TAG, logstring);
+        mLocalLog.log(logstring);
+    }
+
+    private static class ResponseQueue {
+
+        private static class Response {
+            public int cmdNum;
+            public LinkedList<NativeDaemonEvent> responses = new LinkedList<NativeDaemonEvent>();
+            public String request;
+            public Response(int c, String r) {cmdNum = c; request = r;}
+        }
+
+        private final LinkedList<Response> mResponses;
+        private int mMaxCount;
+
+        ResponseQueue(int maxCount) {
+            mResponses = new LinkedList<Response>();
+            mMaxCount = maxCount;
+        }
+
+        public void add(int cmdNum, NativeDaemonEvent response) {
+            Response found = null;
+            synchronized (mResponses) {
+                for (Response r : mResponses) {
+                    if (r.cmdNum == cmdNum) {
+                        found = r;
+                        break;
+                    }
+                }
+                if (found == null) {
+                    // didn't find it - make sure our queue isn't too big before adding
+                    // another..
+                    while (mResponses.size() >= mMaxCount) {
+                        Slog.e("NativeDaemonConnector.ResponseQueue",
+                                "more buffered than allowed: " + mResponses.size() +
+                                " >= " + mMaxCount);
+                        // let any waiter timeout waiting for this
+                        Response r = mResponses.remove();
+                        Slog.e("NativeDaemonConnector.ResponseQueue",
+                                "Removing request: " + r.request + " (" + r.cmdNum + ")");
+                    }
+                    found = new Response(cmdNum, null);
+                    mResponses.add(found);
+                }
+                found.responses.add(response);
+            }
+            synchronized (found) {
+                found.notify();
+            }
+        }
+
+        // note that the timeout does not count time in deep sleep.  If you don't want
+        // the device to sleep, hold a wakelock
+        public NativeDaemonEvent remove(int cmdNum, int timeoutMs, String origCmd) {
+            long endTime = SystemClock.uptimeMillis() + timeoutMs;
+            long nowTime;
+            Response found = null;
+            while (true) {
+                synchronized (mResponses) {
+                    for (Response response : mResponses) {
+                        if (response.cmdNum == cmdNum) {
+                            found = response;
+                            // how many response fragments are left
+                            switch (response.responses.size()) {
+                            case 0:  // haven't got any - must wait
+                                break;
+                            case 1:  // last one - remove this from the master list
+                                mResponses.remove(response); // fall through
+                            default: // take one and move on
+                                response.request = origCmd;
+                                return response.responses.remove();
+                            }
+                        }
+                    }
+                    nowTime = SystemClock.uptimeMillis();
+                    if (endTime <= nowTime) {
+                        Slog.e("NativeDaemonConnector.ResponseQueue",
+                                "Timeout waiting for response");
+                        return null;
+                    }
+                    /* pre-allocate so we have something unique to wait on */
+                    if (found == null) {
+                        found = new Response(cmdNum, origCmd);
+                        mResponses.add(found);
+                    }
+                }
+                try {
+                    synchronized (found) {
+                        found.wait(endTime - nowTime);
+                    }
+                } catch (InterruptedException e) {
+                    // loop around to check if we're done or if it's time to stop waiting
+                }
+            }
+        }
+
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            pw.println("Pending requests:");
+            synchronized (mResponses) {
+                for (Response response : mResponses) {
+                    pw.println("  Cmd " + response.cmdNum + " - " + response.request);
+                }
+            }
+        }
+    }
 }
diff --git a/services/java/com/android/server/NativeDaemonEvent.java b/services/java/com/android/server/NativeDaemonEvent.java
index 62084c0..d5e9f66 100644
--- a/services/java/com/android/server/NativeDaemonEvent.java
+++ b/services/java/com/android/server/NativeDaemonEvent.java
@@ -28,16 +28,22 @@
     // TODO: keep class ranges in sync with ResponseCode.h
     // TODO: swap client and server error ranges to roughly mirror HTTP spec
 
+    private final int mCmdNumber;
     private final int mCode;
     private final String mMessage;
     private final String mRawEvent;
 
-    private NativeDaemonEvent(int code, String message, String rawEvent) {
+    private NativeDaemonEvent(int cmdNumber, int code, String message, String rawEvent) {
+        mCmdNumber = cmdNumber;
         mCode = code;
         mMessage = message;
         mRawEvent = rawEvent;
     }
 
+    public int getCmdNumber() {
+        return mCmdNumber;
+    }
+
     public int getCode() {
         return mCode;
     }
@@ -89,7 +95,11 @@
      * Test if event represents an unsolicited event from native daemon.
      */
     public boolean isClassUnsolicited() {
-        return mCode >= 600 && mCode < 700;
+        return isClassUnsolicited(mCode);
+    }
+
+    private static boolean isClassUnsolicited(int code) {
+        return code >= 600 && code < 700;
     }
 
     /**
@@ -110,20 +120,37 @@
      *             from native side.
      */
     public static NativeDaemonEvent parseRawEvent(String rawEvent) {
-        final int splitIndex = rawEvent.indexOf(' ');
-        if (splitIndex == -1) {
-            throw new IllegalArgumentException("unable to find ' ' separator");
+        final String[] parsed = rawEvent.split(" ");
+        if (parsed.length < 2) {
+            throw new IllegalArgumentException("Insufficient arguments");
         }
 
+        int skiplength = 0;
+
         final int code;
         try {
-            code = Integer.parseInt(rawEvent.substring(0, splitIndex));
+            code = Integer.parseInt(parsed[0]);
+            skiplength = parsed[0].length() + 1;
         } catch (NumberFormatException e) {
             throw new IllegalArgumentException("problem parsing code", e);
         }
 
-        final String message = rawEvent.substring(splitIndex + 1);
-        return new NativeDaemonEvent(code, message, rawEvent);
+        int cmdNumber = -1;
+        if (isClassUnsolicited(code) == false) {
+            if (parsed.length < 3) {
+                throw new IllegalArgumentException("Insufficient arguemnts");
+            }
+            try {
+                cmdNumber = Integer.parseInt(parsed[1]);
+                skiplength += parsed[1].length() + 1;
+            } catch (NumberFormatException e) {
+                throw new IllegalArgumentException("problem parsing cmdNumber", e);
+            }
+        }
+
+        final String message = rawEvent.substring(skiplength);
+
+        return new NativeDaemonEvent(cmdNumber, code, message, rawEvent);
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/IccCard.java b/telephony/java/com/android/internal/telephony/IccCard.java
index 530a8dc..fe80fdf 100644
--- a/telephony/java/com/android/internal/telephony/IccCard.java
+++ b/telephony/java/com/android/internal/telephony/IccCard.java
@@ -35,8 +35,15 @@
 
 import com.android.internal.telephony.PhoneBase;
 import com.android.internal.telephony.CommandsInterface.RadioState;
+import com.android.internal.telephony.gsm.SIMFileHandler;
 import com.android.internal.telephony.gsm.SIMRecords;
+import com.android.internal.telephony.cdma.CDMALTEPhone;
+import com.android.internal.telephony.cdma.CdmaLteUiccFileHandler;
+import com.android.internal.telephony.cdma.CdmaLteUiccRecords;
 import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
+import com.android.internal.telephony.cdma.RuimFileHandler;
+import com.android.internal.telephony.cdma.RuimRecords;
+
 import android.os.SystemProperties;
 
 import com.android.internal.R;
@@ -56,6 +63,8 @@
     protected boolean isSubscriptionFromIccCard = true;
     protected CdmaSubscriptionSourceManager mCdmaSSM = null;
     protected PhoneBase mPhone;
+    private IccRecords mIccRecords;
+    private IccFileHandler mIccFileHandler;
     private RegistrantList mAbsentRegistrants = new RegistrantList();
     private RegistrantList mPinLockedRegistrants = new RegistrantList();
     private RegistrantList mNetworkLockedRegistrants = new RegistrantList();
@@ -167,26 +176,46 @@
     }
 
     public IccCard(PhoneBase phone, String logTag, Boolean is3gpp, Boolean dbg) {
+        mLogTag = logTag;
+        mDbg = dbg;
+        if (mDbg) log("[IccCard] Creating card type " + (is3gpp ? "3gpp" : "3gpp2"));
         mPhone = phone;
         this.is3gpp = is3gpp;
         mCdmaSSM = CdmaSubscriptionSourceManager.getInstance(mPhone.getContext(),
                 mPhone.mCM, mHandler, EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED, null);
+        if (phone.mCM.getLteOnCdmaMode() == Phone.LTE_ON_CDMA_TRUE
+                && phone instanceof CDMALTEPhone) {
+            mIccRecords = new CdmaLteUiccRecords(phone);
+            mIccFileHandler = new CdmaLteUiccFileHandler((CDMALTEPhone)phone);
+        } else {
+            mIccRecords = is3gpp ? new SIMRecords(phone) : new RuimRecords(phone);
+            mIccFileHandler = is3gpp ? new SIMFileHandler(phone) : new RuimFileHandler(phone);
+        }
         mPhone.mCM.registerForOffOrNotAvailable(mHandler, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
         mPhone.mCM.registerForOn(mHandler, EVENT_RADIO_ON, null);
         mPhone.mCM.registerForIccStatusChanged(mHandler, EVENT_ICC_STATUS_CHANGED, null);
-        mLogTag = logTag;
-        mDbg = dbg;
     }
 
     public void dispose() {
+        if (mDbg) log("[IccCard] Disposing card type " + (is3gpp ? "3gpp" : "3gpp2"));
         mPhone.mCM.unregisterForIccStatusChanged(mHandler);
         mPhone.mCM.unregisterForOffOrNotAvailable(mHandler);
         mPhone.mCM.unregisterForOn(mHandler);
         mCdmaSSM.dispose(mHandler);
+        mIccRecords.dispose();
+        mIccFileHandler.dispose();
     }
 
     protected void finalize() {
-        if(mDbg) Log.d(mLogTag, "IccCard finalized");
+        if (mDbg) log("[IccCard] Finalized card type " + (is3gpp ? "3gpp" : "3gpp2"));
+    }
+
+    public IccRecords getIccRecords() {
+        return mIccRecords;
+    }
+
+    public IccFileHandler getIccFileHandler() {
+        return mIccFileHandler;
     }
 
     /**
@@ -541,6 +570,10 @@
         } else if (isIccCardAdded) {
             mHandler.sendMessage(mHandler.obtainMessage(EVENT_CARD_ADDED, null));
         }
+
+        if (oldState != State.READY && newState == State.READY) {
+            mIccRecords.onReady();
+        }
     }
 
     private void onIccSwap(boolean isAdded) {
@@ -932,6 +965,10 @@
 
     public String getAid() {
         String aid = "";
+        if (mIccCardStatus == null) {
+            return aid;
+        }
+
         int appIndex = getCurrentApplicationIndex();
 
         if (appIndex >= 0 && appIndex < IccCardStatus.CARD_MAX_APPS) {
diff --git a/telephony/java/com/android/internal/telephony/IccRecords.java b/telephony/java/com/android/internal/telephony/IccRecords.java
index fc011c0..6e82903 100644
--- a/telephony/java/com/android/internal/telephony/IccRecords.java
+++ b/telephony/java/com/android/internal/telephony/IccRecords.java
@@ -102,6 +102,7 @@
     public abstract void dispose();
 
     protected abstract void onRadioOffOrNotAvailable();
+    public abstract void onReady();
 
     //***** Public Methods
     public AdnRecordCache getAdnCache() {
diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
index 3084c14..14a4b46 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
@@ -36,6 +36,7 @@
 import com.android.internal.telephony.SMSDispatcher;
 import com.android.internal.telephony.gsm.GsmSMSDispatcher;
 import com.android.internal.telephony.ims.IsimRecords;
+import com.android.internal.telephony.uicc.UiccController;
 
 public class CDMALTEPhone extends CDMAPhone {
     static final String LOG_TAG = "CDMA";
@@ -79,9 +80,9 @@
 
     @Override
     protected void initSstIcc() {
-        mIccCard = new IccCard(this, LOG_TAG, IccCard.CARD_IS_3GPP, DBG);
-        mIccRecords = new CdmaLteUiccRecords(this);
-        mIccFileHandler = new CdmaLteUiccFileHandler(this);
+        mIccCard = UiccController.getInstance(this).getIccCard();
+        mIccRecords = mIccCard.getIccRecords();
+        mIccFileHandler = mIccCard.getIccFileHandler();
         // CdmaLteServiceStateTracker registers with IccCard to know
         // when the card is ready. So create mIccCard before the ServiceStateTracker
         mSST = new CdmaLteServiceStateTracker(this);
@@ -164,7 +165,7 @@
         // look for our wrapper within the asyncresult, skip the rest if it
         // is null.
         if (!(ar.userObj instanceof NetworkSelectMessage)) {
-            if (DBG) Log.d(LOG_TAG, "unexpected result from user object.");
+            Log.e(LOG_TAG, "unexpected result from user object.");
             return;
         }
 
@@ -173,7 +174,7 @@
         // found the object, now we send off the message we had originally
         // attached to the request.
         if (nsm.message != null) {
-            if (DBG) Log.d(LOG_TAG, "sending original message to recipient");
+            if (DBG) log("sending original message to recipient");
             AsyncResult.forMessage(nsm.message, ar.result, ar.exception);
             nsm.message.sendToTarget();
         }
@@ -200,14 +201,15 @@
                 ContentValues map = new ContentValues();
                 String operatorNumeric = mIccRecords.getOperatorNumeric();
                 map.put(Telephony.Carriers.NUMERIC, operatorNumeric);
-                log("updateCurrentCarrierInProvider from UICC: numeric=" + operatorNumeric);
+                if (DBG) log("updateCurrentCarrierInProvider from UICC: numeric=" +
+                        operatorNumeric);
                 mContext.getContentResolver().insert(uri, map);
                 return true;
             } catch (SQLException e) {
                 Log.e(LOG_TAG, "[CDMALTEPhone] Can't store current operator ret false", e);
             }
         } else {
-            log("updateCurrentCarrierInProvider mIccRecords == null ret false");
+            if (DBG) log("updateCurrentCarrierInProvider mIccRecords == null ret false");
         }
         return false;
     }
@@ -259,7 +261,6 @@
 
     @Override
     protected void log(String s) {
-        if (DBG)
             Log.d(LOG_TAG, "[CDMALTEPhone] " + s);
     }
 }
diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
index b5dca65..e86e441 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
@@ -64,6 +64,7 @@
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.telephony.UUSInfo;
 import com.android.internal.telephony.cat.CatService;
+import com.android.internal.telephony.uicc.UiccController;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -149,9 +150,9 @@
     }
 
     protected void initSstIcc() {
-        mIccCard = new IccCard(this, LOG_TAG, IccCard.CARD_IS_NOT_3GPP, DBG);
-        mIccRecords = new RuimRecords(this);
-        mIccFileHandler = new RuimFileHandler(this);
+        mIccCard = UiccController.getInstance(this).getIccCard();
+        mIccRecords = mIccCard.getIccRecords();
+        mIccFileHandler = mIccCard.getIccFileHandler();
         // CdmaServiceStateTracker registers with IccCard to know
         // when the Ruim card is ready. So create mIccCard before the ServiceStateTracker
         mSST = new CdmaServiceStateTracker(this);
@@ -242,7 +243,6 @@
             mSMS.dispose();
             mIccFileHandler.dispose(); // instance of RuimFileHandler
             mIccRecords.dispose();
-            mIccCard.dispose();
             mRuimPhoneBookInterfaceManager.dispose();
             mRuimSmsInterfaceManager.dispose();
             mSubInfo.dispose();
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaLteUiccFileHandler.java b/telephony/java/com/android/internal/telephony/cdma/CdmaLteUiccFileHandler.java
index 8375fd0..e195ff2 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaLteUiccFileHandler.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaLteUiccFileHandler.java
@@ -27,7 +27,7 @@
 public final class CdmaLteUiccFileHandler extends IccFileHandler {
     static final String LOG_TAG = "CDMA";
 
-    CdmaLteUiccFileHandler(CDMALTEPhone phone) {
+    public CdmaLteUiccFileHandler(CDMALTEPhone phone) {
         super(phone);
     }
 
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaLteUiccRecords.java b/telephony/java/com/android/internal/telephony/cdma/CdmaLteUiccRecords.java
index 0a285b9..ca1e96d 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaLteUiccRecords.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaLteUiccRecords.java
@@ -436,6 +436,10 @@
             return true;
         }
 
+        if (phone == null || phone.mIccCard == null) {
+            return false;
+        }
+
         if (phone.mIccCard.isApplicationOnIcc(AppType.APPTYPE_CSIM) &&
             ((mMdn == null) || (mMin == null))) {
             return false;
diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimFileHandler.java b/telephony/java/com/android/internal/telephony/cdma/RuimFileHandler.java
index 375cc07..e854d7f 100644
--- a/telephony/java/com/android/internal/telephony/cdma/RuimFileHandler.java
+++ b/telephony/java/com/android/internal/telephony/cdma/RuimFileHandler.java
@@ -25,6 +25,7 @@
 import com.android.internal.telephony.IccFileTypeMismatch;
 import com.android.internal.telephony.IccIoResult;
 import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.PhoneBase;
 import com.android.internal.telephony.PhoneProxy;
 
 import java.util.ArrayList;
@@ -38,7 +39,7 @@
     //***** Instance Variables
 
     //***** Constructor
-    RuimFileHandler(CDMAPhone phone) {
+    public RuimFileHandler(PhoneBase phone) {
         super(phone);
     }
 
diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java b/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java
index e518c4c..265dff7 100755
--- a/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java
+++ b/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java
@@ -31,6 +31,7 @@
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.IccRefreshResponse;
 import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.PhoneBase;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.telephony.MccTable;
 
@@ -61,7 +62,6 @@
 
     // ***** Event Constants
 
-    private static final int EVENT_RUIM_READY = 1;
     private static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE = 2;
     private static final int EVENT_GET_IMSI_DONE = 3;
     private static final int EVENT_GET_DEVICE_IDENTITY_DONE = 4;
@@ -78,7 +78,7 @@
     private static final int EVENT_RUIM_REFRESH = 31;
 
 
-    RuimRecords(CDMAPhone p) {
+    public RuimRecords(PhoneBase p) {
         super(p);
 
         adnCache = new AdnRecordCache(phone);
@@ -88,8 +88,6 @@
         // recordsToLoad is set to 0 because no requests are made yet
         recordsToLoad = 0;
 
-
-        p.mIccCard.registerForRuimReady(this, EVENT_RUIM_READY, null);
         p.mCM.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
         // NOTE the EVENT_SMS_ON_RUIM is not registered
         p.mCM.registerForIccRefresh(this, EVENT_RUIM_REFRESH, null);
@@ -102,7 +100,6 @@
     @Override
     public void dispose() {
         //Unregister for all events
-        phone.mIccCard.unregisterForRuimReady(this);
         phone.mCM.unregisterForOffOrNotAvailable( this);
         phone.mCM.unregisterForIccRefresh(this);
     }
@@ -206,10 +203,6 @@
         }
 
         try { switch (msg.what) {
-            case EVENT_RUIM_READY:
-                onRuimReady();
-            break;
-
             case EVENT_RADIO_OFF_OR_NOT_AVAILABLE:
                 onRadioOffOrNotAvailable();
             break;
@@ -349,7 +342,8 @@
                 IccCard.INTENT_VALUE_ICC_LOADED, null);
     }
 
-    private void onRuimReady() {
+    @Override
+    public void onReady() {
         /* broadcast intent ICC_READY here so that we can make sure
           READY is sent before IMSI ready
         */
diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
index 4c846f1..5e9a4f2 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
@@ -70,6 +70,7 @@
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.telephony.UUSInfo;
 import com.android.internal.telephony.test.SimulatedRadioControl;
+import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.IccVmNotSupportedException;
 import com.android.internal.telephony.ServiceStateTracker;
 
@@ -136,12 +137,12 @@
         }
 
         mCM.setPhoneType(Phone.PHONE_TYPE_GSM);
-        mIccCard = new IccCard(this, LOG_TAG, IccCard.CARD_IS_3GPP, true);
+        mIccCard = UiccController.getInstance(this).getIccCard();
         mCT = new GsmCallTracker(this);
         mSST = new GsmServiceStateTracker (this);
         mSMS = new GsmSMSDispatcher(this, mSmsStorageMonitor, mSmsUsageMonitor);
-        mIccFileHandler = new SIMFileHandler(this);
-        mIccRecords = new SIMRecords(this);
+        mIccFileHandler = mIccCard.getIccFileHandler();
+        mIccRecords = mIccCard.getIccRecords();
         mDataConnectionTracker = new GsmDataConnectionTracker (this);
         if (!unitTestMode) {
             mSimPhoneBookIntManager = new SimPhoneBookInterfaceManager(this);
@@ -220,7 +221,6 @@
             mSST.dispose();
             mIccFileHandler.dispose(); // instance of SimFileHandler
             mIccRecords.dispose();
-            mIccCard.dispose();
             mSimPhoneBookIntManager.dispose();
             mSimSmsIntManager.dispose();
             mSubInfo.dispose();
diff --git a/telephony/java/com/android/internal/telephony/gsm/SIMFileHandler.java b/telephony/java/com/android/internal/telephony/gsm/SIMFileHandler.java
index e8d10f9..8c3bc0e 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SIMFileHandler.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SIMFileHandler.java
@@ -23,22 +23,20 @@
 import com.android.internal.telephony.IccCardApplication;
 import com.android.internal.telephony.IccConstants;
 import com.android.internal.telephony.IccFileHandler;
-import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneBase;
 
 /**
  * {@hide}
  */
 public final class SIMFileHandler extends IccFileHandler implements IccConstants {
     static final String LOG_TAG = "GSM";
-    private Phone mPhone;
 
     //***** Instance Variables
 
     //***** Constructor
 
-    SIMFileHandler(GSMPhone phone) {
+    public SIMFileHandler(PhoneBase phone) {
         super(phone);
-        mPhone = phone;
     }
 
     public void dispose() {
diff --git a/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java b/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
index 1fb99e3..68d3b2a 100755
--- a/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
@@ -123,7 +123,6 @@
 
     // ***** Event Constants
 
-    private static final int EVENT_SIM_READY = 1;
     private static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE = 2;
     protected static final int EVENT_GET_IMSI_DONE = 3;
     protected static final int EVENT_GET_ICCID_DONE = 4;
@@ -188,7 +187,6 @@
         // recordsToLoad is set to 0 because no requests are made yet
         recordsToLoad = 0;
 
-        p.mIccCard.registerForReady(this, EVENT_SIM_READY, null);
         p.mCM.registerForOffOrNotAvailable(
                         this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
         p.mCM.setOnSmsOnSim(this, EVENT_SMS_ON_SIM, null);
@@ -202,7 +200,6 @@
     @Override
     public void dispose() {
         //Unregister for all events
-        phone.mIccCard.unregisterForReady(this);
         phone.mCM.unregisterForOffOrNotAvailable( this);
         phone.mCM.unregisterForIccRefresh(this);
     }
@@ -526,10 +523,6 @@
         }
 
         try { switch (msg.what) {
-            case EVENT_SIM_READY:
-                onSimReady();
-            break;
-
             case EVENT_RADIO_OFF_OR_NOT_AVAILABLE:
                 onRadioOffOrNotAvailable();
             break;
@@ -1296,7 +1289,8 @@
         }
     }
 
-    public void onSimReady() {
+    @Override
+    public void onReady() {
         /* broadcast intent SIM_READY here so that we can make sure
           READY is sent before IMSI ready
         */
diff --git a/telephony/java/com/android/internal/telephony/uicc/UiccController.java b/telephony/java/com/android/internal/telephony/uicc/UiccController.java
new file mode 100644
index 0000000..5961efd
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/uicc/UiccController.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2011 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 com.android.internal.telephony.uicc;
+
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.cdma.CDMALTEPhone;
+import com.android.internal.telephony.cdma.CDMAPhone;
+import com.android.internal.telephony.gsm.GSMPhone;
+
+import android.util.Log;
+
+/* This class is responsible for keeping all knowledge about
+ * ICCs in the system. It is also used as API to get appropriate
+ * applications to pass them to phone and service trackers.
+ */
+public class UiccController {
+    private static final boolean DBG = true;
+    private static final String LOG_TAG = "RIL_UiccController";
+
+    private static UiccController mInstance;
+
+    private PhoneBase mCurrentPhone;
+    private boolean mIsCurrentCard3gpp;
+    private IccCard mIccCard;
+
+    public static synchronized UiccController getInstance(PhoneBase phone) {
+        if (mInstance == null) {
+            mInstance = new UiccController(phone);
+        } else {
+            mInstance.setNewPhone(phone);
+        }
+        return mInstance;
+    }
+
+    public IccCard getIccCard() {
+        return mIccCard;
+    }
+
+    private UiccController(PhoneBase phone) {
+        if (DBG) log("Creating UiccController");
+        setNewPhone(phone);
+    }
+
+    private void setNewPhone(PhoneBase phone) {
+        mCurrentPhone = phone;
+        if (phone instanceof GSMPhone) {
+            if (DBG) log("New phone is GSMPhone");
+            updateCurrentCard(IccCard.CARD_IS_3GPP);
+        } else if (phone instanceof CDMALTEPhone){
+            if (DBG) log("New phone type is CDMALTEPhone");
+            updateCurrentCard(IccCard.CARD_IS_3GPP);
+        } else if (phone instanceof CDMAPhone){
+            if (DBG) log("New phone type is CDMAPhone");
+            updateCurrentCard(IccCard.CARD_IS_NOT_3GPP);
+        } else {
+            Log.e(LOG_TAG, "Unhandled phone type. Critical error!");
+        }
+    }
+
+    private void updateCurrentCard(boolean isNewCard3gpp) {
+        if (mIsCurrentCard3gpp == isNewCard3gpp && mIccCard != null) {
+            return;
+        }
+
+        if (mIccCard != null) {
+            mIccCard.dispose();
+            mIccCard = null;
+        }
+
+        mIsCurrentCard3gpp = isNewCard3gpp;
+        mIccCard = new IccCard(mCurrentPhone, mCurrentPhone.getPhoneName(),
+                isNewCard3gpp, DBG);
+    }
+
+    private void log(String string) {
+        Log.d(LOG_TAG, string);
+    }
+}
\ No newline at end of file