summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/java/android/accounts/AccountManagerService.java16
-rw-r--r--core/java/android/app/ActionBar.java60
-rw-r--r--core/java/android/app/BackStackRecord.java30
-rw-r--r--core/java/android/app/FragmentTransaction.java14
-rw-r--r--core/java/android/content/pm/PackageManager.java2
-rw-r--r--core/java/android/database/sqlite/SQLiteCursor.java4
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java75
-rw-r--r--core/java/android/database/sqlite/SQLiteDirectCursorDriver.java2
-rw-r--r--core/java/android/database/sqlite/SQLiteProgram.java12
-rw-r--r--core/java/android/database/sqlite/SQLiteQuery.java3
-rw-r--r--core/java/android/database/sqlite/SQLiteStatement.java23
-rw-r--r--core/java/android/hardware/Camera.java6
-rw-r--r--core/java/android/hardware/Sensor.java12
-rw-r--r--core/java/android/hardware/SensorEvent.java8
-rw-r--r--core/java/android/net/SSLCertificateSocketFactory.java16
-rw-r--r--core/java/android/view/GLES20Canvas.java24
-rw-r--r--core/java/android/view/HardwareRenderer.java5
-rw-r--r--core/java/android/view/MotionEvent.java43
-rw-r--r--core/java/android/view/View.java191
-rw-r--r--core/java/android/view/ViewGroup.java214
-rw-r--r--core/java/android/view/ViewRoot.java9
-rw-r--r--core/java/android/webkit/HTML5VideoFullScreen.java12
-rw-r--r--core/java/android/webkit/HTML5VideoInline.java17
-rw-r--r--core/java/android/webkit/HTML5VideoView.java61
-rw-r--r--core/java/android/webkit/HTML5VideoViewProxy.java19
-rw-r--r--core/java/android/widget/AbsListView.java5
-rw-r--r--core/java/com/android/internal/app/ActionBarImpl.java144
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java60
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuItemView.java1
-rw-r--r--core/java/com/android/internal/widget/ActionBarView.java15
-rw-r--r--core/java/com/android/internal/widget/PointerLocationView.java6
-rw-r--r--core/jni/Android.mk3
-rw-r--r--core/jni/android/graphics/Bitmap.cpp3
-rw-r--r--core/jni/android/graphics/Canvas.cpp23
-rw-r--r--core/jni/android/graphics/HarfbuzzSkia.cpp239
-rw-r--r--core/jni/android/graphics/HarfbuzzSkia.h48
-rw-r--r--core/jni/android/graphics/Paint.cpp31
-rw-r--r--core/jni/android/graphics/RtlProperties.h2
-rw-r--r--core/jni/android/graphics/TextLayoutCache.cpp10
-rw-r--r--core/jni/android/graphics/TextLayoutCache.h162
-rw-r--r--core/jni/android_view_GLES20Canvas.cpp10
-rw-r--r--core/res/res/drawable/list_selector_background.xml2
-rw-r--r--core/res/res/layout/action_menu_item_layout.xml9
-rw-r--r--core/res/res/layout/tab_indicator_holo.xml28
-rw-r--r--core/res/res/layout/tab_indicator_holo_large.xml46
-rw-r--r--core/res/res/values-xlarge/styles.xml16
-rwxr-xr-xcore/res/res/values/attrs.xml5
-rw-r--r--core/res/res/values/public.xml7
-rw-r--r--core/res/res/values/styles.xml7
-rw-r--r--core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java99
-rw-r--r--core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java55
-rw-r--r--core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java304
-rw-r--r--core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java45
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=&lt;profile&gt;, device=&lt;device&gt;)"
*/
- 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=&lt;profile&gt;, device=&lt;device&gt;)"
*/
- 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.