diff options
45 files changed, 1243 insertions, 1012 deletions
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 @@ package android.database { 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 @@ package android.database { 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 @@ package android.database.sqlite { 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 @@ package android.database.sqlite { 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 @@ package android.database.sqlite { 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.content.ContentResolver; 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 @@ import android.os.Bundle; * 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 85f570c..f1f3017 100644 --- a/core/java/android/database/CursorWindow.java +++ b/core/java/android/database/CursorWindow.java @@ -169,14 +169,6 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { } /** - * 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 class CursorWindow extends SQLiteClosable implements Parcelable { } 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 @@ public class DatabaseUtils { 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; - - 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_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; - } + 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_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; } - 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 @@ public abstract class SQLiteClosable { } } + /** + * 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 @@ public abstract class SQLiteClosable { } } + /** + * 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 @@ public abstract class SQLiteClosable { 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 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen 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 @@ public final class SQLiteDatabase extends SQLiteClosable { 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 @@ public final class SQLiteDatabase extends SQLiteClosable { * are committed and rolled back. */ public void endTransaction() { - getThreadSession().endTransaction(null); + acquireReference(); + try { + getThreadSession().endTransaction(null); + } finally { + releaseReference(); + } } /** @@ -515,7 +527,12 @@ public final class SQLiteDatabase extends SQLiteClosable { * transaction is already marked as successful. */ public void setTransactionSuccessful() { - getThreadSession().setTransactionSuccessful(); + acquireReference(); + try { + getThreadSession().setTransactionSuccessful(); + } finally { + releaseReference(); + } } /** @@ -524,7 +541,12 @@ public final class SQLiteDatabase extends SQLiteClosable { * @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 @@ public final class SQLiteDatabase extends SQLiteClosable { * @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 @@ public final class SQLiteDatabase extends SQLiteClosable { } 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 @@ public final class SQLiteDatabase extends SQLiteClosable { } /** - * 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 @@ public final class SQLiteDatabase extends SQLiteClosable { * {@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 @@ public final class SQLiteDatabase extends SQLiteClosable { 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 final class SQLiteDatabase extends SQLiteClosable { 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 final class SQLiteDatabase extends SQLiteClosable { */ 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('('); - - 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); + 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); + } + 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 @@ public final class SQLiteDatabase extends SQLiteClosable { * 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 @@ public final class SQLiteDatabase extends SQLiteClosable { 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]; + acquireReference(); + try { + 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); } - } - if (!TextUtils.isEmpty(whereClause)) { - sql.append(" WHERE "); - sql.append(whereClause); - } - SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs); - try { - return statement.executeUpdateDelete(); + SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs); + try { + return statement.executeUpdateDelete(); + } finally { + statement.close(); + } } finally { - statement.close(); + releaseReference(); } } @@ -1579,24 +1631,29 @@ public final class SQLiteDatabase extends SQLiteClosable { } 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 @@ public final class SQLiteDatabase extends SQLiteClosable { 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 @@ public final class SQLiteDatabase extends SQLiteClosable { * 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"); + List<Pair<String, String>> attachedDbs = null; + 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())); } - } 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; + 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 { - if (prog != null) prog.close(); } + } finally { + releaseReference(); } return true; } @@ -1955,12 +2021,6 @@ public final class SQLiteDatabase extends SQLiteClosable { 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 @@ public abstract class SQLiteProgram extends SQLiteClosable { } /** - * 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 @@ class MeasuredText { 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 @@ public class TextUtils { 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 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal 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 @@ class ViewStateSerializer { 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 @@ class ViewStateSerializer { 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 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc 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 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc 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 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } // 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 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } } - 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 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } // 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 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc 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 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc 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 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc 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 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ 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 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc | (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..09aa286 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -139,6 +139,8 @@ public final class WebViewCore { 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 @@ public final class WebViewCore { } /** + * 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 @@ public final class WebViewCore { 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 @@ public final class WebViewCore { 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 @@ public final class WebViewCore { 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.Gravity; 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 @@ public class ActionMenuItemView extends LinearLayout 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 @@ public class ActionMenuItemView extends LinearLayout 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 @@ public class ActionMenuItemView extends LinearLayout } 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 class ActionMenuItemView extends LinearLayout public void setTitle(CharSequence title) { mTitle = title; - mTextButton.setText(mTitle); - setContentDescription(mTitle); updateTextButtonVisibility(); } @@ -236,12 +216,17 @@ public class ActionMenuItemView extends LinearLayout @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 @@ public class ActionMenuItemView extends LinearLayout 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 @@ public class ActionMenuPresenter extends BaseMenuPresenter 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 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo 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 @@ public class ActionBarView extends AbsActionBarView { 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 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewTestA 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 @@ public: * 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 @@ status_t AudioTrack::createTrack_l( 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 @@ status_t MediaPlayerService::AudioOutput::dump(int fd, const Vector<String16>& a 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 @@ MediaPlayerService::AudioOutput::AudioOutput(int sessionId) mRightVolume = 1.0; mPlaybackRatePermille = 1000; mSampleRateHz = 0; - mLatency = 0; mMsecsPerFrame = 0; mAuxEffectId = 0; mSendLevel = 0.0; @@ -1443,7 +1442,8 @@ ssize_t MediaPlayerService::AudioOutput::frameSize() const 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 @@ status_t MediaPlayerService::AudioOutput::open( 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 @@ class MediaPlayerService : public BnMediaPlayerService 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 @@ size_t AudioPlayer::fillBuffer(void *data, size_t size) { 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 @@ status_t AudioFlinger::dumpClients(int fd, const Vector<String16>& args) } 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 @@ void AudioFlinger::removeNotificationClient(pid_t pid) 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 @@ if (mType == MIXER) { maxPeriod = seconds(mFrameCount) / mSampleRate * 15; } -if (mType == DUPLICATING) { - updateWaitTime(); -} + updateWaitTime_l(); activeSleepTime = activeSleepTimeUs(); idleSleepTime = idleSleepTimeUs(); @@ -2267,79 +2265,79 @@ if (mType == DUPLICATING) { // 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; + } + // 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++; } - // TODO add standby time extension fct of effect tail + } 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 @@ AudioFlinger::PlaybackThread::mixer_state AudioFlinger::DirectOutputThread::thre sp<Track>& trackToRemove ) { -// 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; - - 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); - - 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; + // FIXME Temporarily renamed to avoid confusion with the member "mixerStatus" + mixer_state mixerStatus_ = MIXER_IDLE; - // 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; - } + // 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; - // 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; - } - } + Track* const track = t.get(); + audio_track_cblk_t* cblk = track->cblk(); - // 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; - } + // 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); - // 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 { - mixerStatus_ = MIXER_TRACKS_ENABLED; - } - } + 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 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)); - // remove all the tracks that need to be... - if (CC_UNLIKELY(trackToRemove != 0)) { - mActiveTracks.remove(trackToRemove); + // 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()) { - ALOGV("stopping track on chain %p for session Id: %d", effectChains[0].get(), - trackToRemove->sessionId()); - mEffectChains[0]->decActiveTrackCnt(); + // Do not ramp volume if volume is controlled by effect + if (mEffectChains[0]->setVolume_l(&vl, &vr)) { + rampVolume = false; + } } - if (trackToRemove->isTerminated()) { - removeTrack_l(trackToRemove); + + // 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 { + 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); + } + } -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 @@ AudioFlinger::DuplicatingThread::~DuplicatingThread() 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 @@ void AudioFlinger::DuplicatingThread::addOutputTrack(MixerThread *thread) 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 @@ void AudioFlinger::DuplicatingThread::removeOutputTrack(MixerThread *thread) 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 @@ void AudioFlinger::acquireAudioSessionId(int audioSession) 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 @@ void AudioFlinger::releaseAudioSessionId(int audioSession) 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 @@ void AudioFlinger::purgeStaleEffects_l() { 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 @@ protected: 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 @@ public: 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 @@ mutable Mutex mLock; // mutex for process, commands and handl // 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.Debug; 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 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { 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 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { 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 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { 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 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { mContext.unregisterReceiver(mReceiver); } + @Override public void add(DropBoxManager.Entry entry) { File temp = null; OutputStream output = null; @@ -227,14 +242,17 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { 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.InputStream; 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 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo 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 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo 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 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo try { listenToSocket(); } catch (Exception e) { - Slog.e(TAG, "Error in NativeDaemonConnector", e); + loge("Error in NativeDaemonConnector: " + e); SystemClock.sleep(5000); } } @@ -90,12 +95,10 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo 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 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo socket.connect(address); InputStream inputStream = socket.getInputStream(); - mOutputStream = socket.getOutputStream(); + synchronized (mDaemonLock) { + mOutputStream = socket.getOutputStream(); + } mCallbacks.onDaemonConnected(); @@ -120,7 +125,10 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo 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 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo 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 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo } } } 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 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo 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 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo 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 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo */ 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 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo 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 @@ public class NativeDaemonEvent { // 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 @@ public class NativeDaemonEvent { * 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 @@ public class NativeDaemonEvent { * 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 android.view.WindowManager; 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 @@ public class IccCard { 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 class IccCard { } 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 @@ public class IccCard { } 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 class IccCard { 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 class IccRecords extends Handler implements IccConstants { 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.PhoneProxy; 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 @@ public class CDMALTEPhone extends CDMAPhone { @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 @@ public class CDMALTEPhone extends CDMAPhone { // 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 @@ public class CDMALTEPhone extends CDMAPhone { // 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 @@ public class CDMALTEPhone extends CDMAPhone { 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 @@ public class CDMALTEPhone extends CDMAPhone { @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.TelephonyIntents; 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 @@ public class CDMAPhone extends PhoneBase { } 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 @@ public class CDMAPhone extends PhoneBase { 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 @@ import android.os.Message; 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 @@ public final class CdmaLteUiccRecords extends SIMRecords { 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.IccFileHandler; 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 @@ public final class RuimFileHandler extends IccFileHandler { //***** 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.AdnRecordLoader; 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 @@ public final class RuimRecords extends IccRecords { // ***** 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 @@ public final class RuimRecords extends IccRecords { 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 @@ public final class RuimRecords extends IccRecords { // 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 @@ public final class RuimRecords extends IccRecords { @Override public void dispose() { //Unregister for all events - phone.mIccCard.unregisterForRuimReady(this); phone.mCM.unregisterForOffOrNotAvailable( this); phone.mCM.unregisterForIccRefresh(this); } @@ -206,10 +203,6 @@ public final class RuimRecords extends IccRecords { } try { switch (msg.what) { - case EVENT_RUIM_READY: - onRuimReady(); - break; - case EVENT_RADIO_OFF_OR_NOT_AVAILABLE: onRadioOffOrNotAvailable(); break; @@ -349,7 +342,8 @@ public final class RuimRecords extends IccRecords { 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.PhoneSubInfo; 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 @@ public class GSMPhone extends PhoneBase { } 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 @@ public class GSMPhone extends PhoneBase { 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.IccCard; 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 @@ public class SIMRecords extends IccRecords { // ***** 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 @@ public class SIMRecords extends IccRecords { // 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 @@ public class SIMRecords extends IccRecords { @Override public void dispose() { //Unregister for all events - phone.mIccCard.unregisterForReady(this); phone.mCM.unregisterForOffOrNotAvailable( this); phone.mCM.unregisterForIccRefresh(this); } @@ -526,10 +523,6 @@ public class SIMRecords extends IccRecords { } try { switch (msg.what) { - case EVENT_SIM_READY: - onSimReady(); - break; - case EVENT_RADIO_OFF_OR_NOT_AVAILABLE: onRadioOffOrNotAvailable(); break; @@ -1296,7 +1289,8 @@ public class SIMRecords extends IccRecords { } } - 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 |
