diff options
Diffstat (limited to 'core/java/android')
81 files changed, 1642 insertions, 662 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index bac3c6c..455d2f0 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -486,7 +486,6 @@ public final class ActivityThread { private static final String HEAP_COLUMN = "%13s %8s %8s %8s %8s %8s %8s"; private static final String ONE_COUNT_COLUMN = "%21s %8d"; private static final String TWO_COUNT_COLUMNS = "%21s %8d %21s %8d"; - private static final String TWO_COUNT_COLUMNS_DB = "%21s %8d %21s %8d"; private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s"; // Formatting for checkin service - update version if row format changes @@ -867,7 +866,6 @@ public final class ActivityThread { int binderProxyObjectCount = Debug.getBinderProxyObjectCount(); int binderDeathObjectCount = Debug.getBinderDeathObjectCount(); long openSslSocketCount = Debug.countInstancesOfClass(OpenSSLSocketImpl.class); - long sqliteAllocated = SQLiteDebug.getHeapAllocatedSize() / 1024; SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo(); // For checkin, we print one long comma-separated list of values @@ -935,9 +933,9 @@ public final class ActivityThread { pw.print(openSslSocketCount); pw.print(','); // SQL - pw.print(sqliteAllocated); pw.print(','); pw.print(stats.memoryUsed / 1024); pw.print(','); - pw.print(stats.pageCacheOverflo / 1024); pw.print(','); + pw.print(stats.memoryUsed / 1024); pw.print(','); + pw.print(stats.pageCacheOverflow / 1024); pw.print(','); pw.print(stats.largestMemAlloc / 1024); for (int i = 0; i < stats.dbStats.size(); i++) { DbStats dbStats = stats.dbStats.get(i); @@ -1003,10 +1001,9 @@ public final class ActivityThread { // SQLite mem info pw.println(" "); pw.println(" SQL"); - printRow(pw, TWO_COUNT_COLUMNS_DB, "heap:", sqliteAllocated, "MEMORY_USED:", - stats.memoryUsed / 1024); - printRow(pw, TWO_COUNT_COLUMNS_DB, "PAGECACHE_OVERFLOW:", - stats.pageCacheOverflo / 1024, "MALLOC_SIZE:", stats.largestMemAlloc / 1024); + printRow(pw, ONE_COUNT_COLUMN, "MEMORY_USED:", stats.memoryUsed / 1024); + printRow(pw, TWO_COUNT_COLUMNS, "PAGECACHE_OVERFLOW:", + stats.pageCacheOverflow / 1024, "MALLOC_SIZE:", stats.largestMemAlloc / 1024); pw.println(" "); int N = stats.dbStats.size(); if (N > 0) { diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java index e45d66d..2ea936e 100644 --- a/core/java/android/database/sqlite/SQLiteConnection.java +++ b/core/java/android/database/sqlite/SQLiteConnection.java @@ -74,10 +74,17 @@ import java.util.regex.Pattern; * queues. * </p> * + * <h2>Reentrance</h2> + * <p> + * This class must tolerate reentrant execution of SQLite operations because + * triggers may call custom SQLite functions that perform additional queries. + * </p> + * * @hide */ public final class SQLiteConnection { private static final String TAG = "SQLiteConnection"; + private static final boolean DEBUG = false; private static final String[] EMPTY_STRING_ARRAY = new String[0]; private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; @@ -205,13 +212,13 @@ public final class SQLiteConnection { } if (mConnectionPtr != 0) { - mRecentOperations.beginOperation("close", null, null); + final int cookie = mRecentOperations.beginOperation("close", null, null); try { mPreparedStatementCache.evictAll(); nativeClose(mConnectionPtr); mConnectionPtr = 0; } finally { - mRecentOperations.endOperation(); + mRecentOperations.endOperation(cookie); } } } @@ -304,9 +311,9 @@ public final class SQLiteConnection { throw new IllegalArgumentException("sql must not be null."); } - mRecentOperations.beginOperation("prepare", sql, null); + final int cookie = mRecentOperations.beginOperation("prepare", sql, null); try { - PreparedStatement statement = acquirePreparedStatement(sql); + final PreparedStatement statement = acquirePreparedStatement(sql); try { if (outStatementInfo != null) { outStatementInfo.numParameters = statement.mNumParameters; @@ -328,10 +335,10 @@ public final class SQLiteConnection { releasePreparedStatement(statement); } } catch (RuntimeException ex) { - mRecentOperations.failOperation(ex); + mRecentOperations.failOperation(cookie, ex); throw ex; } finally { - mRecentOperations.endOperation(); + mRecentOperations.endOperation(cookie); } } @@ -349,9 +356,9 @@ public final class SQLiteConnection { throw new IllegalArgumentException("sql must not be null."); } - mRecentOperations.beginOperation("execute", sql, bindArgs); + final int cookie = mRecentOperations.beginOperation("execute", sql, bindArgs); try { - PreparedStatement statement = acquirePreparedStatement(sql); + final PreparedStatement statement = acquirePreparedStatement(sql); try { throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); @@ -361,10 +368,10 @@ public final class SQLiteConnection { releasePreparedStatement(statement); } } catch (RuntimeException ex) { - mRecentOperations.failOperation(ex); + mRecentOperations.failOperation(cookie, ex); throw ex; } finally { - mRecentOperations.endOperation(); + mRecentOperations.endOperation(cookie); } } @@ -384,9 +391,9 @@ public final class SQLiteConnection { throw new IllegalArgumentException("sql must not be null."); } - mRecentOperations.beginOperation("executeForLong", sql, bindArgs); + final int cookie = mRecentOperations.beginOperation("executeForLong", sql, bindArgs); try { - PreparedStatement statement = acquirePreparedStatement(sql); + final PreparedStatement statement = acquirePreparedStatement(sql); try { throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); @@ -396,10 +403,10 @@ public final class SQLiteConnection { releasePreparedStatement(statement); } } catch (RuntimeException ex) { - mRecentOperations.failOperation(ex); + mRecentOperations.failOperation(cookie, ex); throw ex; } finally { - mRecentOperations.endOperation(); + mRecentOperations.endOperation(cookie); } } @@ -419,9 +426,9 @@ public final class SQLiteConnection { throw new IllegalArgumentException("sql must not be null."); } - mRecentOperations.beginOperation("executeForString", sql, bindArgs); + final int cookie = mRecentOperations.beginOperation("executeForString", sql, bindArgs); try { - PreparedStatement statement = acquirePreparedStatement(sql); + final PreparedStatement statement = acquirePreparedStatement(sql); try { throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); @@ -431,10 +438,10 @@ public final class SQLiteConnection { releasePreparedStatement(statement); } } catch (RuntimeException ex) { - mRecentOperations.failOperation(ex); + mRecentOperations.failOperation(cookie, ex); throw ex; } finally { - mRecentOperations.endOperation(); + mRecentOperations.endOperation(cookie); } } @@ -456,9 +463,10 @@ public final class SQLiteConnection { throw new IllegalArgumentException("sql must not be null."); } - mRecentOperations.beginOperation("executeForBlobFileDescriptor", sql, bindArgs); + final int cookie = mRecentOperations.beginOperation("executeForBlobFileDescriptor", + sql, bindArgs); try { - PreparedStatement statement = acquirePreparedStatement(sql); + final PreparedStatement statement = acquirePreparedStatement(sql); try { throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); @@ -470,10 +478,10 @@ public final class SQLiteConnection { releasePreparedStatement(statement); } } catch (RuntimeException ex) { - mRecentOperations.failOperation(ex); + mRecentOperations.failOperation(cookie, ex); throw ex; } finally { - mRecentOperations.endOperation(); + mRecentOperations.endOperation(cookie); } } @@ -493,9 +501,10 @@ public final class SQLiteConnection { throw new IllegalArgumentException("sql must not be null."); } - mRecentOperations.beginOperation("executeForChangedRowCount", sql, bindArgs); + final int cookie = mRecentOperations.beginOperation("executeForChangedRowCount", + sql, bindArgs); try { - PreparedStatement statement = acquirePreparedStatement(sql); + final PreparedStatement statement = acquirePreparedStatement(sql); try { throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); @@ -506,10 +515,10 @@ public final class SQLiteConnection { releasePreparedStatement(statement); } } catch (RuntimeException ex) { - mRecentOperations.failOperation(ex); + mRecentOperations.failOperation(cookie, ex); throw ex; } finally { - mRecentOperations.endOperation(); + mRecentOperations.endOperation(cookie); } } @@ -529,9 +538,10 @@ public final class SQLiteConnection { throw new IllegalArgumentException("sql must not be null."); } - mRecentOperations.beginOperation("executeForLastInsertedRowId", sql, bindArgs); + final int cookie = mRecentOperations.beginOperation("executeForLastInsertedRowId", + sql, bindArgs); try { - PreparedStatement statement = acquirePreparedStatement(sql); + final PreparedStatement statement = acquirePreparedStatement(sql); try { throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); @@ -542,10 +552,10 @@ public final class SQLiteConnection { releasePreparedStatement(statement); } } catch (RuntimeException ex) { - mRecentOperations.failOperation(ex); + mRecentOperations.failOperation(cookie, ex); throw ex; } finally { - mRecentOperations.endOperation(); + mRecentOperations.endOperation(cookie); } } @@ -581,9 +591,10 @@ public final class SQLiteConnection { int actualPos = -1; int countedRows = -1; int filledRows = -1; - mRecentOperations.beginOperation("executeForCursorWindow", sql, bindArgs); + final int cookie = mRecentOperations.beginOperation("executeForCursorWindow", + sql, bindArgs); try { - PreparedStatement statement = acquirePreparedStatement(sql); + final PreparedStatement statement = acquirePreparedStatement(sql); try { throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); @@ -600,11 +611,11 @@ public final class SQLiteConnection { releasePreparedStatement(statement); } } catch (RuntimeException ex) { - mRecentOperations.failOperation(ex); + mRecentOperations.failOperation(cookie, ex); throw ex; } finally { - if (mRecentOperations.endOperationDeferLog()) { - mRecentOperations.logOperation("window='" + window + if (mRecentOperations.endOperationDeferLog(cookie)) { + mRecentOperations.logOperation(cookie, "window='" + window + "', startPos=" + startPos + ", actualPos=" + actualPos + ", filledRows=" + filledRows @@ -615,8 +626,15 @@ public final class SQLiteConnection { private PreparedStatement acquirePreparedStatement(String sql) { PreparedStatement statement = mPreparedStatementCache.get(sql); + boolean skipCache = false; if (statement != null) { - return statement; + if (!statement.mInUse) { + return statement; + } + // The statement is already in the cache but is in use (this statement appears + // to be not only re-entrant but recursive!). So prepare a new copy of the + // statement but do not cache it. + skipCache = true; } final int statementPtr = nativePrepareStatement(mConnectionPtr, sql); @@ -625,7 +643,7 @@ public final class SQLiteConnection { final int type = DatabaseUtils.getSqlStatementType(sql); final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr); statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly); - if (isCacheable(type)) { + if (!skipCache && isCacheable(type)) { mPreparedStatementCache.put(sql, statement); statement.mInCache = true; } @@ -637,31 +655,38 @@ public final class SQLiteConnection { } throw ex; } + statement.mInUse = true; return statement; } private void releasePreparedStatement(PreparedStatement statement) { + statement.mInUse = false; if (statement.mInCache) { try { nativeResetStatementAndClearBindings(mConnectionPtr, statement.mStatementPtr); } catch (SQLiteException ex) { - // The statement could not be reset due to an error. - // The entryRemoved() callback for the cache will recursively call - // releasePreparedStatement() again, but this time mInCache will be false - // so the statement will be finalized and recycled. - if (SQLiteDebug.DEBUG_SQL_CACHE) { - Log.v(TAG, "Could not reset prepared statement due to an exception. " + // The statement could not be reset due to an error. Remove it from the cache. + // When remove() is called, the cache will invoke its entryRemoved() callback, + // which will in turn call finalizePreparedStatement() to finalize and + // recycle the statement. + if (DEBUG) { + Log.d(TAG, "Could not reset prepared statement due to an exception. " + "Removing it from the cache. SQL: " + trimSqlForDisplay(statement.mSql), ex); } + mPreparedStatementCache.remove(statement.mSql); } } else { - nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr); - recyclePreparedStatement(statement); + finalizePreparedStatement(statement); } } + private void finalizePreparedStatement(PreparedStatement statement) { + nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr); + recyclePreparedStatement(statement); + } + private void bindArguments(PreparedStatement statement, Object[] bindArgs) { final int count = bindArgs != null ? bindArgs.length : 0; if (count != statement.mNumParameters) { @@ -735,9 +760,10 @@ public final class SQLiteConnection { * Dumps debugging information about this connection. * * @param printer The printer to receive the dump, not null. + * @param verbose True to dump more verbose information. */ - public void dump(Printer printer) { - dumpUnsafe(printer); + public void dump(Printer printer, boolean verbose) { + dumpUnsafe(printer, verbose); } /** @@ -752,15 +778,21 @@ public final class SQLiteConnection { * it should not crash. This is ok as it is only used for diagnostic purposes. * * @param printer The printer to receive the dump, not null. + * @param verbose True to dump more verbose information. */ - void dumpUnsafe(Printer printer) { + void dumpUnsafe(Printer printer, boolean verbose) { printer.println("Connection #" + mConnectionId + ":"); + if (verbose) { + printer.println(" connectionPtr: 0x" + Integer.toHexString(mConnectionPtr)); + } printer.println(" isPrimaryConnection: " + mIsPrimaryConnection); - printer.println(" connectionPtr: 0x" + Integer.toHexString(mConnectionPtr)); printer.println(" onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations); mRecentOperations.dump(printer); - mPreparedStatementCache.dump(printer); + + if (verbose) { + mPreparedStatementCache.dump(printer); + } } /** @@ -917,6 +949,12 @@ public final class SQLiteConnection { // True if the statement is in the cache. public boolean mInCache; + + // True if the statement is in use (currently executing). + // We need this flag because due to the use of custom functions in triggers, it's + // possible for SQLite calls to be re-entrant. Consequently we need to prevent + // in use statements from being finalized until they are no longer in use. + public boolean mInUse; } private final class PreparedStatementCache @@ -929,7 +967,9 @@ public final class SQLiteConnection { protected void entryRemoved(boolean evicted, String key, PreparedStatement oldValue, PreparedStatement newValue) { oldValue.mInCache = false; - releasePreparedStatement(oldValue); + if (!oldValue.mInUse) { + finalizePreparedStatement(oldValue); + } } public void dump(Printer printer) { @@ -957,12 +997,15 @@ public final class SQLiteConnection { } private static final class OperationLog { - private static final int MAX_RECENT_OPERATIONS = 10; + private static final int MAX_RECENT_OPERATIONS = 20; + private static final int COOKIE_GENERATION_SHIFT = 8; + private static final int COOKIE_INDEX_MASK = 0xff; private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS]; private int mIndex; + private int mGeneration; - public void beginOperation(String kind, String sql, Object[] bindArgs) { + public int beginOperation(String kind, String sql, Object[] bindArgs) { synchronized (mOperations) { final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS; Operation operation = mOperations[index]; @@ -995,47 +1038,54 @@ public final class SQLiteConnection { } } } + operation.mCookie = newOperationCookieLocked(index); mIndex = index; + return operation.mCookie; } } - public void failOperation(Exception ex) { + public void failOperation(int cookie, Exception ex) { synchronized (mOperations) { - final Operation operation = mOperations[mIndex]; - operation.mException = ex; + final Operation operation = getOperationLocked(cookie); + if (operation != null) { + operation.mException = ex; + } } } - public boolean endOperationDeferLog() { + public void endOperation(int cookie) { synchronized (mOperations) { - return endOperationDeferLogLocked(); + if (endOperationDeferLogLocked(cookie)) { + logOperationLocked(cookie, null); + } } } - private boolean endOperationDeferLogLocked() { - final Operation operation = mOperations[mIndex]; - operation.mEndTime = System.currentTimeMillis(); - operation.mFinished = true; - return SQLiteDebug.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery( - operation.mEndTime - operation.mStartTime); + public boolean endOperationDeferLog(int cookie) { + synchronized (mOperations) { + return endOperationDeferLogLocked(cookie); + } } - public void endOperation() { + public void logOperation(int cookie, String detail) { synchronized (mOperations) { - if (endOperationDeferLogLocked()) { - logOperationLocked(null); - } + logOperationLocked(cookie, detail); } } - public void logOperation(String detail) { - synchronized (mOperations) { - logOperationLocked(detail); + private boolean endOperationDeferLogLocked(int cookie) { + final Operation operation = getOperationLocked(cookie); + if (operation != null) { + operation.mEndTime = System.currentTimeMillis(); + operation.mFinished = true; + return SQLiteDebug.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery( + operation.mEndTime - operation.mStartTime); } + return false; } - private void logOperationLocked(String detail) { - final Operation operation = mOperations[mIndex]; + private void logOperationLocked(int cookie, String detail) { + final Operation operation = getOperationLocked(cookie); StringBuilder msg = new StringBuilder(); operation.describe(msg); if (detail != null) { @@ -1044,6 +1094,17 @@ public final class SQLiteConnection { Log.d(TAG, msg.toString()); } + private int newOperationCookieLocked(int index) { + final int generation = mGeneration++; + return generation << COOKIE_GENERATION_SHIFT | index; + } + + private Operation getOperationLocked(int cookie) { + final int index = cookie & COOKIE_INDEX_MASK; + final Operation operation = mOperations[index]; + return operation.mCookie == cookie ? operation : null; + } + public String describeCurrentOperation() { synchronized (mOperations) { final Operation operation = mOperations[mIndex]; @@ -1097,6 +1158,7 @@ public final class SQLiteConnection { public ArrayList<Object> mBindArgs; public boolean mFinished; public Exception mException; + public int mCookie; public void describe(StringBuilder msg) { msg.append(mKind); diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java index b88bfee..5469213 100644 --- a/core/java/android/database/sqlite/SQLiteConnectionPool.java +++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java @@ -833,8 +833,9 @@ public final class SQLiteConnectionPool implements Closeable { * Dumps debugging information about this connection pool. * * @param printer The printer to receive the dump, not null. + * @param verbose True to dump more verbose information. */ - public void dump(Printer printer) { + public void dump(Printer printer, boolean verbose) { Printer indentedPrinter = PrefixPrinter.create(printer, " "); synchronized (mLock) { printer.println("Connection pool for " + mConfiguration.path + ":"); @@ -843,7 +844,7 @@ public final class SQLiteConnectionPool implements Closeable { printer.println(" Available primary connection:"); if (mAvailablePrimaryConnection != null) { - mAvailablePrimaryConnection.dump(indentedPrinter); + mAvailablePrimaryConnection.dump(indentedPrinter, verbose); } else { indentedPrinter.println("<none>"); } @@ -852,7 +853,7 @@ public final class SQLiteConnectionPool implements Closeable { if (!mAvailableNonPrimaryConnections.isEmpty()) { final int count = mAvailableNonPrimaryConnections.size(); for (int i = 0; i < count; i++) { - mAvailableNonPrimaryConnections.get(i).dump(indentedPrinter); + mAvailableNonPrimaryConnections.get(i).dump(indentedPrinter, verbose); } } else { indentedPrinter.println("<none>"); @@ -863,7 +864,7 @@ public final class SQLiteConnectionPool implements Closeable { for (Map.Entry<SQLiteConnection, Boolean> entry : mAcquiredConnections.entrySet()) { final SQLiteConnection connection = entry.getKey(); - connection.dumpUnsafe(indentedPrinter); + connection.dumpUnsafe(indentedPrinter, verbose); indentedPrinter.println(" Pending reconfiguration: " + entry.getValue()); } } else { diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java index 9dcb498..946300f 100644 --- a/core/java/android/database/sqlite/SQLiteCursor.java +++ b/core/java/android/database/sqlite/SQLiteCursor.java @@ -269,7 +269,6 @@ public class SQLiteCursor extends AbstractWindowedCursor { mStackTrace); } close(); - SQLiteDebug.notifyActiveCursorFinalized(); } } finally { super.finalize(); diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 377a680..9cb6480 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -1665,17 +1665,17 @@ public class SQLiteDatabase extends SQLiteClosable { * Dump detailed information about all open databases in the current process. * Used by bug report. */ - static void dumpAll(Printer printer) { + static void dumpAll(Printer printer, boolean verbose) { for (SQLiteDatabase db : getActiveDatabases()) { - db.dump(printer); + db.dump(printer, verbose); } } - private void dump(Printer printer) { + private void dump(Printer printer, boolean verbose) { synchronized (mLock) { if (mConnectionPoolLocked != null) { printer.println(""); - mConnectionPoolLocked.dump(printer); + mConnectionPoolLocked.dump(printer, verbose); } } } diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java index d87c3e4..95350ba 100644 --- a/core/java/android/database/sqlite/SQLiteDebug.java +++ b/core/java/android/database/sqlite/SQLiteDebug.java @@ -29,6 +29,8 @@ import android.util.Printer; * {@hide} */ public final class SQLiteDebug { + private static native void nativeGetPagerStats(PagerStats stats); + /** * Controls the printing of informational SQL log messages. */ @@ -49,31 +51,6 @@ public final class SQLiteDebug { Log.isLoggable("SQLiteTime", Log.VERBOSE); /** - * Controls the printing of compiled-sql-statement cache stats. - */ - public static final boolean DEBUG_SQL_CACHE = - Log.isLoggable("SQLiteCompiledSql", Log.VERBOSE); - - /** - * Controls the stack trace reporting of active cursors being - * finalized. - */ - public static final boolean DEBUG_ACTIVE_CURSOR_FINALIZATION = - Log.isLoggable("SQLiteCursorClosing", Log.VERBOSE); - - /** - * Controls the tracking of time spent holding the database lock. - */ - public static final boolean DEBUG_LOCK_TIME_TRACKING = - Log.isLoggable("SQLiteLockTime", Log.VERBOSE); - - /** - * Controls the printing of stack traces when tracking the time spent holding the database lock. - */ - public static final boolean DEBUG_LOCK_TIME_TRACKING_STACK_TRACE = - Log.isLoggable("SQLiteLockStackTrace", Log.VERBOSE); - - /** * True to enable database performance testing instrumentation. * @hide */ @@ -98,30 +75,9 @@ public final class SQLiteDebug { /** * Contains statistics about the active pagers in the current process. * - * @see #getPagerStats(PagerStats) + * @see #nativeGetPagerStats(PagerStats) */ public static class PagerStats { - /** The total number of bytes in all pagers in the current process - * @deprecated not used any longer - */ - @Deprecated - public long totalBytes; - /** The number of bytes in referenced pages in all pagers in the current process - * @deprecated not used any longer - * */ - @Deprecated - public long referencedBytes; - /** The number of bytes in all database files opened in the current process - * @deprecated not used any longer - */ - @Deprecated - public long databaseBytes; - /** The number of pagers opened in the current process - * @deprecated not used any longer - */ - @Deprecated - public int numPagers; - /** the current amount of memory checked out by sqlite using sqlite3_malloc(). * documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html */ @@ -134,7 +90,7 @@ public final class SQLiteDebug { * that overflowed because no space was left in the page cache. * documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html */ - public int pageCacheOverflo; + public int pageCacheOverflow; /** records the largest memory allocation request handed to sqlite3. * documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html @@ -182,7 +138,8 @@ public final class SQLiteDebug { */ public static PagerStats getDatabaseInfo() { PagerStats stats = new PagerStats(); - getPagerStats(stats); + SQLiteGlobal.initializeOnce(); + nativeGetPagerStats(stats); stats.dbStats = SQLiteDatabase.getDbStats(); return stats; } @@ -190,52 +147,16 @@ public final class SQLiteDebug { /** * Dumps detailed information about all databases used by the process. * @param printer The printer for dumping database state. + * @param args Command-line arguments supplied to dumpsys dbinfo */ public static void dump(Printer printer, String[] args) { - SQLiteDatabase.dumpAll(printer); - } - - /** - * Gathers statistics about all pagers in the current process. - */ - public static native void getPagerStats(PagerStats stats); - - /** - * Returns the size of the SQLite heap. - * @return The size of the SQLite heap in bytes. - */ - public static native long getHeapSize(); - - /** - * Returns the amount of allocated memory in the SQLite heap. - * @return The allocated size in bytes. - */ - public static native long getHeapAllocatedSize(); - - /** - * Returns the amount of free memory in the SQLite heap. - * @return The freed size in bytes. - */ - public static native long getHeapFreeSize(); - - /** - * Determines the number of dirty belonging to the SQLite - * heap segments of this process. pages[0] returns the number of - * shared pages, pages[1] returns the number of private pages - */ - public static native void getHeapDirtyPages(int[] pages); - - private static int sNumActiveCursorsFinalized = 0; - - /** - * Returns the number of active cursors that have been finalized. This depends on the GC having - * run but is still useful for tests. - */ - public static int getNumActiveCursorsFinalized() { - return sNumActiveCursorsFinalized; - } + boolean verbose = false; + for (String arg : args) { + if (arg.equals("-v")) { + verbose = true; + } + } - static synchronized void notifyActiveCursorFinalized() { - sNumActiveCursorsFinalized++; + SQLiteDatabase.dumpAll(printer, verbose); } } diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java index 61fe45a..a933051 100644 --- a/core/java/android/database/sqlite/SQLiteSession.java +++ b/core/java/android/database/sqlite/SQLiteSession.java @@ -150,6 +150,12 @@ import android.os.ParcelFileDescriptor; * A query that works well on 100 rows may struggle with 10,000.</li> * </ul> * + * <h2>Reentrance</h2> + * <p> + * This class must tolerate reentrant execution of SQLite operations because + * triggers may call custom SQLite functions that perform additional queries. + * </p> + * * TODO: Support timeouts on all possibly blocking operations. * * @hide @@ -159,6 +165,7 @@ public final class SQLiteSession { private SQLiteConnection mConnection; private int mConnectionFlags; + private int mConnectionUseCount; private Transaction mTransactionPool; private Transaction mTransactionStack; @@ -289,7 +296,9 @@ public final class SQLiteSession { private void beginTransactionUnchecked(int transactionMode, SQLiteTransactionListener transactionListener, int connectionFlags) { - acquireConnectionIfNoTransaction(null, connectionFlags); // might throw + if (mTransactionStack == null) { + acquireConnection(null, connectionFlags); // might throw + } try { // Set up the transaction such that we can back out safely // in case we fail part way. @@ -325,7 +334,9 @@ public final class SQLiteSession { transaction.mParent = mTransactionStack; mTransactionStack = transaction; } finally { - releaseConnectionIfNoTransaction(); // might throw + if (mTransactionStack == null) { + releaseConnection(); // might throw + } } } @@ -408,7 +419,7 @@ public final class SQLiteSession { mConnection.execute("ROLLBACK;", null); // might throw } } finally { - releaseConnectionIfNoTransaction(); // might throw + releaseConnection(); // might throw } } @@ -534,11 +545,11 @@ public final class SQLiteSession { throw new IllegalArgumentException("sql must not be null."); } - acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw + acquireConnection(sql, connectionFlags); // might throw try { mConnection.prepare(sql, outStatementInfo); // might throw } finally { - releaseConnectionIfNoTransaction(); // might throw + releaseConnection(); // might throw } } @@ -562,11 +573,11 @@ public final class SQLiteSession { return; } - acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw + acquireConnection(sql, connectionFlags); // might throw try { mConnection.execute(sql, bindArgs); // might throw } finally { - releaseConnectionIfNoTransaction(); // might throw + releaseConnection(); // might throw } } @@ -592,11 +603,11 @@ public final class SQLiteSession { return 0; } - acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw + acquireConnection(sql, connectionFlags); // might throw try { return mConnection.executeForLong(sql, bindArgs); // might throw } finally { - releaseConnectionIfNoTransaction(); // might throw + releaseConnection(); // might throw } } @@ -622,11 +633,11 @@ public final class SQLiteSession { return null; } - acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw + acquireConnection(sql, connectionFlags); // might throw try { return mConnection.executeForString(sql, bindArgs); // might throw } finally { - releaseConnectionIfNoTransaction(); // might throw + releaseConnection(); // might throw } } @@ -655,11 +666,11 @@ public final class SQLiteSession { return null; } - acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw + acquireConnection(sql, connectionFlags); // might throw try { return mConnection.executeForBlobFileDescriptor(sql, bindArgs); // might throw } finally { - releaseConnectionIfNoTransaction(); // might throw + releaseConnection(); // might throw } } @@ -685,11 +696,11 @@ public final class SQLiteSession { return 0; } - acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw + acquireConnection(sql, connectionFlags); // might throw try { return mConnection.executeForChangedRowCount(sql, bindArgs); // might throw } finally { - releaseConnectionIfNoTransaction(); // might throw + releaseConnection(); // might throw } } @@ -715,11 +726,11 @@ public final class SQLiteSession { return 0; } - acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw + acquireConnection(sql, connectionFlags); // might throw try { return mConnection.executeForLastInsertedRowId(sql, bindArgs); // might throw } finally { - releaseConnectionIfNoTransaction(); // might throw + releaseConnection(); // might throw } } @@ -760,12 +771,12 @@ public final class SQLiteSession { return 0; } - acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw + acquireConnection(sql, connectionFlags); // might throw try { return mConnection.executeForCursorWindow(sql, bindArgs, window, startPos, requiredPos, countAllRows); // might throw } finally { - releaseConnectionIfNoTransaction(); // might throw + releaseConnection(); // might throw } } @@ -807,16 +818,19 @@ public final class SQLiteSession { return false; } - private void acquireConnectionIfNoTransaction(String sql, int connectionFlags) { - if (mTransactionStack == null) { - assert mConnection == null; + private void acquireConnection(String sql, int connectionFlags) { + if (mConnection == null) { + assert mConnectionUseCount == 0; mConnection = mConnectionPool.acquireConnection(sql, connectionFlags); // might throw mConnectionFlags = connectionFlags; } + mConnectionUseCount += 1; } - private void releaseConnectionIfNoTransaction() { - if (mTransactionStack == null && mConnection != null) { + private void releaseConnection() { + assert mConnection != null; + assert mConnectionUseCount > 0; + if (--mConnectionUseCount == 0) { try { mConnectionPool.releaseConnection(mConnection); // might throw } finally { diff --git a/core/java/android/database/sqlite/SQLiteUnfinalizedObjectsException.java b/core/java/android/database/sqlite/SQLiteUnfinalizedObjectsException.java deleted file mode 100644 index bcf95e2..0000000 --- a/core/java/android/database/sqlite/SQLiteUnfinalizedObjectsException.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2010 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.database.sqlite; - -/** - * Thrown if the database can't be closed because of some un-closed - * Cursor or SQLiteStatement objects. Could happen when a thread is trying to close - * the database while another thread still hasn't closed a Cursor on that database. - * @hide - */ -public class SQLiteUnfinalizedObjectsException extends SQLiteException { - public SQLiteUnfinalizedObjectsException() {} - - public SQLiteUnfinalizedObjectsException(String error) { - super(error); - } -} diff --git a/core/java/android/inputmethodservice/ExtractEditText.java b/core/java/android/inputmethodservice/ExtractEditText.java index 10c1195..23ae21b 100644 --- a/core/java/android/inputmethodservice/ExtractEditText.java +++ b/core/java/android/inputmethodservice/ExtractEditText.java @@ -100,6 +100,9 @@ public class ExtractEditText extends EditText { @Override public boolean onTextContextMenuItem(int id) { if (mIME != null && mIME.onExtractTextContextMenuItem(id)) { + // Mode was started on Extracted, needs to be stopped here. + // Cut and paste will change the text, which stops selection mode. + if (id == android.R.id.copy) stopSelectionActionMode(); return true; } return super.onTextContextMenuItem(id); diff --git a/core/java/android/nfc/LlcpPacket.aidl b/core/java/android/nfc/LlcpPacket.aidl deleted file mode 100644 index 80f424d..0000000 --- a/core/java/android/nfc/LlcpPacket.aidl +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2010 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.nfc; - -/** - * @hide - */ -parcelable LlcpPacket;
\ No newline at end of file diff --git a/core/java/android/nfc/LlcpPacket.java b/core/java/android/nfc/LlcpPacket.java deleted file mode 100644 index 9919dc4..0000000 --- a/core/java/android/nfc/LlcpPacket.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2010 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.nfc; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Represents a LLCP packet received in a LLCP Connectionless communication; - * @hide - */ -public class LlcpPacket implements Parcelable { - - private final int mRemoteSap; - - private final byte[] mDataBuffer; - - /** - * Creates a LlcpPacket to be sent to a remote Service Access Point number - * (SAP) - * - * @param sap Remote Service Access Point number - * @param data Data buffer - */ - public LlcpPacket(int sap, byte[] data) { - mRemoteSap = sap; - mDataBuffer = data; - } - - /** - * Returns the remote Service Access Point number - */ - public int getRemoteSap() { - return mRemoteSap; - } - - /** - * Returns the data buffer - */ - public byte[] getDataBuffer() { - return mDataBuffer; - } - - public int describeContents() { - return 0; - } - - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mRemoteSap); - dest.writeInt(mDataBuffer.length); - dest.writeByteArray(mDataBuffer); - } - - public static final Parcelable.Creator<LlcpPacket> CREATOR = new Parcelable.Creator<LlcpPacket>() { - public LlcpPacket createFromParcel(Parcel in) { - // Remote SAP - short sap = (short)in.readInt(); - - // Data Buffer - int dataLength = in.readInt(); - byte[] data = new byte[dataLength]; - in.readByteArray(data); - - return new LlcpPacket(sap, data); - } - - public LlcpPacket[] newArray(int size) { - return new LlcpPacket[size]; - } - }; -}
\ No newline at end of file diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index e1bc275..cdf235d 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -219,6 +219,36 @@ public class Process { public static final int THREAD_PRIORITY_LESS_FAVORABLE = +1; /** + * Default scheduling policy + * @hide + */ + public static final int SCHED_OTHER = 0; + + /** + * First-In First-Out scheduling policy + * @hide + */ + public static final int SCHED_FIFO = 1; + + /** + * Round-Robin scheduling policy + * @hide + */ + public static final int SCHED_RR = 2; + + /** + * Batch scheduling policy + * @hide + */ + public static final int SCHED_BATCH = 3; + + /** + * Idle scheduling policy + * @hide + */ + public static final int SCHED_IDLE = 5; + + /** * Default thread group - gets a 'normal' share of the CPU * @hide */ @@ -675,6 +705,24 @@ public class Process { throws IllegalArgumentException; /** + * Set the scheduling policy and priority of a thread, based on Linux. + * + * @param tid The identifier of the thread/process to change. + * @param policy A Linux scheduling policy such as SCHED_OTHER etc. + * @param priority A Linux priority level in a range appropriate for the given policy. + * + * @throws IllegalArgumentException Throws IllegalArgumentException if + * <var>tid</var> does not exist, or if <var>priority</var> is out of range for the policy. + * @throws SecurityException Throws SecurityException if your process does + * not have permission to modify the given thread, or to use the given + * scheduling policy or priority. + * + * {@hide} + */ + public static final native void setThreadScheduler(int tid, int policy, int priority) + throws IllegalArgumentException; + + /** * Determine whether the current environment supports multiple processes. * * @return Returns true if the system can run in multiple processes, else diff --git a/core/java/android/provider/UserDictionary.java b/core/java/android/provider/UserDictionary.java index 5a7ef85..a9b106a 100644 --- a/core/java/android/provider/UserDictionary.java +++ b/core/java/android/provider/UserDictionary.java @@ -40,6 +40,9 @@ public class UserDictionary { public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY); + private static final int FREQUENCY_MIN = 0; + private static final int FREQUENCY_MAX = 255; + /** * Contains the user defined words. */ @@ -87,12 +90,24 @@ public class UserDictionary { */ public static final String APP_ID = "appid"; - /** The locale type to specify that the word is common to all locales. */ + /** + * An optional shortcut for this word. When the shortcut is typed, supporting IMEs should + * suggest the word in this row as an alternate spelling too. + */ + public static final String SHORTCUT = "shortcut"; + + /** + * @deprecated Use {@link #addWord(Context, String, int, String, Locale)}. + */ + @Deprecated public static final int LOCALE_TYPE_ALL = 0; - - /** The locale type to specify that the word is for the current locale. */ + + /** + * @deprecated Use {@link #addWord(Context, String, int, String, Locale)}. + */ + @Deprecated public static final int LOCALE_TYPE_CURRENT = 1; - + /** * Sort by descending order of frequency. */ @@ -100,35 +115,65 @@ public class UserDictionary { /** Adds a word to the dictionary, with the given frequency and the specified * specified locale type. + * + * @deprecated Please use + * {@link #addWord(Context, String, int, String, Locale)} instead. + * * @param context the current application context * @param word the word to add to the dictionary. This should not be null or * empty. * @param localeType the locale type for this word. It should be one of * {@link #LOCALE_TYPE_ALL} or {@link #LOCALE_TYPE_CURRENT}. */ - public static void addWord(Context context, String word, + @Deprecated + public static void addWord(Context context, String word, int frequency, int localeType) { - final ContentResolver resolver = context.getContentResolver(); - if (TextUtils.isEmpty(word) || localeType < 0 || localeType > 1) { + if (localeType != LOCALE_TYPE_ALL && localeType != LOCALE_TYPE_CURRENT) { return; } - - if (frequency < 0) frequency = 0; - if (frequency > 255) frequency = 255; - String locale = null; + final Locale locale; - // TODO: Verify if this is the best way to get the current locale if (localeType == LOCALE_TYPE_CURRENT) { - locale = Locale.getDefault().toString(); + locale = Locale.getDefault(); + } else { + locale = null; } - ContentValues values = new ContentValues(4); + + addWord(context, word, frequency, null, locale); + } + + /** Adds a word to the dictionary, with the given frequency and the specified + * locale type. + * + * @param context the current application context + * @param word the word to add to the dictionary. This should not be null or + * empty. + * @param shortcut optional shortcut spelling for this word. When the shortcut + * is typed, the word may be suggested by applications that support it. May be null. + * @param locale the locale to insert the word for, or null to insert the word + * for all locales. + */ + public static void addWord(Context context, String word, + int frequency, String shortcut, Locale locale) { + final ContentResolver resolver = context.getContentResolver(); + + if (TextUtils.isEmpty(word)) { + return; + } + + if (frequency < FREQUENCY_MIN) frequency = FREQUENCY_MIN; + if (frequency > FREQUENCY_MAX) frequency = FREQUENCY_MAX; + + final int COLUMN_COUNT = 5; + ContentValues values = new ContentValues(COLUMN_COUNT); values.put(WORD, word); values.put(FREQUENCY, frequency); - values.put(LOCALE, locale); + values.put(LOCALE, null == locale ? null : locale.toString()); values.put(APP_ID, 0); // TODO: Get App UID + values.put(SHORTCUT, shortcut); Uri result = resolver.insert(CONTENT_URI, values); // It's ok if the insert doesn't succeed because the word diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 8a9be85..e06d661 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -61,6 +61,7 @@ class GLES20Canvas extends HardwareCanvas { private final float[] mLine = new float[4]; private final Rect mClipBounds = new Rect(); + private final RectF mPathBounds = new RectF(); private DrawFilter mFilter; @@ -406,12 +407,18 @@ class GLES20Canvas extends HardwareCanvas { @Override public boolean clipPath(Path path) { - throw new UnsupportedOperationException(); + // TODO: Implement + path.computeBounds(mPathBounds, true); + return nClipRect(mRenderer, mPathBounds.left, mPathBounds.top, + mPathBounds.right, mPathBounds.bottom, Region.Op.INTERSECT.nativeInt); } @Override public boolean clipPath(Path path, Region.Op op) { - throw new UnsupportedOperationException(); + // TODO: Implement + path.computeBounds(mPathBounds, true); + return nClipRect(mRenderer, mPathBounds.left, mPathBounds.top, + mPathBounds.right, mPathBounds.bottom, op.nativeInt); } @Override @@ -459,12 +466,18 @@ class GLES20Canvas extends HardwareCanvas { @Override public boolean clipRegion(Region region) { - throw new UnsupportedOperationException(); + // TODO: Implement + region.getBounds(mClipBounds); + return nClipRect(mRenderer, mClipBounds.left, mClipBounds.top, + mClipBounds.right, mClipBounds.bottom, Region.Op.INTERSECT.nativeInt); } @Override public boolean clipRegion(Region region, Region.Op op) { - throw new UnsupportedOperationException(); + // TODO: Implement + region.getBounds(mClipBounds); + return nClipRect(mRenderer, mClipBounds.left, mClipBounds.top, + mClipBounds.right, mClipBounds.bottom, op.nativeInt); } @Override @@ -484,12 +497,14 @@ class GLES20Canvas extends HardwareCanvas { @Override public boolean quickReject(Path path, EdgeType type) { - throw new UnsupportedOperationException(); + path.computeBounds(mPathBounds, true); + return nQuickReject(mRenderer, mPathBounds.left, mPathBounds.top, + mPathBounds.right, mPathBounds.bottom, type.nativeInt); } @Override public boolean quickReject(RectF rect, EdgeType type) { - return quickReject(rect.left, rect.top, rect.right, rect.bottom, type); + return nQuickReject(mRenderer, rect.left, rect.top, rect.right, rect.bottom, type.nativeInt); } /////////////////////////////////////////////////////////////////////////// @@ -893,17 +908,42 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawPicture(Picture picture) { - throw new UnsupportedOperationException(); + if (picture.createdFromStream) { + return; + } + + picture.endRecording(); + // TODO: Implement rendering } @Override public void drawPicture(Picture picture, Rect dst) { - throw new UnsupportedOperationException(); + if (picture.createdFromStream) { + return; + } + + save(); + translate(dst.left, dst.top); + if (picture.getWidth() > 0 && picture.getHeight() > 0) { + scale(dst.width() / picture.getWidth(), dst.height() / picture.getHeight()); + } + drawPicture(picture); + restore(); } @Override public void drawPicture(Picture picture, RectF dst) { - throw new UnsupportedOperationException(); + if (picture.createdFromStream) { + return; + } + + save(); + translate(dst.left, dst.top); + if (picture.getWidth() > 0 && picture.getHeight() > 0) { + scale(dst.width() / picture.getWidth(), dst.height() / picture.getHeight()); + } + drawPicture(picture); + restore(); } @Override @@ -930,14 +970,38 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawPosText(char[] text, int index, int count, float[] pos, Paint paint) { - // TODO: Implement + if (index < 0 || index + count > text.length || count * 2 > pos.length) { + throw new IndexOutOfBoundsException(); + } + + int modifiers = setupModifiers(paint); + try { + nDrawPosText(mRenderer, text, index, count, pos, paint.mNativePaint); + } finally { + if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); + } } + private static native void nDrawPosText(int renderer, char[] text, int index, int count, + float[] pos, int paint); + @Override public void drawPosText(String text, float[] pos, Paint paint) { - // TODO: Implement + if (text.length() * 2 > pos.length) { + throw new ArrayIndexOutOfBoundsException(); + } + + int modifiers = setupModifiers(paint); + try { + nDrawPosText(mRenderer, text, 0, text.length(), pos, paint.mNativePaint); + } finally { + if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); + } } + private static native void nDrawPosText(int renderer, String text, int start, int end, + float[] pos, int paint); + @Override public void drawRect(float left, float top, float right, float bottom, Paint paint) { int modifiers = setupModifiers(paint); diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java index dfb2c32..1c35e31 100644 --- a/core/java/android/view/VelocityTracker.java +++ b/core/java/android/view/VelocityTracker.java @@ -29,7 +29,7 @@ import android.util.PoolableManager; * to begin tracking. Put the motion events you receive into it with * {@link #addMovement(MotionEvent)}. When you want to determine the velocity call * {@link #computeCurrentVelocity(int)} and then call {@link #getXVelocity(int)} - * and {@link #getXVelocity(int)} to retrieve the velocity for each pointer id. + * and {@link #getYVelocity(int)} to retrieve the velocity for each pointer id. */ public final class VelocityTracker implements Poolable<VelocityTracker> { private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool( @@ -39,6 +39,7 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { } public void onAcquired(VelocityTracker element) { + // Intentionally empty } public void onReleased(VelocityTracker element) { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index e280286..a9d6cdf 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -4107,7 +4107,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { event.setSource(this); - event.setClassName(getClass().getName()); + event.setClassName(View.class.getName()); event.setPackageName(getContext().getPackageName()); event.setEnabled(isEnabled()); event.setContentDescription(mContentDescription); @@ -4212,7 +4212,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } info.setPackageName(mContext.getPackageName()); - info.setClassName(getClass().getName()); + info.setClassName(View.class.getName()); info.setContentDescription(getContentDescription()); info.setEnabled(isEnabled()); @@ -7597,15 +7597,17 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ public void setAlpha(float alpha) { ensureTransformationInfo(); - mTransformationInfo.mAlpha = alpha; - invalidateParentCaches(); - if (onSetAlpha((int) (alpha * 255))) { - mPrivateFlags |= ALPHA_SET; - // subclass is handling alpha - don't optimize rendering cache invalidation - invalidate(true); - } else { - mPrivateFlags &= ~ALPHA_SET; - invalidate(false); + if (mTransformationInfo.mAlpha != alpha) { + mTransformationInfo.mAlpha = alpha; + invalidateParentCaches(); + if (onSetAlpha((int) (alpha * 255))) { + mPrivateFlags |= ALPHA_SET; + // subclass is handling alpha - don't optimize rendering cache invalidation + invalidate(true); + } else { + mPrivateFlags &= ~ALPHA_SET; + invalidate(false); + } } } @@ -7616,18 +7618,22 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * alpha (the return value for onSetAlpha()). * * @param alpha The new value for the alpha property - * @return true if the View subclass handles alpha (the return value for onSetAlpha()) + * @return true if the View subclass handles alpha (the return value for onSetAlpha()) and + * the new value for the alpha property is different from the old value */ boolean setAlphaNoInvalidation(float alpha) { ensureTransformationInfo(); - mTransformationInfo.mAlpha = alpha; - boolean subclassHandlesAlpha = onSetAlpha((int) (alpha * 255)); - if (subclassHandlesAlpha) { - mPrivateFlags |= ALPHA_SET; - } else { - mPrivateFlags &= ~ALPHA_SET; + if (mTransformationInfo.mAlpha != alpha) { + mTransformationInfo.mAlpha = alpha; + boolean subclassHandlesAlpha = onSetAlpha((int) (alpha * 255)); + if (subclassHandlesAlpha) { + mPrivateFlags |= ALPHA_SET; + return true; + } else { + mPrivateFlags &= ~ALPHA_SET; + } } - return subclassHandlesAlpha; + return false; } /** diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 09901ff..5c63366 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -2229,6 +2229,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @Override void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfoInternal(info); + info.setClassName(ViewGroup.class.getName()); for (int i = 0, count = mChildrenCount; i < count; i++) { View child = mChildren[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE @@ -2238,6 +2239,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + @Override + void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); + event.setClassName(ViewGroup.class.getName()); + } + /** * {@inheritDoc} */ diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java index c3c74a7..c28b220 100755 --- a/core/java/android/view/WindowOrientationListener.java +++ b/core/java/android/view/WindowOrientationListener.java @@ -21,6 +21,7 @@ import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.util.FloatMath; import android.util.Log; import android.util.Slog; @@ -48,6 +49,8 @@ public abstract class WindowOrientationListener { private static final boolean DEBUG = false; private static final boolean localLOGV = DEBUG || false; + private static final boolean USE_GRAVITY_SENSOR = false; + private SensorManager mSensorManager; private boolean mEnabled; private int mRate; @@ -79,7 +82,8 @@ public abstract class WindowOrientationListener { private WindowOrientationListener(Context context, int rate) { mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); mRate = rate; - mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + mSensor = mSensorManager.getDefaultSensor(USE_GRAVITY_SENSOR + ? Sensor.TYPE_GRAVITY : Sensor.TYPE_ACCELEROMETER); if (mSensor != null) { // Create listener only if sensors do exist mSensorEventListener = new SensorEventListenerImpl(this); @@ -179,7 +183,7 @@ public abstract class WindowOrientationListener { * cartesian space because the orientation calculations are sensitive to the * absolute magnitude of the acceleration. In particular, there are singularities * in the calculation as the magnitude approaches 0. By performing the low-pass - * filtering early, we can eliminate high-frequency impulses systematically. + * filtering early, we can eliminate most spurious high-frequency impulses due to noise. * * - Convert the acceleromter vector from cartesian to spherical coordinates. * Since we're dealing with rotation of the device, this is the sensible coordinate @@ -204,11 +208,17 @@ public abstract class WindowOrientationListener { * new orientation proposal. * * Details are explained inline. + * + * See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for + * signal processing background. */ static final class SensorEventListenerImpl implements SensorEventListener { // We work with all angles in degrees in this class. private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI); + // Number of nanoseconds per millisecond. + private static final long NANOS_PER_MS = 1000000; + // Indices into SensorEvent.values for the accelerometer sensor. private static final int ACCELEROMETER_DATA_X = 0; private static final int ACCELEROMETER_DATA_Y = 1; @@ -216,38 +226,41 @@ public abstract class WindowOrientationListener { private final WindowOrientationListener mOrientationListener; - /* State for first order low-pass filtering of accelerometer data. - * See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for - * signal processing background. - */ - - private long mLastTimestamp = Long.MAX_VALUE; // in nanoseconds - private float mLastFilteredX, mLastFilteredY, mLastFilteredZ; - - // The current proposal. We wait for the proposal to be stable for a - // certain amount of time before accepting it. - // - // The basic idea is to ignore intermediate poses of the device while the - // user is picking up, putting down or turning the device. - private int mProposalRotation; - private long mProposalAgeMS; - - // A historical trace of tilt and orientation angles. Used to determine whether - // the device posture has settled down. - private static final int HISTORY_SIZE = 20; - private int mHistoryIndex; // index of most recent sample - private int mHistoryLength; // length of historical trace - private final long[] mHistoryTimestampMS = new long[HISTORY_SIZE]; - private final float[] mHistoryMagnitudes = new float[HISTORY_SIZE]; - private final int[] mHistoryTiltAngles = new int[HISTORY_SIZE]; - private final int[] mHistoryOrientationAngles = new int[HISTORY_SIZE]; + // The minimum amount of time that a predicted rotation must be stable before it + // is accepted as a valid rotation proposal. This value can be quite small because + // the low-pass filter already suppresses most of the noise so we're really just + // looking for quick confirmation that the last few samples are in agreement as to + // the desired orientation. + private static final long PROPOSAL_SETTLE_TIME_NANOS = 40 * NANOS_PER_MS; + + // The minimum amount of time that must have elapsed since the device last exited + // the flat state (time since it was picked up) before the proposed rotation + // can change. + private static final long PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS = 500 * NANOS_PER_MS; + + // The mininum amount of time that must have elapsed since the device stopped + // swinging (time since device appeared to be in the process of being put down + // or put away into a pocket) before the proposed rotation can change. + private static final long PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS = 300 * NANOS_PER_MS; + + // If the tilt angle remains greater than the specified angle for a minimum of + // the specified time, then the device is deemed to be lying flat + // (just chillin' on a table). + private static final float FLAT_ANGLE = 75; + private static final long FLAT_TIME_NANOS = 1000 * NANOS_PER_MS; + + // If the tilt angle has increased by at least delta degrees within the specified amount + // of time, then the device is deemed to be swinging away from the user + // down towards flat (tilt = 90). + private static final float SWING_AWAY_ANGLE_DELTA = 20; + private static final long SWING_TIME_NANOS = 300 * NANOS_PER_MS; // The maximum sample inter-arrival time in milliseconds. // If the acceleration samples are further apart than this amount in time, we reset the // state of the low-pass filter and orientation properties. This helps to handle // boundary conditions when the device is turned on, wakes from suspend or there is // a significant gap in samples. - private static final float MAX_FILTER_DELTA_TIME_MS = 1000; + private static final long MAX_FILTER_DELTA_TIME_NANOS = 1000 * NANOS_PER_MS; // The acceleration filter time constant. // @@ -267,8 +280,10 @@ public abstract class WindowOrientationListener { // // Filtering adds latency proportional the time constant (inversely proportional // to the cutoff frequency) so we don't want to make the time constant too - // large or we can lose responsiveness. - private static final float FILTER_TIME_CONSTANT_MS = 100.0f; + // large or we can lose responsiveness. Likewise we don't want to make it too + // small or we do a poor job suppressing acceleration spikes. + // Empirically, 100ms seems to be too small and 500ms is too large. + private static final float FILTER_TIME_CONSTANT_MS = 200.0f; /* State for orientation detection. */ @@ -286,9 +301,9 @@ public abstract class WindowOrientationListener { // // In both cases, we postpone choosing an orientation. private static final float MIN_ACCELERATION_MAGNITUDE = - SensorManager.STANDARD_GRAVITY * 0.5f; + SensorManager.STANDARD_GRAVITY * 0.3f; private static final float MAX_ACCELERATION_MAGNITUDE = - SensorManager.STANDARD_GRAVITY * 1.5f; + SensorManager.STANDARD_GRAVITY * 1.25f; // Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e. // when screen is facing the sky or ground), we completely ignore orientation data. @@ -306,10 +321,10 @@ public abstract class WindowOrientationListener { // The ideal tilt angle is 0 (when the device is vertical) so the limits establish // how close to vertical the device must be in order to change orientation. private static final int[][] TILT_TOLERANCE = new int[][] { - /* ROTATION_0 */ { -20, 70 }, - /* ROTATION_90 */ { -20, 60 }, - /* ROTATION_180 */ { -20, 50 }, - /* ROTATION_270 */ { -20, 60 } + /* ROTATION_0 */ { -25, 70 }, + /* ROTATION_90 */ { -25, 65 }, + /* ROTATION_180 */ { -25, 60 }, + /* ROTATION_270 */ { -25, 65 } }; // The gap angle in degrees between adjacent orientation angles for hysteresis. @@ -319,29 +334,38 @@ public abstract class WindowOrientationListener { // orientation. private static final int ADJACENT_ORIENTATION_ANGLE_GAP = 45; - // The number of milliseconds for which the device posture must be stable - // before we perform an orientation change. If the device appears to be rotating - // (being picked up, put down) then we keep waiting until it settles. - private static final int SETTLE_TIME_MS = 200; + // Timestamp and value of the last accelerometer sample. + private long mLastFilteredTimestampNanos; + private float mLastFilteredX, mLastFilteredY, mLastFilteredZ; + + // The last proposed rotation, -1 if unknown. + private int mProposedRotation; + + // Value of the current predicted rotation, -1 if unknown. + private int mPredictedRotation; + + // Timestamp of when the predicted rotation most recently changed. + private long mPredictedRotationTimestampNanos; - // The maximum change in magnitude that can occur during the settle time. - // Tuning this constant particularly helps to filter out situations where the - // device is being picked up or put down by the user. - private static final float SETTLE_MAGNITUDE_MAX_DELTA = - SensorManager.STANDARD_GRAVITY * 0.2f; + // Timestamp when the device last appeared to be flat for sure (the flat delay elapsed). + private long mFlatTimestampNanos; - // The maximum change in tilt angle that can occur during the settle time. - private static final int SETTLE_TILT_ANGLE_MAX_DELTA = 5; + // Timestamp when the device last appeared to be swinging. + private long mSwingTimestampNanos; - // The maximum change in orientation angle that can occur during the settle time. - private static final int SETTLE_ORIENTATION_ANGLE_MAX_DELTA = 5; + // History of observed tilt angles. + private static final int TILT_HISTORY_SIZE = 40; + private float[] mTiltHistory = new float[TILT_HISTORY_SIZE]; + private long[] mTiltHistoryTimestampNanos = new long[TILT_HISTORY_SIZE]; + private int mTiltHistoryIndex; public SensorEventListenerImpl(WindowOrientationListener orientationListener) { mOrientationListener = orientationListener; + reset(); } public int getProposedRotation() { - return mProposalAgeMS >= SETTLE_TIME_MS ? mProposalRotation : -1; + return mProposedRotation; } @Override @@ -359,8 +383,9 @@ public abstract class WindowOrientationListener { float z = event.values[ACCELEROMETER_DATA_Z]; if (log) { - Slog.v(TAG, "Raw acceleration vector: " + - "x=" + x + ", y=" + y + ", z=" + z); + Slog.v(TAG, "Raw acceleration vector: " + + "x=" + x + ", y=" + y + ", z=" + z + + ", magnitude=" + FloatMath.sqrt(x * x + y * y + z * z)); } // Apply a low-pass filter to the acceleration up vector in cartesian space. @@ -368,14 +393,16 @@ public abstract class WindowOrientationListener { // or when we see values of (0, 0, 0) which indicates that we polled the // accelerometer too soon after turning it on and we don't have any data yet. final long now = event.timestamp; - final float timeDeltaMS = (now - mLastTimestamp) * 0.000001f; - boolean skipSample; - if (timeDeltaMS <= 0 || timeDeltaMS > MAX_FILTER_DELTA_TIME_MS + final long then = mLastFilteredTimestampNanos; + final float timeDeltaMS = (now - then) * 0.000001f; + final boolean skipSample; + if (now < then + || now > then + MAX_FILTER_DELTA_TIME_NANOS || (x == 0 && y == 0 && z == 0)) { if (log) { Slog.v(TAG, "Resetting orientation listener."); } - clearProposal(); + reset(); skipSample = true; } else { final float alpha = timeDeltaMS / (FILTER_TIME_CONSTANT_MS + timeDeltaMS); @@ -383,27 +410,28 @@ public abstract class WindowOrientationListener { y = alpha * (y - mLastFilteredY) + mLastFilteredY; z = alpha * (z - mLastFilteredZ) + mLastFilteredZ; if (log) { - Slog.v(TAG, "Filtered acceleration vector: " + - "x=" + x + ", y=" + y + ", z=" + z); + Slog.v(TAG, "Filtered acceleration vector: " + + "x=" + x + ", y=" + y + ", z=" + z + + ", magnitude=" + FloatMath.sqrt(x * x + y * y + z * z)); } skipSample = false; } - mLastTimestamp = now; + mLastFilteredTimestampNanos = now; mLastFilteredX = x; mLastFilteredY = y; mLastFilteredZ = z; - final int oldProposedRotation = getProposedRotation(); + boolean isFlat = false; + boolean isSwinging = false; if (!skipSample) { // Calculate the magnitude of the acceleration vector. - final float magnitude = (float) Math.sqrt(x * x + y * y + z * z); + final float magnitude = FloatMath.sqrt(x * x + y * y + z * z); if (magnitude < MIN_ACCELERATION_MAGNITUDE || magnitude > MAX_ACCELERATION_MAGNITUDE) { if (log) { - Slog.v(TAG, "Ignoring sensor data, magnitude out of range: " - + "magnitude=" + magnitude); + Slog.v(TAG, "Ignoring sensor data, magnitude out of range."); } - clearProposal(); + clearPredictedRotation(); } else { // Calculate the tilt angle. // This is the angle between the up vector and the x-y plane (the plane of @@ -414,14 +442,25 @@ public abstract class WindowOrientationListener { final int tiltAngle = (int) Math.round( Math.asin(z / magnitude) * RADIANS_TO_DEGREES); + // Determine whether the device appears to be flat or swinging. + if (isFlat(now)) { + isFlat = true; + mFlatTimestampNanos = now; + } + if (isSwinging(now, tiltAngle)) { + isSwinging = true; + mSwingTimestampNanos = now; + } + addTiltHistoryEntry(now, tiltAngle); + // If the tilt angle is too close to horizontal then we cannot determine // the orientation angle of the screen. if (Math.abs(tiltAngle) > MAX_TILT) { if (log) { Slog.v(TAG, "Ignoring sensor data, tilt angle too high: " - + "magnitude=" + magnitude + ", tiltAngle=" + tiltAngle); + + "tiltAngle=" + tiltAngle); } - clearProposal(); + clearPredictedRotation(); } else { // Calculate the orientation angle. // This is the angle between the x-y projection of the up vector onto @@ -439,89 +478,93 @@ public abstract class WindowOrientationListener { nearestRotation = 0; } - // Determine the proposed orientation. - // The confidence of the proposal is 1.0 when it is ideal and it - // decays exponentially as the proposal moves further from the ideal - // angle, tilt and magnitude of the proposed orientation. - if (!isTiltAngleAcceptable(nearestRotation, tiltAngle) - || !isOrientationAngleAcceptable(nearestRotation, + // Determine the predicted orientation. + if (isTiltAngleAcceptable(nearestRotation, tiltAngle) + && isOrientationAngleAcceptable(nearestRotation, orientationAngle)) { + updatePredictedRotation(now, nearestRotation); if (log) { - Slog.v(TAG, "Ignoring sensor data, no proposal: " - + "magnitude=" + magnitude + ", tiltAngle=" + tiltAngle - + ", orientationAngle=" + orientationAngle); + Slog.v(TAG, "Predicted: " + + "tiltAngle=" + tiltAngle + + ", orientationAngle=" + orientationAngle + + ", predictedRotation=" + mPredictedRotation + + ", predictedRotationAgeMS=" + + ((now - mPredictedRotationTimestampNanos) + * 0.000001f)); } - clearProposal(); } else { if (log) { - Slog.v(TAG, "Proposal: " - + "magnitude=" + magnitude - + ", tiltAngle=" + tiltAngle - + ", orientationAngle=" + orientationAngle - + ", proposalRotation=" + mProposalRotation); + Slog.v(TAG, "Ignoring sensor data, no predicted rotation: " + + "tiltAngle=" + tiltAngle + + ", orientationAngle=" + orientationAngle); } - updateProposal(nearestRotation, now / 1000000L, - magnitude, tiltAngle, orientationAngle); + clearPredictedRotation(); } } } } + // Determine new proposed rotation. + final int oldProposedRotation = mProposedRotation; + if (mPredictedRotation < 0 || isPredictedRotationAcceptable(now)) { + mProposedRotation = mPredictedRotation; + } + // Write final statistics about where we are in the orientation detection process. - final int proposedRotation = getProposedRotation(); if (log) { - final float proposalConfidence = Math.min( - mProposalAgeMS * 1.0f / SETTLE_TIME_MS, 1.0f); Slog.v(TAG, "Result: currentRotation=" + mOrientationListener.mCurrentRotation - + ", proposedRotation=" + proposedRotation + + ", proposedRotation=" + mProposedRotation + + ", predictedRotation=" + mPredictedRotation + ", timeDeltaMS=" + timeDeltaMS - + ", proposalRotation=" + mProposalRotation - + ", proposalAgeMS=" + mProposalAgeMS - + ", proposalConfidence=" + proposalConfidence); + + ", isFlat=" + isFlat + + ", isSwinging=" + isSwinging + + ", timeUntilSettledMS=" + remainingMS(now, + mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS) + + ", timeUntilFlatDelayExpiredMS=" + remainingMS(now, + mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS) + + ", timeUntilSwingDelayExpiredMS=" + remainingMS(now, + mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS)); } // Tell the listener. - if (proposedRotation != oldProposedRotation && proposedRotation >= 0) { + if (mProposedRotation != oldProposedRotation && mProposedRotation >= 0) { if (log) { - Slog.v(TAG, "Proposed rotation changed! proposedRotation=" + proposedRotation + Slog.v(TAG, "Proposed rotation changed! proposedRotation=" + mProposedRotation + ", oldProposedRotation=" + oldProposedRotation); } - mOrientationListener.onProposedRotationChanged(proposedRotation); + mOrientationListener.onProposedRotationChanged(mProposedRotation); } } /** - * Returns true if the tilt angle is acceptable for a proposed - * orientation transition. + * Returns true if the tilt angle is acceptable for a given predicted rotation. */ - private boolean isTiltAngleAcceptable(int proposedRotation, - int tiltAngle) { - return tiltAngle >= TILT_TOLERANCE[proposedRotation][0] - && tiltAngle <= TILT_TOLERANCE[proposedRotation][1]; + private boolean isTiltAngleAcceptable(int rotation, int tiltAngle) { + return tiltAngle >= TILT_TOLERANCE[rotation][0] + && tiltAngle <= TILT_TOLERANCE[rotation][1]; } /** - * Returns true if the orientation angle is acceptable for a proposed - * orientation transition. + * Returns true if the orientation angle is acceptable for a given predicted rotation. * * This function takes into account the gap between adjacent orientations * for hysteresis. */ - private boolean isOrientationAngleAcceptable(int proposedRotation, int orientationAngle) { + private boolean isOrientationAngleAcceptable(int rotation, int orientationAngle) { // If there is no current rotation, then there is no gap. // The gap is used only to introduce hysteresis among advertised orientation // changes to avoid flapping. final int currentRotation = mOrientationListener.mCurrentRotation; if (currentRotation >= 0) { - // If the proposed rotation is the same or is counter-clockwise adjacent, - // then we set a lower bound on the orientation angle. + // If the specified rotation is the same or is counter-clockwise adjacent + // to the current rotation, then we set a lower bound on the orientation angle. // For example, if currentRotation is ROTATION_0 and proposed is ROTATION_90, // then we want to check orientationAngle > 45 + GAP / 2. - if (proposedRotation == currentRotation - || proposedRotation == (currentRotation + 1) % 4) { - int lowerBound = proposedRotation * 90 - 45 + if (rotation == currentRotation + || rotation == (currentRotation + 1) % 4) { + int lowerBound = rotation * 90 - 45 + ADJACENT_ORIENTATION_ANGLE_GAP / 2; - if (proposedRotation == 0) { + if (rotation == 0) { if (orientationAngle >= 315 && orientationAngle < lowerBound + 360) { return false; } @@ -532,15 +575,15 @@ public abstract class WindowOrientationListener { } } - // If the proposed rotation is the same or is clockwise adjacent, + // If the specified rotation is the same or is clockwise adjacent, // then we set an upper bound on the orientation angle. - // For example, if currentRotation is ROTATION_0 and proposed is ROTATION_270, + // For example, if currentRotation is ROTATION_0 and rotation is ROTATION_270, // then we want to check orientationAngle < 315 - GAP / 2. - if (proposedRotation == currentRotation - || proposedRotation == (currentRotation + 3) % 4) { - int upperBound = proposedRotation * 90 + 45 + if (rotation == currentRotation + || rotation == (currentRotation + 3) % 4) { + int upperBound = rotation * 90 + 45 - ADJACENT_ORIENTATION_ANGLE_GAP / 2; - if (proposedRotation == 0) { + if (rotation == 0) { if (orientationAngle <= 45 && orientationAngle > upperBound) { return false; } @@ -554,58 +597,97 @@ public abstract class WindowOrientationListener { return true; } - private void clearProposal() { - mProposalRotation = -1; - mProposalAgeMS = 0; - } + /** + * Returns true if the predicted rotation is ready to be advertised as a + * proposed rotation. + */ + private boolean isPredictedRotationAcceptable(long now) { + // The predicted rotation must have settled long enough. + if (now < mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS) { + return false; + } + + // The last flat state (time since picked up) must have been sufficiently long ago. + if (now < mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS) { + return false; + } - private void updateProposal(int rotation, long timestampMS, - float magnitude, int tiltAngle, int orientationAngle) { - if (mProposalRotation != rotation) { - mProposalRotation = rotation; - mHistoryIndex = 0; - mHistoryLength = 0; + // The last swing state (time since last movement to put down) must have been + // sufficiently long ago. + if (now < mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS) { + return false; } - final int index = mHistoryIndex; - mHistoryTimestampMS[index] = timestampMS; - mHistoryMagnitudes[index] = magnitude; - mHistoryTiltAngles[index] = tiltAngle; - mHistoryOrientationAngles[index] = orientationAngle; - mHistoryIndex = (index + 1) % HISTORY_SIZE; - if (mHistoryLength < HISTORY_SIZE) { - mHistoryLength += 1; + // Looks good! + return true; + } + + private void reset() { + mLastFilteredTimestampNanos = Long.MIN_VALUE; + mProposedRotation = -1; + mFlatTimestampNanos = Long.MIN_VALUE; + mSwingTimestampNanos = Long.MIN_VALUE; + clearPredictedRotation(); + clearTiltHistory(); + } + + private void clearPredictedRotation() { + mPredictedRotation = -1; + mPredictedRotationTimestampNanos = Long.MIN_VALUE; + } + + private void updatePredictedRotation(long now, int rotation) { + if (mPredictedRotation != rotation) { + mPredictedRotation = rotation; + mPredictedRotationTimestampNanos = now; } + } - long age = 0; - for (int i = 1; i < mHistoryLength; i++) { - final int olderIndex = (index + HISTORY_SIZE - i) % HISTORY_SIZE; - if (Math.abs(mHistoryMagnitudes[olderIndex] - magnitude) - > SETTLE_MAGNITUDE_MAX_DELTA) { + private void clearTiltHistory() { + mTiltHistoryTimestampNanos[0] = Long.MIN_VALUE; + mTiltHistoryIndex = 1; + } + + private void addTiltHistoryEntry(long now, float tilt) { + mTiltHistory[mTiltHistoryIndex] = tilt; + mTiltHistoryTimestampNanos[mTiltHistoryIndex] = now; + mTiltHistoryIndex = (mTiltHistoryIndex + 1) % TILT_HISTORY_SIZE; + mTiltHistoryTimestampNanos[mTiltHistoryIndex] = Long.MIN_VALUE; + } + + private boolean isFlat(long now) { + for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndex(i)) >= 0; ) { + if (mTiltHistory[i] < FLAT_ANGLE) { break; } - if (angleAbsoluteDelta(mHistoryTiltAngles[olderIndex], - tiltAngle) > SETTLE_TILT_ANGLE_MAX_DELTA) { - break; + if (mTiltHistoryTimestampNanos[i] + FLAT_TIME_NANOS <= now) { + // Tilt has remained greater than FLAT_TILT_ANGLE for FLAT_TIME_NANOS. + return true; } - if (angleAbsoluteDelta(mHistoryOrientationAngles[olderIndex], - orientationAngle) > SETTLE_ORIENTATION_ANGLE_MAX_DELTA) { + } + return false; + } + + private boolean isSwinging(long now, float tilt) { + for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndex(i)) >= 0; ) { + if (mTiltHistoryTimestampNanos[i] + SWING_TIME_NANOS < now) { break; } - age = timestampMS - mHistoryTimestampMS[olderIndex]; - if (age >= SETTLE_TIME_MS) { - break; + if (mTiltHistory[i] + SWING_AWAY_ANGLE_DELTA <= tilt) { + // Tilted away by SWING_AWAY_ANGLE_DELTA within SWING_TIME_NANOS. + return true; } } - mProposalAgeMS = age; + return false; } - private static int angleAbsoluteDelta(int a, int b) { - int delta = Math.abs(a - b); - if (delta > 180) { - delta = 360 - delta; - } - return delta; + private int nextTiltHistoryIndex(int index) { + index = (index == 0 ? TILT_HISTORY_SIZE : index) - 1; + return mTiltHistoryTimestampNanos[index] != Long.MIN_VALUE ? index : -1; + } + + private static float remainingMS(long now, long until) { + return now >= until ? 0 : (until - now) * 0.000001f; } } } diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java index 21364c1..bc0557e 100644 --- a/core/java/android/webkit/HTML5VideoFullScreen.java +++ b/core/java/android/webkit/HTML5VideoFullScreen.java @@ -198,6 +198,10 @@ public class HTML5VideoFullScreen extends HTML5VideoView // Call into the native to ask for the state, if still in play mode, // this will trigger the video to play. mProxy.dispatchOnRestoreState(); + + if (getStartWhenPrepared()) { + mPlayer.start(); + } } public boolean fullScreenExited() { diff --git a/core/java/android/webkit/HTML5VideoView.java b/core/java/android/webkit/HTML5VideoView.java index 1d8bda7..73166cb 100644 --- a/core/java/android/webkit/HTML5VideoView.java +++ b/core/java/android/webkit/HTML5VideoView.java @@ -194,20 +194,9 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { mPlayer.setOnInfoListener(proxy); } - // Normally called immediately after setVideoURI. But for full screen, - // this should be after surface holder created - public void prepareDataAndDisplayMode(HTML5VideoViewProxy proxy) { - // SurfaceTexture will be created lazily here for inline mode - decideDisplayMode(); - - setOnCompletionListener(proxy); - setOnPreparedListener(proxy); - setOnErrorListener(proxy); - setOnInfoListener(proxy); - // When there is exception, we could just bail out silently. - // No Video will be played though. Write the stack for debug + public void prepareDataCommon(HTML5VideoViewProxy proxy) { try { - mPlayer.setDataSource(mProxy.getContext(), mUri, mHeaders); + mPlayer.setDataSource(proxy.getContext(), mUri, mHeaders); mPlayer.prepareAsync(); } catch (IllegalArgumentException e) { e.printStackTrace(); @@ -219,6 +208,25 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { mCurrentState = STATE_NOTPREPARED; } + public void reprepareData(HTML5VideoViewProxy proxy) { + mPlayer.reset(); + prepareDataCommon(proxy); + } + + // Normally called immediately after setVideoURI. But for full screen, + // this should be after surface holder created + public void prepareDataAndDisplayMode(HTML5VideoViewProxy proxy) { + // SurfaceTexture will be created lazily here for inline mode + decideDisplayMode(); + + setOnCompletionListener(proxy); + setOnPreparedListener(proxy); + setOnErrorListener(proxy); + setOnInfoListener(proxy); + + prepareDataCommon(proxy); + } + // Common code public int getVideoLayerId() { @@ -324,4 +332,14 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { return false; } + private boolean m_startWhenPrepared = false; + + public void setStartWhenPrepared(boolean willPlay) { + m_startWhenPrepared = willPlay; + } + + public boolean getStartWhenPrepared() { + return m_startWhenPrepared; + } + } diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java index 7c2dc38..d306c86 100644 --- a/core/java/android/webkit/HTML5VideoViewProxy.java +++ b/core/java/android/webkit/HTML5VideoViewProxy.java @@ -162,6 +162,16 @@ class HTML5VideoViewProxy extends Handler mHTML5VideoView.enterFullScreenVideoState(layerId, proxy, webView); } + public static void exitFullScreenVideo(HTML5VideoViewProxy proxy, + WebView webView) { + if (!mHTML5VideoView.fullScreenExited() && mHTML5VideoView.isFullScreenMode()) { + WebChromeClient client = webView.getWebChromeClient(); + if (client != null) { + client.onHideCustomView(); + } + } + } + // This is on the UI thread. // When native tell Java to play, we need to check whether or not it is // still the same video by using videoLayerId and treat it differently. @@ -172,6 +182,21 @@ class HTML5VideoViewProxy extends Handler if (mHTML5VideoView != null) { currentVideoLayerId = mHTML5VideoView.getVideoLayerId(); backFromFullScreenMode = mHTML5VideoView.fullScreenExited(); + + // When playing video back to back in full screen mode, + // javascript will switch the src and call play. + // In this case, we can just reuse the same full screen view, + // and play the video after prepared. + if (mHTML5VideoView.isFullScreenMode() + && !backFromFullScreenMode + && currentVideoLayerId != videoLayerId + && mCurrentProxy != proxy) { + mCurrentProxy = proxy; + mHTML5VideoView.setStartWhenPrepared(true); + mHTML5VideoView.setVideoURI(url, proxy); + mHTML5VideoView.reprepareData(proxy); + return; + } } if (backFromFullScreenMode @@ -682,6 +707,10 @@ class HTML5VideoViewProxy extends Handler VideoPlayer.enterFullScreenVideo(layerId, url, this, mWebView); } + public void exitFullScreenVideo() { + VideoPlayer.exitFullScreenVideo(this, mWebView); + } + /** * The factory for HTML5VideoViewProxy instances. * @param webViewCore is the WebViewCore that is requesting the proxy. diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index ab4665a..69c15a6 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -327,6 +327,15 @@ import static javax.microedition.khronos.egl.EGL10.EGL_DEFAULT_DISPLAY; * property to {@code device-dpi}. This stops Android from performing scaling in your web page and * allows you to make the necessary adjustments for each density via CSS and JavaScript.</p> * + * <h3>HTML5 Video support</h3> + * + * <p>In order to support inline HTML5 video in your application, you need to have hardware + * acceleration turned on, and set a {@link android.webkit.WebChromeClient}. For full screen support, + * implementations of {@link WebChromeClient#onShowCustomView(View, WebChromeClient.CustomViewCallback)} + * and {@link WebChromeClient#onHideCustomView()} are required, + * {@link WebChromeClient#getVideoLoadingProgressView()} is optional. + * </p> + * * */ @Widget @@ -413,7 +422,7 @@ public class WebView extends AbsoluteLayout private final Rect mViewRectViewport = new Rect(); private final RectF mVisibleContentRect = new RectF(); private boolean mGLViewportEmpty = false; - WebViewInputConnection mInputConnection = new WebViewInputConnection(); + WebViewInputConnection mInputConnection = null; /** @@ -611,6 +620,7 @@ public class WebView extends AbsoluteLayout private boolean mIsPaused; private HitTestResult mInitialHitTestResult; + private WebKitHitTest mFocusedNode; /** * Customizable constant @@ -702,13 +712,11 @@ public class WebView extends AbsoluteLayout static boolean sDisableNavcache = false; // the color used to highlight the touch rectangles - private static final int HIGHLIGHT_COLOR = 0x6633b5e5; - // the round corner for the highlight path - private static final float TOUCH_HIGHLIGHT_ARC = 5.0f; + static final int HIGHLIGHT_COLOR = 0x6633b5e5; // the region indicating where the user touched on the screen private Region mTouchHighlightRegion = new Region(); // the paint for the touch highlight - private Paint mTouchHightlightPaint; + private Paint mTouchHightlightPaint = new Paint(); // debug only private static final boolean DEBUG_TOUCH_HIGHLIGHT = true; private static final int TOUCH_HIGHLIGHT_ELAPSE_TIME = 2000; @@ -787,6 +795,7 @@ public class WebView extends AbsoluteLayout static final int ENTER_FULLSCREEN_VIDEO = 137; static final int UPDATE_SELECTION = 138; static final int UPDATE_ZOOM_DENSITY = 139; + static final int EXIT_FULLSCREEN_VIDEO = 140; private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID; private static final int LAST_PACKAGE_MSG_ID = HIT_TEST_RESULT; @@ -1073,6 +1082,15 @@ public class WebView extends AbsoluteLayout } /** + * Refer to {@link WebView#requestFocusNodeHref(Message)} for more information + */ + static class FocusNodeHref { + static final String TITLE = "title"; + static final String URL = "url"; + static final String SRC = "src"; + } + + /** * Construct a new WebView with a Context object. * @param context A Context object used to access application assets. */ @@ -2704,6 +2722,14 @@ public class WebView extends AbsoluteLayout } int contentX = viewToContentX(mLastTouchX + mScrollX); int contentY = viewToContentY(mLastTouchY + mScrollY); + if (mFocusedNode != null && mFocusedNode.mHitTestX == contentX + && mFocusedNode.mHitTestY == contentY) { + hrefMsg.getData().putString(FocusNodeHref.URL, mFocusedNode.mLinkUrl); + hrefMsg.getData().putString(FocusNodeHref.TITLE, mFocusedNode.mAnchorText); + hrefMsg.getData().putString(FocusNodeHref.SRC, mFocusedNode.mImageUrl); + hrefMsg.sendToTarget(); + return; + } if (nativeHasCursorNode()) { Rect cursorBounds = nativeGetCursorRingBounds(); if (!cursorBounds.contains(contentX, contentY)) { @@ -4402,10 +4428,6 @@ public class WebView extends AbsoluteLayout Rect r = mTouchHighlightRegion.getBounds(); postInvalidateDelayed(delay, r.left, r.top, r.right, r.bottom); } else { - if (mTouchHightlightPaint == null) { - mTouchHightlightPaint = new Paint(); - mTouchHightlightPaint.setColor(HIGHLIGHT_COLOR); - } RegionIterator iter = new RegionIterator(mTouchHighlightRegion); Rect r = new Rect(); while (iter.next(r)) { @@ -4920,15 +4942,13 @@ public class WebView extends AbsoluteLayout } @Override - public boolean onCheckIsTextEditor() { - return true; - } - - @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN | EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_NORMAL; + if (mInputConnection == null) { + mInputConnection = new WebViewInputConnection(); + } return mInputConnection; } @@ -8745,6 +8765,12 @@ public class WebView extends AbsoluteLayout } break; + case EXIT_FULLSCREEN_VIDEO: + if (mHTML5VideoViewProxy != null) { + mHTML5VideoViewProxy.exitFullScreenVideo(); + } + break; + case SHOW_FULLSCREEN: { View view = (View) msg.obj; int orientation = msg.arg1; @@ -8839,13 +8865,25 @@ public class WebView extends AbsoluteLayout case HIT_TEST_RESULT: WebKitHitTest hit = (WebKitHitTest) msg.obj; - setTouchHighlightRects(hit != null ? hit.mTouchRects : null); + mFocusedNode = hit; + setTouchHighlightRects(hit); if (hit == null) { mInitialHitTestResult = null; } else { mInitialHitTestResult = new HitTestResult(); - mInitialHitTestResult.mType = hit.mType; - mInitialHitTestResult.mExtra = hit.mExtra; + if (hit.mLinkUrl != null) { + mInitialHitTestResult.mType = HitTestResult.SRC_ANCHOR_TYPE; + mInitialHitTestResult.mExtra = hit.mLinkUrl; + if (hit.mImageUrl != null) { + mInitialHitTestResult.mType = HitTestResult.SRC_IMAGE_ANCHOR_TYPE; + mInitialHitTestResult.mExtra = hit.mImageUrl; + } + } else if (hit.mImageUrl != null) { + mInitialHitTestResult.mType = HitTestResult.IMAGE_TYPE; + mInitialHitTestResult.mExtra = hit.mImageUrl; + } else if (hit.mEditable) { + mInitialHitTestResult.mType = HitTestResult.EDIT_TEXT_TYPE; + } } break; @@ -8883,10 +8921,14 @@ public class WebView extends AbsoluteLayout } } - private void setTouchHighlightRects(Rect[] rects) { - invalidate(mTouchHighlightRegion.getBounds()); - mTouchHighlightRegion.setEmpty(); + private void setTouchHighlightRects(WebKitHitTest hit) { + Rect[] rects = hit != null ? hit.mTouchRects : null; + if (!mTouchHighlightRegion.isEmpty()) { + invalidate(mTouchHighlightRegion.getBounds()); + mTouchHighlightRegion.setEmpty(); + } if (rects != null) { + mTouchHightlightPaint.setColor(hit.mTapHighlightColor); for (Rect rect : rects) { Rect viewRect = contentToViewRect(rect); // some sites, like stories in nytimes.com, set diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 962a8f1..c4981e1 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -26,6 +26,7 @@ import android.graphics.Region; import android.media.MediaFile; import android.net.ProxyProperties; import android.net.Uri; +import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -37,6 +38,7 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.SurfaceView; import android.view.View; +import android.webkit.WebView.FocusNodeHref; import junit.framework.Assert; @@ -502,6 +504,17 @@ public final class WebViewCore { } /** + * Notify the webview that we want to exit the video fullscreen. + * This is called through JNI by webcore. + */ + protected void exitFullscreenVideo() { + if (mWebView == null) return; + Message message = Message.obtain(mWebView.mPrivateHandler, + WebView.EXIT_FULLSCREEN_VIDEO); + message.sendToTarget(); + } + + /** * Clear the picture set. To be called only on the WebCore thread. */ /* package */ void clearContent() { @@ -861,9 +874,20 @@ public final class WebViewCore { } static class WebKitHitTest { - int mType; - String mExtra; + String mLinkUrl; + String mAnchorText; + String mImageUrl; + String mAltDisplayString; + String mTitle; Rect[] mTouchRects; + boolean mEditable; + int mTapHighlightColor = WebView.HIGHLIGHT_COLOR; + + // These are the input values that produced this hit test + int mHitTestX; + int mHitTestY; + int mHitTestSlop; + boolean mHitTestMovedMouse; } static class AutoFillData { @@ -1514,13 +1538,12 @@ public final class WebViewCore { break; case REQUEST_CURSOR_HREF: { + WebKitHitTest hit = performHitTest(msg.arg1, msg.arg2, 1, false); Message hrefMsg = (Message) msg.obj; - hrefMsg.getData().putString("url", - nativeRetrieveHref(mNativeClass, msg.arg1, msg.arg2)); - hrefMsg.getData().putString("title", - nativeRetrieveAnchorText(mNativeClass, msg.arg1, msg.arg2)); - hrefMsg.getData().putString("src", - nativeRetrieveImageSource(mNativeClass, msg.arg1, msg.arg2)); + Bundle data = hrefMsg.getData(); + data.putString(FocusNodeHref.URL,hit.mLinkUrl); + data.putString(FocusNodeHref.TITLE, hit.mAnchorText); + data.putString(FocusNodeHref.SRC, hit.mImageUrl); hrefMsg.sendToTarget(); break; } @@ -1685,8 +1708,7 @@ public final class WebViewCore { nativeScrollLayer(mNativeClass, d.mNativeLayer, d.mNativeLayerRect); } - WebKitHitTest hit = nativeHitTest(mNativeClass, - d.mX, d.mY, d.mSlop); + WebKitHitTest hit = performHitTest(d.mX, d.mY, d.mSlop, true); mWebView.mPrivateHandler.obtainMessage( WebView.HIT_TEST_RESULT, hit) .sendToTarget(); @@ -1890,6 +1912,15 @@ public final class WebViewCore { // WebViewCore private methods //------------------------------------------------------------------------- + private WebKitHitTest performHitTest(int x, int y, int slop, boolean moveMouse) { + WebKitHitTest hit = nativeHitTest(mNativeClass, x, y, slop, moveMouse); + hit.mHitTestX = x; + hit.mHitTestY = y; + hit.mHitTestSlop = slop; + hit.mHitTestMovedMouse = moveMouse; + return hit; + } + private void clearCache(boolean includeDiskFiles) { mBrowserFrame.clearCache(); if (includeDiskFiles) { @@ -2940,7 +2971,8 @@ public final class WebViewCore { private native boolean nativeValidNodeAndBounds(int nativeClass, int frame, int node, Rect bounds); - private native WebKitHitTest nativeHitTest(int nativeClass, int x, int y, int slop); + private native WebKitHitTest nativeHitTest(int nativeClass, int x, int y, + int slop, boolean moveMouse); private native void nativeAutoFillForm(int nativeClass, int queryId); private native void nativeScrollLayer(int nativeClass, int layer, Rect rect); diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 38bb2e1..e94b1cb 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -20,7 +20,6 @@ import com.android.internal.R; import android.content.Context; import android.content.Intent; -import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; @@ -1297,6 +1296,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te super.sendAccessibilityEvent(eventType); } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(AbsListView.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(AbsListView.class.getName()); + } + /** * Indicates whether the children's drawing cache is used during a scroll. * By default, the drawing cache is enabled but this will consume more memory. @@ -5572,6 +5583,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } /** + * Hints the RemoteViewsAdapter, if it exists, about which views are currently + * being displayed by the AbsListView. + */ + void setVisibleRangeHint(int start, int end) { + if (mRemoteAdapter != null) { + mRemoteAdapter.setVisibleRangeHint(start, end); + } + } + + /** * Sets the recycler listener to be notified whenever a View is set aside in * the recycler for later reuse. This listener can be used to free resources * associated to the View. diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index bdaf89e..e36afa3 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -25,6 +25,8 @@ import android.util.AttributeSet; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.ViewConfiguration; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; public abstract class AbsSeekBar extends ProgressBar { private Drawable mThumb; @@ -464,4 +466,15 @@ public abstract class AbsSeekBar extends ProgressBar { return super.onKeyDown(keyCode, event); } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(AbsSeekBar.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(AbsSeekBar.class.getName()); + } } diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java index 3d79205..efdfae3 100644 --- a/core/java/android/widget/AbsSpinner.java +++ b/core/java/android/widget/AbsSpinner.java @@ -28,6 +28,8 @@ import android.util.AttributeSet; import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; /** * An abstract base class for spinner widgets. SDK users will probably not @@ -40,7 +42,6 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> { int mHeightMeasureSpec; int mWidthMeasureSpec; - boolean mBlockLayoutRequests; int mSelectionLeftPadding = 0; int mSelectionTopPadding = 0; @@ -463,4 +464,16 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> { scrapHeap.clear(); } } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(AbsSpinner.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(AbsSpinner.class.getName()); + } } diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java index 40df168..97a864c 100644 --- a/core/java/android/widget/AdapterView.java +++ b/core/java/android/widget/AdapterView.java @@ -913,6 +913,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(AdapterView.class.getName()); info.setScrollable(isScrollableForAccessibility()); View selectedView = getSelectedView(); if (selectedView != null) { @@ -923,6 +924,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); + event.setClassName(AdapterView.class.getName()); event.setScrollable(isScrollableForAccessibility()); View selectedView = getSelectedView(); if (selectedView != null) { diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java index c83c780..e226d37 100644 --- a/core/java/android/widget/AdapterViewAnimator.java +++ b/core/java/android/widget/AdapterViewAnimator.java @@ -29,6 +29,8 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import java.util.ArrayList; import java.util.HashMap; @@ -555,6 +557,9 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> mCurrentWindowStart = newWindowStart; mCurrentWindowEnd = newWindowEnd; mCurrentWindowStartUnbounded = newWindowStartUnbounded; + if (mRemoteViewsAdapter != null) { + mRemoteViewsAdapter.setVisibleRangeHint(mCurrentWindowStart, mCurrentWindowEnd); + } } requestLayout(); invalidate(); @@ -1045,4 +1050,16 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> */ public void fyiWillBeAdvancedByHostKThx() { } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(AdapterViewAnimator.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(AdapterViewAnimator.class.getName()); + } } diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java index 4419886..5096227 100644 --- a/core/java/android/widget/AdapterViewFlipper.java +++ b/core/java/android/widget/AdapterViewFlipper.java @@ -16,7 +16,6 @@ package android.widget; -import android.animation.ObjectAnimator; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -27,7 +26,8 @@ import android.os.Message; import android.util.AttributeSet; import android.util.Log; import android.view.RemotableViewMethod; -import android.view.animation.AlphaAnimation; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RemoteViews.RemoteView; /** @@ -268,4 +268,16 @@ public class AdapterViewFlipper extends AdapterViewAnimator { mAdvancedByHost = true; updateRunning(false); } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(AdapterViewFlipper.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(AdapterViewFlipper.class.getName()); + } } diff --git a/core/java/android/widget/Button.java b/core/java/android/widget/Button.java index 8d58a6d..99f4cae 100644 --- a/core/java/android/widget/Button.java +++ b/core/java/android/widget/Button.java @@ -18,9 +18,8 @@ package android.widget; import android.content.Context; import android.util.AttributeSet; -import android.util.Log; -import android.view.MotionEvent; -import android.view.KeyEvent; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RemoteViews.RemoteView; @@ -107,4 +106,16 @@ public class Button extends TextView { public Button(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(Button.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(Button.class.getName()); + } } diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java index e0403ff..85252af 100644 --- a/core/java/android/widget/CalendarView.java +++ b/core/java/android/widget/CalendarView.java @@ -39,6 +39,8 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.AbsListView.OnScrollListener; import com.android.internal.R; @@ -431,6 +433,18 @@ public class CalendarView extends FrameLayout { setCurrentLocale(newConfig.locale); } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(CalendarView.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(CalendarView.class.getName()); + } + /** * Gets the minimal date supported by this {@link CalendarView} in milliseconds * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time diff --git a/core/java/android/widget/CheckBox.java b/core/java/android/widget/CheckBox.java index 2788846..0685eea 100644 --- a/core/java/android/widget/CheckBox.java +++ b/core/java/android/widget/CheckBox.java @@ -19,6 +19,7 @@ package android.widget; import android.content.Context; import android.util.AttributeSet; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import com.android.internal.R; @@ -78,4 +79,16 @@ public class CheckBox extends CompoundButton { event.getText().add(mContext.getString(R.string.checkbox_not_checked)); } } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(CheckBox.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(CheckBox.class.getName()); + } } diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java index 0a54743..5c7e5a3 100644 --- a/core/java/android/widget/CheckedTextView.java +++ b/core/java/android/widget/CheckedTextView.java @@ -220,6 +220,7 @@ public class CheckedTextView extends TextView implements Checkable { @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); + event.setClassName(CheckedTextView.class.getName()); event.setChecked(mChecked); } @@ -236,6 +237,7 @@ public class CheckedTextView extends TextView implements Checkable { @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(CheckedTextView.class.getName()); info.setChecked(mChecked); } } diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java index 7e66722..0370049 100644 --- a/core/java/android/widget/Chronometer.java +++ b/core/java/android/widget/Chronometer.java @@ -25,6 +25,8 @@ import android.os.SystemClock; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.Log; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RemoteViews.RemoteView; import java.util.Formatter; @@ -276,4 +278,16 @@ public class Chronometer extends TextView { mOnChronometerTickListener.onChronometerTick(this); } } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(Chronometer.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(Chronometer.class.getName()); + } } diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java index d3cdad8..02c4c4f 100644 --- a/core/java/android/widget/CompoundButton.java +++ b/core/java/android/widget/CompoundButton.java @@ -211,12 +211,14 @@ public abstract class CompoundButton extends Button implements Checkable { @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); + event.setClassName(CompoundButton.class.getName()); event.setChecked(mChecked); } @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(CompoundButton.class.getName()); info.setCheckable(true); info.setChecked(mChecked); } diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index 0f462ff..110c8f3 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -31,6 +31,7 @@ import android.util.SparseArray; import android.view.LayoutInflater; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.NumberPicker.OnValueChangeListener; @@ -391,6 +392,18 @@ public class DatePicker extends FrameLayout { } @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(DatePicker.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(DatePicker.class.getName()); + } + + @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); setCurrentLocale(newConfig.locale); diff --git a/core/java/android/widget/DigitalClock.java b/core/java/android/widget/DigitalClock.java index 379883a..add9d9b 100644 --- a/core/java/android/widget/DigitalClock.java +++ b/core/java/android/widget/DigitalClock.java @@ -24,6 +24,8 @@ import android.os.SystemClock; import android.provider.Settings; import android.text.format.DateFormat; import android.util.AttributeSet; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import java.util.Calendar; @@ -126,4 +128,16 @@ public class DigitalClock extends TextView { setFormat(); } } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(DigitalClock.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(DigitalClock.class.getName()); + } } diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java index 0da68a4..2fd8768 100644 --- a/core/java/android/widget/EditText.java +++ b/core/java/android/widget/EditText.java @@ -24,6 +24,8 @@ import android.text.TextUtils; import android.text.method.ArrowKeyMovementMethod; import android.text.method.MovementMethod; import android.util.AttributeSet; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; /* @@ -114,4 +116,16 @@ public class EditText extends TextView { } super.setEllipsize(ellipsis); } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(EditText.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(EditText.class.getName()); + } } diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java index ead9b4f..badfaa7 100644 --- a/core/java/android/widget/ExpandableListView.java +++ b/core/java/android/widget/ExpandableListView.java @@ -30,6 +30,8 @@ import android.view.ContextMenu; import android.view.SoundEffectConstants; import android.view.View; import android.view.ContextMenu.ContextMenuInfo; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ExpandableListConnector.PositionMetadata; import java.util.ArrayList; @@ -1167,4 +1169,15 @@ public class ExpandableListView extends ListView { } } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(ExpandableListView.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(ExpandableListView.class.getName()); + } } diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java index 74a57b0..da98884 100644 --- a/core/java/android/widget/FrameLayout.java +++ b/core/java/android/widget/FrameLayout.java @@ -29,6 +29,8 @@ import android.view.Gravity; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RemoteViews.RemoteView; @@ -555,6 +557,19 @@ public class FrameLayout extends ViewGroup { return new LayoutParams(p); } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(FrameLayout.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(FrameLayout.class.getName()); + } + /** * Per-child layout information for layouts that support margins. * See {@link android.R.styleable#FrameLayout_Layout FrameLayout Layout Attributes} diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java index 5e37fa8..03fdc39 100644 --- a/core/java/android/widget/Gallery.java +++ b/core/java/android/widget/Gallery.java @@ -32,6 +32,8 @@ import android.view.SoundEffectConstants; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Transformation; import com.android.internal.R; @@ -1355,6 +1357,18 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(Gallery.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(Gallery.class.getName()); + } + /** * Responsible for fling behavior. Use {@link #startUsingVelocity(int)} to * initiate a fling. Each frame of the fling is handled in {@link #run()}. diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index 7cf5168..7d58011 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -27,6 +27,9 @@ import android.util.Pair; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; + import com.android.internal.R; import java.lang.reflect.Array; @@ -1041,6 +1044,18 @@ public class GridLayout extends ViewGroup { } } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(GridLayout.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(GridLayout.class.getName()); + } + // Inner classes /* diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java index 5d406de..be2df8e 100644 --- a/core/java/android/widget/GridView.java +++ b/core/java/android/widget/GridView.java @@ -27,6 +27,8 @@ import android.view.SoundEffectConstants; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.GridLayoutAnimationController; import android.widget.RemoteViews.RemoteView; @@ -290,6 +292,7 @@ public class GridView extends AbsListView { pos += mNumColumns; } + setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1); return selectedView; } @@ -382,6 +385,7 @@ public class GridView extends AbsListView { mFirstPosition = Math.max(0, pos + 1); } + setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1); return selectedView; } @@ -2116,5 +2120,16 @@ public class GridView extends AbsListView { } return result; } -} + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(GridView.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(GridView.class.getName()); + } +} diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 1683d20..0b4ebf4 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -721,12 +721,14 @@ public class HorizontalScrollView extends FrameLayout { @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(HorizontalScrollView.class.getName()); info.setScrollable(getScrollRange() > 0); } @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); + event.setClassName(HorizontalScrollView.class.getName()); event.setScrollable(getScrollRange() > 0); event.setScrollX(mScrollX); event.setScrollY(mScrollY); diff --git a/core/java/android/widget/ImageButton.java b/core/java/android/widget/ImageButton.java index d680fad..59a8f28 100644 --- a/core/java/android/widget/ImageButton.java +++ b/core/java/android/widget/ImageButton.java @@ -21,6 +21,8 @@ import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.view.MotionEvent; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RemoteViews.RemoteView; import java.util.Map; @@ -90,4 +92,16 @@ public class ImageButton extends ImageView { protected boolean onSetAlpha(int alpha) { return false; } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(ImageButton.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(ImageButton.class.getName()); + } } diff --git a/core/java/android/widget/ImageSwitcher.java b/core/java/android/widget/ImageSwitcher.java index bcb750a..c048970 100644 --- a/core/java/android/widget/ImageSwitcher.java +++ b/core/java/android/widget/ImageSwitcher.java @@ -16,12 +16,12 @@ package android.widget; -import java.util.Map; - import android.content.Context; import android.graphics.drawable.Drawable; import android.net.Uri; import android.util.AttributeSet; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; public class ImageSwitcher extends ViewSwitcher @@ -55,5 +55,16 @@ public class ImageSwitcher extends ViewSwitcher image.setImageDrawable(drawable); showNext(); } -} + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(ImageSwitcher.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(ImageSwitcher.class.getName()); + } +} diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index 73e1273..07ae93b 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -37,6 +37,7 @@ import android.view.RemotableViewMethod; import android.view.View; import android.view.ViewDebug; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RemoteViews.RemoteView; /** @@ -1060,4 +1061,16 @@ public class ImageView extends View { mDrawable.setVisible(false, false); } } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(ImageView.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(ImageView.class.getName()); + } } diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java index 427fd3e..b5deec7 100644 --- a/core/java/android/widget/LinearLayout.java +++ b/core/java/android/widget/LinearLayout.java @@ -27,6 +27,8 @@ import android.view.Gravity; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RemoteViews.RemoteView; @@ -1729,7 +1731,19 @@ public class LinearLayout extends ViewGroup { protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LinearLayout.LayoutParams; } - + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(LinearLayout.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(LinearLayout.class.getName()); + } + /** * Per-child layout information associated with ViewLinearLayout. * diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 7f7a3a7..e20d12a 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -32,13 +32,13 @@ import android.util.AttributeSet; import android.util.SparseBooleanArray; import android.view.FocusFinder; import android.view.KeyEvent; -import android.view.MotionEvent; import android.view.SoundEffectConstants; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.ViewParent; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RemoteViews.RemoteView; import java.util.ArrayList; @@ -678,6 +678,7 @@ public class ListView extends AbsListView { pos++; } + setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1); return selectedView; } @@ -711,7 +712,7 @@ public class ListView extends AbsListView { } mFirstPosition = pos + 1; - + setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1); return selectedView; } @@ -3609,4 +3610,16 @@ public class ListView extends AbsListView { } return new long[0]; } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(ListView.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(ListView.class.getName()); + } } diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java index f2ea3fc..fc35f05 100644 --- a/core/java/android/widget/MediaController.java +++ b/core/java/android/widget/MediaController.java @@ -31,6 +31,8 @@ import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.SeekBar.OnSeekBarChangeListener; import com.android.internal.policy.PolicyManager; @@ -592,6 +594,18 @@ public class MediaController extends FrameLayout { super.setEnabled(enabled); } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(MediaController.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(MediaController.class.getName()); + } + private View.OnClickListener mRewListener = new View.OnClickListener() { public void onClick(View v) { int pos = mPlayer.getCurrentPosition(); diff --git a/core/java/android/widget/MultiAutoCompleteTextView.java b/core/java/android/widget/MultiAutoCompleteTextView.java index 134e4c4..0b30c84 100644 --- a/core/java/android/widget/MultiAutoCompleteTextView.java +++ b/core/java/android/widget/MultiAutoCompleteTextView.java @@ -23,7 +23,8 @@ import android.text.Spanned; import android.text.TextUtils; import android.text.method.QwertyKeyListener; import android.util.AttributeSet; -import android.widget.MultiAutoCompleteTextView.Tokenizer; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; /** * An editable text view, extending {@link AutoCompleteTextView}, that @@ -196,6 +197,18 @@ public class MultiAutoCompleteTextView extends AutoCompleteTextView { editable.replace(start, end, mTokenizer.terminateToken(text)); } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(MultiAutoCompleteTextView.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(MultiAutoCompleteTextView.class.getName()); + } + public static interface Tokenizer { /** * Returns the start of the token that ends at offset diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java index 7d0f98e..a210f0b 100644 --- a/core/java/android/widget/NumberPicker.java +++ b/core/java/android/widget/NumberPicker.java @@ -47,6 +47,7 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.DecelerateInterpolator; import android.view.inputmethod.InputMethodManager; @@ -1382,6 +1383,18 @@ public class NumberPicker extends LinearLayout { // perceive this widget as several controls rather as a whole. } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(NumberPicker.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(NumberPicker.class.getName()); + } + /** * Makes a measure spec that tries greedily to use the max value. * diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index df88fec..ace3f60 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -45,6 +45,7 @@ import android.view.View; import android.view.ViewDebug; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationUtils; @@ -1124,10 +1125,17 @@ public class ProgressBar extends View { @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); + event.setClassName(ProgressBar.class.getName()); event.setItemCount(mMax); event.setCurrentItemIndex(mProgress); } + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(ProgressBar.class.getName()); + } + /** * Schedule a command for sending an accessibility event. * </br> diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java index adc0fb0..786afe2 100644 --- a/core/java/android/widget/QuickContactBadge.java +++ b/core/java/android/widget/QuickContactBadge.java @@ -36,6 +36,8 @@ import android.provider.ContactsContract.RawContacts; import android.util.AttributeSet; import android.view.View; import android.view.View.OnClickListener; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; /** * Widget used to show an image with the standard QuickContact badge @@ -228,6 +230,18 @@ public class QuickContactBadge extends ImageView implements OnClickListener { } } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(QuickContactBadge.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(QuickContactBadge.class.getName()); + } + /** * Set a list of specific MIME-types to exclude and not display. For * example, this can be used to hide the {@link Contacts#CONTENT_ITEM_TYPE} diff --git a/core/java/android/widget/RadioButton.java b/core/java/android/widget/RadioButton.java index 9fa649f..b6dac3e 100644 --- a/core/java/android/widget/RadioButton.java +++ b/core/java/android/widget/RadioButton.java @@ -19,6 +19,7 @@ package android.widget; import android.content.Context; import android.util.AttributeSet; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import com.android.internal.R; @@ -85,4 +86,16 @@ public class RadioButton extends CompoundButton { event.getText().add(mContext.getString(R.string.radiobutton_not_selected)); } } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(RadioButton.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(RadioButton.class.getName()); + } } diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java index 393346a..7f53ffd 100644 --- a/core/java/android/widget/RadioGroup.java +++ b/core/java/android/widget/RadioGroup.java @@ -23,6 +23,8 @@ import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; /** @@ -236,6 +238,18 @@ public class RadioGroup extends LinearLayout { return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(RadioGroup.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(RadioGroup.class.getName()); + } + /** * <p>This set of layout parameters defaults the width and the height of * the children to {@link #WRAP_CONTENT} when they are not specified in the diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java index 9e6ff4b..e69577b 100644 --- a/core/java/android/widget/RatingBar.java +++ b/core/java/android/widget/RatingBar.java @@ -21,6 +21,8 @@ import android.content.res.TypedArray; import android.graphics.drawable.shapes.RectShape; import android.graphics.drawable.shapes.Shape; import android.util.AttributeSet; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import com.android.internal.R; @@ -324,4 +326,15 @@ public class RatingBar extends AbsSeekBar { super.setMax(max); } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(RatingBar.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(RatingBar.class.getName()); + } } diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java index a452fec..e4b8f34 100644 --- a/core/java/android/widget/RelativeLayout.java +++ b/core/java/android/widget/RelativeLayout.java @@ -40,6 +40,7 @@ import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RemoteViews.RemoteView; import static android.util.Log.d; @@ -985,6 +986,18 @@ public class RelativeLayout extends ViewGroup { return false; } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(RelativeLayout.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(RelativeLayout.class.getName()); + } + /** * Compares two views in left-to-right and top-to-bottom fashion. */ diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java index 7b43032..586fdf4 100644 --- a/core/java/android/widget/RemoteViewsAdapter.java +++ b/core/java/android/widget/RemoteViewsAdapter.java @@ -68,6 +68,8 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback private RemoteViewsAdapterServiceConnection mServiceConnection; private WeakReference<RemoteAdapterConnectionCallback> mCallback; private FixedSizeRemoteViewsCache mCache; + private int mVisibleWindowLowerBound; + private int mVisibleWindowUpperBound; // A flag to determine whether we should notify data set changed after we connect private boolean mNotifyDataSetChangedAfterOnServiceConnected = false; @@ -765,7 +767,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback } if (position > -1) { // Load the item, and notify any existing RemoteViewsFrameLayouts - updateRemoteViews(position, isRequested); + updateRemoteViews(position, isRequested, true); // Queue up for the next one to load loadNextIndexInBackground(); @@ -827,8 +829,8 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback } } - private void updateRemoteViews(final int position, boolean isRequested) { - if (!mServiceConnection.isConnected()) return; + private void updateRemoteViews(final int position, boolean isRequested, boolean + notifyWhenLoaded) { IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory(); // Load the item information from the remote service @@ -864,12 +866,14 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback // there is new data for it. final RemoteViews rv = remoteViews; final int typeId = mCache.getMetaDataAt(position).typeId; - mMainQueue.post(new Runnable() { - @Override - public void run() { - mRequestedViews.notifyOnRemoteViewsLoaded(position, rv, typeId); - } - }); + if (notifyWhenLoaded) { + mMainQueue.post(new Runnable() { + @Override + public void run() { + mRequestedViews.notifyOnRemoteViewsLoaded(position, rv, typeId); + } + }); + } } } @@ -929,6 +933,16 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback return typeId; } + /** + * This method allows an AdapterView using this Adapter to provide information about which + * views are currently being displayed. This allows for certain optimizations and preloading + * which wouldn't otherwise be possible. + */ + public void setVisibleRangeHint(int lowerBound, int upperBound) { + mVisibleWindowLowerBound = lowerBound; + mVisibleWindowUpperBound = upperBound; + } + public View getView(int position, View convertView, ViewGroup parent) { // "Request" an index so that we can queue it for loading, initiate subsequent // preloading, etc. @@ -1059,6 +1073,13 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback // Re-request the new metadata (only after the notification to the factory) updateTemporaryMetaData(); + // Pre-load (our best guess of) the views which are currently visible in the AdapterView. + // This mitigates flashing and flickering of loading views when a widget notifies that + // its data has changed. + for (int i = mVisibleWindowLowerBound; i <= mVisibleWindowUpperBound; i++) { + updateRemoteViews(i, false, false); + } + // Propagate the notification back to the base adapter mMainQueue.post(new Runnable() { @Override diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 767eaee..3ffc0fe 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -721,12 +721,14 @@ public class ScrollView extends FrameLayout { @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(ScrollView.class.getName()); info.setScrollable(getScrollRange() > 0); } @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); + event.setClassName(ScrollView.class.getName()); final boolean scrollable = getScrollRange() > 0; event.setScrollable(scrollable); event.setScrollX(mScrollX); diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java index 9d2ff2e..99cd0b8 100644 --- a/core/java/android/widget/SearchView.java +++ b/core/java/android/widget/SearchView.java @@ -35,7 +35,6 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; import android.speech.RecognizerIntent; import android.text.Editable; import android.text.InputType; @@ -51,6 +50,8 @@ import android.view.CollapsibleActionView; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView.OnItemClickListener; @@ -1206,6 +1207,18 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { setIconified(false); } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(SearchView.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(SearchView.class.getName()); + } + private void adjustDropDownSizeAndPosition() { if (mDropDownAnchor.getWidth() > 1) { Resources res = getContext().getResources(); diff --git a/core/java/android/widget/SeekBar.java b/core/java/android/widget/SeekBar.java index c76728f..2737f94 100644 --- a/core/java/android/widget/SeekBar.java +++ b/core/java/android/widget/SeekBar.java @@ -18,6 +18,8 @@ package android.widget; import android.content.Context; import android.util.AttributeSet; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; @@ -117,5 +119,16 @@ public class SeekBar extends AbsSeekBar { mOnSeekBarChangeListener.onStopTrackingTouch(this); } } - + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(SeekBar.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(SeekBar.class.getName()); + } } diff --git a/core/java/android/widget/SlidingDrawer.java b/core/java/android/widget/SlidingDrawer.java index bdeb5c2..14edd10 100644 --- a/core/java/android/widget/SlidingDrawer.java +++ b/core/java/android/widget/SlidingDrawer.java @@ -32,6 +32,7 @@ import android.view.VelocityTracker; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; /** * SlidingDrawer hides content out of the screen and allows the user to drag a handle @@ -810,6 +811,18 @@ public class SlidingDrawer extends ViewGroup { } } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(SlidingDrawer.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(SlidingDrawer.class.getName()); + } + private void closeDrawer() { moveHandle(COLLAPSED_FULL_CLOSED); mContent.setVisibility(View.GONE); diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java index ec3790e..ecf19b3 100644 --- a/core/java/android/widget/Spinner.java +++ b/core/java/android/widget/Spinner.java @@ -29,6 +29,8 @@ import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; /** @@ -456,12 +458,24 @@ public class Spinner extends AbsSpinner implements OnClickListener { return handled; } - + public void onClick(DialogInterface dialog, int which) { setSelection(which); dialog.dismiss(); } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(Spinner.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(Spinner.class.getName()); + } + /** * Sets the prompt to display when the dialog is shown. * @param prompt the prompt to set diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java index 03e6e99..22df3bc 100644 --- a/core/java/android/widget/StackView.java +++ b/core/java/android/widget/StackView.java @@ -40,6 +40,8 @@ import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.LinearInterpolator; import android.widget.RemoteViews.RemoteView; @@ -1216,6 +1218,18 @@ public class StackView extends AdapterViewAnimator { measureChildren(); } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(StackView.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(StackView.class.getName()); + } + class LayoutParams extends ViewGroup.LayoutParams { int horizontalOffset; int verticalOffset; diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java index 02c9d03..334b9c4 100644 --- a/core/java/android/widget/Switch.java +++ b/core/java/android/widget/Switch.java @@ -35,6 +35,7 @@ import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import com.android.internal.R; @@ -651,4 +652,16 @@ public class Switch extends CompoundButton { mThumbDrawable.jumpToCurrentState(); mTrackDrawable.jumpToCurrentState(); } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(Switch.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(Switch.class.getName()); + } } diff --git a/core/java/android/widget/TabHost.java b/core/java/android/widget/TabHost.java index 88d7230..9b292be 100644 --- a/core/java/android/widget/TabHost.java +++ b/core/java/android/widget/TabHost.java @@ -33,6 +33,8 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.Window; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import java.util.ArrayList; import java.util.List; @@ -321,6 +323,18 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); } } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(TabHost.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(TabHost.class.getName()); + } + public void setCurrentTab(int index) { if (index < 0 || index >= mTabSpecs.size()) { return; diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java index 80bfe99..8901037 100644 --- a/core/java/android/widget/TabWidget.java +++ b/core/java/android/widget/TabWidget.java @@ -29,6 +29,7 @@ import android.view.View; import android.view.View.OnFocusChangeListener; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; /** * @@ -416,10 +417,28 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); + event.setClassName(TabWidget.class.getName()); event.setItemCount(getTabCount()); event.setCurrentItemIndex(mSelectedTab); } + + @Override + public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { + // this class fires events only when tabs are focused or selected + if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED && isFocused()) { + event.recycle(); + return; + } + super.sendAccessibilityEventUnchecked(event); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(TabWidget.class.getName()); + } + /** * Sets the current tab and focuses the UI on it. * This method makes sure that the focused tab matches the selected @@ -485,16 +504,6 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { mSelectedTab = -1; } - @Override - public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { - // this class fires events only when tabs are focused or selected - if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED && isFocused()) { - event.recycle(); - return; - } - super.sendAccessibilityEventUnchecked(event); - } - /** * Provides a way for {@link TabHost} to be notified that the user clicked on a tab indicator. */ diff --git a/core/java/android/widget/TableLayout.java b/core/java/android/widget/TableLayout.java index 842b087..f5d3746 100644 --- a/core/java/android/widget/TableLayout.java +++ b/core/java/android/widget/TableLayout.java @@ -24,6 +24,8 @@ import android.util.AttributeSet; import android.util.SparseBooleanArray; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import java.util.regex.Pattern; @@ -658,6 +660,18 @@ public class TableLayout extends LinearLayout { return new LayoutParams(p); } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(TableLayout.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(TableLayout.class.getName()); + } + /** * <p>This set of layout parameters enforces the width of each child to be * {@link #MATCH_PARENT} and the height of each child to be diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java index 3fd4631..01c4c2c 100644 --- a/core/java/android/widget/TableRow.java +++ b/core/java/android/widget/TableRow.java @@ -24,6 +24,8 @@ import android.view.Gravity; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; /** @@ -377,6 +379,18 @@ public class TableRow extends LinearLayout { return new LayoutParams(p); } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(TableRow.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(TableRow.class.getName()); + } + /** * <p>Set of layout parameters used in table rows.</p> * diff --git a/core/java/android/widget/TextSwitcher.java b/core/java/android/widget/TextSwitcher.java index a8794a3..1aefd2b 100644 --- a/core/java/android/widget/TextSwitcher.java +++ b/core/java/android/widget/TextSwitcher.java @@ -21,6 +21,8 @@ import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; /** * Specialized {@link android.widget.ViewSwitcher} that contains @@ -88,4 +90,16 @@ public class TextSwitcher extends ViewSwitcher { public void setCurrentText(CharSequence text) { ((TextView)getCurrentView()).setText(text); } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(TextSwitcher.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(TextSwitcher.class.getName()); + } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index acea1a1..e508e9a 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -357,7 +357,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private float mLastDownPositionX, mLastDownPositionY; private Callback mCustomSelectionActionModeCallback; - private final int mSquaredTouchSlopDistance; // Set when this TextView gained focus with some text selected. Will start selection mode. private boolean mCreatedWithASelection = false; @@ -443,15 +442,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener this(context, null); } - public TextView(Context context, - AttributeSet attrs) { + public TextView(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.textViewStyle); } @SuppressWarnings("deprecation") - public TextView(Context context, - AttributeSet attrs, - int defStyle) { + public TextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mText = ""; @@ -1134,10 +1130,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener setLongClickable(longClickable); prepareCursorControllers(); - - final ViewConfiguration viewConfiguration = ViewConfiguration.get(context); - final int touchSlop = viewConfiguration.getScaledTouchSlop(); - mSquaredTouchSlopDistance = touchSlop * touchSlop; } private void setTypefaceByIndex(int typefaceIndex, int styleIndex) { @@ -3202,8 +3194,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int n = mFilters.length; for (int i = 0; i < n; i++) { - CharSequence out = mFilters[i].filter(text, 0, text.length(), - EMPTY_SPANNED, 0, 0); + CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0); if (out != null) { text = out; } @@ -5273,10 +5264,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener state.handleUpEvent(event); } if (event.isTracking() && !event.isCanceled()) { - if (isInSelectionMode) { - stopSelectionActionMode(); - return true; - } + stopSelectionActionMode(); + return true; } } } @@ -5621,11 +5610,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return super.onKeyUp(keyCode, event); } - @Override public boolean onCheckIsTextEditor() { + @Override + public boolean onCheckIsTextEditor() { return mInputType != EditorInfo.TYPE_NULL; } - @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { if (onCheckIsTextEditor() && isEnabled()) { if (mInputMethodState == null) { mInputMethodState = new InputMethodState(); @@ -9029,6 +9020,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); + event.setClassName(TextView.class.getName()); final boolean isPassword = hasPasswordTransformationMethod(); event.setPassword(isPassword); @@ -9043,11 +9035,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(TextView.class.getName()); final boolean isPassword = hasPasswordTransformationMethod(); + info.setPassword(isPassword); + if (!isPassword) { info.setText(getTextForAccessibility()); } - info.setPassword(isPassword); } @Override @@ -9245,7 +9239,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean vibrate = true; if (super.performLongClick()) { - mDiscardNextActionUp = true; handled = true; } @@ -10181,7 +10174,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } - private void stopSelectionActionMode() { + /** + * @hide + */ + protected void stopSelectionActionMode() { if (mSelectionActionMode != null) { // This will hide the mSelectionModifierCursorController mSelectionActionMode.finish(); @@ -10795,7 +10791,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final float deltaX = mDownPositionX - ev.getRawX(); final float deltaY = mDownPositionY - ev.getRawY(); final float distanceSquared = deltaX * deltaX + deltaY * deltaY; - if (distanceSquared < mSquaredTouchSlopDistance) { + + final ViewConfiguration viewConfiguration = ViewConfiguration.get( + TextView.this.getContext()); + final int touchSlop = viewConfiguration.getScaledTouchSlop(); + + if (distanceSquared < touchSlop * touchSlop) { if (mActionPopupWindow != null && mActionPopupWindow.isShowing()) { // Tapping on the handle dismisses the displayed action popup mActionPopupWindow.hide(); @@ -11009,7 +11010,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Double tap detection private long mPreviousTapUpTime = 0; - private float mPreviousTapPositionX, mPreviousTapPositionY; + private float mDownPositionX, mDownPositionY; + private boolean mGestureStayedInTapRegion; SelectionModifierCursorController() { resetTouchOffsets(); @@ -11072,20 +11074,28 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mMinTouchOffset = mMaxTouchOffset = getOffsetForPosition(x, y); // Double tap detection - long duration = SystemClock.uptimeMillis() - mPreviousTapUpTime; - if (duration <= ViewConfiguration.getDoubleTapTimeout() && - isPositionOnText(x, y)) { - final float deltaX = x - mPreviousTapPositionX; - final float deltaY = y - mPreviousTapPositionY; - final float distanceSquared = deltaX * deltaX + deltaY * deltaY; - if (distanceSquared < mSquaredTouchSlopDistance) { - startSelectionActionMode(); - mDiscardNextActionUp = true; + if (mGestureStayedInTapRegion) { + long duration = SystemClock.uptimeMillis() - mPreviousTapUpTime; + if (duration <= ViewConfiguration.getDoubleTapTimeout()) { + final float deltaX = x - mDownPositionX; + final float deltaY = y - mDownPositionY; + final float distanceSquared = deltaX * deltaX + deltaY * deltaY; + + ViewConfiguration viewConfiguration = ViewConfiguration.get( + TextView.this.getContext()); + int doubleTapSlop = viewConfiguration.getScaledDoubleTapSlop(); + boolean stayedInArea = distanceSquared < doubleTapSlop * doubleTapSlop; + + if (stayedInArea && isPositionOnText(x, y)) { + startSelectionActionMode(); + mDiscardNextActionUp = true; + } } } - mPreviousTapPositionX = x; - mPreviousTapPositionY = y; + mDownPositionX = x; + mDownPositionY = y; + mGestureStayedInTapRegion = true; break; case MotionEvent.ACTION_POINTER_DOWN: @@ -11098,6 +11108,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } break; + case MotionEvent.ACTION_MOVE: + if (mGestureStayedInTapRegion) { + final float deltaX = event.getX() - mDownPositionX; + final float deltaY = event.getY() - mDownPositionY; + final float distanceSquared = deltaX * deltaX + deltaY * deltaY; + + final ViewConfiguration viewConfiguration = ViewConfiguration.get( + TextView.this.getContext()); + int doubleTapTouchSlop = viewConfiguration.getScaledDoubleTapTouchSlop(); + + if (distanceSquared > doubleTapTouchSlop * doubleTapTouchSlop) { + mGestureStayedInTapRegion = false; + } + } + break; + case MotionEvent.ACTION_UP: mPreviousTapUpTime = SystemClock.uptimeMillis(); break; diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java index afca2db..8f10fff 100644 --- a/core/java/android/widget/TimePicker.java +++ b/core/java/android/widget/TimePicker.java @@ -27,6 +27,7 @@ import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.NumberPicker.OnValueChangeListener; @@ -476,6 +477,18 @@ public class TimePicker extends FrameLayout { event.getText().add(selectedDateUtterance); } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(TimePicker.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(TimePicker.class.getName()); + } + private void updateHourControl() { if (is24HourView()) { mHourSpinner.setMinValue(0); diff --git a/core/java/android/widget/ToggleButton.java b/core/java/android/widget/ToggleButton.java index a754268..a0edafe 100644 --- a/core/java/android/widget/ToggleButton.java +++ b/core/java/android/widget/ToggleButton.java @@ -23,6 +23,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.util.AttributeSet; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import com.android.internal.R; @@ -161,4 +162,16 @@ public class ToggleButton extends CompoundButton { event.getText().add(mContext.getString(R.string.togglebutton_not_pressed)); } } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(ToggleButton.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(ToggleButton.class.getName()); + } } diff --git a/core/java/android/widget/TwoLineListItem.java b/core/java/android/widget/TwoLineListItem.java index eab6f2d..e707ea3 100644 --- a/core/java/android/widget/TwoLineListItem.java +++ b/core/java/android/widget/TwoLineListItem.java @@ -16,14 +16,12 @@ package android.widget; -import com.android.internal.R; - - import android.annotation.Widget; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; -import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RelativeLayout; /** @@ -86,4 +84,16 @@ public class TwoLineListItem extends RelativeLayout { public TextView getText2() { return mText2; } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(TwoLineListItem.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(TwoLineListItem.class.getName()); + } } diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java index 64fdf34..0fba498 100644 --- a/core/java/android/widget/VideoView.java +++ b/core/java/android/widget/VideoView.java @@ -34,6 +34,8 @@ import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.MediaController.MediaPlayerControl; import java.io.IOException; @@ -124,6 +126,18 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { setMeasuredDimension(width, height); } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(VideoView.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(VideoView.class.getName()); + } + public int resolveAdjustedSize(int desiredSize, int measureSpec) { int result = desiredSize; int specMode = MeasureSpec.getMode(measureSpec); diff --git a/core/java/android/widget/ViewAnimator.java b/core/java/android/widget/ViewAnimator.java index 71ff66b..6a68240 100644 --- a/core/java/android/widget/ViewAnimator.java +++ b/core/java/android/widget/ViewAnimator.java @@ -22,6 +22,8 @@ import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Animation; import android.view.animation.AnimationUtils; @@ -341,4 +343,16 @@ public class ViewAnimator extends FrameLayout { public int getBaseline() { return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline(); } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(ViewAnimator.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(ViewAnimator.class.getName()); + } } diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java index c6f6e81..061bb00 100644 --- a/core/java/android/widget/ViewFlipper.java +++ b/core/java/android/widget/ViewFlipper.java @@ -25,6 +25,8 @@ import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.util.Log; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RemoteViews.RemoteView; /** @@ -139,6 +141,18 @@ public class ViewFlipper extends ViewAnimator { updateRunning(); } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(ViewFlipper.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(ViewFlipper.class.getName()); + } + /** * Internal method to start or stop dispatching flip {@link Message} based * on {@link #mRunning} and {@link #mVisible} state. diff --git a/core/java/android/widget/ViewSwitcher.java b/core/java/android/widget/ViewSwitcher.java index 71ae624..0376918 100644 --- a/core/java/android/widget/ViewSwitcher.java +++ b/core/java/android/widget/ViewSwitcher.java @@ -20,6 +20,8 @@ import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; /** * {@link ViewAnimator} that switches between two views, and has a factory @@ -66,6 +68,18 @@ public class ViewSwitcher extends ViewAnimator { super.addView(child, index, params); } + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(ViewSwitcher.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(ViewSwitcher.class.getName()); + } + /** * Returns the next view to be displayed. * diff --git a/core/java/android/widget/ZoomButton.java b/core/java/android/widget/ZoomButton.java index eb372ca..af17c94 100644 --- a/core/java/android/widget/ZoomButton.java +++ b/core/java/android/widget/ZoomButton.java @@ -23,6 +23,8 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.View.OnLongClickListener; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; public class ZoomButton extends ImageButton implements OnLongClickListener { @@ -96,4 +98,16 @@ public class ZoomButton extends ImageButton implements OnLongClickListener { clearFocus(); return super.dispatchUnhandledMove(focused, direction); } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(ZoomButton.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(ZoomButton.class.getName()); + } } diff --git a/core/java/android/widget/ZoomControls.java b/core/java/android/widget/ZoomControls.java index a12aee5..8897875 100644 --- a/core/java/android/widget/ZoomControls.java +++ b/core/java/android/widget/ZoomControls.java @@ -22,6 +22,8 @@ import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.AlphaAnimation; import com.android.internal.R; @@ -106,4 +108,16 @@ public class ZoomControls extends LinearLayout { public boolean hasFocus() { return mZoomIn.hasFocus() || mZoomOut.hasFocus(); } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(ZoomControls.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(ZoomControls.class.getName()); + } } |