diff options
Diffstat (limited to 'core')
53 files changed, 1637 insertions, 626 deletions
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java index 894e196..93983a6 100644 --- a/core/java/android/accounts/AccountManagerService.java +++ b/core/java/android/accounts/AccountManagerService.java @@ -1786,22 +1786,6 @@ public class AccountManagerService } } - private String getMetaValue(String key) { - synchronized (mCacheLock) { - final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); - Cursor c = db.query(TABLE_META, - new String[]{META_VALUE}, META_KEY + "=?", new String[]{key}, null, null, null); - try { - if (c.moveToNext()) { - return c.getString(0); - } - return null; - } finally { - c.close(); - } - } - } - public IBinder onBind(Intent intent) { return asBinder(); } diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java index fc5fac6..a9e84d7 100644 --- a/core/java/android/app/ActionBar.java +++ b/core/java/android/app/ActionBar.java @@ -160,6 +160,66 @@ public abstract class ActionBar { public abstract void setCustomView(int resId); /** + * Set the icon to display in the 'home' section of the action bar. + * The action bar will use an icon specified by its style or the + * activity icon by default. + * + * Whether the home section shows an icon or logo is controlled + * by the display option {@link #DISPLAY_USE_LOGO}. + * + * @param resId Resource ID of a drawable to show as an icon. + * + * @see #setDisplayUseLogoEnabled(boolean) + * @see #setDisplayShowHomeEnabled(boolean) + */ + public abstract void setIcon(int resId); + + /** + * Set the icon to display in the 'home' section of the action bar. + * The action bar will use an icon specified by its style or the + * activity icon by default. + * + * Whether the home section shows an icon or logo is controlled + * by the display option {@link #DISPLAY_USE_LOGO}. + * + * @param icon Drawable to show as an icon. + * + * @see #setDisplayUseLogoEnabled(boolean) + * @see #setDisplayShowHomeEnabled(boolean) + */ + public abstract void setIcon(Drawable icon); + + /** + * Set the logo to display in the 'home' section of the action bar. + * The action bar will use a logo specified by its style or the + * activity logo by default. + * + * Whether the home section shows an icon or logo is controlled + * by the display option {@link #DISPLAY_USE_LOGO}. + * + * @param resId Resource ID of a drawable to show as a logo. + * + * @see #setDisplayUseLogoEnabled(boolean) + * @see #setDisplayShowHomeEnabled(boolean) + */ + public abstract void setLogo(int resId); + + /** + * Set the logo to display in the 'home' section of the action bar. + * The action bar will use a logo specified by its style or the + * activity logo by default. + * + * Whether the home section shows an icon or logo is controlled + * by the display option {@link #DISPLAY_USE_LOGO}. + * + * @param logo Drawable to show as a logo. + * + * @see #setDisplayUseLogoEnabled(boolean) + * @see #setDisplayShowHomeEnabled(boolean) + */ + public abstract void setLogo(Drawable logo); + + /** * Set the adapter and navigation callback for list navigation mode. * * The supplied adapter will provide views for the expanded list as well as diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java index 1d217f0..850f56a 100644 --- a/core/java/android/app/BackStackRecord.java +++ b/core/java/android/app/BackStackRecord.java @@ -43,7 +43,7 @@ final class BackStackState implements Parcelable { if (op.removed != null) numRemoved += op.removed.size(); op = op.next; } - mOps = new int[bse.mNumOp*5 + numRemoved]; + mOps = new int[bse.mNumOp*7 + numRemoved]; if (!bse.mAddToBackStack) { throw new IllegalStateException("Not on back stack"); @@ -56,6 +56,8 @@ final class BackStackState implements Parcelable { mOps[pos++] = op.fragment.mIndex; mOps[pos++] = op.enterAnim; mOps[pos++] = op.exitAnim; + mOps[pos++] = op.popEnterAnim; + mOps[pos++] = op.popExitAnim; if (op.removed != null) { final int N = op.removed.size(); mOps[pos++] = N; @@ -101,6 +103,8 @@ final class BackStackState implements Parcelable { op.fragment = f; op.enterAnim = mOps[pos++]; op.exitAnim = mOps[pos++]; + op.popEnterAnim = mOps[pos++]; + op.popExitAnim = mOps[pos++]; final int N = mOps[pos++]; if (N > 0) { op.removed = new ArrayList<Fragment>(N); @@ -177,6 +181,8 @@ final class BackStackRecord extends FragmentTransaction implements Fragment fragment; int enterAnim; int exitAnim; + int popEnterAnim; + int popExitAnim; ArrayList<Fragment> removed; } @@ -185,6 +191,8 @@ final class BackStackRecord extends FragmentTransaction implements int mNumOp; int mEnterAnim; int mExitAnim; + int mPopEnterAnim; + int mPopExitAnim; int mTransition; int mTransitionStyle; boolean mAddToBackStack; @@ -241,6 +249,11 @@ final class BackStackRecord extends FragmentTransaction implements writer.print(prefix); writer.print("enterAnim="); writer.print(op.enterAnim); writer.print(" exitAnim="); writer.println(op.exitAnim); } + if (op.popEnterAnim != 0 || op.popExitAnim != 0) { + writer.print(prefix); + writer.print("popEnterAnim="); writer.print(op.popEnterAnim); + writer.print(" popExitAnim="); writer.println(op.popExitAnim); + } if (op.removed != null && op.removed.size() > 0) { for (int i=0; i<op.removed.size(); i++) { writer.print(innerPrefix); @@ -299,6 +312,8 @@ final class BackStackRecord extends FragmentTransaction implements } op.enterAnim = mEnterAnim; op.exitAnim = mExitAnim; + op.popEnterAnim = mPopEnterAnim; + op.popExitAnim = mPopExitAnim; mNumOp++; } @@ -402,8 +417,15 @@ final class BackStackRecord extends FragmentTransaction implements } public FragmentTransaction setCustomAnimations(int enter, int exit) { + return setCustomAnimations(enter, exit, 0, 0); + } + + public FragmentTransaction setCustomAnimations(int enter, int exit, + int popEnter, int popExit) { mEnterAnim = enter; mExitAnim = exit; + mPopEnterAnim = popEnter; + mPopExitAnim = popExit; return this; } @@ -593,6 +615,7 @@ final class BackStackRecord extends FragmentTransaction implements switch (op.cmd) { case OP_ADD: { Fragment f = op.fragment; + f.mNextAnim = op.popExitAnim; f.mImmediateActivity = null; mManager.removeFragment(f, FragmentManagerImpl.reverseTransit(mTransition), @@ -600,6 +623,7 @@ final class BackStackRecord extends FragmentTransaction implements } break; case OP_REPLACE: { Fragment f = op.fragment; + f.mNextAnim = op.popExitAnim; f.mImmediateActivity = null; mManager.removeFragment(f, FragmentManagerImpl.reverseTransit(mTransition), @@ -607,6 +631,7 @@ final class BackStackRecord extends FragmentTransaction implements if (op.removed != null) { for (int i=0; i<op.removed.size(); i++) { Fragment old = op.removed.get(i); + old.mNextAnim = op.popEnterAnim; f.mImmediateActivity = mManager.mActivity; mManager.addFragment(old, false); } @@ -614,16 +639,19 @@ final class BackStackRecord extends FragmentTransaction implements } break; case OP_REMOVE: { Fragment f = op.fragment; + f.mNextAnim = op.popEnterAnim; f.mImmediateActivity = mManager.mActivity; mManager.addFragment(f, false); } break; case OP_HIDE: { Fragment f = op.fragment; + f.mNextAnim = op.popEnterAnim; mManager.showFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); } break; case OP_SHOW: { Fragment f = op.fragment; + f.mNextAnim = op.popExitAnim; mManager.hideFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); } break; diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java index 0cc774d..68600b3 100644 --- a/core/java/android/app/FragmentTransaction.java +++ b/core/java/android/app/FragmentTransaction.java @@ -116,10 +116,20 @@ public abstract class FragmentTransaction { /** * Set specific animation resources to run for the fragments that are - * entering and exiting in this transaction. + * entering and exiting in this transaction. These animations will not be + * played when popping the back stack. */ public abstract FragmentTransaction setCustomAnimations(int enter, int exit); - + + /** + * Set specific animation resources to run for the fragments that are + * entering and exiting in this transaction. The <code>popEnter</code> + * and <code>popExit</code> animations will be played for enter/exit + * operations specifically when popping the back stack. + */ + public abstract FragmentTransaction setCustomAnimations(int enter, int exit, + int popEnter, int popExit); + /** * Select a standard transition animation for this transaction. May be * one of {@link #TRANSIT_NONE}, {@link #TRANSIT_FRAGMENT_OPEN}, diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index fc07478..80bed0d 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1442,7 +1442,7 @@ public abstract class PackageManager { * {@link Intent#resolveActivity} finds an activity if a class has not * been explicitly specified. * - * <p><em>Note: if using an implicit Intent (without an explicit ComponentName + * <p><em>Note:</em> if using an implicit Intent (without an explicit ComponentName * specified), be sure to consider whether to set the {@link #MATCH_DEFAULT_ONLY} * only flag. You need to do so to resolve the activity in the same way * that {@link android.content.Context#startActivity(Intent)} and diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java index 4c2d123..83f3891 100644 --- a/core/java/android/database/sqlite/SQLiteCursor.java +++ b/core/java/android/database/sqlite/SQLiteCursor.java @@ -241,7 +241,7 @@ public class SQLiteCursor extends AbstractWindowedCursor { mColumnNameMap = null; mQuery = query; - query.mDatabase.lock(); + query.mDatabase.lock(query.mSql); try { // Setup the list of columns int columnCount = mQuery.columnCountLocked(); @@ -419,7 +419,7 @@ public class SQLiteCursor extends AbstractWindowedCursor { // since we need to use a different database connection handle, // re-compile the query try { - db.lock(); + db.lock(mQuery.mSql); } catch (IllegalStateException e) { // for backwards compatibility, just return false Log.w(TAG, "requery() failed " + e.getMessage(), e); diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 90a5b5d..2f2b4eb 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -230,9 +230,23 @@ public class SQLiteDatabase extends SQLiteClosable { private static int sQueryLogTimeInMillis = 0; // lazily initialized private static final int QUERY_LOG_SQL_LENGTH = 64; private static final String COMMIT_SQL = "COMMIT;"; + private static final String BEGIN_SQL = "BEGIN;"; private final Random mRandom = new Random(); + /** the last non-commit/rollback sql statement in a transaction */ + // guarded by 'this' private String mLastSqlStatement = null; + synchronized String getLastSqlStatement() { + return mLastSqlStatement; + } + + synchronized void setLastSqlStatement(String sql) { + mLastSqlStatement = sql; + } + + /** guarded by {@link #mLock} */ + private long mTransStartTime; + // String prefix for slow database query EventLog records that show // lock acquistions of the database. /* package */ static final String GET_LOCK_LOG_PREFIX = "GETLOCK:"; @@ -386,11 +400,16 @@ public class SQLiteDatabase extends SQLiteClosable { * * @see #unlock() */ - /* package */ void lock() { - lock(false); + /* package */ void lock(String sql) { + lock(sql, false); + } + + /* pachage */ void lock() { + lock(null, false); } + private static final long LOCK_WAIT_PERIOD = 30L; - private void lock(boolean forced) { + private void lock(String sql, boolean forced) { // make sure this method is NOT being called from a 'synchronized' method if (Thread.holdsLock(this)) { Log.w(TAG, "don't lock() while in a synchronized method"); @@ -398,6 +417,7 @@ public class SQLiteDatabase extends SQLiteClosable { verifyDbIsOpen(); if (!forced && !mLockingEnabled) return; boolean done = false; + long timeStart = SystemClock.uptimeMillis(); while (!done) { try { // wait for 30sec to acquire the lock @@ -420,6 +440,9 @@ public class SQLiteDatabase extends SQLiteClosable { mLockAcquiredThreadTime = Debug.threadCpuTimeNanos(); } } + if (sql != null) { + logTimeStat(sql, timeStart, GET_LOCK_LOG_PREFIX); + } } private static class DatabaseReentrantLock extends ReentrantLock { DatabaseReentrantLock(boolean fair) { @@ -444,7 +467,11 @@ public class SQLiteDatabase extends SQLiteClosable { * @see #unlockForced() */ private void lockForced() { - lock(true); + lock(null, true); + } + + private void lockForced(String sql) { + lock(sql, true); } /** @@ -612,7 +639,7 @@ public class SQLiteDatabase extends SQLiteClosable { private void beginTransaction(SQLiteTransactionListener transactionListener, boolean exclusive) { verifyDbIsOpen(); - lockForced(); + lockForced(BEGIN_SQL); boolean ok = false; try { // If this thread already had the lock then get out @@ -635,6 +662,7 @@ public class SQLiteDatabase extends SQLiteClosable { } else { execSQL("BEGIN IMMEDIATE;"); } + mTransStartTime = SystemClock.uptimeMillis(); mTransactionListener = transactionListener; mTransactionIsSuccessful = true; mInnerTransactionIsSuccessful = false; @@ -698,6 +726,8 @@ public class SQLiteDatabase extends SQLiteClosable { Log.i(TAG, "PRAGMA wal_Checkpoint done"); } } + // log the transaction time to the Eventlog. + logTimeStat(getLastSqlStatement(), mTransStartTime, COMMIT_SQL); } else { try { execSQL("ROLLBACK;"); @@ -1855,24 +1885,7 @@ public class SQLiteDatabase extends SQLiteClosable { * @throws SQLException if the SQL string is invalid */ public void execSQL(String sql) throws SQLException { - int stmtType = DatabaseUtils.getSqlStatementType(sql); - if (stmtType == DatabaseUtils.STATEMENT_ATTACH) { - disableWriteAheadLogging(); - } - long timeStart = SystemClock.uptimeMillis(); - logTimeStat(mLastSqlStatement, timeStart, GET_LOCK_LOG_PREFIX); executeSql(sql, null); - - if (stmtType == DatabaseUtils.STATEMENT_ATTACH) { - mHasAttachedDbs = true; - } - // Log commit statements along with the most recently executed - // SQL statement for disambiguation. - if (stmtType == DatabaseUtils.STATEMENT_COMMIT) { - logTimeStat(mLastSqlStatement, timeStart, COMMIT_SQL); - } else { - logTimeStat(sql, timeStart, null); - } } /** @@ -1926,19 +1939,19 @@ public class SQLiteDatabase extends SQLiteClosable { } private int executeSql(String sql, Object[] bindArgs) throws SQLException { - long timeStart = SystemClock.uptimeMillis(); - int n; + if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) { + disableWriteAheadLogging(); + mHasAttachedDbs = true; + } SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs); try { - n = statement.executeUpdateDelete(); + return statement.executeUpdateDelete(); } catch (SQLiteDatabaseCorruptException e) { onCorruption(); throw e; } finally { statement.close(); } - logTimeStat(sql, timeStart); - return n; } @Override @@ -2027,12 +2040,7 @@ public class SQLiteDatabase extends SQLiteClosable { logTimeStat(sql, beginMillis, null); } - /* package */ void logTimeStat(String sql, long beginMillis, String prefix) { - // Keep track of the last statement executed here, as this is - // the common funnel through which all methods of hitting - // libsqlite eventually flow. - mLastSqlStatement = sql; - + private void logTimeStat(String sql, long beginMillis, String prefix) { // Sample fast queries in proportion to the time taken. // Quantize the % first, so the logged sampling probability // exactly equals the actual sampling rate for this query. @@ -2059,7 +2067,6 @@ public class SQLiteDatabase extends SQLiteClosable { if (prefix != null) { sql = prefix + sql; } - if (sql.length() > QUERY_LOG_SQL_LENGTH) sql = sql.substring(0, QUERY_LOG_SQL_LENGTH); // ActivityThread.currentPackageName() only returns non-null if the diff --git a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java index de2fca9..a5e762e 100644 --- a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java +++ b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java @@ -42,7 +42,7 @@ public class SQLiteDirectCursorDriver implements SQLiteCursorDriver { SQLiteQuery query = null; try { - mDatabase.lock(); + mDatabase.lock(mSql); mDatabase.closePendingStatements(); query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs); diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java index 88246e8..89552dc 100644 --- a/core/java/android/database/sqlite/SQLiteProgram.java +++ b/core/java/android/database/sqlite/SQLiteProgram.java @@ -105,12 +105,9 @@ public abstract class SQLiteProgram extends SQLiteClosable { case DatabaseUtils.STATEMENT_SELECT: mStatementType = n | STATEMENT_CACHEABLE | STATEMENT_USE_POOLED_CONN; break; - case DatabaseUtils.STATEMENT_ATTACH: case DatabaseUtils.STATEMENT_BEGIN: case DatabaseUtils.STATEMENT_COMMIT: case DatabaseUtils.STATEMENT_ABORT: - case DatabaseUtils.STATEMENT_DDL: - case DatabaseUtils.STATEMENT_UNPREPARED: mStatementType = n | STATEMENT_DONT_PREPARE; break; default: @@ -353,13 +350,10 @@ public abstract class SQLiteProgram extends SQLiteClosable { /* package */ void compileAndbindAllArgs() { if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) { - // no need to prepare this SQL statement - if (SQLiteDebug.DEBUG_SQL_STATEMENTS) { - if (mBindArgs != null) { - throw new IllegalArgumentException("no need to pass bindargs for this sql :" + - mSql); - } + if (mBindArgs != null) { + throw new IllegalArgumentException("Can't pass bindargs for this sql :" + mSql); } + // no need to prepare this SQL statement return; } if (nStatement == 0) { diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java index e9e0172..dc882d9 100644 --- a/core/java/android/database/sqlite/SQLiteQuery.java +++ b/core/java/android/database/sqlite/SQLiteQuery.java @@ -70,9 +70,8 @@ public class SQLiteQuery extends SQLiteProgram { */ /* package */ int fillWindow(CursorWindow window, int maxRead, int lastPos) { + mDatabase.lock(mSql); long timeStart = SystemClock.uptimeMillis(); - mDatabase.lock(); - mDatabase.logTimeStat(mSql, timeStart, SQLiteDatabase.GET_LOCK_LOG_PREFIX); try { acquireReference(); try { diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java index c76cc6c..ff973a7 100644 --- a/core/java/android/database/sqlite/SQLiteStatement.java +++ b/core/java/android/database/sqlite/SQLiteStatement.java @@ -80,7 +80,8 @@ public class SQLiteStatement extends SQLiteProgram */ public int executeUpdateDelete() { try { - long timeStart = acquireAndLock(WRITE); + saveSqlAsLastSqlStatement(); + acquireAndLock(WRITE); int numChanges = 0; if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) { // since the statement doesn't have to be prepared, @@ -90,7 +91,6 @@ public class SQLiteStatement extends SQLiteProgram } else { numChanges = native_execute(); } - mDatabase.logTimeStat(mSql, timeStart); return numChanges; } finally { releaseAndUnlock(); @@ -108,15 +108,22 @@ public class SQLiteStatement extends SQLiteProgram */ public long executeInsert() { try { - long timeStart = acquireAndLock(WRITE); - long lastInsertedRowId = native_executeInsert(); - mDatabase.logTimeStat(mSql, timeStart); - return lastInsertedRowId; + saveSqlAsLastSqlStatement(); + acquireAndLock(WRITE); + return native_executeInsert(); } finally { releaseAndUnlock(); } } + private void saveSqlAsLastSqlStatement() { + if (((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == + DatabaseUtils.STATEMENT_UPDATE) || + (mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == + DatabaseUtils.STATEMENT_BEGIN) { + mDatabase.setLastSqlStatement(mSql); + } + } /** * Execute a statement that returns a 1 by 1 table with a numeric value. * For example, SELECT COUNT(*) FROM table; @@ -199,7 +206,7 @@ public class SQLiteStatement extends SQLiteProgram * <li>if the SQL statement is an update, start transaction if not already in one. * otherwise, get lock on the database</li> * <li>acquire reference on this object</li> - * <li>and then return the current time _before_ the database lock was acquired</li> + * <li>and then return the current time _after_ the database lock was acquired</li> * </ul> * <p> * This method removes the duplicate code from the other public @@ -243,7 +250,7 @@ public class SQLiteStatement extends SQLiteProgram } // do I have database lock? if not, grab it. if (!mDatabase.isDbLockedByCurrentThread()) { - mDatabase.lock(); + mDatabase.lock(mSql); mState = LOCK_ACQUIRED; } diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index ed2b205..e525c95 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -374,6 +374,12 @@ public class Camera { * The preview surface texture may not otherwise change while preview is * running. * + * The timestamps provided by {@link SurfaceTexture#getTimestamp()} for a + * SurfaceTexture set as the preview texture have an unspecified zero point, + * and cannot be directly compared between different cameras or different + * instances of the same camera, or across multiple runs of the same + * program. + * * @param surfaceTexture the {@link SurfaceTexture} to which the preview * images are to be sent or null to remove the current preview surface * texture diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java index f2b907b..a4ba3bd 100644 --- a/core/java/android/hardware/Sensor.java +++ b/core/java/android/hardware/Sensor.java @@ -66,7 +66,14 @@ public class Sensor { /** A constant describing a pressure sensor type */ public static final int TYPE_PRESSURE = 6; - /** A constant describing a temperature sensor type */ + /** + * A constant describing a temperature sensor type + * + * @deprecated use + * {@link android.hardware.Sensor#TYPE_AMBIENT_TEMPERATURE + * Sensor.TYPE_AMBIENT_TEMPERATURE} instead. + */ + @Deprecated public static final int TYPE_TEMPERATURE = 7; /** @@ -97,6 +104,9 @@ public class Sensor { */ public static final int TYPE_ROTATION_VECTOR = 11; + /** A constant describing an ambient temperature sensor type */ + public static final int TYPE_AMBIENT_TEMPERATURE = 13; + /** * A constant describing all sensor types. */ diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java index 78d7991..91f0098 100644 --- a/core/java/android/hardware/SensorEvent.java +++ b/core/java/android/hardware/SensorEvent.java @@ -305,6 +305,14 @@ public class SensorEvent { * positive in the counter-clockwise direction). * </p> * + * <h4>{@link android.hardware.Sensor#TYPE_AMBIENT_TEMPERATURE Sensor.TYPE_AMBIENT_TEMPERATURE}: + * </h4> + * + * <ul> + * <p> + * values[0]: ambient (room) temperature in degree Celsius. + * </ul> + * * @see SensorEvent * @see GeomagneticField */ diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java index f8f8a29..3bf64b2 100644 --- a/core/java/android/net/SSLCertificateSocketFactory.java +++ b/core/java/android/net/SSLCertificateSocketFactory.java @@ -17,18 +17,12 @@ package android.net; import android.os.SystemProperties; -import android.util.Config; import android.util.Log; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; -import java.security.GeneralSecurityException; import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.Certificate; import java.security.cert.X509Certificate; import javax.net.SocketFactory; @@ -40,7 +34,6 @@ import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import org.apache.harmony.xnet.provider.jsse.OpenSSLContextImpl; @@ -128,7 +121,7 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { * * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0 * for none. The socket timeout is reset to 0 after the handshake. - * @param cache The {@link SSLClientSessionCache} to use, or null for no cache. + * @param cache The {@link SSLSessionCache} to use, or null for no cache. * @return a new SSLSocketFactory with the specified parameters */ public static SSLSocketFactory getDefault(int handshakeTimeoutMillis, SSLSessionCache cache) { @@ -144,7 +137,7 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { * * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0 * for none. The socket timeout is reset to 0 after the handshake. - * @param cache The {@link SSLClientSessionCache} to use, or null for no cache. + * @param cache The {@link SSLSessionCache} to use, or null for no cache. * @return an insecure SSLSocketFactory with the specified parameters */ public static SSLSocketFactory getInsecure(int handshakeTimeoutMillis, SSLSessionCache cache) { @@ -157,12 +150,11 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { * * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0 * for none. The socket timeout is reset to 0 after the handshake. - * @param cache The {@link SSLClientSessionCache} to use, or null for no cache. + * @param cache The {@link SSLSessionCache} to use, or null for no cache. * @return a new SocketFactory with the specified parameters */ public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory( - int handshakeTimeoutMillis, - SSLSessionCache cache) { + int handshakeTimeoutMillis, SSLSessionCache cache) { return new org.apache.http.conn.ssl.SSLSocketFactory( new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, true)); } diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index d841419..b8c5c2a 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -773,19 +773,24 @@ class GLES20Canvas extends HardwareCanvas { public void drawPoint(float x, float y, Paint paint) { mPoint[0] = x; mPoint[1] = y; - drawPoints(mPoint, 0, 1, paint); + drawPoints(mPoint, 0, 2, paint); } @Override - public void drawPoints(float[] pts, int offset, int count, Paint paint) { - // TODO: Implement + public void drawPoints(float[] pts, Paint paint) { + drawPoints(pts, 0, pts.length, paint); } @Override - public void drawPoints(float[] pts, Paint paint) { - drawPoints(pts, 0, pts.length / 2, paint); + public void drawPoints(float[] pts, int offset, int count, Paint paint) { + int modifiers = setupModifiers(paint); + nDrawPoints(mRenderer, pts, offset, count, paint.mNativePaint); + if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); } + private static native void nDrawPoints(int renderer, float[] points, + int offset, int count, int paint); + @Override public void drawPosText(char[] text, int index, int count, float[] pos, Paint paint) { // TODO: Implement @@ -978,6 +983,13 @@ class GLES20Canvas extends HardwareCanvas { if (b.getConfig() == Bitmap.Config.ALPHA_8) { return setupModifiers(paint); } + + final ColorFilter filter = paint.getColorFilter(); + if (filter != null) { + nSetupColorFilter(mRenderer, filter.nativeColorFilter); + return MODIFIER_COLOR_FILTER; + } + return MODIFIER_NONE; } @@ -1011,7 +1023,7 @@ class GLES20Canvas extends HardwareCanvas { nSetupColorFilter(mRenderer, filter.nativeColorFilter); return MODIFIER_COLOR_FILTER; } - return MODIFIER_NONE; + return MODIFIER_NONE; } private static native void nSetupShader(int renderer, int shader); diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index 8584bf2..28541fe 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -20,7 +20,7 @@ package android.view; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; -import android.os.SystemClock; +import android.os.*; import android.util.EventLog; import android.util.Log; @@ -256,6 +256,7 @@ public abstract class HardwareRenderer { @SuppressWarnings({"deprecation"}) static abstract class GlRenderer extends HardwareRenderer { + // These values are not exposed in our EGL APIs private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; private static final int EGL_SURFACE_TYPE = 0x3033; private static final int EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400; @@ -290,7 +291,7 @@ public abstract class HardwareRenderer { GlRenderer(int glVersion, boolean translucent) { mGlVersion = glVersion; mTranslucent = translucent; - final String dirtyProperty = System.getProperty(RENDER_DIRTY_REGIONS_PROPERTY, "true"); + final String dirtyProperty = SystemProperties.get(RENDER_DIRTY_REGIONS_PROPERTY, "true"); //noinspection PointlessBooleanExpression,ConstantConditions mDirtyRegions = RENDER_DIRTY_REGIONS && "true".equalsIgnoreCase(dirtyProperty); } diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index a17db5d..3c34479 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -172,6 +172,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { * recent point, as well as any intermediate points since the last * hover move event. * <p> + * This action is always delivered to the window or view under the pointer. + * </p><p> * This action is not a touch event so it is delivered to * {@link View#onGenericMotionEvent(MotionEvent)} rather than * {@link View#onTouchEvent(MotionEvent)}. @@ -184,8 +186,9 @@ public final class MotionEvent extends InputEvent implements Parcelable { * vertical and/or horizontal scroll offsets. Use {@link #getAxisValue(int)} * to retrieve the information from {@link #AXIS_VSCROLL} and {@link #AXIS_HSCROLL}. * The pointer may or may not be down when this event is dispatched. - * This action is always delivered to the winder under the pointer, which - * may not be the window currently touched. + * <p></p> + * This action is always delivered to the window or view under the pointer, which + * may not be the window or view currently touched. * <p> * This action is not a touch event so it is delivered to * {@link View#onGenericMotionEvent(MotionEvent)} rather than @@ -195,6 +198,32 @@ public final class MotionEvent extends InputEvent implements Parcelable { public static final int ACTION_SCROLL = 8; /** + * Constant for {@link #getAction}: The pointer is not down but has entered the + * boundaries of a window or view. + * <p> + * This action is always delivered to the window or view under the pointer. + * </p><p> + * This action is not a touch event so it is delivered to + * {@link View#onGenericMotionEvent(MotionEvent)} rather than + * {@link View#onTouchEvent(MotionEvent)}. + * </p> + */ + public static final int ACTION_HOVER_ENTER = 9; + + /** + * Constant for {@link #getAction}: The pointer is not down but has exited the + * boundaries of a window or view. + * <p> + * This action is always delivered to the window or view that was previously under the pointer. + * </p><p> + * This action is not a touch event so it is delivered to + * {@link View#onGenericMotionEvent(MotionEvent)} rather than + * {@link View#onTouchEvent(MotionEvent)}. + * </p> + */ + public static final int ACTION_HOVER_EXIT = 10; + + /** * Bits in the action code that represent a pointer index, used with * {@link #ACTION_POINTER_DOWN} and {@link #ACTION_POINTER_UP}. Shifting * down by {@link #ACTION_POINTER_INDEX_SHIFT} provides the actual pointer @@ -1354,9 +1383,9 @@ public final class MotionEvent extends InputEvent implements Parcelable { /** * Returns true if this motion event is a touch event. * <p> - * Specifically excludes pointer events with action {@link #ACTION_HOVER_MOVE} - * or {@link #ACTION_SCROLL} because they are not actually touch events - * (the pointer is not down). + * Specifically excludes pointer events with action {@link #ACTION_HOVER_MOVE}, + * {@link #ACTION_HOVER_ENTER}, {@link #ACTION_HOVER_EXIT}, or {@link #ACTION_SCROLL} + * because they are not actually touch events (the pointer is not down). * </p> * @return True if this motion event is a touch event. * @hide @@ -2313,6 +2342,10 @@ public final class MotionEvent extends InputEvent implements Parcelable { return "ACTION_HOVER_MOVE"; case ACTION_SCROLL: return "ACTION_SCROLL"; + case ACTION_HOVER_ENTER: + return "ACTION_HOVER_ENTER"; + case ACTION_HOVER_EXIT: + return "ACTION_HOVER_EXIT"; } int index = (action & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT; switch (action & ACTION_MASK) { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index c729ccd..96cddfa 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -1304,6 +1304,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility static final int VIEW_STATE_PRESSED = 1 << 4; static final int VIEW_STATE_ACTIVATED = 1 << 5; static final int VIEW_STATE_ACCELERATED = 1 << 6; + static final int VIEW_STATE_HOVERED = 1 << 7; static final int[] VIEW_STATE_IDS = new int[] { R.attr.state_window_focused, VIEW_STATE_WINDOW_FOCUSED, @@ -1313,6 +1314,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility R.attr.state_pressed, VIEW_STATE_PRESSED, R.attr.state_activated, VIEW_STATE_ACTIVATED, R.attr.state_accelerated, VIEW_STATE_ACCELERATED, + R.attr.state_hovered, VIEW_STATE_HOVERED, }; static { @@ -1623,6 +1625,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility private static final int AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000; /** + * Indicates that the view has received HOVER_ENTER. Cleared on HOVER_EXIT. + * @hide + */ + private static final int HOVERED = 0x10000000; + + /** * Indicates that pivotX or pivotY were explicitly set and we should not assume the center * for transform operations * @@ -4643,23 +4651,81 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * <p> * Generic motion events with source class {@link InputDevice#SOURCE_CLASS_POINTER} * are delivered to the view under the pointer. All other generic motion events are - * delivered to the focused view. + * delivered to the focused view. Hover events are handled specially and are delivered + * to {@link #onHoverEvent}. * </p> * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. */ public boolean dispatchGenericMotionEvent(MotionEvent event) { + final int source = event.getSource(); + if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { + final int action = event.getAction(); + if (action == MotionEvent.ACTION_HOVER_ENTER + || action == MotionEvent.ACTION_HOVER_MOVE + || action == MotionEvent.ACTION_HOVER_EXIT) { + if (dispatchHoverEvent(event)) { + return true; + } + } else if (dispatchGenericPointerEvent(event)) { + return true; + } + } else if (dispatchGenericFocusedEvent(event)) { + return true; + } + //noinspection SimplifiableIfStatement if (mOnGenericMotionListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnGenericMotionListener.onGenericMotion(this, event)) { return true; } - return onGenericMotionEvent(event); } /** + * Dispatch a hover event. + * <p> + * Do not call this method directly. Call {@link #dispatchGenericMotionEvent} instead. + * </p> + * + * @param event The motion event to be dispatched. + * @return True if the event was handled by the view, false otherwise. + * @hide + */ + protected boolean dispatchHoverEvent(MotionEvent event) { + return onHoverEvent(event); + } + + /** + * Dispatch a generic motion event to the view under the first pointer. + * <p> + * Do not call this method directly. Call {@link #dispatchGenericMotionEvent} instead. + * </p> + * + * @param event The motion event to be dispatched. + * @return True if the event was handled by the view, false otherwise. + * @hide + */ + protected boolean dispatchGenericPointerEvent(MotionEvent event) { + return false; + } + + /** + * Dispatch a generic motion event to the currently focused view. + * <p> + * Do not call this method directly. Call {@link #dispatchGenericMotionEvent} instead. + * </p> + * + * @param event The motion event to be dispatched. + * @return True if the event was handled by the view, false otherwise. + * @hide + */ + protected boolean dispatchGenericFocusedEvent(MotionEvent event) { + return false; + } + + /** * Dispatch a pointer event. * <p> * Dispatches touch related pointer events to {@link #onTouchEvent} and all @@ -5223,15 +5289,92 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * </code> * * @param event The generic motion event being processed. - * - * @return Return true if you have consumed the event, false if you haven't. - * The default implementation always returns false. + * @return True if the event was handled, false otherwise. */ public boolean onGenericMotionEvent(MotionEvent event) { return false; } /** + * Implement this method to handle hover events. + * <p> + * Hover events are pointer events with action {@link MotionEvent#ACTION_HOVER_ENTER}, + * {@link MotionEvent#ACTION_HOVER_MOVE}, or {@link MotionEvent#ACTION_HOVER_EXIT}. + * </p><p> + * The view receives hover enter as the pointer enters the bounds of the view and hover + * exit as the pointer exits the bound of the view or just before the pointer goes down + * (which implies that {@link #onTouchEvent} will be called soon). + * </p><p> + * If the view would like to handle the hover event itself and prevent its children + * from receiving hover, it should return true from this method. If this method returns + * true and a child has already received a hover enter event, the child will + * automatically receive a hover exit event. + * </p><p> + * The default implementation sets the hovered state of the view if the view is + * clickable. + * </p> + * + * @param event The motion event that describes the hover. + * @return True if this view handled the hover event and does not want its children + * to receive the hover event. + */ + public boolean onHoverEvent(MotionEvent event) { + final int viewFlags = mViewFlags; + + if (((viewFlags & CLICKABLE) != CLICKABLE && + (viewFlags & LONG_CLICKABLE) != LONG_CLICKABLE)) { + // Nothing to do if the view is not clickable. + return false; + } + + if ((viewFlags & ENABLED_MASK) == DISABLED) { + // A disabled view that is clickable still consumes the hover events, it just doesn't + // respond to them. + return true; + } + + switch (event.getAction()) { + case MotionEvent.ACTION_HOVER_ENTER: + setHovered(true); + break; + + case MotionEvent.ACTION_HOVER_EXIT: + setHovered(false); + break; + } + + return true; + } + + /** + * Returns true if the view is currently hovered. + * + * @return True if the view is currently hovered. + */ + public boolean isHovered() { + return (mPrivateFlags & HOVERED) != 0; + } + + /** + * Sets whether the view is currently hovered. + * + * @param hovered True if the view is hovered. + */ + public void setHovered(boolean hovered) { + if (hovered) { + if ((mPrivateFlags & HOVERED) == 0) { + mPrivateFlags |= HOVERED; + refreshDrawableState(); + } + } else { + if ((mPrivateFlags & HOVERED) != 0) { + mPrivateFlags &= ~HOVERED; + refreshDrawableState(); + } + } + } + + /** * Implement this method to handle touch screen motion events. * * @param event The motion event. @@ -7216,8 +7359,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility mPrivateFlags &= ~DRAWN; mPrivateFlags |= INVALIDATED; mPrivateFlags &= ~DRAWING_CACHE_VALID; - if (mParent != null && mAttachInfo != null && mAttachInfo.mHardwareAccelerated) { - mParent.invalidateChild(this, null); + if (mParent != null && mAttachInfo != null) { + if (mAttachInfo.mHardwareAccelerated) { + mParent.invalidateChild(this, null); + } else { + final Rect r = mAttachInfo.mTmpInvalRect; + r.set(0, 0, mRight - mLeft, mBottom - mTop); + // Don't call invalidate -- we don't want to internally scroll + // our own bounds + mParent.invalidateChild(this, r); + } } } } @@ -7323,8 +7474,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility */ public boolean post(Runnable action) { Handler handler; - if (mAttachInfo != null) { - handler = mAttachInfo.mHandler; + AttachInfo attachInfo = mAttachInfo; + if (attachInfo != null) { + handler = attachInfo.mHandler; } else { // Assume that post will succeed later ViewRoot.getRunQueue().post(action); @@ -7352,8 +7504,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility */ public boolean postDelayed(Runnable action, long delayMillis) { Handler handler; - if (mAttachInfo != null) { - handler = mAttachInfo.mHandler; + AttachInfo attachInfo = mAttachInfo; + if (attachInfo != null) { + handler = attachInfo.mHandler; } else { // Assume that post will succeed later ViewRoot.getRunQueue().postDelayed(action, delayMillis); @@ -7375,8 +7528,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility */ public boolean removeCallbacks(Runnable action) { Handler handler; - if (mAttachInfo != null) { - handler = mAttachInfo.mHandler; + AttachInfo attachInfo = mAttachInfo; + if (attachInfo != null) { + handler = attachInfo.mHandler; } else { // Assume that post will succeed later ViewRoot.getRunQueue().removeCallbacks(action); @@ -7423,11 +7577,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility public void postInvalidateDelayed(long delayMilliseconds) { // We try only with the AttachInfo because there's no point in invalidating // if we are not attached to our window - if (mAttachInfo != null) { + AttachInfo attachInfo = mAttachInfo; + if (attachInfo != null) { Message msg = Message.obtain(); msg.what = AttachInfo.INVALIDATE_MSG; msg.obj = this; - mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds); + attachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds); } } @@ -7447,7 +7602,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility // We try only with the AttachInfo because there's no point in invalidating // if we are not attached to our window - if (mAttachInfo != null) { + AttachInfo attachInfo = mAttachInfo; + if (attachInfo != null) { final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.acquire(); info.target = this; info.left = left; @@ -7458,7 +7614,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility final Message msg = Message.obtain(); msg.what = AttachInfo.INVALIDATE_RECT_MSG; msg.obj = info; - mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds); + attachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds); } } @@ -9877,6 +10033,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility // windows to better match their app. viewStateIndex |= VIEW_STATE_ACCELERATED; } + if ((privateFlags & HOVERED) != 0) viewStateIndex |= VIEW_STATE_HOVERED; drawableState = VIEW_STATE_SETS[viewStateIndex]; diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 8dc86ac..058b826 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -147,6 +147,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @ViewDebug.ExportedProperty(category = "events") private float mLastTouchDownY; + // Child which last received ACTION_HOVER_ENTER and ACTION_HOVER_MOVE. + private View mHoveredChild; + /** * Internal flags. * @@ -1140,13 +1143,50 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return false; } - /** - * {@inheritDoc} - */ + /** @hide */ @Override - public boolean dispatchGenericMotionEvent(MotionEvent event) { - if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { - // Send the event to the child under the pointer. + protected boolean dispatchHoverEvent(MotionEvent event) { + // Send the hover enter or hover move event to the view group first. + // If it handles the event then a hovered child should receive hover exit. + boolean handled = false; + final boolean interceptHover; + final int action = event.getAction(); + if (action == MotionEvent.ACTION_HOVER_EXIT) { + interceptHover = true; + } else { + handled = super.dispatchHoverEvent(event); + interceptHover = handled; + } + + // Send successive hover events to the hovered child as long as the pointer + // remains within the child's bounds. + MotionEvent eventNoHistory = event; + if (mHoveredChild != null) { + final float x = event.getX(); + final float y = event.getY(); + + if (interceptHover + || !isTransformedTouchPointInView(x, y, mHoveredChild, null)) { + // Pointer exited the child. + // Send it a hover exit with only the most recent coordinates. We could + // try to find the exact point in history when the pointer left the view + // but it is not worth the effort. + eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory); + eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT); + handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, mHoveredChild); + eventNoHistory.setAction(action); + + mHoveredChild = null; + } else if (action == MotionEvent.ACTION_HOVER_MOVE) { + // Pointer is still within the child. + handled |= dispatchTransformedGenericPointerEvent(event, mHoveredChild); + } + } + + // Find a new hovered child if needed. + if (!interceptHover && mHoveredChild == null + && (action == MotionEvent.ACTION_HOVER_ENTER + || action == MotionEvent.ACTION_HOVER_MOVE)) { final int childrenCount = mChildrenCount; if (childrenCount != 0) { final View[] children = mChildren; @@ -1155,45 +1195,88 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager for (int i = childrenCount - 1; i >= 0; i--) { final View child = children[i]; - if ((child.mViewFlags & VISIBILITY_MASK) != VISIBLE - && child.getAnimation() == null) { - // Skip invisible child unless it is animating. + if (!canViewReceivePointerEvents(child) + || !isTransformedTouchPointInView(x, y, child, null)) { continue; } - if (!isTransformedTouchPointInView(x, y, child, null)) { - // Scroll point is out of child's bounds. - continue; + // Found the hovered child. + mHoveredChild = child; + if (action == MotionEvent.ACTION_HOVER_MOVE) { + // Pointer was moving within the view group and entered the child. + // Send it a hover enter and hover move with only the most recent + // coordinates. We could try to find the exact point in history when + // the pointer entered the view but it is not worth the effort. + eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory); + eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER); + handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, child); + eventNoHistory.setAction(action); + + handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, child); + } else { /* must be ACTION_HOVER_ENTER */ + // Pointer entered the child. + handled |= dispatchTransformedGenericPointerEvent(event, child); } + break; + } + } + } - final float offsetX = mScrollX - child.mLeft; - final float offsetY = mScrollY - child.mTop; - final boolean handled; - if (!child.hasIdentityMatrix()) { - MotionEvent transformedEvent = MotionEvent.obtain(event); - transformedEvent.offsetLocation(offsetX, offsetY); - transformedEvent.transform(child.getInverseMatrix()); - handled = child.dispatchGenericMotionEvent(transformedEvent); - transformedEvent.recycle(); - } else { - event.offsetLocation(offsetX, offsetY); - handled = child.dispatchGenericMotionEvent(event); - event.offsetLocation(-offsetX, -offsetY); - } + // Recycle the copy of the event that we made. + if (eventNoHistory != event) { + eventNoHistory.recycle(); + } - if (handled) { - return true; - } + // Send hover exit to the view group. If there was a child, we will already have + // sent the hover exit to it. + if (action == MotionEvent.ACTION_HOVER_EXIT) { + handled |= super.dispatchHoverEvent(event); + } + + // Done. + return handled; + } + + private static MotionEvent obtainMotionEventNoHistoryOrSelf(MotionEvent event) { + if (event.getHistorySize() == 0) { + return event; + } + return MotionEvent.obtainNoHistory(event); + } + + /** @hide */ + @Override + protected boolean dispatchGenericPointerEvent(MotionEvent event) { + // Send the event to the child under the pointer. + final int childrenCount = mChildrenCount; + if (childrenCount != 0) { + final View[] children = mChildren; + final float x = event.getX(); + final float y = event.getY(); + + for (int i = childrenCount - 1; i >= 0; i--) { + final View child = children[i]; + if (!canViewReceivePointerEvents(child) + || !isTransformedTouchPointInView(x, y, child, null)) { + continue; } - } - // No child handled the event. Send it to this view group. - return super.dispatchGenericMotionEvent(event); + if (dispatchTransformedGenericPointerEvent(event, child)) { + return true; + } + } } + // No child handled the event. Send it to this view group. + return super.dispatchGenericPointerEvent(event); + } + + /** @hide */ + @Override + protected boolean dispatchGenericFocusedEvent(MotionEvent event) { // Send the event to the focused child or to this view group if it has focus. if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) { - return super.dispatchGenericMotionEvent(event); + return super.dispatchGenericFocusedEvent(event); } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) { return mFocused.dispatchGenericMotionEvent(event); } @@ -1201,6 +1284,33 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** + * Dispatches a generic pointer event to a child, taking into account + * transformations that apply to the child. + * + * @param event The event to send. + * @param child The view to send the event to. + * @return {@code true} if the child handled the event. + */ + private boolean dispatchTransformedGenericPointerEvent(MotionEvent event, View child) { + final float offsetX = mScrollX - child.mLeft; + final float offsetY = mScrollY - child.mTop; + + boolean handled; + if (!child.hasIdentityMatrix()) { + MotionEvent transformedEvent = MotionEvent.obtain(event); + transformedEvent.offsetLocation(offsetX, offsetY); + transformedEvent.transform(child.getInverseMatrix()); + handled = child.dispatchGenericMotionEvent(transformedEvent); + transformedEvent.recycle(); + } else { + event.offsetLocation(offsetX, offsetY); + handled = child.dispatchGenericMotionEvent(event); + event.offsetLocation(-offsetX, -offsetY); + } + return handled; + } + + /** * {@inheritDoc} */ @Override @@ -1213,8 +1323,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final int actionMasked = action & MotionEvent.ACTION_MASK; // Handle an initial down. - if (actionMasked == MotionEvent.ACTION_DOWN - || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { + if (actionMasked == MotionEvent.ACTION_DOWN) { // Throw away all previous state when starting a new touch gesture. // The framework may have dropped the up or cancel event for the previous gesture // due to an app switch, ANR, or some other state change. @@ -1268,14 +1377,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager for (int i = childrenCount - 1; i >= 0; i--) { final View child = children[i]; - if ((child.mViewFlags & VISIBILITY_MASK) != VISIBLE - && child.getAnimation() == null) { - // Skip invisible child unless it is animating. - continue; - } - - if (!isTransformedTouchPointInView(x, y, child, null)) { - // New pointer is out of child's bounds. + if (!canViewReceivePointerEvents(child) + || !isTransformedTouchPointInView(x, y, child, null)) { continue; } @@ -1476,6 +1579,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** + * Returns true if a child view can receive pointer events. + * @hide + */ + private static boolean canViewReceivePointerEvents(View child) { + return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE + || child.getAnimation() != null; + } + + /** * Returns true if a child view contains the specified point when transformed * into its coordinate space. * Child must not be null. @@ -1975,7 +2087,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager public void setPadding(int left, int top, int right, int bottom) { super.setPadding(left, top, right, bottom); - if ((mPaddingLeft | mPaddingTop | mPaddingRight | mPaddingRight) != 0) { + if ((mPaddingLeft | mPaddingTop | mPaddingRight | mPaddingBottom) != 0) { mGroupFlags |= FLAG_PADDING_NOT_NULL; } else { mGroupFlags &= ~FLAG_PADDING_NOT_NULL; @@ -3244,6 +3356,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager mTransition.removeChild(this, view); } + if (view == mHoveredChild) { + mHoveredChild = null; + } + boolean clearChildFocus = false; if (view == mFocused) { view.clearFocusForRemoval(); @@ -3307,6 +3423,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final OnHierarchyChangeListener onHierarchyChangeListener = mOnHierarchyChangeListener; final boolean notifyListener = onHierarchyChangeListener != null; final View focused = mFocused; + final View hoveredChild = mHoveredChild; final boolean detach = mAttachInfo != null; View clearChildFocus = null; @@ -3320,6 +3437,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager mTransition.removeChild(this, view); } + if (view == hoveredChild) { + mHoveredChild = null; + } + if (view == focused) { view.clearFocusForRemoval(); clearChildFocus = view; @@ -3377,6 +3498,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final OnHierarchyChangeListener listener = mOnHierarchyChangeListener; final boolean notify = listener != null; final View focused = mFocused; + final View hoveredChild = mHoveredChild; final boolean detach = mAttachInfo != null; View clearChildFocus = null; @@ -3389,6 +3511,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager mTransition.removeChild(this, view); } + if (view == hoveredChild) { + mHoveredChild = null; + } + if (view == focused) { view.clearFocusForRemoval(); clearChildFocus = view; diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index 7d6e18f..3c386b4 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -501,7 +501,12 @@ public final class ViewRoot extends Handler implements ViewParent, final boolean hardwareAccelerated = (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0; - if (attrs != null && hardwareAccelerated) { + if (hardwareAccelerated) { + if (!HardwareRenderer.isAvailable()) { + mAttachInfo.mHardwareAccelerationRequested = true; + return; + } + // Only enable hardware acceleration if we are not in the system process // The window manager creates ViewRoots to display animated preview windows // of launching apps and we don't want those to be hardware accelerated @@ -524,8 +529,6 @@ public final class ViewRoot extends Handler implements ViewParent, mAttachInfo.mHardwareRenderer = HardwareRenderer.createGlRenderer(2, translucent); mAttachInfo.mHardwareAccelerated = mAttachInfo.mHardwareAccelerationRequested = mAttachInfo.mHardwareRenderer != null; - } else if (HardwareRenderer.isAvailable()) { - mAttachInfo.mHardwareAccelerationRequested = true; } } } diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java index 6be988e..9636513 100644 --- a/core/java/android/webkit/HTML5VideoFullScreen.java +++ b/core/java/android/webkit/HTML5VideoFullScreen.java @@ -114,6 +114,13 @@ public class HTML5VideoFullScreen extends HTML5VideoView return mVideoSurfaceView; } + @Override + public void start() { + if (getAutostart()) { + super.start(); + } + } + HTML5VideoFullScreen(Context context, int videoLayerId, int position, boolean autoStart) { mVideoSurfaceView = new VideoSurfaceView(context); @@ -146,7 +153,7 @@ public class HTML5VideoFullScreen extends HTML5VideoView // So in full screen, we reset the MediaPlayer mPlayer.reset(); setMediaController(new MediaController(mProxy.getContext())); - + mPlayer.setScreenOnWhilePlaying(true); prepareDataAndDisplayMode(mProxy); } @@ -209,9 +216,6 @@ public class HTML5VideoFullScreen extends HTML5VideoView // which happens when the video view is detached from its parent // view. This happens in the WebChromeClient before this method // is invoked. - mTimer.cancel(); - mTimer = null; - pauseAndDispatch(mProxy); mLayout.removeView(getSurfaceView()); diff --git a/core/java/android/webkit/HTML5VideoInline.java b/core/java/android/webkit/HTML5VideoInline.java index f1d9189..25921bc 100644 --- a/core/java/android/webkit/HTML5VideoInline.java +++ b/core/java/android/webkit/HTML5VideoInline.java @@ -17,24 +17,17 @@ public class HTML5VideoInline extends HTML5VideoView{ private static SurfaceTexture mSurfaceTexture = null; private static int[] mTextureNames; - // Only when the video is prepared, we render using SurfaceTexture. - // This in fact is used to avoid showing the obsolete content when - // switching videos. - private static boolean mReadyToUseSurfTex = false; - // Video control FUNCTIONS: @Override public void start() { - super.start(); - if (mCurrentState == STATE_PREPARED) { - mReadyToUseSurfTex = true; + if (!getPauseDuringPreparing()) { + super.start(); } } HTML5VideoInline(int videoLayerId, int position, boolean autoStart) { init(videoLayerId, position, autoStart); - mReadyToUseSurfTex = false; } @Override @@ -54,7 +47,6 @@ public class HTML5VideoInline extends HTML5VideoView{ @Override public void pauseAndDispatch(HTML5VideoViewProxy proxy) { super.pauseAndDispatch(proxy); - mReadyToUseSurfTex = false; } // Inline Video specific FUNCTIONS: @@ -87,11 +79,6 @@ public class HTML5VideoInline extends HTML5VideoView{ return mTextureNames[0]; } - @Override - public boolean getReadyToUseSurfTex() { - return mReadyToUseSurfTex; - } - private void setFrameAvailableListener(SurfaceTexture.OnFrameAvailableListener l) { mSurfaceTexture.setOnFrameAvailableListener(l); } diff --git a/core/java/android/webkit/HTML5VideoView.java b/core/java/android/webkit/HTML5VideoView.java index 663497c..8ea73b5 100644 --- a/core/java/android/webkit/HTML5VideoView.java +++ b/core/java/android/webkit/HTML5VideoView.java @@ -27,9 +27,12 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener{ // prepared and not prepared. // When the video is not prepared, we will have to save the seekTo time, // and use it when prepared to play. - protected static final int STATE_NOTPREPARED = 0; - protected static final int STATE_PREPARED = 1; - + // NOTE: these values are in sync with VideoLayerAndroid.h in webkit side. + // Please keep them in sync when changed. + static final int STATE_INITIALIZED = 0; + static final int STATE_NOTPREPARED = 1; + static final int STATE_PREPARED = 2; + static final int STATE_PLAYING = 3; protected int mCurrentState; protected HTML5VideoViewProxy mProxy; @@ -62,9 +65,18 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener{ // The spec says the timer should fire every 250 ms or less. private static final int TIMEUPDATE_PERIOD = 250; // ms + protected boolean mPauseDuringPreparing; // common Video control FUNCTIONS: public void start() { if (mCurrentState == STATE_PREPARED) { + // When replaying the same video, there is no onPrepared call. + // Therefore, the timer should be set up here. + if (mTimer == null) + { + mTimer = new Timer(); + mTimer.schedule(new TimeupdateTask(mProxy), TIMEUPDATE_PERIOD, + TIMEUPDATE_PERIOD); + } mPlayer.start(); } } @@ -72,9 +84,14 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener{ public void pause() { if (mCurrentState == STATE_PREPARED && mPlayer.isPlaying()) { mPlayer.pause(); + } else if (mCurrentState == STATE_NOTPREPARED) { + mPauseDuringPreparing = true; } + // Delete the Timer to stop it since there is no stop call. if (mTimer != null) { mTimer.purge(); + mTimer.cancel(); + mTimer = null; } } @@ -118,14 +135,20 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener{ return mAutostart; } + public boolean getPauseDuringPreparing() { + return mPauseDuringPreparing; + } + // Every time we start a new Video, we create a VideoView and a MediaPlayer public void init(int videoLayerId, int position, boolean autoStart) { mPlayer = new MediaPlayer(); - mCurrentState = STATE_NOTPREPARED; + mCurrentState = STATE_INITIALIZED; mProxy = null; mVideoLayerId = videoLayerId; mSaveSeekTime = position; mAutostart = autoStart; + mTimer = null; + mPauseDuringPreparing = false; } protected HTML5VideoView() { @@ -150,8 +173,6 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener{ // When switching players, surface texture will be reused. mUri = uri; mHeaders = generateHeaders(uri, proxy); - - mTimer = new Timer(); } // Listeners setup FUNCTIONS: @@ -190,6 +211,7 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener{ } catch (IOException e) { e.printStackTrace(); } + mCurrentState = STATE_NOTPREPARED; } @@ -198,6 +220,15 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener{ return mVideoLayerId; } + + public int getCurrentState() { + if (mPlayer.isPlaying()) { + return STATE_PLAYING; + } else { + return mCurrentState; + } + } + private static final class TimeupdateTask extends TimerTask { private HTML5VideoViewProxy mProxy; @@ -215,20 +246,20 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener{ public void onPrepared(MediaPlayer mp) { mCurrentState = STATE_PREPARED; seekTo(mSaveSeekTime); - if (mProxy != null) + if (mProxy != null) { mProxy.onPrepared(mp); - - mTimer.schedule(new TimeupdateTask(mProxy), TIMEUPDATE_PERIOD, TIMEUPDATE_PERIOD); - + } + if (mPauseDuringPreparing) { + pauseAndDispatch(mProxy); + mPauseDuringPreparing = false; + } } // Pause the play and update the play/pause button public void pauseAndDispatch(HTML5VideoViewProxy proxy) { - if (isPlaying()) { - pause(); - if (proxy != null) { - proxy.dispatchOnPaused(); - } + pause(); + if (proxy != null) { + proxy.dispatchOnPaused(); } } diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java index d3fcfa5..acd7eab 100644 --- a/core/java/android/webkit/HTML5VideoViewProxy.java +++ b/core/java/android/webkit/HTML5VideoViewProxy.java @@ -105,12 +105,12 @@ class HTML5VideoViewProxy extends Handler int currentVideoLayerId = mHTML5VideoView.getVideoLayerId(); if (layer != 0 && surfTexture != null && currentVideoLayerId != -1) { - boolean readyToUseSurfTex = - mHTML5VideoView.getReadyToUseSurfTex(); + int playerState = mHTML5VideoView.getCurrentState(); boolean foundInTree = nativeSendSurfaceTexture(surfTexture, layer, currentVideoLayerId, textureName, - readyToUseSurfTex); - if (readyToUseSurfTex && !foundInTree) { + playerState); + if (playerState >= HTML5VideoView.STATE_PREPARED + && !foundInTree) { mHTML5VideoView.pauseAndDispatch(mCurrentProxy); mHTML5VideoView.deleteSurfaceTexture(); } @@ -224,10 +224,11 @@ class HTML5VideoViewProxy extends Handler } public static void onPrepared() { - if (!mHTML5VideoView.isFullScreenMode() || - mHTML5VideoView.isFullScreenMode() && - mHTML5VideoView.getAutostart() ) - mHTML5VideoView.start(); + // The VideoView will decide whether to really kick off to play. + mHTML5VideoView.start(); + if (mBaseLayer != 0) { + setBaseLayer(mBaseLayer); + } } public static void end() { @@ -668,5 +669,5 @@ class HTML5VideoViewProxy extends Handler private native void nativeOnTimeupdate(int position, int nativePointer); private native static boolean nativeSendSurfaceTexture(SurfaceTexture texture, int baseLayer, int videoLayerId, int textureName, - boolean updateTexture); + int playerState); } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index d39271e..6cb5c35 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -4529,8 +4529,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * Otherwise resurrects the selection and returns true if resurrected. */ boolean resurrectSelectionIfNeeded() { - if (mSelectedPosition < 0) { - return resurrectSelection(); + if (mSelectedPosition < 0 && resurrectSelection()) { + updateSelectorState(); + return true; } return false; } diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java index 8f1354b..11b594c 100644 --- a/core/java/com/android/internal/app/ActionBarImpl.java +++ b/core/java/com/android/internal/app/ActionBarImpl.java @@ -25,13 +25,13 @@ import com.android.internal.widget.ActionBarView; import android.animation.Animator; import android.animation.Animator.AnimatorListener; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.app.ActionBar; import android.app.Activity; import android.app.Dialog; -import android.app.Fragment; import android.app.FragmentTransaction; import android.content.Context; import android.graphics.drawable.Drawable; @@ -44,7 +44,6 @@ import android.view.MenuItem; import android.view.View; import android.view.Window; import android.view.animation.DecelerateInterpolator; -import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.SpinnerAdapter; @@ -59,6 +58,7 @@ import java.util.ArrayList; * which is normally hidden. */ public class ActionBarImpl extends ActionBar { + private static final String TAG = "ActionBarImpl"; private static final int NORMAL_VIEW = 0; private static final int CONTEXT_VIEW = 1; @@ -92,60 +92,34 @@ public class ActionBarImpl extends ActionBar { final Handler mHandler = new Handler(); - private Animator mCurrentAnim; + private Animator mCurrentShowAnim; + private Animator mCurrentModeAnim; private boolean mShowHideAnimationEnabled; + boolean mWasHiddenBeforeMode; private static final TimeInterpolator sFadeOutInterpolator = new DecelerateInterpolator(); final AnimatorListener[] mAfterAnimation = new AnimatorListener[] { - new AnimatorListener() { // NORMAL_VIEW - @Override - public void onAnimationStart(Animator animation) { - } - + new AnimatorListenerAdapter() { // NORMAL_VIEW @Override public void onAnimationEnd(Animator animation) { if (mLowerContextView != null) { mLowerContextView.removeAllViews(); } - mCurrentAnim = null; + mCurrentModeAnim = null; hideAllExcept(NORMAL_VIEW); } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } }, - new AnimatorListener() { // CONTEXT_VIEW - @Override - public void onAnimationStart(Animator animation) { - } - + new AnimatorListenerAdapter() { // CONTEXT_VIEW @Override public void onAnimationEnd(Animator animation) { - mCurrentAnim = null; + mCurrentModeAnim = null; hideAllExcept(CONTEXT_VIEW); } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } } }; - final AnimatorListener mHideListener = new AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - + final AnimatorListener mHideListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (mContentView != null) { @@ -153,36 +127,16 @@ public class ActionBarImpl extends ActionBar { } mContainerView.setVisibility(View.GONE); mContainerView.setTransitioning(false); - mCurrentAnim = null; - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { + mCurrentShowAnim = null; } }; - final AnimatorListener mShowListener = new AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - + final AnimatorListener mShowListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - mCurrentAnim = null; + mCurrentShowAnim = null; mContainerView.requestLayout(); } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } }; public ActionBarImpl(Activity activity) { @@ -229,8 +183,8 @@ public class ActionBarImpl extends ActionBar { */ public void setShowHideAnimationEnabled(boolean enabled) { mShowHideAnimationEnabled = enabled; - if (!enabled && mCurrentAnim != null) { - mCurrentAnim.end(); + if (!enabled && mCurrentShowAnim != null) { + mCurrentShowAnim.end(); } } @@ -370,6 +324,7 @@ public class ActionBarImpl extends ActionBar { mUpperContextView.killMode(); ActionMode mode = new ActionModeImpl(callback); if (callback.onCreateActionMode(mode, mode.getMenu())) { + mWasHiddenBeforeMode = !isShowing(); mode.invalidate(); mUpperContextView.initForMode(mode); animateTo(CONTEXT_VIEW); @@ -378,7 +333,6 @@ public class ActionBarImpl extends ActionBar { mLowerContextView.setVisibility(View.VISIBLE); } mActionMode = mode; - show(); return mode; } return null; @@ -498,10 +452,15 @@ public class ActionBarImpl extends ActionBar { @Override public void show() { - if (mCurrentAnim != null) { - mCurrentAnim.end(); + show(true); + } + + void show(boolean markHiddenBeforeMode) { + if (mCurrentShowAnim != null) { + mCurrentShowAnim.end(); } if (mContainerView.getVisibility() == View.VISIBLE) { + if (markHiddenBeforeMode) mWasHiddenBeforeMode = false; return; } mContainerView.setVisibility(View.VISIBLE); @@ -517,17 +476,19 @@ public class ActionBarImpl extends ActionBar { b.with(ObjectAnimator.ofFloat(mContainerView, "translationY", 0)); } anim.addListener(mShowListener); - mCurrentAnim = anim; + mCurrentShowAnim = anim; anim.start(); } else { + mContainerView.setAlpha(1); + mContainerView.setTranslationY(0); mShowListener.onAnimationEnd(null); } } @Override public void hide() { - if (mCurrentAnim != null) { - mCurrentAnim.end(); + if (mCurrentShowAnim != null) { + mCurrentShowAnim.end(); } if (mContainerView.getVisibility() == View.GONE) { return; @@ -545,7 +506,7 @@ public class ActionBarImpl extends ActionBar { -mContainerView.getHeight())); } anim.addListener(mHideListener); - mCurrentAnim = anim; + mCurrentShowAnim = anim; anim.start(); } else { mHideListener.onAnimationEnd(null); @@ -556,13 +517,17 @@ public class ActionBarImpl extends ActionBar { return mContainerView.getVisibility() == View.VISIBLE; } - private long animateTo(int viewIndex) { - show(); + long animateTo(int viewIndex) { + show(false); + if (mCurrentModeAnim != null) { + mCurrentModeAnim.end(); + } AnimatorSet set = new AnimatorSet(); final View targetChild = mContainerView.getChildAt(viewIndex); targetChild.setVisibility(View.VISIBLE); + targetChild.setAlpha(0); AnimatorSet.Builder b = set.play(ObjectAnimator.ofFloat(targetChild, "alpha", 1)); final int count = mContainerView.getChildCount(); @@ -581,7 +546,7 @@ public class ActionBarImpl extends ActionBar { set.addListener(mAfterAnimation[viewIndex]); - mCurrentAnim = set; + mCurrentModeAnim = set; set.start(); return set.getDuration(); } @@ -636,6 +601,10 @@ public class ActionBarImpl extends ActionBar { mLowerContextView.setVisibility(View.GONE); } mActionMode = null; + + if (mWasHiddenBeforeMode) { + hide(); + } } @Override @@ -889,23 +858,24 @@ public class ActionBarImpl extends ActionBar { return mTabs.get(index); } - /** - * This fragment is added when we're keeping a back stack in a tab switch - * transaction. We use it to change the selected tab in the action bar view - * when we back out. - */ - private class SwitchSelectedTabViewFragment extends Fragment { - private int mSelectedTabIndex; - public SwitchSelectedTabViewFragment(int oldSelectedTab) { - mSelectedTabIndex = oldSelectedTab; - } + @Override + public void setIcon(int resId) { + mActionView.setIcon(mContext.getResources().getDrawable(resId)); + } - @Override - public void onDetach() { - if (mSelectedTabIndex >= 0 && mSelectedTabIndex < getTabCount()) { - mActionView.setTabSelected(mSelectedTabIndex); - } - } + @Override + public void setIcon(Drawable icon) { + mActionView.setIcon(icon); + } + + @Override + public void setLogo(int resId) { + mActionView.setLogo(mContext.getResources().getDrawable(resId)); + } + + @Override + public void setLogo(Drawable logo) { + mActionView.setLogo(logo); } } diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index dea53bf..32ddc22 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -31,7 +31,6 @@ import android.util.Log; import dalvik.system.VMRuntime; import dalvik.system.Zygote; -import dalvik.system.SamplingProfiler; import java.io.BufferedReader; import java.io.FileDescriptor; @@ -99,25 +98,6 @@ public class ZygoteInit { private static final boolean PRELOAD_RESOURCES = true; /** - * List of methods we "warm up" in the register map cache. These were - * chosen because they appeared on the stack in GCs in multiple - * applications. - * - * This is in a VM-ready format, to minimize string processing. If a - * class is not already loaded, or a method is not found, the entry - * will be skipped. - * - * This doesn't really merit a separately-generated input file at this - * time. The list is fairly short, and the consequences of failure - * are minor. - */ - private static final String[] REGISTER_MAP_METHODS = { - // (currently not doing any) - //"Landroid/app/Activity;.setContentView:(I)V", - }; - - - /** * Invokes a static "main(argv[]) method on class "className". * Converts various failing exceptions into RuntimeExceptions, with * the assumption that they will then cause the VM instance to exit. @@ -338,45 +318,6 @@ public class ZygoteInit { } /** - * Pre-caches register maps for methods that are commonly used. - */ - private static void cacheRegisterMaps() { - String failed = null; - int failure; - long startTime = System.nanoTime(); - - failure = 0; - - for (int i = 0; i < REGISTER_MAP_METHODS.length; i++) { - String str = REGISTER_MAP_METHODS[i]; - - if (!Debug.cacheRegisterMap(str)) { - if (failed == null) - failed = str; - failure++; - } - } - - long delta = System.nanoTime() - startTime; - - if (failure == REGISTER_MAP_METHODS.length) { - if (REGISTER_MAP_METHODS.length > 0) { - Log.i(TAG, - "Register map caching failed (precise GC not enabled?)"); - } - return; - } - - Log.i(TAG, "Register map cache: found " + - (REGISTER_MAP_METHODS.length - failure) + " of " + - REGISTER_MAP_METHODS.length + " methods in " + - (delta / 1000000L) + "ms"); - if (failure > 0) { - Log.i(TAG, " First failure: " + failed); - } - } - - /** * Load in commonly used resources, so they can be shared across * processes. * @@ -564,7 +505,6 @@ public class ZygoteInit { EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START, SystemClock.uptimeMillis()); preloadClasses(); - //cacheRegisterMaps(); preloadResources(); EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END, SystemClock.uptimeMillis()); diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java index 3325df6..ca1aa0b 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java +++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java @@ -56,6 +56,7 @@ public class ActionMenuItemView extends LinearLayout mTextButton = (Button) findViewById(com.android.internal.R.id.textButton); mImageButton.setOnClickListener(this); mTextButton.setOnClickListener(this); + setOnClickListener(this); } public MenuItemImpl getItemData() { diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index 81d02ee..2d9a9f2 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -416,6 +416,21 @@ public class ActionBarView extends ViewGroup { } } + public void setIcon(Drawable icon) { + mIcon = icon; + if (icon != null && + ((mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) == 0 || mLogo == null)) { + mIconView.setImageDrawable(icon); + } + } + + public void setLogo(Drawable logo) { + mLogo = logo; + if (logo != null && (mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) != 0) { + mIconView.setImageDrawable(logo); + } + } + public void setNavigationMode(int mode) { final int oldMode = mNavigationMode; if (mode != oldMode) { diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java index 076a1cb..c34cb9e 100644 --- a/core/java/com/android/internal/widget/PointerLocationView.java +++ b/core/java/com/android/internal/widget/PointerLocationView.java @@ -357,6 +357,12 @@ public class PointerLocationView extends View { case MotionEvent.ACTION_HOVER_MOVE: prefix = "HOVER MOVE"; break; + case MotionEvent.ACTION_HOVER_ENTER: + prefix = "HOVER ENTER"; + break; + case MotionEvent.ACTION_HOVER_EXIT: + prefix = "HOVER EXIT"; + break; case MotionEvent.ACTION_SCROLL: prefix = "SCROLL"; break; diff --git a/core/jni/Android.mk b/core/jni/Android.mk index b4a0e4f..66d8a36 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -93,6 +93,7 @@ LOCAL_SRC_FILES:= \ android/graphics/DrawFilter.cpp \ android/graphics/CreateJavaOutputStreamAdaptor.cpp \ android/graphics/Graphics.cpp \ + android/graphics/HarfbuzzSkia.cpp \ android/graphics/Interpolator.cpp \ android/graphics/LayerRasterizer.cpp \ android/graphics/MaskFilter.cpp \ @@ -174,6 +175,7 @@ LOCAL_C_INCLUDES += \ external/icu4c/i18n \ external/icu4c/common \ external/jpeg \ + external/harfbuzz/src \ frameworks/opt/emoji LOCAL_SHARED_LIBRARIES := \ @@ -206,6 +208,7 @@ LOCAL_SHARED_LIBRARIES := \ libjpeg \ libnfc_ndef \ libusbhost \ + libharfbuzz \ ifeq ($(USE_OPENGL_RENDERER),true) LOCAL_SHARED_LIBRARIES += libhwui diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 8064836..05a46a8 100644 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -100,6 +100,8 @@ bool GraphicsJNI::SetPixels(JNIEnv* env, jintArray srcColors, dst = (char*)dst + dstBitmap.rowBytes();
}
+ dstBitmap.notifyPixelsChanged();
+
env->ReleaseIntArrayElements(srcColors, const_cast<jint*>(array),
JNI_ABORT);
return true;
@@ -524,6 +526,7 @@ static void Bitmap_setPixel(JNIEnv* env, jobject, const SkBitmap* bitmap, }
proc(bitmap->getAddr(x, y), &color, 1, x, y);
+ bitmap->notifyPixelsChanged();
}
static void Bitmap_setPixels(JNIEnv* env, jobject, const SkBitmap* bitmap,
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp index 0cdb357..b4ad9e9 100644 --- a/core/jni/android/graphics/Canvas.cpp +++ b/core/jni/android/graphics/Canvas.cpp @@ -755,6 +755,27 @@ public: env->ReleaseStringChars(text, textArray); } + static void drawGlyphs___CIIFFIPaint(JNIEnv* env, jobject, SkCanvas* canvas, + jcharArray glyphs, int index, int count, + jfloat x, jfloat y, int flags, SkPaint* paint) { + jchar* glyphArray = env->GetCharArrayElements(glyphs, NULL); + + // TODO: need to suppress this code after the GL renderer is modified for not + // copying the paint + + // Save old text encoding + SkPaint::TextEncoding oldEncoding = paint->getTextEncoding(); + // Define Glyph encoding + paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding); + + TextLayout::drawText(paint, glyphArray + index, count, flags, x, y, canvas); + + // Get back old encoding + paint->setTextEncoding(oldEncoding); + + env->ReleaseCharArrayElements(glyphs, glyphArray, JNI_ABORT); + } + static void drawTextRun___CIIIIFFIPaint( JNIEnv* env, jobject, SkCanvas* canvas, jcharArray text, int index, int count, int contextIndex, int contextCount, @@ -946,6 +967,8 @@ static JNINativeMethod gCanvasMethods[] = { (void*) SkCanvasGlue::drawText___CIIFFIPaint}, {"native_drawText","(ILjava/lang/String;IIFFII)V", (void*) SkCanvasGlue::drawText__StringIIFFIPaint}, + {"native_drawGlyphs","(I[CIIFFII)V", + (void*) SkCanvasGlue::drawGlyphs___CIIFFIPaint}, {"native_drawTextRun","(I[CIIIIFFII)V", (void*) SkCanvasGlue::drawTextRun___CIIIIFFIPaint}, {"native_drawTextRun","(ILjava/lang/String;IIIIFFII)V", diff --git a/core/jni/android/graphics/HarfbuzzSkia.cpp b/core/jni/android/graphics/HarfbuzzSkia.cpp new file mode 100644 index 0000000..58fb32b --- /dev/null +++ b/core/jni/android/graphics/HarfbuzzSkia.cpp @@ -0,0 +1,239 @@ +/* + * Copyright 2011, The Android Open Source Project + * Copyright 2011, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "HarfbuzzSkia.h" + +#include "SkFontHost.h" + +#include "SkPaint.h" +#include "SkPath.h" +#include "SkPoint.h" +#include "SkRect.h" +#include "SkTypeface.h" + +extern "C" { +#include "harfbuzz-shaper.h" +} + +// This file implements the callbacks which Harfbuzz requires by using Skia +// calls. See the Harfbuzz source for references about what these callbacks do. + +namespace android { + +static HB_Fixed SkiaScalarToHarfbuzzFixed(SkScalar value) +{ + // HB_Fixed is a 26.6 fixed point format. + return value * 64; +} + +static void setupPaintWithFontData(SkPaint* paint, FontData* data) { + paint->setAntiAlias(true); + paint->setSubpixelText(true); + paint->setHinting(SkPaint::kSlight_Hinting); + paint->setTextSize(SkFloatToScalar(data->textSize)); + paint->setTypeface(data->typeFace); + paint->setFakeBoldText(data->fakeBold); + paint->setTextSkewX(data->fakeItalic ? -SK_Scalar1/4 : 0); +} + +static HB_Bool stringToGlyphs(HB_Font hbFont, const HB_UChar16* characters, hb_uint32 length, + HB_Glyph* glyphs, hb_uint32* glyphsSize, HB_Bool isRTL) +{ + FontData* data = reinterpret_cast<FontData*>(hbFont->userData); + SkPaint paint; + setupPaintWithFontData(&paint, data); + + paint.setTextEncoding(SkPaint::kUTF16_TextEncoding); + int numGlyphs = paint.textToGlyphs(characters, length * sizeof(uint16_t), + reinterpret_cast<uint16_t*>(glyphs)); + + // HB_Glyph is 32-bit, but Skia outputs only 16-bit numbers. So our + // |glyphs| array needs to be converted. + for (int i = numGlyphs - 1; i >= 0; --i) { + uint16_t value; + // We use a memcpy to avoid breaking strict aliasing rules. + memcpy(&value, reinterpret_cast<char*>(glyphs) + sizeof(uint16_t) * i, sizeof(value)); + glyphs[i] = value; + } + + *glyphsSize = numGlyphs; + return 1; +} + +static void glyphsToAdvances(HB_Font hbFont, const HB_Glyph* glyphs, hb_uint32 numGlyphs, + HB_Fixed* advances, int flags) +{ + FontData* data = reinterpret_cast<FontData*>(hbFont->userData); + SkPaint paint; + setupPaintWithFontData(&paint, data); + + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + + uint16_t* glyphs16 = new uint16_t[numGlyphs]; + if (!glyphs16) + return; + for (unsigned i = 0; i < numGlyphs; ++i) + glyphs16[i] = glyphs[i]; + paint.getTextWidths(glyphs16, numGlyphs * sizeof(uint16_t), reinterpret_cast<SkScalar*>(advances)); + + // The |advances| values which Skia outputs are SkScalars, which are floats + // in Chromium. However, Harfbuzz wants them in 26.6 fixed point format. + // These two formats are both 32-bits long. + for (unsigned i = 0; i < numGlyphs; ++i) { + float value; + // We use a memcpy to avoid breaking strict aliasing rules. + memcpy(&value, reinterpret_cast<char*>(advances) + sizeof(float) * i, sizeof(value)); + advances[i] = SkiaScalarToHarfbuzzFixed(value); + } + delete glyphs16; +} + +static HB_Bool canRender(HB_Font hbFont, const HB_UChar16* characters, hb_uint32 length) +{ + FontData* data = reinterpret_cast<FontData*>(hbFont->userData); + SkPaint paint; + setupPaintWithFontData(&paint, data); + + paint.setTextEncoding(SkPaint::kUTF16_TextEncoding); + + uint16_t* glyphs16 = new uint16_t[length]; + int numGlyphs = paint.textToGlyphs(characters, length * sizeof(uint16_t), glyphs16); + + bool result = true; + for (int i = 0; i < numGlyphs; ++i) { + if (!glyphs16[i]) { + result = false; + break; + } + } + delete glyphs16; + return result; +} + +static HB_Error getOutlinePoint(HB_Font hbFont, HB_Glyph glyph, int flags, hb_uint32 point, + HB_Fixed* xPos, HB_Fixed* yPos, hb_uint32* resultingNumPoints) +{ + FontData* data = reinterpret_cast<FontData*>(hbFont->userData); + SkPaint paint; + setupPaintWithFontData(&paint, data); + + if (flags & HB_ShaperFlag_UseDesignMetrics) + // This is requesting pre-hinted positions. We can't support this. + return HB_Err_Invalid_Argument; + + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + uint16_t glyph16 = glyph; + SkPath path; + paint.getTextPath(&glyph16, sizeof(glyph16), 0, 0, &path); + uint32_t numPoints = path.getPoints(0, 0); + if (point >= numPoints) + return HB_Err_Invalid_SubTable; + SkPoint* points = reinterpret_cast<SkPoint*>(malloc(sizeof(SkPoint) * (point + 1))); + if (!points) + return HB_Err_Invalid_SubTable; + // Skia does let us get a single point from the path. + path.getPoints(points, point + 1); + *xPos = SkiaScalarToHarfbuzzFixed(points[point].fX); + *yPos = SkiaScalarToHarfbuzzFixed(points[point].fY); + *resultingNumPoints = numPoints; + delete points; + + return HB_Err_Ok; +} + +static void getGlyphMetrics(HB_Font hbFont, HB_Glyph glyph, HB_GlyphMetrics* metrics) +{ + FontData* data = reinterpret_cast<FontData*>(hbFont->userData); + SkPaint paint; + setupPaintWithFontData(&paint, data); + + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + uint16_t glyph16 = glyph; + SkScalar width; + SkRect bounds; + paint.getTextWidths(&glyph16, sizeof(glyph16), &width, &bounds); + + metrics->x = SkiaScalarToHarfbuzzFixed(bounds.fLeft); + metrics->y = SkiaScalarToHarfbuzzFixed(bounds.fTop); + metrics->width = SkiaScalarToHarfbuzzFixed(bounds.width()); + metrics->height = SkiaScalarToHarfbuzzFixed(bounds.height()); + + metrics->xOffset = SkiaScalarToHarfbuzzFixed(width); + // We can't actually get the |y| correct because Skia doesn't export + // the vertical advance. However, nor we do ever render vertical text at + // the moment so it's unimportant. + metrics->yOffset = 0; +} + +static HB_Fixed getFontMetric(HB_Font hbFont, HB_FontMetric metric) +{ + FontData* data = reinterpret_cast<FontData*>(hbFont->userData); + SkPaint paint; + setupPaintWithFontData(&paint, data); + + SkPaint::FontMetrics skiaMetrics; + paint.getFontMetrics(&skiaMetrics); + + switch (metric) { + case HB_FontAscent: + return SkiaScalarToHarfbuzzFixed(-skiaMetrics.fAscent); + // We don't support getting the rest of the metrics and Harfbuzz doesn't seem to need them. + default: + return 0; + } + return 0; +} + +const HB_FontClass harfbuzzSkiaClass = { + stringToGlyphs, + glyphsToAdvances, + canRender, + getOutlinePoint, + getGlyphMetrics, + getFontMetric, +}; + +HB_Error harfbuzzSkiaGetTable(void* voidface, const HB_Tag tag, HB_Byte* buffer, HB_UInt* len) +{ + FontData* data = reinterpret_cast<FontData*>(voidface); + SkTypeface* typeface = data->typeFace; + + const size_t tableSize = SkFontHost::GetTableSize(typeface->uniqueID(), tag); + if (!tableSize) + return HB_Err_Invalid_Argument; + // If Harfbuzz specified a NULL buffer then it's asking for the size of the table. + if (!buffer) { + *len = tableSize; + return HB_Err_Ok; + } + + if (*len < tableSize) + return HB_Err_Invalid_Argument; + SkFontHost::GetTableData(typeface->uniqueID(), tag, 0, tableSize, buffer); + return HB_Err_Ok; +} + +} // namespace android diff --git a/core/jni/android/graphics/HarfbuzzSkia.h b/core/jni/android/graphics/HarfbuzzSkia.h new file mode 100644 index 0000000..d057d76 --- /dev/null +++ b/core/jni/android/graphics/HarfbuzzSkia.h @@ -0,0 +1,48 @@ +/* + * Copyright 2011, The Android Open Source Project + * Copyright 2011, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef HarfbuzzSkia_h +#define HarfbuzzSkia_h + +#include "SkTypeface.h" + +extern "C" { +#include "harfbuzz-shaper.h" +} + +namespace android { + typedef struct { + SkTypeface* typeFace; + float textSize; + bool fakeBold; + bool fakeItalic; + } FontData; + + HB_Error harfbuzzSkiaGetTable(void* voidface, const HB_Tag, HB_Byte* buffer, HB_UInt* len); + extern const HB_FontClass harfbuzzSkiaClass; +} // namespace android + +#endif diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index e62b034..5c3497f 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -393,13 +393,40 @@ public: return count; } - static int getTextWidths__StringII_F(JNIEnv* env, jobject clazz, SkPaint* paint, jstring text, int start, int end, jfloatArray widths) { + static int getTextWidths__StringII_F(JNIEnv* env, jobject clazz, SkPaint* paint, jstring text, + int start, int end, jfloatArray widths) { const jchar* textArray = env->GetStringChars(text, NULL); int count = dotextwidths(env, paint, textArray + start, end - start, widths); env->ReleaseStringChars(text, textArray); return count; } + static int doTextGlyphs(JNIEnv* env, SkPaint* paint, const jchar* text, jint start, jint count, + jint contextCount, jint flags, jcharArray glyphs) { + jchar* glyphsArray = env->GetCharArrayElements(glyphs, NULL); + HB_ShaperItem shaperItem; + HB_FontRec font; + FontData fontData; + RunAdvanceDescription::shapeWithHarfbuzz(&shaperItem, &font, &fontData, paint, text, + start, count, contextCount, flags); + + int glyphCount = shaperItem.num_glyphs; + for (int i = 0; i < glyphCount; i++) { + glyphsArray[i] = (jchar) shaperItem.glyphs[i]; + } + return glyphCount; + } + + static int getTextGlyphs__StringIIIII_C(JNIEnv* env, jobject clazz, SkPaint* paint, + jstring text, jint start, jint end, jint contextStart, jint contextEnd, jint flags, + jcharArray glyphs) { + const jchar* textArray = env->GetStringChars(text, NULL); + int count = doTextGlyphs(env, paint, textArray + contextStart, start - contextStart, + end - start, contextEnd - contextStart, flags, glyphs); + env->ReleaseStringChars(text, textArray); + return count; + } + static jfloat doTextRunAdvances(JNIEnv *env, SkPaint *paint, const jchar *text, jint start, jint count, jint contextCount, jint flags, jfloatArray advances, jint advancesIndex) { @@ -725,6 +752,8 @@ static JNINativeMethod methods[] = { SkPaintGlue::getTextRunAdvances___CIIIII_FI}, {"native_getTextRunAdvances","(ILjava/lang/String;IIIII[FI)F", (void*) SkPaintGlue::getTextRunAdvances__StringIIIII_FI}, + {"native_getTextGlyphs","(ILjava/lang/String;IIIII[C)I", + (void*) SkPaintGlue::getTextGlyphs__StringIIIII_C}, {"native_getTextRunCursor", "(I[CIIIII)I", (void*) SkPaintGlue::getTextRunCursor___C}, {"native_getTextRunCursor", "(ILjava/lang/String;IIIII)I", (void*) SkPaintGlue::getTextRunCursor__String}, diff --git a/core/jni/android/graphics/RtlProperties.h b/core/jni/android/graphics/RtlProperties.h index 6d8ba91..2c68fa3 100644 --- a/core/jni/android/graphics/RtlProperties.h +++ b/core/jni/android/graphics/RtlProperties.h @@ -45,5 +45,7 @@ static RtlDebugLevel readRtlDebugLevel() { return kRtlDebugDisabled; } +#define RTL_USE_HARFBUZZ 1 + } // namespace android #endif // ANDROID_RTL_PROPERTIES_H diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp index 7888769..a7265be 100644 --- a/core/jni/android/graphics/TextLayoutCache.cpp +++ b/core/jni/android/graphics/TextLayoutCache.cpp @@ -47,8 +47,16 @@ void TextLayoutCache::init() { if (mDebugEnabled) { LOGD("TextLayoutCache start time: %lld", mCacheStartTime); } - mInitialized = true; + + if (mDebugEnabled) { +#if RTL_USE_HARFBUZZ + LOGD("TextLayoutCache is using HARFBUZZ"); +#else + LOGD("TextLayoutCache is using ICU"); +#endif + } + if (mDebugEnabled) { LOGD("TextLayoutCache initialization is done"); } diff --git a/core/jni/android/graphics/TextLayoutCache.h b/core/jni/android/graphics/TextLayoutCache.h index 9d55918..e962a86 100644 --- a/core/jni/android/graphics/TextLayoutCache.h +++ b/core/jni/android/graphics/TextLayoutCache.h @@ -30,6 +30,8 @@ #include "unicode/ubidi.h" #include "unicode/ushape.h" +#include "HarfbuzzSkia.h" +#include "harfbuzz-shaper.h" #include <android_runtime/AndroidRuntime.h> @@ -52,8 +54,14 @@ // Define the interval in number of cache hits between two statistics dump #define DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL 100 +// Define if we want to have Advances debug values +#define DEBUG_ADVANCES 0 + namespace android { +// Harfbuzz uses 26.6 fixed point values for pixel offsets +#define HB_FIXED_TO_FLOAT(v) (((float) v) * (1.0 / 64)) + /** * TextLayoutCacheKey is the Cache key */ @@ -149,8 +157,18 @@ public: advances = new float[count]; this->count = count; - computeAdvances(paint, chars, start, count, contextCount, dirFlags, +#if RTL_USE_HARFBUZZ + computeAdvancesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags, advances, &totalAdvance); +#else + computeAdvancesWithICU(paint, chars, start, count, contextCount, dirFlags, + advances, &totalAdvance); +#endif +#if DEBUG_ADVANCES + LOGD("Advances - count=%d - countextCount=%d - totalAdvance=%f - " + "adv[0]=%f adv[1]=%f adv[2]=%f adv[3]=%f", count, contextCount, totalAdvance, + advances[0], advances[1], advances[2], advances[3]); +#endif } void copyResult(jfloat* outAdvances, jfloat* outTotalAdvance) { @@ -165,8 +183,108 @@ public: return sizeof(RunAdvanceDescription) + sizeof(jfloat) * count; } - static void computeAdvances(SkPaint* paint, const UChar* chars, size_t start, size_t count, - size_t contextCount, int dirFlags, jfloat* outAdvances, jfloat* outTotalAdvance) { + static void setupShaperItem(HB_ShaperItem* shaperItem, HB_FontRec* font, FontData* fontData, + SkPaint* paint, const UChar* chars, size_t start, size_t count, size_t contextCount, + int dirFlags) { + bool isRTL = dirFlags & 0x1; + + font->klass = &harfbuzzSkiaClass; + font->userData = 0; + // The values which harfbuzzSkiaClass returns are already scaled to + // pixel units, so we just set all these to one to disable further + // scaling. + font->x_ppem = 1; + font->y_ppem = 1; + font->x_scale = 1; + font->y_scale = 1; + + memset(shaperItem, 0, sizeof(*shaperItem)); + shaperItem->font = font; + shaperItem->face = HB_NewFace(shaperItem->font, harfbuzzSkiaGetTable); + + // We cannot know, ahead of time, how many glyphs a given script run + // will produce. We take a guess that script runs will not produce more + // than twice as many glyphs as there are code points plus a bit of + // padding and fallback if we find that we are wrong. + createGlyphArrays(shaperItem, (contextCount + 2) * 2); + + // Free memory for clusters if needed and recreate the clusters array + if (shaperItem->log_clusters) { + delete shaperItem->log_clusters; + } + shaperItem->log_clusters = new unsigned short[contextCount]; + + shaperItem->item.pos = start; + shaperItem->item.length = count; + shaperItem->item.bidiLevel = isRTL; + shaperItem->item.script = isRTL ? HB_Script_Arabic : HB_Script_Common; + + shaperItem->string = chars; + shaperItem->stringLength = contextCount; + + fontData->textSize = paint->getTextSize(); + fontData->fakeBold = paint->isFakeBoldText(); + fontData->fakeItalic = (paint->getTextSkewX() > 0); + fontData->typeFace = paint->getTypeface(); + + shaperItem->font->userData = fontData; + } + + static void shapeWithHarfbuzz(HB_ShaperItem* shaperItem, HB_FontRec* font, FontData* fontData, + SkPaint* paint, const UChar* chars, size_t start, size_t count, size_t contextCount, + int dirFlags) { + // Setup Harfbuzz Shaper + setupShaperItem(shaperItem, font, fontData, paint, chars, start, count, + contextCount, dirFlags); + + // Shape + resetGlyphArrays(shaperItem); + while (!HB_ShapeItem(shaperItem)) { + // We overflowed our arrays. Resize and retry. + // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size. + deleteGlyphArrays(shaperItem); + createGlyphArrays(shaperItem, shaperItem->num_glyphs << 1); + resetGlyphArrays(shaperItem); + } + } + + static void computeAdvancesWithHarfbuzz(SkPaint* paint, const UChar* chars, size_t start, + size_t count, size_t contextCount, int dirFlags, + jfloat* outAdvances, jfloat* outTotalAdvance) { + + bool isRTL = dirFlags & 0x1; + + HB_ShaperItem shaperItem; + HB_FontRec font; + FontData fontData; + shapeWithHarfbuzz(&shaperItem, &font, &fontData, paint, chars, start, count, + contextCount, dirFlags); + +#if DEBUG_ADVANCES + LOGD("HARFBUZZ -- num_glypth=%d", shaperItem.num_glyphs); +#endif + + jfloat totalAdvance = 0; + for (size_t i = 0; i < count; i++) { + // Be careful: we need to use roundf() for doing the same way as Skia is doing + totalAdvance += outAdvances[i] = roundf(HB_FIXED_TO_FLOAT(shaperItem.advances[i])); + +#if DEBUG_ADVANCES + LOGD("hb-adv = %d - rebased = %f - total = %f", shaperItem.advances[i], outAdvances[i], + totalAdvance); +#endif + } + + deleteGlyphArrays(&shaperItem); + HB_FreeFace(shaperItem.face); + + *outTotalAdvance = totalAdvance; + } + + static void computeAdvancesWithICU(SkPaint* paint, const UChar* chars, size_t start, + size_t count, size_t contextCount, int dirFlags, + jfloat* outAdvances, jfloat* outTotalAdvance) { + SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount); jchar* buffer = tempBuffer.get(); @@ -199,6 +317,9 @@ public: jfloat totalAdvance = 0; if (widths < count) { +#if DEBUG_ADVANCES + LOGD("ICU -- count=%d", widths); +#endif // Skia operates on code points, not code units, so surrogate pairs return only // one value. Expand the result so we have one value per UTF-16 code unit. @@ -213,10 +334,19 @@ public: text[p-1] < UNICODE_FIRST_LOW_SURROGATE) { outAdvances[p++] = 0; } +#if DEBUG_ADVANCES + LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance); +#endif } } else { +#if DEBUG_ADVANCES + LOGD("ICU -- count=%d", count); +#endif for (size_t i = 0; i < count; i++) { totalAdvance += outAdvances[i] = SkScalarToFloat(scalarArray[i]); +#if DEBUG_ADVANCES + LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance); +#endif } } *outTotalAdvance = totalAdvance; @@ -228,6 +358,32 @@ private: size_t count; uint32_t elapsedTime; + + static void deleteGlyphArrays(HB_ShaperItem* shaperItem) { + delete[] shaperItem->glyphs; + delete[] shaperItem->attributes; + delete[] shaperItem->advances; + delete[] shaperItem->offsets; + } + + static void createGlyphArrays(HB_ShaperItem* shaperItem, int size) { + shaperItem->glyphs = new HB_Glyph[size]; + shaperItem->attributes = new HB_GlyphAttributes[size]; + shaperItem->advances = new HB_Fixed[size]; + shaperItem->offsets = new HB_FixedPoint[size]; + shaperItem->num_glyphs = size; + } + + static void resetGlyphArrays(HB_ShaperItem* shaperItem) { + int size = shaperItem->num_glyphs; + // All the types here don't have pointers. It is safe to reset to + // zero unless Harfbuzz breaks the compatibility in the future. + memset(shaperItem->glyphs, 0, size * sizeof(shaperItem->glyphs[0])); + memset(shaperItem->attributes, 0, size * sizeof(shaperItem->attributes[0])); + memset(shaperItem->advances, 0, size * sizeof(shaperItem->advances[0])); + memset(shaperItem->offsets, 0, size * sizeof(shaperItem->offsets[0])); + } + }; // RunAdvanceDescription diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp index d6d3e4f..a4931ac 100644 --- a/core/jni/android_view_GLES20Canvas.cpp +++ b/core/jni/android_view_GLES20Canvas.cpp @@ -366,6 +366,13 @@ static void android_view_GLES20Canvas_drawRects(JNIEnv* env, jobject clazz, } } +static void android_view_GLES20Canvas_drawPoints(JNIEnv* env, jobject clazz, + OpenGLRenderer* renderer, jfloatArray points, jint offset, jint count, SkPaint* paint) { + jfloat* storage = env->GetFloatArrayElements(points, NULL); + renderer->drawPoints(storage + offset, count, paint); + env->ReleaseFloatArrayElements(points, storage, 0); +} + static void android_view_GLES20Canvas_drawPath(JNIEnv* env, jobject clazz, OpenGLRenderer* renderer, SkPath* path, SkPaint* paint) { renderer->drawPath(path, paint); @@ -374,9 +381,7 @@ static void android_view_GLES20Canvas_drawPath(JNIEnv* env, jobject clazz, static void android_view_GLES20Canvas_drawLines(JNIEnv* env, jobject clazz, OpenGLRenderer* renderer, jfloatArray points, jint offset, jint count, SkPaint* paint) { jfloat* storage = env->GetFloatArrayElements(points, NULL); - renderer->drawLines(storage + offset, count, paint); - env->ReleaseFloatArrayElements(points, storage, 0); } @@ -645,6 +650,7 @@ static JNINativeMethod gMethods[] = { { "nDrawCircle", "(IFFFI)V", (void*) android_view_GLES20Canvas_drawCircle }, { "nDrawOval", "(IFFFFI)V", (void*) android_view_GLES20Canvas_drawOval }, { "nDrawArc", "(IFFFFFFZI)V", (void*) android_view_GLES20Canvas_drawArc }, + { "nDrawPoints", "(I[FIII)V", (void*) android_view_GLES20Canvas_drawPoints }, { "nDrawPath", "(III)V", (void*) android_view_GLES20Canvas_drawPath }, { "nDrawLines", "(I[FIII)V", (void*) android_view_GLES20Canvas_drawLines }, diff --git a/core/res/res/drawable/list_selector_background.xml b/core/res/res/drawable/list_selector_background.xml index f5eb12d..1222155 100644 --- a/core/res/res/drawable/list_selector_background.xml +++ b/core/res/res/drawable/list_selector_background.xml @@ -24,6 +24,6 @@ <item android:state_focused="true" android:state_enabled="false" android:drawable="@drawable/list_selector_background_disabled" /> <item android:state_focused="true" android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition" /> <item android:state_focused="false" android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition" /> - <item android:state_focused="true" android:drawable="@drawable/list_selector_background_focused" /> + <item android:state_focused="true" android:drawable="@drawable/list_selector_background_focus" /> </selector> diff --git a/core/res/res/layout/action_menu_item_layout.xml b/core/res/res/layout/action_menu_item_layout.xml index 15dfea3..4a73368 100644 --- a/core/res/res/layout/action_menu_item_layout.xml +++ b/core/res/res/layout/action_menu_item_layout.xml @@ -24,7 +24,8 @@ android:paddingLeft="12dip" android:paddingRight="12dip" android:minWidth="64dip" - android:minHeight="?attr/actionBarSize"> + android:minHeight="?attr/actionBarSize" + android:focusable="true"> <ImageButton android:id="@+id/imageButton" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -34,7 +35,8 @@ android:paddingRight="4dip" android:minHeight="56dip" android:scaleType="center" - android:background="@null" /> + android:background="@null" + android:focusable="false" /> <Button android:id="@+id/textButton" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -45,5 +47,6 @@ android:textColor="?attr/actionMenuTextColor" android:background="@null" android:paddingLeft="4dip" - android:paddingRight="4dip" /> + android:paddingRight="4dip" + android:focusable="false" /> </com.android.internal.view.menu.ActionMenuItemView> diff --git a/core/res/res/layout/tab_indicator_holo.xml b/core/res/res/layout/tab_indicator_holo.xml index d37476b..60c80e9 100644 --- a/core/res/res/layout/tab_indicator_holo.xml +++ b/core/res/res/layout/tab_indicator_holo.xml @@ -15,32 +15,24 @@ --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" - android:layout_height="56dip" - android:layout_weight="0" - android:layout_marginLeft="0dip" - android:layout_marginRight="0dip" + android:layout_width="0dp" + android:layout_height="58dp" + android:layout_weight="1" + android:layout_marginLeft="-3dip" + android:layout_marginRight="-3dip" + android:paddingBottom="8dp" android:background="@android:drawable/tab_indicator_holo"> - <View android:id="@+id/tab_indicator_left_spacer" - android:layout_width="16dip" - android:layout_height="0dip" /> - <ImageView android:id="@+id/icon" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_centerVertical="true" - android:visibility="gone" - android:layout_toRightOf="@id/tab_indicator_left_spacer" - android:paddingRight="8dip" /> + android:layout_centerHorizontal="true" /> <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_centerVertical="true" - android:layout_toRightOf="@id/icon" - android:paddingLeft="0dip" - android:paddingRight="16dip" + android:layout_alignParentBottom="true" + android:layout_centerHorizontal="true" style="?android:attr/tabWidgetStyle" /> - + </RelativeLayout> diff --git a/core/res/res/layout/tab_indicator_holo_large.xml b/core/res/res/layout/tab_indicator_holo_large.xml new file mode 100644 index 0000000..bdd8d11 --- /dev/null +++ b/core/res/res/layout/tab_indicator_holo_large.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2011 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="56dip" + android:layout_weight="0" + android:layout_marginLeft="0dip" + android:layout_marginRight="0dip" + android:background="@android:drawable/tab_indicator_holo"> + + <View android:id="@+id/tab_indicator_left_spacer" + android:layout_width="16dip" + android:layout_height="0dip" /> + + <ImageView android:id="@+id/icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:visibility="gone" + android:layout_toRightOf="@id/tab_indicator_left_spacer" + android:paddingRight="8dip" /> + + <TextView android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toRightOf="@id/icon" + android:paddingLeft="0dip" + android:paddingRight="16dip" + style="?android:attr/tabWidgetStyle" /> + +</RelativeLayout> diff --git a/core/res/res/values-xlarge/styles.xml b/core/res/res/values-xlarge/styles.xml index dd78920..a39d9d6 100644 --- a/core/res/res/values-xlarge/styles.xml +++ b/core/res/res/values-xlarge/styles.xml @@ -36,6 +36,22 @@ <item name="android:textColor">?android:attr/textColorPrimary</item> </style> + <style name="TextAppearance.Holo.Widget.TabWidget"> + <item name="android:textSize">18sp</item> + <item name="android:textStyle">normal</item> + <item name="android:textColor">@android:color/tab_indicator_text</item> + </style> + + <style name="Widget.Holo.TabWidget" parent="Widget.TabWidget"> + <item name="android:textAppearance">@style/TextAppearance.Holo.Widget.TabWidget</item> + <item name="android:tabStripLeft">@null</item> + <item name="android:tabStripRight">@null</item> + <item name="android:tabStripEnabled">false</item> + <item name="android:divider">@null</item> + <item name="android:gravity">left|center_vertical</item> + <item name="android:tabLayout">@android:layout/tab_indicator_holo_large</item> + </style> + <style name="PreferencePanel"> <item name="android:layout_marginLeft">@dimen/preference_screen_side_margin</item> <item name="android:layout_marginRight">@dimen/preference_screen_side_margin</item> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index c81f8c0..e2c440a 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3817,6 +3817,7 @@ <li>"state_rect" <li>"state_grow" <li>"state_move" + <li>"state_hovered" </ul> --> <declare-styleable name="DrawableStates"> <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable}, @@ -3866,6 +3867,9 @@ ignored even if it specifies a solid color, since that optimization is not needed. --> <attr name="state_accelerated" format="boolean" /> + <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable}, + set when a pointer is hovering over the view. --> + <attr name="state_hovered" format="boolean" /> </declare-styleable> <declare-styleable name="ViewDrawableStates"> <attr name="state_pressed" /> @@ -3875,6 +3879,7 @@ <attr name="state_enabled" /> <attr name="state_activated" /> <attr name="state_accelerated" /> + <attr name="state_hovered" /> </declare-styleable> <!-- State array representing a menu item that is currently checked. --> <declare-styleable name="MenuItemCheckedState"> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index f1ec398..5432212 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1648,4 +1648,11 @@ <eat-comment /> <public type="attr" name="textCursorDrawable" id="0x01010362" /> <public type="attr" name="resizeMode" /> + +<!-- =============================================================== + Resources added in version 13 of the platform (Ice Cream Sandwich) + =============================================================== --> + <eat-comment /> + <public type="attr" name="state_hovered" /> + </resources> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 08f5410..f7d3c3f 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -1214,8 +1214,10 @@ <item name="android:textColor">?textColorPrimary</item> </style> + <!-- This style is for smaller screens; values-xlarge defines a version + for larger screens. --> <style name="TextAppearance.Holo.Widget.TabWidget"> - <item name="android:textSize">18sp</item> + <item name="android:textSize">14sp</item> <item name="android:textStyle">normal</item> <item name="android:textColor">@android:color/tab_indicator_text</item> </style> @@ -1664,6 +1666,9 @@ <item name="android:button">@android:drawable/btn_star_holo_dark</item> </style> + <!-- The holo style for smaller screens actually uses the non-holo layout, + which is more compact. values-xlarge defines an alternative version + for the real holo look on a large screen. --> <style name="Widget.Holo.TabWidget" parent="Widget.TabWidget"> <item name="android:textAppearance">@style/TextAppearance.Holo.Widget.TabWidget</item> <item name="android:tabStripLeft">@null</item> diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java b/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java index 5dedd4a..7f13791 100644 --- a/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java +++ b/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java @@ -60,6 +60,7 @@ public class BluetoothStressTest extends InstrumentationTestCase { } BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + mTestUtils.disable(adapter); for (int i = 0; i < iterations; i++) { mTestUtils.writeOutput("enable iteration " + (i + 1) + " of " + iterations); @@ -78,7 +79,9 @@ public class BluetoothStressTest extends InstrumentationTestCase { } BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + mTestUtils.disable(adapter); mTestUtils.enable(adapter); + mTestUtils.undiscoverable(adapter); for (int i = 0; i < iterations; i++) { mTestUtils.writeOutput("discoverable iteration " + (i + 1) + " of " + iterations); @@ -99,7 +102,9 @@ public class BluetoothStressTest extends InstrumentationTestCase { } BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + mTestUtils.disable(adapter); mTestUtils.enable(adapter); + mTestUtils.stopScan(adapter); for (int i = 0; i < iterations; i++) { mTestUtils.writeOutput("scan iteration " + (i + 1) + " of " + iterations); @@ -116,7 +121,9 @@ public class BluetoothStressTest extends InstrumentationTestCase { public void testEnablePan() { int iterations = BluetoothTestRunner.sEnablePanIterations; BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + mTestUtils.disable(adapter); mTestUtils.enable(adapter); + mTestUtils.disablePan(adapter); for (int i = 0; i < iterations; i++) { mTestUtils.writeOutput("testEnablePan iteration " + (i + 1) + " of " @@ -141,13 +148,15 @@ public class BluetoothStressTest extends InstrumentationTestCase { } BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sPairAddress); + BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); + mTestUtils.disable(adapter); mTestUtils.enable(adapter); + mTestUtils.unpair(adapter, device); for (int i = 0; i < iterations; i++) { mTestUtils.writeOutput("pair iteration " + (i + 1) + " of " + iterations); - mTestUtils.pair(adapter, device, BluetoothTestRunner.sPairPasskey, - BluetoothTestRunner.sPairPin); + mTestUtils.pair(adapter, device, BluetoothTestRunner.sDevicePairPasskey, + BluetoothTestRunner.sDevicePairPin); mTestUtils.unpair(adapter, device); } mTestUtils.disable(adapter); @@ -162,13 +171,15 @@ public class BluetoothStressTest extends InstrumentationTestCase { public void testAcceptPair() { int iterations = BluetoothTestRunner.sPairIterations; BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sPairAddress); + BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); + mTestUtils.disable(adapter); mTestUtils.enable(adapter); + mTestUtils.unpair(adapter, device); for (int i = 0; i < iterations; i++) { mTestUtils.writeOutput("acceptPair iteration " + (i + 1) + " of " + iterations); - mTestUtils.acceptPair(adapter, device, BluetoothTestRunner.sPairPasskey, - BluetoothTestRunner.sPairPin); + mTestUtils.acceptPair(adapter, device, BluetoothTestRunner.sDevicePairPasskey, + BluetoothTestRunner.sDevicePairPin); mTestUtils.unpair(adapter, device); } mTestUtils.disable(adapter); @@ -187,15 +198,20 @@ public class BluetoothStressTest extends InstrumentationTestCase { } BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sA2dpAddress); + BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); + mTestUtils.disable(adapter); mTestUtils.enable(adapter); - mTestUtils.pair(adapter, device, BluetoothTestRunner.sPairPasskey, - BluetoothTestRunner.sPairPin); + mTestUtils.unpair(adapter, device); + mTestUtils.pair(adapter, device, BluetoothTestRunner.sDevicePairPasskey, + BluetoothTestRunner.sDevicePairPin); + mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.A2DP, null); for (int i = 0; i < iterations; i++) { mTestUtils.writeOutput("connectA2dp iteration " + (i + 1) + " of " + iterations); - mTestUtils.connectProfile(adapter, device, BluetoothProfile.A2DP); - mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.A2DP); + mTestUtils.connectProfile(adapter, device, BluetoothProfile.A2DP, + String.format("connectA2dp(device=%s)", device)); + mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.A2DP, + String.format("disconnectA2dp(device=%s)", device)); } mTestUtils.unpair(adapter, device); @@ -215,15 +231,20 @@ public class BluetoothStressTest extends InstrumentationTestCase { } BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sHeadsetAddress); + BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); + mTestUtils.disable(adapter); mTestUtils.enable(adapter); - mTestUtils.pair(adapter, device, BluetoothTestRunner.sPairPasskey, - BluetoothTestRunner.sPairPin); + mTestUtils.unpair(adapter, device); + mTestUtils.pair(adapter, device, BluetoothTestRunner.sDevicePairPasskey, + BluetoothTestRunner.sDevicePairPin); + mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.HEADSET, null); for (int i = 0; i < iterations; i++) { mTestUtils.writeOutput("connectHeadset iteration " + (i + 1) + " of " + iterations); - mTestUtils.connectProfile(adapter, device, BluetoothProfile.HEADSET); - mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.HEADSET); + mTestUtils.connectProfile(adapter, device, BluetoothProfile.HEADSET, + String.format("connectHeadset(device=%s)", device)); + mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.HEADSET, + String.format("disconnectHeadset(device=%s)", device)); } mTestUtils.unpair(adapter, device); @@ -243,15 +264,20 @@ public class BluetoothStressTest extends InstrumentationTestCase { } BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sInputAddress); + BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); + mTestUtils.disable(adapter); mTestUtils.enable(adapter); - mTestUtils.pair(adapter, device, BluetoothTestRunner.sPairPasskey, - BluetoothTestRunner.sPairPin); + mTestUtils.unpair(adapter, device); + mTestUtils.pair(adapter, device, BluetoothTestRunner.sDevicePairPasskey, + BluetoothTestRunner.sDevicePairPin); + mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.INPUT_DEVICE, null); for (int i = 0; i < iterations; i++) { mTestUtils.writeOutput("connectInput iteration " + (i + 1) + " of " + iterations); - mTestUtils.connectProfile(adapter, device, BluetoothProfile.INPUT_DEVICE); - mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.INPUT_DEVICE); + mTestUtils.connectProfile(adapter, device, BluetoothProfile.INPUT_DEVICE, + String.format("connectInput(device=%s)", device)); + mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.INPUT_DEVICE, + String.format("disconnectInput(device=%s)", device)); } mTestUtils.unpair(adapter, device); @@ -271,10 +297,12 @@ public class BluetoothStressTest extends InstrumentationTestCase { } BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sPanAddress); + BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); + mTestUtils.disable(adapter); mTestUtils.enable(adapter); - mTestUtils.pair(adapter, device, BluetoothTestRunner.sPairPasskey, - BluetoothTestRunner.sPairPin); + mTestUtils.unpair(adapter, device); + mTestUtils.pair(adapter, device, BluetoothTestRunner.sDevicePairPasskey, + BluetoothTestRunner.sDevicePairPin); for (int i = 0; i < iterations; i++) { mTestUtils.writeOutput("connectPan iteration " + (i + 1) + " of " + iterations); @@ -299,11 +327,14 @@ public class BluetoothStressTest extends InstrumentationTestCase { } BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sPanAddress); + BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); + mTestUtils.disable(adapter); mTestUtils.enable(adapter); + mTestUtils.disablePan(adapter); mTestUtils.enablePan(adapter); - mTestUtils.acceptPair(adapter, device, BluetoothTestRunner.sPairPasskey, - BluetoothTestRunner.sPairPin); + mTestUtils.unpair(adapter, device); + mTestUtils.acceptPair(adapter, device, BluetoothTestRunner.sDevicePairPasskey, + BluetoothTestRunner.sDevicePairPin); for (int i = 0; i < iterations; i++) { mTestUtils.writeOutput("incomingPanConnection iteration " + (i + 1) + " of " @@ -330,11 +361,15 @@ public class BluetoothStressTest extends InstrumentationTestCase { } BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sHeadsetAddress); + BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); + mTestUtils.disable(adapter); mTestUtils.enable(adapter); - mTestUtils.pair(adapter, device, BluetoothTestRunner.sPairPasskey, - BluetoothTestRunner.sPairPin); - mTestUtils.connectProfile(adapter, device, BluetoothProfile.HEADSET); + mTestUtils.unpair(adapter, device); + mTestUtils.pair(adapter, device, BluetoothTestRunner.sDevicePairPasskey, + BluetoothTestRunner.sDevicePairPin); + mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.HEADSET, null); + mTestUtils.connectProfile(adapter, device, BluetoothProfile.HEADSET, null); + mTestUtils.stopSco(adapter, device); for (int i = 0; i < iterations; i++) { mTestUtils.writeOutput("startStopSco iteration " + (i + 1) + " of " + iterations); @@ -342,7 +377,7 @@ public class BluetoothStressTest extends InstrumentationTestCase { mTestUtils.stopSco(adapter, device); } - mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.HEADSET); + mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.HEADSET, null); mTestUtils.unpair(adapter, device); mTestUtils.disable(adapter); } diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java b/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java index 1febc5c..64d2c12 100644 --- a/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java +++ b/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java @@ -65,14 +65,9 @@ public class BluetoothTestRunner extends InstrumentationTestRunner { public static int sConnectPanIterations = 100; public static int sStartStopScoIterations = 100; - public static String sPairAddress = ""; - public static String sHeadsetAddress = ""; - public static String sA2dpAddress = ""; - public static String sInputAddress = ""; - public static String sPanAddress = ""; - - public static byte[] sPairPin = {'1', '2', '3', '4'}; - public static int sPairPasskey = 123456; + public static String sDeviceAddress = ""; + public static byte[] sDevicePairPin = {'1', '2', '3', '4'}; + public static int sDevicePairPasskey = 123456; @Override public TestSuite getAllTests() { @@ -177,40 +172,24 @@ public class BluetoothTestRunner extends InstrumentationTestRunner { // Invalid argument, fall back to default value } } - val = arguments.getString("pair_address"); - if (val != null) { - sPairAddress = val; - } - val = arguments.getString("headset_address"); + val = arguments.getString("device_address"); if (val != null) { - sHeadsetAddress = val; + sDeviceAddress = val; } - val = arguments.getString("a2dp_address"); + val = arguments.getString("device_pair_pin"); if (val != null) { - sA2dpAddress = val; - } - - val = arguments.getString("input_address"); - if (val != null) { - sInputAddress = val; - } - - val = arguments.getString("pan_address"); - if (val != null) { - sPanAddress = val; - } - - val = arguments.getString("pair_pin"); - if (val != null) { - sPairPin = BluetoothDevice.convertPinToBytes(val); + byte[] pin = BluetoothDevice.convertPinToBytes(val); + if (pin != null) { + sDevicePairPin = pin; + } } - val = arguments.getString("pair_passkey"); + val = arguments.getString("device_pair_passkey"); if (val != null) { try { - sPairPasskey = Integer.parseInt(val); + sDevicePairPasskey = Integer.parseInt(val); } catch (NumberFormatException e) { // Invalid argument, fall back to default value } @@ -225,13 +204,9 @@ public class BluetoothTestRunner extends InstrumentationTestRunner { Log.i(TAG, String.format("connect_input_iterations=%d", sConnectInputIterations)); Log.i(TAG, String.format("connect_pan_iterations=%d", sConnectPanIterations)); Log.i(TAG, String.format("start_stop_sco_iterations=%d", sStartStopScoIterations)); - Log.i(TAG, String.format("pair_address=%s", sPairAddress)); - Log.i(TAG, String.format("a2dp_address=%s", sA2dpAddress)); - Log.i(TAG, String.format("headset_address=%s", sHeadsetAddress)); - Log.i(TAG, String.format("input_address=%s", sInputAddress)); - Log.i(TAG, String.format("pan_address=%s", sPanAddress)); - Log.i(TAG, String.format("pair_pin=%s", new String(sPairPin))); - Log.i(TAG, String.format("pair_passkey=%d", sPairPasskey)); + Log.i(TAG, String.format("device_address=%s", sDeviceAddress)); + Log.i(TAG, String.format("device_pair_pin=%s", new String(sDevicePairPin))); + Log.i(TAG, String.format("device_pair_passkey=%d", sDevicePairPasskey)); // Call onCreate last since we want to set the static variables first. super.onCreate(arguments); diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java index 1741119..f1dd8fe 100644 --- a/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java +++ b/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java @@ -37,44 +37,21 @@ import java.util.List; public class BluetoothTestUtils extends Assert { - /** - * Timeout for enable/disable in ms. - */ + /** Timeout for enable/disable in ms. */ private static final int ENABLE_DISABLE_TIMEOUT = 20000; - - /** - * Timeout for discoverable/undiscoverable in ms. - */ + /** Timeout for discoverable/undiscoverable in ms. */ private static final int DISCOVERABLE_UNDISCOVERABLE_TIMEOUT = 5000; - - /** - * Timeout for starting/stopping a scan in ms. - */ + /** Timeout for starting/stopping a scan in ms. */ private static final int START_STOP_SCAN_TIMEOUT = 5000; - - /** - * Timeout for pair/unpair in ms. - */ + /** Timeout for pair/unpair in ms. */ private static final int PAIR_UNPAIR_TIMEOUT = 20000; - - /** - * Timeout for connecting/disconnecting a profile in ms. - */ + /** Timeout for connecting/disconnecting a profile in ms. */ private static final int CONNECT_DISCONNECT_PROFILE_TIMEOUT = 20000; - - /** - * Timeout to connect a profile proxy in ms. - */ - private static final int CONNECT_PROXY_TIMEOUT = 5000; - - /** - * Timeout to start or stop a SCO channel in ms. - */ + /** Timeout to start or stop a SCO channel in ms. */ private static final int START_STOP_SCO_TIMEOUT = 10000; - - /** - * Time between polls in ms. - */ + /** Timeout to connect a profile proxy in ms. */ + private static final int CONNECT_PROXY_TIMEOUT = 5000; + /** Time between polls in ms. */ private static final int POLL_TIME = 100; private abstract class FlagReceiver extends BroadcastReceiver { @@ -249,6 +226,9 @@ public class BluetoothTestUtils extends Assert { case BluetoothProfile.INPUT_DEVICE: mConnectionAction = BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED; break; + case BluetoothProfile.PAN: + mConnectionAction = BluetoothPan.ACTION_CONNECTION_STATE_CHANGED; + break; default: mConnectionAction = null; } @@ -281,47 +261,22 @@ public class BluetoothTestUtils extends Assert { } } - private class ConnectPanReceiver extends FlagReceiver { - private static final int STATE_DISCONNECTED_FLAG = 1; - private static final int STATE_CONNECTING_FLAG = 1 << 1; - private static final int STATE_CONNECTED_FLAG = 1 << 2; - private static final int STATE_DISCONNECTING_FLAG = 1 << 3; - - private BluetoothDevice mDevice; + private class ConnectPanReceiver extends ConnectProfileReceiver { private int mRole; public ConnectPanReceiver(BluetoothDevice device, int role, int expectedFlags) { - super (expectedFlags); + super(device, BluetoothProfile.PAN, expectedFlags); - mDevice = device; mRole = role; } @Override public void onReceive(Context context, Intent intent) { - if (!mDevice.equals(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)) - || mRole != intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, -1)) { + if (mRole != intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, -1)) { return; } - if (BluetoothPan.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { - int state = intent.getIntExtra(BluetoothPan.EXTRA_STATE, -1); - assertNotSame(-1, state); - switch (state) { - case BluetoothPan.STATE_DISCONNECTED: - setFiredFlag(STATE_DISCONNECTED_FLAG); - break; - case BluetoothPan.STATE_CONNECTING: - setFiredFlag(STATE_CONNECTING_FLAG); - break; - case BluetoothPan.STATE_CONNECTED: - setFiredFlag(STATE_CONNECTED_FLAG); - break; - case BluetoothPan.STATE_DISCONNECTING: - setFiredFlag(STATE_DISCONNECTING_FLAG); - break; - } - } + super.onReceive(context, intent); } } @@ -353,6 +308,7 @@ public class BluetoothTestUtils extends Assert { private BluetoothProfile.ServiceListener mServiceListener = new BluetoothProfile.ServiceListener() { + @Override public void onServiceConnected(int profile, BluetoothProfile proxy) { synchronized (this) { switch (profile) { @@ -372,6 +328,7 @@ public class BluetoothTestUtils extends Assert { } } + @Override public void onServiceDisconnected(int profile) { synchronized (this) { switch (profile) { @@ -399,10 +356,10 @@ public class BluetoothTestUtils extends Assert { private String mOutputFile; private Context mContext; - private BluetoothA2dp mA2dp; - private BluetoothHeadset mHeadset; - private BluetoothInputDevice mInput; - private BluetoothPan mPan; + private BluetoothA2dp mA2dp = null; + private BluetoothHeadset mHeadset = null; + private BluetoothInputDevice mInput = null; + private BluetoothPan mPan = null; /** * Creates a utility instance for testing Bluetooth. @@ -818,10 +775,15 @@ public class BluetoothTestUtils extends Assert { byte[] pin, boolean shouldPair) { int mask = PairReceiver.STATE_BONDING_FLAG | PairReceiver.STATE_BONDED_FLAG; long start = -1; - String methodName = shouldPair ? "pair()" : "acceptPair()"; + String methodName; + if (shouldPair) { + methodName = String.format("pair(device=%s)", device); + } else { + methodName = String.format("acceptPair(device=%s)", device); + } if (!adapter.isEnabled()) { - fail(methodName + " bluetooth not enabled"); + fail(String.format("%s bluetooth not enabled", methodName)); } PairReceiver receiver = getPairReceiver(device, passkey, pin, mask); @@ -843,8 +805,7 @@ public class BluetoothTestUtils extends Assert { return; default: removeReceiver(receiver); - fail(String.format("%s invalid state: device=%s, state=%d", methodName, device, - state)); + fail(String.format("%s invalid state: state=%d", methodName, state)); } long s = System.currentTimeMillis(); @@ -854,10 +815,10 @@ public class BluetoothTestUtils extends Assert { assertTrue(adapter.getBondedDevices().contains(device)); long finish = receiver.getCompletedTime(); if (start != -1 && finish != -1) { - writeOutput(String.format("%s completed in %d ms: device=%s", methodName, - (finish - start), device)); + writeOutput(String.format("%s completed in %d ms", methodName, + (finish - start))); } else { - writeOutput(String.format("%s completed: device=%s", methodName, device)); + writeOutput(String.format("%s completed", methodName)); } removeReceiver(receiver); return; @@ -867,9 +828,8 @@ public class BluetoothTestUtils extends Assert { int firedFlags = receiver.getFiredFlags(); removeReceiver(receiver); - fail(String.format("%s timeout: device=%s, state=%d (expected %d), " - + "flags=0x%x (expected 0x%x)", methodName, device, state, - BluetoothDevice.BOND_BONDED, firedFlags, mask)); + fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", + methodName, state, BluetoothDevice.BOND_BONDED, firedFlags, mask)); } /** @@ -882,9 +842,10 @@ public class BluetoothTestUtils extends Assert { public void unpair(BluetoothAdapter adapter, BluetoothDevice device) { int mask = PairReceiver.STATE_NONE_FLAG; long start = -1; + String methodName = String.format("unpair(device=%s)", device); if (!adapter.isEnabled()) { - fail("unpair() bluetooth not enabled"); + fail(String.format("%s bluetooth not enabled", methodName)); } PairReceiver receiver = getPairReceiver(device, 0, null, mask); @@ -906,7 +867,7 @@ public class BluetoothTestUtils extends Assert { break; default: removeReceiver(receiver); - fail(String.format("unpair() invalid state: device=%s, state=%d", device, state)); + fail(String.format("%s invalid state: state=%d", methodName, state)); } long s = System.currentTimeMillis(); @@ -916,10 +877,10 @@ public class BluetoothTestUtils extends Assert { assertFalse(adapter.getBondedDevices().contains(device)); long finish = receiver.getCompletedTime(); if (start != -1 && finish != -1) { - writeOutput(String.format("unpair() completed in %d ms: device=%s", - (finish - start), device)); + writeOutput(String.format("%s completed in %d ms", methodName, + (finish - start))); } else { - writeOutput(String.format("unpair() completed: device=%s", device)); + writeOutput(String.format("%s completed", methodName)); } removeReceiver(receiver); return; @@ -928,9 +889,8 @@ public class BluetoothTestUtils extends Assert { int firedFlags = receiver.getFiredFlags(); removeReceiver(receiver); - fail(String.format("unpair() timeout: device=%s, state=%d (expected %d), " - + "flags=0x%x (expected 0x%x)", device, state, BluetoothDevice.BOND_BONDED, - firedFlags, mask)); + fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", + methodName, state, BluetoothDevice.BOND_BONDED, firedFlags, mask)); } /** @@ -939,29 +899,30 @@ public class BluetoothTestUtils extends Assert { * * @param adapter The BT adapter. * @param device The remote device. - * @param profile The profile to connect. One of {@link BluetoothProfile#A2DP} or - * {@link BluetoothProfile#HEADSET}. + * @param profile The profile to connect. One of {@link BluetoothProfile#A2DP}, + * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#INPUT_DEVICE}. + * @param methodName The method name to printed in the logs. If null, will be + * "connectProfile(profile=<profile>, device=<device>)" */ - public void connectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile) { + public void connectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile, + String methodName) { + if (methodName == null) { + methodName = String.format("connectProfile(profile=%d, device=%s)", profile, device); + } int mask = (ConnectProfileReceiver.STATE_CONNECTING_FLAG | ConnectProfileReceiver.STATE_CONNECTED_FLAG); long start = -1; if (!adapter.isEnabled()) { - fail(String.format("connectProfile() bluetooth not enabled: device=%s, profile=%d", - device, profile)); + fail(String.format("%s bluetooth not enabled", methodName)); } if (!adapter.getBondedDevices().contains(device)) { - fail(String.format("connectProfile() device not paired: device=%s, profile=%d", - device, profile)); + fail(String.format("%s device not paired", methodName)); } BluetoothProfile proxy = connectProxy(adapter, profile); - if (proxy == null) { - fail(String.format("connectProfile() unknown profile: device=%s, profile=%d", - device, profile)); - } + assertNotNull(proxy); ConnectProfileReceiver receiver = getConnectProfileReceiver(device, profile, mask); @@ -980,8 +941,7 @@ public class BluetoothTestUtils extends Assert { break; default: removeReceiver(receiver); - fail(String.format("connectProfile() invalid state: device=%s, profile=%d, " - + "state=%d", device, profile, state)); + fail(String.format("%s invalid state: state=%d", methodName, state)); } long s = System.currentTimeMillis(); @@ -991,11 +951,10 @@ public class BluetoothTestUtils extends Assert { && (receiver.getFiredFlags() & mask) == mask) { long finish = receiver.getCompletedTime(); if (start != -1 && finish != -1) { - writeOutput(String.format("connectProfile() completed in %d ms: " - + "device=%s, profile=%d", (finish - start), device, profile)); + writeOutput(String.format("%s completed in %d ms", methodName, + (finish - start))); } else { - writeOutput(String.format("connectProfile() completed: device=%s, " - + "profile=%d", device, profile)); + writeOutput(String.format("%s completed", methodName)); } removeReceiver(receiver); return; @@ -1005,9 +964,8 @@ public class BluetoothTestUtils extends Assert { int firedFlags = receiver.getFiredFlags(); removeReceiver(receiver); - fail(String.format("connectProfile() timeout: device=%s, profile=%s, " - + "state=%d (expected %d), flags=0x%x (expected 0x%x)", device, profile, state, - BluetoothProfile.STATE_CONNECTED, firedFlags, mask)); + fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", + methodName, state, BluetoothProfile.STATE_CONNECTED, firedFlags, mask)); } /** @@ -1016,29 +974,30 @@ public class BluetoothTestUtils extends Assert { * * @param adapter The BT adapter. * @param device The remote device. - * @param profile The profile to disconnect. One of {@link BluetoothProfile#A2DP} or - * {@link BluetoothProfile#HEADSET}. + * @param profile The profile to disconnect. One of {@link BluetoothProfile#A2DP}, + * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#INPUT_DEVICE}. + * @param methodName The method name to printed in the logs. If null, will be + * "connectProfile(profile=<profile>, device=<device>)" */ - public void disconnectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile) { + public void disconnectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile, + String methodName) { + if (methodName == null) { + methodName = String.format("disconnectProfile(profile=%d, device=%s)", profile, device); + } int mask = (ConnectProfileReceiver.STATE_DISCONNECTING_FLAG | ConnectProfileReceiver.STATE_DISCONNECTED_FLAG); long start = -1; if (!adapter.isEnabled()) { - fail(String.format("disconnectProfile() bluetooth not enabled: device=%s, profile=%d", - device, profile)); + fail(String.format("%s bluetooth not enabled", methodName)); } if (!adapter.getBondedDevices().contains(device)) { - fail(String.format("disconnectProfile() device not paired: device=%s, profile=%d", - device, profile)); + fail(String.format("%s device not paired", methodName)); } BluetoothProfile proxy = connectProxy(adapter, profile); - if (proxy == null) { - fail(String.format("disconnectProfile() unknown profile: device=%s, profile=%d", - device, profile)); - } + assertNotNull(proxy); ConnectProfileReceiver receiver = getConnectProfileReceiver(device, profile, mask); @@ -1057,8 +1016,7 @@ public class BluetoothTestUtils extends Assert { break; default: removeReceiver(receiver); - fail(String.format("disconnectProfile() invalid state: device=%s, profile=%d, " - + "state=%d", device, profile, state)); + fail(String.format("%s invalid state: state=%d", methodName, state)); } long s = System.currentTimeMillis(); @@ -1068,11 +1026,10 @@ public class BluetoothTestUtils extends Assert { && (receiver.getFiredFlags() & mask) == mask) { long finish = receiver.getCompletedTime(); if (start != -1 && finish != -1) { - writeOutput(String.format("disconnectProfile() completed in %d ms: " - + "device=%s, profile=%d", (finish - start), device, profile)); + writeOutput(String.format("%s completed in %d ms", methodName, + (finish - start))); } else { - writeOutput(String.format("disconnectProfile() completed: device=%s, " - + "profile=%d", device, profile)); + writeOutput(String.format("%s completed", methodName)); } removeReceiver(receiver); return; @@ -1082,9 +1039,8 @@ public class BluetoothTestUtils extends Assert { int firedFlags = receiver.getFiredFlags(); removeReceiver(receiver); - fail(String.format("disconnectProfile() timeout: device=%s, profile=%s, " - + "state=%d (expected %d), flags=0x%x (expected 0x%x)", device, profile, state, - BluetoothProfile.STATE_DISCONNECTED, firedFlags, mask)); + fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", + methodName, state, BluetoothProfile.STATE_DISCONNECTED, firedFlags, mask)); } /** @@ -1125,25 +1081,25 @@ public class BluetoothTestUtils extends Assert { String methodName; if (connect) { - methodName = "connectPan()"; - mask = (ConnectPanReceiver.STATE_CONNECTED_FLAG | - ConnectPanReceiver.STATE_CONNECTING_FLAG); + methodName = String.format("connectPan(device=%s)", device); + mask = (ConnectProfileReceiver.STATE_CONNECTED_FLAG | + ConnectProfileReceiver.STATE_CONNECTING_FLAG); role = BluetoothPan.LOCAL_PANU_ROLE; } else { - methodName = "incomingPanConnection()"; - mask = ConnectPanReceiver.STATE_CONNECTED_FLAG; + methodName = String.format("incomingPanConnection(device=%s)", device); + mask = ConnectProfileReceiver.STATE_CONNECTED_FLAG; role = BluetoothPan.LOCAL_NAP_ROLE; } if (!adapter.isEnabled()) { - fail(String.format("%s bluetooth not enabled: device=%s", methodName, device)); + fail(String.format("%s bluetooth not enabled", methodName)); } if (!adapter.getBondedDevices().contains(device)) { - fail(String.format("%s device not paired: device=%s", methodName, device)); + fail(String.format("%s device not paired", methodName)); } - if (mPan == null) mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); + mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); assertNotNull(mPan); ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask); @@ -1165,8 +1121,7 @@ public class BluetoothTestUtils extends Assert { break; default: removeReceiver(receiver); - fail(String.format("%s invalid state: device=%s, state=%d", methodName, device, - state)); + fail(String.format("%s invalid state: state=%d", methodName, state)); } long s = System.currentTimeMillis(); @@ -1176,10 +1131,10 @@ public class BluetoothTestUtils extends Assert { && (receiver.getFiredFlags() & mask) == mask) { long finish = receiver.getCompletedTime(); if (start != -1 && finish != -1) { - writeOutput(String.format("%s completed in %d ms: device=%s", methodName, - (finish - start), device)); + writeOutput(String.format("%s completed in %d ms", methodName, + (finish - start))); } else { - writeOutput(String.format("%s completed: device=%s", methodName, device)); + writeOutput(String.format("%s completed", methodName)); } removeReceiver(receiver); return; @@ -1189,9 +1144,8 @@ public class BluetoothTestUtils extends Assert { int firedFlags = receiver.getFiredFlags(); removeReceiver(receiver); - fail(String.format("%s timeout: device=%s, state=%d (expected %d), " - + "flags=0x%x (expected 0x%s)", methodName, device, state, - BluetoothPan.STATE_CONNECTED, firedFlags, mask)); + fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", + methodName, state, BluetoothPan.STATE_CONNECTED, firedFlags, mask)); } /** @@ -1232,25 +1186,25 @@ public class BluetoothTestUtils extends Assert { String methodName; if (disconnect) { - methodName = "disconnectPan()"; - mask = (ConnectPanReceiver.STATE_DISCONNECTED_FLAG | - ConnectPanReceiver.STATE_DISCONNECTING_FLAG); + methodName = String.format("disconnectPan(device=%s)", device); + mask = (ConnectProfileReceiver.STATE_DISCONNECTED_FLAG | + ConnectProfileReceiver.STATE_DISCONNECTING_FLAG); role = BluetoothPan.LOCAL_PANU_ROLE; } else { - methodName = "incomingPanDisconnection()"; - mask = ConnectPanReceiver.STATE_DISCONNECTED_FLAG; + methodName = String.format("incomingPanDisconnection(device=%s)", device); + mask = ConnectProfileReceiver.STATE_DISCONNECTED_FLAG; role = BluetoothPan.LOCAL_NAP_ROLE; } if (!adapter.isEnabled()) { - fail(String.format("%s bluetooth not enabled: device=%s", methodName, device)); + fail(String.format("%s bluetooth not enabled", methodName)); } if (!adapter.getBondedDevices().contains(device)) { - fail(String.format("%s device not paired: device=%s", methodName, device)); + fail(String.format("%s device not paired", methodName)); } - if (mPan == null) mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); + mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); assertNotNull(mPan); ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask); @@ -1271,8 +1225,7 @@ public class BluetoothTestUtils extends Assert { break; default: removeReceiver(receiver); - fail(String.format("%s invalid state: device=%s, state=%d", methodName, device, - state)); + fail(String.format("%s invalid state: state=%d", methodName, state)); } long s = System.currentTimeMillis(); @@ -1282,10 +1235,10 @@ public class BluetoothTestUtils extends Assert { && (receiver.getFiredFlags() & mask) == mask) { long finish = receiver.getCompletedTime(); if (start != -1 && finish != -1) { - writeOutput(String.format("%s completed in %d ms: device=%s", methodName, - (finish - start), device)); + writeOutput(String.format("%s completed in %d ms", methodName, + (finish - start))); } else { - writeOutput(String.format("%s completed: device=%s", methodName, device)); + writeOutput(String.format("%s completed", methodName)); } removeReceiver(receiver); return; @@ -1295,9 +1248,8 @@ public class BluetoothTestUtils extends Assert { int firedFlags = receiver.getFiredFlags(); removeReceiver(receiver); - fail(String.format("%s timeout: device=%s, state=%d (expected %d), " - + "flags=0x%x (expected 0x%s)", methodName, device, state, - BluetoothInputDevice.STATE_DISCONNECTED, firedFlags, mask)); + fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", + methodName, state, BluetoothInputDevice.STATE_DISCONNECTED, firedFlags, mask)); } /** @@ -1335,29 +1287,26 @@ public class BluetoothTestUtils extends Assert { String methodName; if (isStart) { - methodName = "startSco()"; + methodName = String.format("startSco(device=%s)", device); mask = StartStopScoReceiver.STATE_CONNECTED_FLAG; } else { - methodName = "stopSco()"; + methodName = String.format("stopSco(device=%s)", device); mask = StartStopScoReceiver.STATE_DISCONNECTED_FLAG; } if (!adapter.isEnabled()) { - fail(String.format("%s bluetooth not enabled: device=%s, start=%b", methodName, device, - isStart)); + fail(String.format("%s bluetooth not enabled", methodName)); } if (!adapter.getBondedDevices().contains(device)) { - fail(String.format("%s device not paired: device=%s, start=%b", methodName, device, - isStart)); + fail(String.format("%s device not paired", methodName)); } AudioManager manager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); assertNotNull(manager); if (!manager.isBluetoothScoAvailableOffCall()) { - fail(String.format("%s device does not support SCO: device=%s, start=%b", methodName, - device, isStart)); + fail(String.format("%s device does not support SCO", methodName)); } boolean isScoOn = manager.isBluetoothScoOn(); @@ -1376,8 +1325,7 @@ public class BluetoothTestUtils extends Assert { long s = System.currentTimeMillis(); while (System.currentTimeMillis() - s < START_STOP_SCO_TIMEOUT) { isScoOn = manager.isBluetoothScoOn(); - if ((isStart == isScoOn) && - (receiver.getFiredFlags() & mask) == mask) { + if (isStart == isScoOn && (receiver.getFiredFlags() & mask) == mask) { long finish = receiver.getCompletedTime(); if (start != -1 && finish != -1) { writeOutput(String.format("%s completed in %d ms", methodName, @@ -1393,7 +1341,7 @@ public class BluetoothTestUtils extends Assert { int firedFlags = receiver.getFiredFlags(); removeReceiver(receiver); - fail(String.format("%s timeout: start=%b (expected %b), flags=0x%x (expected 0x%x)", + fail(String.format("%s timeout: on=%b (expected %b), flags=0x%x (expected 0x%x)", methodName, isScoOn, isStart, firedFlags, mask)); } @@ -1478,6 +1426,30 @@ public class BluetoothTestUtils extends Assert { } private BluetoothProfile connectProxy(BluetoothAdapter adapter, int profile) { + switch (profile) { + case BluetoothProfile.A2DP: + if (mA2dp != null) { + return mA2dp; + } + break; + case BluetoothProfile.HEADSET: + if (mHeadset != null) { + return mHeadset; + } + break; + case BluetoothProfile.INPUT_DEVICE: + if (mInput != null) { + return mInput; + } + break; + case BluetoothProfile.PAN: + if (mPan != null) { + return mPan; + } + break; + default: + return null; + } adapter.getProfileProxy(mContext, mServiceListener, profile); long s = System.currentTimeMillis(); switch (profile) { diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java index 39258ae..5ef8d11 100644 --- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java @@ -31,9 +31,11 @@ import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.Suppress; import android.util.Log; +import android.util.Pair; import java.io.File; import java.util.ArrayList; +import java.util.List; public class SQLiteDatabaseTest extends AndroidTestCase { private static final String TAG = "DatabaseGeneralTest"; @@ -892,6 +894,49 @@ public class SQLiteDatabaseTest extends AndroidTestCase { c.close(); } + @SmallTest + public void testAttachDb() { + String newDb = "/sdcard/mydata.db"; + File f = new File(newDb); + if (f.exists()) { + f.delete(); + } + assertFalse(f.exists()); + SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(newDb, null); + db.execSQL("create table test1 (i int);"); + db.execSQL("insert into test1 values(1);"); + db.execSQL("insert into test1 values(11);"); + Cursor c = null; + try { + c = db.rawQuery("select * from test1", null); + int count = c.getCount(); + Log.i(TAG, "count: " + count); + assertEquals(2, count); + } finally { + c.close(); + db.close(); + c = null; + } + + mDatabase.execSQL("attach database ? as newDb" , new String[]{newDb}); + Cursor c1 = null; + try { + c1 = mDatabase.rawQuery("select * from newDb.test1", null); + assertEquals(2, c1.getCount()); + } catch (Exception e) { + fail("unexpected exception: " + e.getMessage()); + } finally { + if (c1 != null) { + c1.close(); + } + } + List<Pair<String, String>> dbs = mDatabase.getAttachedDbs(); + for (Pair<String, String> p: dbs) { + Log.i(TAG, "attached dbs: " + p.first + " : " + p.second); + } + assertEquals(2, dbs.size()); + } + /** * http://b/issue?id=2943028 * SQLiteOpenHelper maintains a Singleton even if it is in bad state. |