Merge "Set initial view scale to be at least overview scale."
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 5c4f57a..c0714e3 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -870,12 +870,6 @@
(dbStats.dbSize > 0) ? String.valueOf(dbStats.dbSize) : " ",
(dbStats.lookaside > 0) ? String.valueOf(dbStats.lookaside) : " ",
dbStats.cache, dbStats.dbName);
- if (dbStats.dataDump != null) {
- int size = dbStats.dataDump.size();
- for (int dumpIndex = 0; dumpIndex < size; dumpIndex++) {
- printRow(pw, "%s", dbStats.dataDump.get(dumpIndex));
- }
- }
}
}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 87f55d2..7efb7fd 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -2507,7 +2507,7 @@
if (pageCount > 0) {
dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(),
lookasideUsed, db.getCacheHitNum(), db.getCacheMissNum(),
- db.getCachesize(), getDataDump(db)));
+ db.getCachesize()));
}
}
// if there are pooled connections, return the cache stats for them also.
@@ -2518,7 +2518,7 @@
for (SQLiteDatabase pDb : connPool.getConnectionList()) {
dbStatsList.add(new DbStats("(pooled # " + pDb.mConnectionNum + ") "
+ lastnode, 0, 0, 0, pDb.getCacheHitNum(),
- pDb.getCacheMissNum(), pDb.getCachesize(), null));
+ pDb.getCacheMissNum(), pDb.getCachesize()));
}
}
} catch (SQLiteException e) {
@@ -2529,44 +2529,6 @@
return dbStatsList;
}
- private static ArrayList<String> getDataDump(SQLiteDatabase db) {
- // create database dump of certain data from certain databases for debugging purposes
- if (db.getPath().equalsIgnoreCase(
- "/data/data/com.android.providers.downloads/databases/downloads.db")) {
- String sql =
- "select * from downloads " +
- " where notificationpackage = 'com.google.android.gsf'" +
- " or status >= 400";
- Cursor cursor = db.rawQuery(sql, null);
- try {
- int count = cursor.getCount();
- if (count == 0) {
- return null;
- }
- ArrayList<String> buff = new ArrayList<String>();
- buff.add(" Data from downloads.db");
- int columnCount = cursor.getColumnCount();
- for (int i =0; i < count && cursor.moveToNext(); i++) {
- buff.add(" Row#" + i + "");
- for (int j = 0; j < columnCount; j++) {
- String colName = cursor.getColumnName(j);
- String value = cursor.getString(j);
- buff.add(" " + colName + " = " + value);
- }
- }
- for (String s : buff) Log.i("vnoritag", s);
- return buff;
- } catch (SQLiteException e) {
- Log.w(TAG, "exception in executing the sql: " + sql, e);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
- return null;
- }
-
/**
* Returns list of full pathnames of all attached databases including the main database
* by executing 'pragma database_list' on the database.
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index 72377f0..94960791 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -121,31 +121,27 @@
*/
public static class DbStats {
/** name of the database */
- public final String dbName;
+ public String dbName;
/** the page size for the database */
- public final long pageSize;
+ public long pageSize;
/** the database size */
- public final long dbSize;
+ public long dbSize;
/** documented here http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html */
- public final int lookaside;
+ public int lookaside;
/** statement cache stats: hits/misses/cachesize */
- public final String cache;
-
- /** database dump of 'useful info for debugging only */
- public final ArrayList<String> dataDump;
+ public String cache;
public DbStats(String dbName, long pageCount, long pageSize, int lookaside,
- int hits, int misses, int cachesize, ArrayList<String> data) {
+ int hits, int misses, int cachesize) {
this.dbName = dbName;
this.pageSize = pageSize / 1024;
dbSize = (pageCount * pageSize) / 1024;
this.lookaside = lookaside;
this.cache = hits + "/" + misses + "/" + cachesize;
- this.dataDump = data;
}
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 88d3f7a..45c46db 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -7103,7 +7103,7 @@
handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
}
- if (isTextEditable()) {
+ if (isTextEditable() || mTextIsSelectable) {
if (mScrollX != oldScrollX || mScrollY != oldScrollY) {
// Hide insertion anchor while scrolling. Leave selection.
hideInsertionPointCursorController();
@@ -7153,7 +7153,7 @@
}
mInsertionControllerEnabled = windowSupportsHandles && isTextEditable() && mCursorVisible &&
- mLayout != null && !mTextIsSelectable;
+ mLayout != null;
mSelectionControllerEnabled = windowSupportsHandles && textCanBeSelected() &&
mLayout != null;
@@ -7172,8 +7172,7 @@
* a selectable TextView.
*/
private boolean isTextEditable() {
- return (mText instanceof Editable && onCheckIsTextEditor() && isEnabled())
- || mTextIsSelectable;
+ return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
}
/**
@@ -7748,7 +7747,7 @@
}
}
if (clip != null) {
- clipboard.setPrimaryClip(clip);
+ setPrimaryClip(clip);
}
}
return true;
@@ -7839,26 +7838,29 @@
return true;
}
- if (!isPositionOnText(mLastDownPositionX, mLastDownPositionY) && mInsertionControllerEnabled) {
+ if (!isPositionOnText(mLastDownPositionX, mLastDownPositionY) &&
+ mInsertionControllerEnabled) {
+ // Long press in empty space moves cursor and shows the Paste affordance if available.
final int offset = getOffset(mLastDownPositionX, mLastDownPositionY);
Selection.setSelection((Spannable)mText, offset);
- if (canPaste()) {
- getInsertionController().showWithPaste();
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- } else {
- getInsertionController().show();
- }
+ getInsertionController().show(0);
mEatTouchRelease = true;
return true;
}
- if (mSelectionActionMode != null && touchPositionIsInSelection()) {
- final int start = getSelectionStart();
- final int end = getSelectionEnd();
- CharSequence selectedText = mTransformed.subSequence(start, end);
- ClipData data = ClipData.newPlainText(null, null, selectedText);
- startDrag(data, getTextThumbnailBuilder(selectedText), false);
- stopSelectionActionMode();
+ if (mSelectionActionMode != null) {
+ if (touchPositionIsInSelection()) {
+ // Start a drag
+ final int start = getSelectionStart();
+ final int end = getSelectionEnd();
+ CharSequence selectedText = mTransformed.subSequence(start, end);
+ ClipData data = ClipData.newPlainText(null, null, selectedText);
+ startDrag(data, getTextThumbnailBuilder(selectedText), false);
+ stopSelectionActionMode();
+ } else {
+ selectCurrentWord();
+ getSelectionController().show();
+ }
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
mEatTouchRelease = true;
return true;
@@ -7950,10 +7952,10 @@
/**
* Paste clipboard content between min and max positions.
- *
- * @param clipboard getSystemService(Context.CLIPBOARD_SERVICE)
*/
- private void paste(ClipboardManager clipboard, int min, int max) {
+ private void paste(int min, int max) {
+ ClipboardManager clipboard =
+ (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = clipboard.getPrimaryClip();
if (clip != null) {
boolean didfirst = false;
@@ -7973,9 +7975,17 @@
}
}
stopSelectionActionMode();
+ sLastCutOrCopyTime = 0;
}
}
+ private void setPrimaryClip(ClipData clip) {
+ ClipboardManager clipboard = (ClipboardManager) getContext().
+ getSystemService(Context.CLIPBOARD_SERVICE);
+ clipboard.setPrimaryClip(clip);
+ sLastCutOrCopyTime = SystemClock.uptimeMillis();
+ }
+
private class SelectionActionModeCallback implements ActionMode.Callback {
@Override
@@ -8061,9 +8071,6 @@
return true;
}
- ClipboardManager clipboard = (ClipboardManager) getContext().
- getSystemService(Context.CLIPBOARD_SERVICE);
-
int min = 0;
int max = mText.length();
@@ -8077,18 +8084,18 @@
switch (item.getItemId()) {
case ID_PASTE:
- paste(clipboard, min, max);
+ paste(min, max);
return true;
case ID_CUT:
- clipboard.setPrimaryClip(ClipData.newPlainText(null, null,
+ setPrimaryClip(ClipData.newPlainText(null, null,
mTransformed.subSequence(min, max)));
((Editable) mText).delete(min, max);
stopSelectionActionMode();
return true;
case ID_COPY:
- clipboard.setPrimaryClip(ClipData.newPlainText(null, null,
+ setPrimaryClip(ClipData.newPlainText(null, null,
mTransformed.subSequence(min, max)));
stopSelectionActionMode();
return true;
@@ -8211,9 +8218,7 @@
@Override
public void onClick(View v) {
if (canPaste()) {
- ClipboardManager clipboard =
- (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
- paste(clipboard, getSelectionStart(), getSelectionEnd());
+ paste(getSelectionStart(), getSelectionEnd());
}
hide();
}
@@ -8502,6 +8507,9 @@
}
case MotionEvent.ACTION_UP:
if (mPastePopupWindow != null) {
+ // Will show the paste popup after a delay.
+ mController.show();
+ /* TEMP USER TEST: Display Paste as soon as handle is draggged
long delay = SystemClock.uptimeMillis() - mTouchTimer;
if (delay < ViewConfiguration.getTapTimeout()) {
final float touchOffsetX = ev.getRawX() - mPositionX;
@@ -8515,7 +8523,7 @@
if (distanceSquared < slopSquared) {
showPastePopupWindow();
}
- }
+ }*/
}
mIsDragging = false;
break;
@@ -8561,6 +8569,8 @@
private class InsertionPointCursorController implements CursorController {
private static final int DELAY_BEFORE_FADE_OUT = 4100;
+ private static final int DELAY_BEFORE_PASTE = 2000;
+ private static final int RECENT_CUT_COPY_DURATION = 15 * 1000;
// The cursor controller image. Lazily created.
private HandleView mHandle;
@@ -8571,14 +8581,27 @@
}
};
+ private final Runnable mPastePopupShower = new Runnable() {
+ public void run() {
+ getHandle().showPastePopupWindow();
+ }
+ };
+
public void show() {
- updatePosition();
- getHandle().show();
+ show(DELAY_BEFORE_PASTE);
}
- void showWithPaste() {
- show();
- getHandle().showPastePopupWindow();
+ public void show(int delayBeforePaste) {
+ updatePosition();
+ hideDelayed();
+ getHandle().show();
+ removeCallbacks(mPastePopupShower);
+ if (canPaste()) {
+ final long durationSinceCutOrCopy = SystemClock.uptimeMillis() - sLastCutOrCopyTime;
+ if (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION)
+ delayBeforePaste = 0;
+ postDelayed(mPastePopupShower, delayBeforePaste);
+ }
}
public void hide() {
@@ -8586,6 +8609,7 @@
mHandle.hide();
}
removeCallbacks(mHider);
+ removeCallbacks(mPastePopupShower);
}
private void hideDelayed() {
@@ -8618,7 +8642,6 @@
return;
}
- // updatePosition is called only when isShowing. Handle has been created at this point.
getHandle().positionAtCursor(offset, true);
}
@@ -8681,6 +8704,7 @@
mEndHandle.show();
hideInsertionPointCursorController();
+ hideDelayed();
}
public void hide() {
@@ -8735,6 +8759,7 @@
Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
updatePosition();
+ hideDelayed();
}
public void updatePosition() {
@@ -8755,13 +8780,12 @@
// The handles have been created since the controller isShowing().
mStartHandle.positionAtCursor(selectionStart, true);
mEndHandle.positionAtCursor(selectionEnd, true);
- hideDelayed();
}
public boolean onTouchEvent(MotionEvent event) {
// This is done even when the View does not have focus, so that long presses can start
// selection and tap can move cursor from this tap position.
- if (isTextEditable()) {
+ if (isTextEditable() || mTextIsSelectable) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
final int x = (int) event.getX();
@@ -9143,4 +9167,6 @@
private InputFilter[] mFilters = NO_FILTERS;
private static final Spanned EMPTY_SPANNED = new SpannedString("");
private static int DRAG_THUMBNAIL_MAX_TEXT_LENGTH = 20;
+ // System wide time for last cut or copy action.
+ private static long sLastCutOrCopyTime;
}
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
index 040dac3..fad9539 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -63,7 +63,8 @@
}
if (!window->initBuffer(localOnly)) {
- jniThrowException(env, "java/lang/IllegalStateException", "Couldn't init cursor window");
+ jniThrowException(env, "java/lang/RuntimeException",
+ "Memory couldn't be allocated for 1MB CursorWindow object.");
delete window;
return;
}
@@ -82,11 +83,13 @@
CursorWindow * window = new CursorWindow();
if (!window) {
- jniThrowException(env, "java/lang/RuntimeException", "No memory for native window object");
+ jniThrowException(env, "java/lang/RuntimeException",
+ "CursorWindow of size 1MB couldn't be created. No memory?");
return;
}
if (!window->setMemory(memory)) {
- jniThrowException(env, "java/lang/RuntimeException", "No memory in memObj");
+ jniThrowException(env, "java/lang/RuntimeException",
+ "Memory couldn't be initialized for 1MB CursorWindow object.");
delete window;
return;
}
@@ -131,8 +134,9 @@
static void throwExceptionWithRowCol(JNIEnv * env, jint row, jint column)
{
- char buf[100];
- snprintf(buf, sizeof(buf), "get field slot from row %d col %d failed", row, column);
+ char buf[200];
+ snprintf(buf, sizeof(buf), "Couldn't read row %d, col %d from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it",
+ row, column);
jniThrowException(env, "java/lang/IllegalStateException", buf);
}
diff --git a/libs/binder/CursorWindow.cpp b/libs/binder/CursorWindow.cpp
index fbba281..47bbd04 100644
--- a/libs/binder/CursorWindow.cpp
+++ b/libs/binder/CursorWindow.cpp
@@ -219,7 +219,8 @@
field_slot_t * CursorWindow::getFieldSlotWithCheck(int row, int column)
{
if (row < 0 || row >= mHeader->numRows || column < 0 || column >= mHeader->numColumns) {
- LOGE("Bad request for field slot %d,%d. numRows = %d, numColumns = %d", row, column, mHeader->numRows, mHeader->numColumns);
+ LOGE("Failed to read row# %d, column# from a CursorWindow which has %d rows, %d columns.",
+ row, column, mHeader->numRows, mHeader->numColumns);
return NULL;
}
row_slot_t * rowSlot = getRowSlot(row);
@@ -238,7 +239,8 @@
uint32_t CursorWindow::read_field_slot(int row, int column, field_slot_t * slotOut)
{
if (row < 0 || row >= mHeader->numRows || column < 0 || column >= mHeader->numColumns) {
- LOGE("Bad request for field slot %d,%d. numRows = %d, numColumns = %d", row, column, mHeader->numRows, mHeader->numColumns);
+ LOGE("Can't read row# %d, col# %d from CursorWindow. Make sure your Cursor is initialized correctly.",
+ row, column);
return -1;
}
row_slot_t * rowSlot = getRowSlot(row);
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
index cc01bc5..069e1b8 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
@@ -110,7 +110,7 @@
public static final int EVENT_CLEAN_UP_CONNECTION = 34;
protected static final int EVENT_CDMA_OTA_PROVISION = 35;
protected static final int EVENT_RESTART_RADIO = 36;
- protected static final int EVENT_SET_MASTER_DATA_ENABLE = 37;
+ protected static final int EVENT_SET_INTERNAL_DATA_ENABLE = 37;
protected static final int EVENT_RESET_DONE = 38;
/***** Constants *****/
@@ -126,8 +126,9 @@
protected static final int DISABLED = 0;
protected static final int ENABLED = 1;
- // responds to the setDataEnabled call - used independently from the APN requests
- protected boolean mMasterDataEnabled = true;
+ // responds to the setInternalDataEnabled call - used internally to turn off data
+ // for example during emergency calls
+ protected boolean mInternalDataEnabled = true;
protected boolean[] dataEnabled = new boolean[APN_NUM_TYPES];
@@ -489,9 +490,9 @@
onCleanUpConnection(tearDown, (String) msg.obj);
break;
- case EVENT_SET_MASTER_DATA_ENABLE:
+ case EVENT_SET_INTERNAL_DATA_ENABLE:
boolean enabled = (msg.arg1 == ENABLED) ? true : false;
- onSetDataEnabled(enabled);
+ onSetInternalDataEnabled(enabled);
break;
case EVENT_RESET_DONE:
@@ -505,23 +506,13 @@
}
/**
- * Report the current state of data connectivity (enabled or disabled)
- *
- * @return {@code false} if data connectivity has been explicitly disabled,
- * {@code true} otherwise.
- */
- public synchronized boolean getDataEnabled() {
- return (mMasterDataEnabled && dataEnabled[APN_DEFAULT_ID]);
- }
-
- /**
* Report on whether data connectivity is enabled
*
* @return {@code false} if data connectivity has been explicitly disabled,
* {@code true} otherwise.
*/
public synchronized boolean getAnyDataEnabled() {
- return (mMasterDataEnabled && (enabledCount != 0));
+ return (mInternalDataEnabled && (enabledCount != 0));
}
protected abstract void startNetStatPoll();
@@ -676,7 +667,7 @@
*/
protected boolean isDataPossible() {
boolean possible = (isDataAllowed()
- && !(getDataEnabled() && (mState == State.FAILED || mState == State.IDLE)));
+ && !(getAnyDataEnabled() && (mState == State.FAILED || mState == State.IDLE)));
if (!possible && DBG && isDataAllowed()) {
log("Data not possible. No coverage: dataState = " + mState);
}
@@ -847,20 +838,20 @@
* {@code false}) data
* @return {@code true} if the operation succeeded
*/
- public boolean setDataEnabled(boolean enable) {
+ public boolean setInternalDataEnabled(boolean enable) {
if (DBG)
- log("setDataEnabled(" + enable + ")");
+ log("setInternalDataEnabled(" + enable + ")");
- Message msg = obtainMessage(EVENT_SET_MASTER_DATA_ENABLE);
+ Message msg = obtainMessage(EVENT_SET_INTERNAL_DATA_ENABLE);
msg.arg1 = (enable ? ENABLED : DISABLED);
sendMessage(msg);
return true;
}
- protected void onSetDataEnabled(boolean enable) {
- if (mMasterDataEnabled != enable) {
+ protected void onSetInternalDataEnabled(boolean enable) {
+ if (mInternalDataEnabled != enable) {
synchronized (this) {
- mMasterDataEnabled = enable;
+ mInternalDataEnabled = enable;
}
if (enable) {
mRetryMgr.resetRetryCount();
diff --git a/telephony/java/com/android/internal/telephony/Phone.java b/telephony/java/com/android/internal/telephony/Phone.java
index 69b7de6..25ad48d 100644
--- a/telephony/java/com/android/internal/telephony/Phone.java
+++ b/telephony/java/com/android/internal/telephony/Phone.java
@@ -1325,36 +1325,6 @@
SimulatedRadioControl getSimulatedRadioControl();
/**
- * Allow mobile data connections.
- * @return {@code true} if the operation started successfully
- * <br/>{@code false} if it
- * failed immediately.<br/>
- * Even in the {@code true} case, it may still fail later
- * during setup, in which case an asynchronous indication will
- * be supplied.
- */
- boolean enableDataConnectivity();
-
- /**
- * Disallow mobile data connections, and terminate any that
- * are in progress.
- * @return {@code true} if the operation started successfully
- * <br/>{@code false} if it
- * failed immediately.<br/>
- * Even in the {@code true} case, it may still fail later
- * during setup, in which case an asynchronous indication will
- * be supplied.
- */
- boolean disableDataConnectivity();
-
- /**
- * Report the current state of data connectivity (enabled or disabled)
- * @return {@code false} if data connectivity has been explicitly disabled,
- * {@code true} otherwise.
- */
- boolean isDataConnectivityEnabled();
-
- /**
* Enables the specified APN type. Only works for "special" APN types,
* i.e., not the default APN.
* @param type The desired APN type. Cannot be {@link #APN_TYPE_DEFAULT}.
diff --git a/telephony/java/com/android/internal/telephony/PhoneBase.java b/telephony/java/com/android/internal/telephony/PhoneBase.java
index fe4fdb3..fce7b9b 100644
--- a/telephony/java/com/android/internal/telephony/PhoneBase.java
+++ b/telephony/java/com/android/internal/telephony/PhoneBase.java
@@ -941,10 +941,6 @@
logUnexpectedCdmaMethodCall("unsetOnEcbModeExitResponse");
}
- public boolean isDataConnectivityEnabled() {
- return mDataConnection.getDataEnabled();
- }
-
public String[] getActiveApnTypes() {
return mDataConnection.getActiveApnTypes();
}
diff --git a/telephony/java/com/android/internal/telephony/PhoneProxy.java b/telephony/java/com/android/internal/telephony/PhoneProxy.java
index 2e79762..219efbb 100644
--- a/telephony/java/com/android/internal/telephony/PhoneProxy.java
+++ b/telephony/java/com/android/internal/telephony/PhoneProxy.java
@@ -650,14 +650,6 @@
return mActivePhone.getSimulatedRadioControl();
}
- public boolean enableDataConnectivity() {
- return mActivePhone.enableDataConnectivity();
- }
-
- public boolean disableDataConnectivity() {
- return mActivePhone.disableDataConnectivity();
- }
-
public int enableApnType(String type) {
return mActivePhone.enableApnType(type);
}
@@ -666,10 +658,6 @@
return mActivePhone.disableApnType(type);
}
- public boolean isDataConnectivityEnabled() {
- return mActivePhone.isDataConnectivityEnabled();
- }
-
public boolean isDataConnectivityPossible() {
return mActivePhone.isDataConnectivityPossible();
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
index 099bc30..1e77589 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
@@ -478,10 +478,6 @@
return mSST.cellLoc;
}
- public boolean disableDataConnectivity() {
- return mDataConnection.setDataEnabled(false);
- }
-
public CdmaCall getForegroundCall() {
return mCT.foregroundCall;
}
@@ -761,21 +757,6 @@
return ret;
}
- public boolean enableDataConnectivity() {
-
- // block data activities when phone is in emergency callback mode
- if (mIsPhoneInEcmState) {
- Intent intent = new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS);
- ActivityManagerNative.broadcastStickyIntent(intent, null);
- return false;
- } else if ((mCT.state == Phone.State.OFFHOOK) && mCT.isInEmergencyCall()) {
- // Do not allow data call to be enabled when emergency call is going on
- return false;
- } else {
- return mDataConnection.setDataEnabled(true);
- }
- }
-
public boolean getIccRecordsLoaded() {
return mRuimRecords.getRecordsLoaded();
}
@@ -921,7 +902,7 @@
// send an Intent
sendEmergencyCallbackModeChange();
// Re-initiate data connection
- mDataConnection.setDataEnabled(true);
+ mDataConnection.setInternalDataEnabled(true);
}
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java
index 325c2e1..a89f783 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java
@@ -1058,7 +1058,7 @@
if (PhoneNumberUtils.isEmergencyNumber(dialString)) {
if (Phone.DEBUG_PHONE) log("disableDataCallInEmergencyCall");
mIsInEmergencyCall = true;
- phone.disableDataConnectivity();
+ phone.mDataConnection.setInternalDataEnabled(false);
}
}
@@ -1075,8 +1075,7 @@
}
if (inEcm.compareTo("false") == 0) {
// Re-initiate data connection
- // TODO - can this be changed to phone.enableDataConnectivity();
- phone.mDataConnection.setDataEnabled(true);
+ phone.mDataConnection.setInternalDataEnabled(true);
}
}
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
index f7664ca5..b005cd3 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
@@ -204,7 +204,7 @@
(mCdmaPhone.mSST.isConcurrentVoiceAndData() ||
mPhone.getState() == Phone.State.IDLE) &&
!roaming &&
- mMasterDataEnabled &&
+ mInternalDataEnabled &&
desiredPowerState &&
!mPendingRestartRadio &&
!mCdmaPhone.needsOtaServiceProvisioning();
@@ -222,7 +222,7 @@
reason += " - concurrentVoiceAndData not allowed and state= " + mPhone.getState();
}
if (roaming) reason += " - Roaming";
- if (!mMasterDataEnabled) reason += " - mMasterDataEnabled= false";
+ if (!mInternalDataEnabled) reason += " - mInternalDataEnabled= false";
if (!desiredPowerState) reason += " - desiredPowerState= false";
if (mPendingRestartRadio) reason += " - mPendingRestartRadio= true";
if (mCdmaPhone.needsOtaServiceProvisioning()) reason += " - needs Provisioning";
diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
index fc03d1a..628f11a 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
@@ -1084,14 +1084,6 @@
mDataConnection.setDataOnRoamingEnabled(enable);
}
- public boolean enableDataConnectivity() {
- return mDataConnection.setDataEnabled(true);
- }
-
- public boolean disableDataConnectivity() {
- return mDataConnection.setDataEnabled(false);
- }
-
/**
* Removes the given MMI from the pending list and notifies
* registrants that it is complete.
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index b41402c..4713c24 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -284,7 +284,7 @@
(gprsState == ServiceState.STATE_IN_SERVICE || mAutoAttachOnCreation) &&
mGsmPhone.mSIMRecords.getRecordsLoaded() &&
mPhone.getState() == Phone.State.IDLE &&
- mMasterDataEnabled &&
+ mInternalDataEnabled &&
(!mPhone.getServiceState().getRoaming() || getDataOnRoamingEnabled()) &&
!mIsPsRestricted &&
desiredPowerState;
@@ -297,7 +297,7 @@
if (mPhone.getState() != Phone.State.IDLE) {
reason += " - PhoneState= " + mPhone.getState();
}
- if (!mMasterDataEnabled) reason += " - mMasterDataEnabled= false";
+ if (!mInternalDataEnabled) reason += " - mInternalDataEnabled= false";
if (mPhone.getServiceState().getRoaming() && !getDataOnRoamingEnabled()) {
reason += " - Roaming and data roaming not enabled";
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index 08f3c7a..b901e0d 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -366,13 +366,59 @@
/*package*/ static void native_concat(int nCanvas, int nMatrix) {
- // FIXME
- throw new UnsupportedOperationException();
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+ if (canvasDelegate == null) {
+ assert false;
+ return;
+ }
+
+ Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
+ if (matrixDelegate == null) {
+ assert false;
+ return;
+ }
+
+ // get the current top graphics2D object.
+ Graphics2D g = canvasDelegate.getGraphics2d();
+
+ // get its current matrix
+ AffineTransform currentTx = g.getTransform();
+ // get the AffineTransform of the given matrix
+ AffineTransform matrixTx = matrixDelegate.getAffineTransform();
+
+ // combine them so that the given matrix is applied after.
+ currentTx.preConcatenate(matrixTx);
+
+ // give it to the graphics2D as a new matrix replacing all previous transform
+ g.setTransform(currentTx);
}
/*package*/ static void native_setMatrix(int nCanvas, int nMatrix) {
- // FIXME
- throw new UnsupportedOperationException();
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+ if (canvasDelegate == null) {
+ assert false;
+ }
+
+ Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
+ if (matrixDelegate == null) {
+ assert false;
+ }
+
+ // get the current top graphics2D object.
+ Graphics2D g = canvasDelegate.getGraphics2d();
+
+ // get the AffineTransform of the given matrix
+ AffineTransform matrixTx = matrixDelegate.getAffineTransform();
+
+ // give it to the graphics2D as a new matrix replacing all previous transform
+ g.setTransform(matrixTx);
+
+ // FIXME: log
+// if (mLogger != null && matrixDelegate.hasPerspective()) {
+// mLogger.warning("android.graphics.Canvas#setMatrix(android.graphics.Matrix) only supports affine transformations in the Layout Editor.");
+// }
}
/*package*/ static boolean native_clipRect(int nCanvas,
diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
index 6e80268..b2333f6 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
@@ -64,7 +64,7 @@
return null;
}
- return getAffineTransform(delegate);
+ return delegate.getAffineTransform();
}
public static boolean hasPerspective(Matrix m) {
@@ -74,7 +74,7 @@
return false;
}
- return (delegate.mValues[6] != 0 || delegate.mValues[7] != 0 || delegate.mValues[8] != 1);
+ return delegate.hasPerspective();
}
/**
@@ -106,6 +106,18 @@
return true;
}
+ /**
+ * Returns an {@link AffineTransform} matching the matrix.
+ */
+ public AffineTransform getAffineTransform() {
+ return getAffineTransform(mValues);
+ }
+
+ public boolean hasPerspective() {
+ return (mValues[6] != 0 || mValues[7] != 0 || mValues[8] != 1);
+ }
+
+
// ---- native methods ----
@@ -599,7 +611,7 @@
try {
- AffineTransform affineTransform = getAffineTransform(d);
+ AffineTransform affineTransform = d.getAffineTransform();
AffineTransform inverseTransform = affineTransform.createInverse();
inv_mtx.mValues[0] = (float)inverseTransform.getScaleX();
inv_mtx.mValues[1] = (float)inverseTransform.getShearX();
@@ -713,10 +725,6 @@
// ---- Private helper methods ----
- private static AffineTransform getAffineTransform(Matrix_Delegate d) {
- return getAffineTransform(d.mValues);
- }
-
/*package*/ static AffineTransform getAffineTransform(float[] matrix) {
// the AffineTransform constructor takes the value in a different order
// for a matrix [ 0 1 2 ]
diff --git a/tools/layoutlib/bridge/src/android/os/Handler_Delegate.java b/tools/layoutlib/bridge/src/android/os/Handler_Delegate.java
new file mode 100644
index 0000000..4d4ec7f4
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/os/Handler_Delegate.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+
+/**
+ * Delegate overriding selected methods of android.os.Handler
+ *
+ * Through the layoutlib_create tool, selected methods of Handler have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ *
+ */
+public class Handler_Delegate {
+
+ // -------- Delegate methods
+
+ /*package*/ static boolean sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) {
+ // get the callback
+ IHandlerCallback callback = sCallbacks.get();
+ if (callback != null) {
+ callback.sendMessageAtTime(handler, msg, uptimeMillis);
+ }
+ return true;
+ }
+
+ // -------- Delegate implementation
+
+ public interface IHandlerCallback {
+ void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis);
+ }
+
+ private final static ThreadLocal<IHandlerCallback> sCallbacks =
+ new ThreadLocal<IHandlerCallback>();
+
+ public static void setCallback(IHandlerCallback callback) {
+ sCallbacks.set(callback);
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java
new file mode 100644
index 0000000..be222fc
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+
+/**
+ * Delegate implementing the native methods of android.os.SystemClock
+ *
+ * Through the layoutlib_create tool, the original native methods of SystemClock have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
+ * around to map int to instance of the delegate.
+ *
+ */
+public class SystemClock_Delegate {
+ private static long sBootTime = System.currentTimeMillis();
+
+ /*package*/ static boolean setCurrentTimeMillis(long millis) {
+ return true;
+ }
+
+ /**
+ * Returns milliseconds since boot, not counting time spent in deep sleep.
+ * <b>Note:</b> This value may get reset occasionally (before it would
+ * otherwise wrap around).
+ *
+ * @return milliseconds of non-sleep uptime since boot.
+ */
+ /*package*/ static long uptimeMillis() {
+ return System.currentTimeMillis() - sBootTime;
+ }
+
+ /**
+ * Returns milliseconds since boot, including time spent in sleep.
+ *
+ * @return elapsed milliseconds since boot.
+ */
+ /*package*/ static long elapsedRealtime() {
+ return System.currentTimeMillis() - sBootTime;
+ }
+
+ /**
+ * Returns milliseconds running in the current thread.
+ *
+ * @return elapsed milliseconds in the thread
+ */
+ /*package*/ static long currentThreadTimeMillis() {
+ return System.currentTimeMillis();
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index 35ba73d..2de1cbb 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -40,6 +40,7 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
/**
* Main entry point of the LayoutLib Bridge.
@@ -57,6 +58,12 @@
}
/**
+ * Lock to ensure only one rendering/inflating happens at a time.
+ * This is due to some singleton in the Android framework.
+ */
+ private final static ReentrantLock sLock = new ReentrantLock();
+
+ /**
* Maps from id to resource name/type. This is for android.R only.
*/
private final static Map<Integer, String[]> sRMap = new HashMap<Integer, String[]>();
@@ -160,7 +167,6 @@
BridgeAssetManager.initSystem();
-
// When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener
// on static (native) methods which prints the signature on the console and
// throws an exception.
@@ -252,27 +258,6 @@
}
/**
- * Sets a 9 patch chunk in a project cache or in the framework cache.
- * @param value the path of the 9 patch
- * @param ninePatch the 9 patch object
- * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
- */
- public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) {
- if (projectKey != null) {
- Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
-
- if (map == null) {
- map = new HashMap<String, SoftReference<NinePatchChunk>>();
- sProject9PatchCache.put(projectKey, map);
- }
-
- map.put(value, new SoftReference<NinePatchChunk>(ninePatch));
- } else {
- sFramework9PatchCache.put(value, new SoftReference<NinePatchChunk>(ninePatch));
- }
- }
-
- /**
* Starts a layout session by inflating and rendering it. The method returns a
* {@link ILayoutScene} on which further actions can be taken.
*
@@ -306,27 +291,25 @@
public BridgeLayoutScene createScene(SceneParams params) {
try {
SceneResult lastResult = SceneResult.SUCCESS;
- LayoutSceneImpl scene = null;
- synchronized (this) {
- try {
- scene = new LayoutSceneImpl(params);
-
- scene.prepare();
+ LayoutSceneImpl scene = new LayoutSceneImpl(params);
+ try {
+ scene.prepareThread();
+ lastResult = scene.init(params.getTimeout());
+ if (lastResult == SceneResult.SUCCESS) {
lastResult = scene.inflate();
if (lastResult == SceneResult.SUCCESS) {
lastResult = scene.render();
}
- } finally {
- if (scene != null) {
- scene.cleanup();
- }
}
+ } finally {
+ scene.release();
+ scene.cleanupThread();
}
- return new BridgeLayoutScene(this, scene, lastResult);
+ return new BridgeLayoutScene(scene, lastResult);
} catch (Throwable t) {
t.printStackTrace();
- return new BridgeLayoutScene(this, null, new SceneResult("error!", t));
+ return new BridgeLayoutScene(null, new SceneResult("error!", t));
}
}
@@ -343,6 +326,13 @@
}
/**
+ * Returns the lock for the bridge
+ */
+ public static ReentrantLock getLock() {
+ return sLock;
+ }
+
+ /**
* Returns details of a framework resource from its integer value.
* @param value the integer value
* @return an array of 2 strings containing the resource name and type, or null if the id
@@ -461,4 +451,27 @@
return null;
}
+
+ /**
+ * Sets a 9 patch chunk in a project cache or in the framework cache.
+ * @param value the path of the 9 patch
+ * @param ninePatch the 9 patch object
+ * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
+ */
+ public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) {
+ if (projectKey != null) {
+ Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
+
+ if (map == null) {
+ map = new HashMap<String, SoftReference<NinePatchChunk>>();
+ sProject9PatchCache.put(projectKey, map);
+ }
+
+ map.put(value, new SoftReference<NinePatchChunk>(ninePatch));
+ } else {
+ sFramework9PatchCache.put(value, new SoftReference<NinePatchChunk>(ninePatch));
+ }
+ }
+
+
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeLayoutScene.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeLayoutScene.java
index 8b67166..97bf857 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeLayoutScene.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeLayoutScene.java
@@ -17,6 +17,7 @@
package com.android.layoutlib.bridge;
import com.android.layoutlib.api.LayoutScene;
+import com.android.layoutlib.api.SceneParams;
import com.android.layoutlib.api.SceneResult;
import com.android.layoutlib.api.ViewInfo;
import com.android.layoutlib.bridge.impl.LayoutSceneImpl;
@@ -33,7 +34,6 @@
*/
public class BridgeLayoutScene extends LayoutScene {
- private final Bridge mBridge;
private final LayoutSceneImpl mScene;
private SceneResult mLastResult;
@@ -58,15 +58,34 @@
}
@Override
- public SceneResult render() {
-
- synchronized (mBridge) {
- try {
- mScene.prepare();
+ public SceneResult render(long timeout) {
+ try {
+ mScene.prepareThread();
+ mLastResult = mScene.acquire(timeout);
+ if (mLastResult == SceneResult.SUCCESS) {
mLastResult = mScene.render();
- } finally {
- mScene.cleanup();
}
+ } finally {
+ mScene.release();
+ mScene.cleanupThread();
+ }
+
+ return mLastResult;
+ }
+
+ @Override
+ public SceneResult animate(Object targetObject, String animationName,
+ boolean isFrameworkAnimation, IAnimationListener listener) {
+ try {
+ mScene.prepareThread();
+ mLastResult = mScene.acquire(SceneParams.DEFAULT_TIMEOUT);
+ if (mLastResult == SceneResult.SUCCESS) {
+ mLastResult = mScene.animate(targetObject, animationName, isFrameworkAnimation,
+ listener);
+ }
+ } finally {
+ mScene.release();
+ mScene.cleanupThread();
}
return mLastResult;
@@ -78,8 +97,7 @@
}
- /*package*/ BridgeLayoutScene(Bridge bridge, LayoutSceneImpl scene, SceneResult lastResult) {
- mBridge = bridge;
+ /*package*/ BridgeLayoutScene(LayoutSceneImpl scene, SceneResult lastResult) {
mScene = scene;
mLastResult = lastResult;
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java
index 1011173..affd1c6 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java
@@ -261,6 +261,41 @@
}
@Override
+ public XmlResourceParser getAnimation(int id) throws NotFoundException {
+ IResourceValue value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ XmlPullParser parser = null;
+
+ try {
+ File xml = new File(value.getValue());
+ if (xml.isFile()) {
+ // we need to create a pull parser around the layout XML file, and then
+ // give that to our XmlBlockParser
+ parser = new KXmlParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ parser.setInput(new FileReader(xml));
+
+ return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
+ }
+ } catch (XmlPullParserException e) {
+ mContext.getLogger().error(e);
+ // we'll return null below.
+ } catch (FileNotFoundException e) {
+ // this shouldn't happen since we check above.
+ }
+
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+
+ @Override
public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
return mContext.obtainStyledAttributes(set, attrs);
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java
new file mode 100644
index 0000000..c20bdfd
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.impl;
+
+import com.android.layoutlib.api.SceneResult;
+import com.android.layoutlib.api.LayoutScene.IAnimationListener;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.os.Handler;
+import android.os.Handler_Delegate;
+import android.os.Message;
+import android.os.Handler_Delegate.IHandlerCallback;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+public class AnimationThread extends Thread {
+
+ private static class MessageBundle {
+ final Handler mTarget;
+ final Message mMessage;
+ final long mUptimeMillis;
+
+ MessageBundle(Handler target, Message message, long uptimeMillis) {
+ mTarget = target;
+ mMessage = message;
+ mUptimeMillis = uptimeMillis;
+ }
+ }
+
+ private final LayoutSceneImpl mScene;
+ private final Animator mAnimator;
+
+ Queue<MessageBundle> mQueue = new LinkedList<MessageBundle>();
+ private final IAnimationListener mListener;
+
+ public AnimationThread(LayoutSceneImpl scene, Animator animator, IAnimationListener listener) {
+ mScene = scene;
+ mAnimator = animator;
+ mListener = listener;
+ }
+
+ @Override
+ public void run() {
+ mScene.prepareThread();
+ try {
+ Handler_Delegate.setCallback(new IHandlerCallback() {
+ public void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) {
+ if (msg.what == ValueAnimator.ANIMATION_START ||
+ msg.what == ValueAnimator.ANIMATION_FRAME) {
+ mQueue.add(new MessageBundle(handler, msg, uptimeMillis));
+ } else {
+ // just ignore.
+ }
+ }
+ });
+
+ // start the animation. This will send a message to the handler right away, so
+ // mQueue is filled when this method returns.
+ mAnimator.start();
+
+ // loop the animation
+ do {
+ // get the next message.
+ MessageBundle bundle = mQueue.poll();
+ if (bundle == null) {
+ break;
+ }
+
+ // sleep enough for this bundle to be on time
+ long currentTime = System.currentTimeMillis();
+ if (currentTime < bundle.mUptimeMillis) {
+ try {
+ sleep(bundle.mUptimeMillis - currentTime);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ // ready to do the work, acquire the scene.
+ SceneResult result = mScene.acquire(250);
+ if (result != SceneResult.SUCCESS) {
+ mListener.done(result);
+ return;
+ }
+
+ // process the bundle. If the animation is not finished, this will enqueue
+ // the next message, so mQueue will have another one.
+ try {
+ bundle.mTarget.handleMessage(bundle.mMessage);
+ if (mScene.render() == SceneResult.SUCCESS) {
+ mListener.onNewFrame(mScene.getImage());
+ }
+ } finally {
+ mScene.release();
+ }
+ } while (mQueue.size() > 0);
+
+ mListener.done(SceneResult.SUCCESS);
+ } finally {
+ Handler_Delegate.setCallback(null);
+ mScene.cleanupThread();
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java
index f7d249e..0859976 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java
@@ -16,6 +16,10 @@
package com.android.layoutlib.bridge.impl;
+import static com.android.layoutlib.api.SceneResult.SceneStatus.ERROR_LOCK_INTERRUPTED;
+import static com.android.layoutlib.api.SceneResult.SceneStatus.ERROR_TIMEOUT;
+import static com.android.layoutlib.api.SceneResult.SceneStatus.SUCCESS;
+
import com.android.internal.util.XmlUtils;
import com.android.layoutlib.api.IProjectCallback;
import com.android.layoutlib.api.IResourceValue;
@@ -25,7 +29,9 @@
import com.android.layoutlib.api.SceneResult;
import com.android.layoutlib.api.ViewInfo;
import com.android.layoutlib.api.IDensityBasedResourceValue.Density;
+import com.android.layoutlib.api.LayoutScene.IAnimationListener;
import com.android.layoutlib.api.SceneParams.RenderingMode;
+import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.BridgeConstants;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeInflater;
@@ -33,6 +39,8 @@
import com.android.layoutlib.bridge.android.BridgeWindowSession;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import android.animation.AnimatorInflater;
+import android.animation.ObjectAnimator;
import android.app.Fragment_Delegate;
import android.graphics.Bitmap;
import android.graphics.Bitmap_Delegate;
@@ -59,6 +67,8 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
/**
* Class managing a layout "scene".
@@ -73,6 +83,12 @@
private static final int DEFAULT_TITLE_BAR_HEIGHT = 25;
private static final int DEFAULT_STATUS_BAR_HEIGHT = 25;
+ /**
+ * The current context being rendered. This is set through {@link #acquire(long)} and
+ * {@link #init(long)}, and unset in {@link #release()}.
+ */
+ private static BridgeContext sCurrentContext = null;
+
private final SceneParams mParams;
// scene state
@@ -98,22 +114,35 @@
/**
* Creates a layout scene with all the information coming from the layout bridge API.
- *
- * This also calls {@link LayoutSceneImpl#prepare()}.
* <p>
- * <b>THIS MUST BE INSIDE A SYNCHRONIZED BLOCK on the BRIDGE OBJECT.<b>
+ * This <b>must</b> be followed by a call to {@link LayoutSceneImpl#init()}, which act as a
+ * call to {@link LayoutSceneImpl#acquire(long)}
*
* @see LayoutBridge#createScene(com.android.layoutlib.api.SceneParams)
*/
public LayoutSceneImpl(SceneParams params) {
- // we need to make sure the Looper has been initialized for this thread.
- // this is required for View that creates Handler objects.
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
-
// copy the params.
mParams = new SceneParams(params);
+ }
+
+ /**
+ * Initializes and acquires the scene, creating various Android objects such as context,
+ * inflater, and parser.
+ *
+ * @param timeout the time to wait if another rendering is happening.
+ *
+ * @return whether the scene was prepared
+ *
+ * @see #acquire(long)
+ * @see #release()
+ */
+ public SceneResult init(long timeout) {
+ // acquire the lock. if the result is null, lock was just acquired, otherwise, return
+ // the result.
+ SceneResult result = acquireLock(timeout);
+ if (result != null) {
+ return result;
+ }
// setup the display Metrics.
DisplayMetrics metrics = new DisplayMetrics();
@@ -138,6 +167,9 @@
mParams.getProjectResources(), mParams.getFrameworkResources(),
styleParentMap, mParams.getProjectCallback(), mParams.getLogger());
+ // set the current rendering context
+ sCurrentContext = mContext;
+
// make sure the Resources object references the context (and other objects) for this
// scene
mContext.initResources();
@@ -149,7 +181,8 @@
mWindowBackground = mContext.findItemInStyle(mCurrentTheme, "windowBackground");
mWindowBackground = mContext.resolveResValue(mWindowBackground);
- mScreenOffset = getScreenOffset(mParams.getFrameworkResources(), mCurrentTheme, mContext);
+ mScreenOffset = getScreenOffset(mParams.getFrameworkResources(), mCurrentTheme,
+ mContext);
}
// build the inflater and parser.
@@ -159,44 +192,144 @@
mBlockParser = new BridgeXmlBlockParser(mParams.getLayoutDescription(),
mContext, false /* platformResourceFlag */);
+
+ return SceneResult.SUCCESS;
}
/**
- * Prepares the scene for action.
- * <p>
- * <b>THIS MUST BE INSIDE A SYNCHRONIZED BLOCK on the BRIDGE OBJECT.<b>
+ * Prepares the current thread for rendering.
+ *
+ * Note that while this can be called several time, the first call to {@link #cleanupThread()}
+ * will do the clean-up, and make the thread unable to do further scene actions.
*/
- public void prepare() {
+ public void prepareThread() {
// we need to make sure the Looper has been initialized for this thread.
// this is required for View that creates Handler objects.
if (Looper.myLooper() == null) {
Looper.prepare();
}
+ }
+
+ /**
+ * Cleans up thread-specific data. After this, the thread cannot be used for scene actions.
+ * <p>
+ * Note that it doesn't matter how many times {@link #prepareThread()} was called, a single
+ * call to this will prevent the thread from doing further scene actions
+ */
+ public void cleanupThread() {
+ // clean up the looper
+ Looper.sThreadLocal.remove();
+ }
+
+ /**
+ * Prepares the scene for action.
+ * <p>
+ * This call is blocking if another rendering/inflating is currently happening, and will return
+ * whether the preparation worked.
+ *
+ * The preparation can fail if another rendering took too long and the timeout was elapsed.
+ *
+ * More than one call to this from the same thread will have no effect and will return
+ * {@link SceneResult#SUCCESS}.
+ *
+ * After scene actions have taken place, only one call to {@link #release()} must be
+ * done.
+ *
+ * @param timeout the time to wait if another rendering is happening.
+ *
+ * @return whether the scene was prepared
+ *
+ * @see #release()
+ *
+ * @throws IllegalStateException if {@link #init(long)} was never called.
+ */
+ public SceneResult acquire(long timeout) {
+ if (mContext == null) {
+ throw new IllegalStateException("After scene creation, #init() must be called");
+ }
+
+ // acquire the lock. if the result is null, lock was just acquired, otherwise, return
+ // the result.
+ SceneResult result = acquireLock(timeout);
+ if (result != null) {
+ return result;
+ }
// make sure the Resources object references the context (and other objects) for this
// scene
mContext.initResources();
+ sCurrentContext = mContext;
+
+ return SUCCESS.getResult();
+ }
+
+ /**
+ * Acquire the lock so that the scene can be acted upon.
+ * <p>
+ * This returns null if the lock was just acquired, otherwise it returns
+ * {@link SceneResult#SUCCESS} if the lock already belonged to that thread, or another
+ * instance (see {@link SceneResult#getStatus()}) if an error occurred.
+ *
+ * @param timeout the time to wait if another rendering is happening.
+ * @return null if the lock was just acquire or another result depending on the state.
+ *
+ * @throws IllegalStateException if the current context is different than the one owned by
+ * the scene.
+ */
+ private SceneResult acquireLock(long timeout) {
+ ReentrantLock lock = Bridge.getLock();
+ if (lock.isHeldByCurrentThread() == false) {
+ try {
+ boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
+
+ if (acquired == false) {
+ return ERROR_TIMEOUT.getResult();
+ }
+ } catch (InterruptedException e) {
+ return ERROR_LOCK_INTERRUPTED.getResult();
+ }
+ } else {
+ // This thread holds the lock already. Checks that this wasn't for a different context.
+ // If this is called by init, mContext will be null and so should sCurrentContext
+ // anyway
+ if (mContext != sCurrentContext) {
+ throw new IllegalStateException("Acquiring different scenes from same thread without releases");
+ }
+ return SUCCESS.getResult();
+ }
+
+ return null;
}
/**
* Cleans up the scene after an action.
- * <p>
- * <b>THIS MUST BE INSIDE A SYNCHRONIZED BLOCK on the BRIDGE OBJECT.<b>
*/
- public void cleanup() {
- // clean up the looper
- Looper.sThreadLocal.remove();
+ public void release() {
+ ReentrantLock lock = Bridge.getLock();
- // Make sure to remove static references, otherwise we could not unload the lib
- mContext.disposeResources();
+ // with the use of finally blocks, it is possible to find ourself calling this
+ // without a successful call to prepareScene. This test makes sure that unlock() will
+ // not throw IllegalMonitorStateException.
+ if (lock.isHeldByCurrentThread()) {
+ // Make sure to remove static references, otherwise we could not unload the lib
+ mContext.disposeResources();
+ sCurrentContext = null;
+
+ lock.unlock();
+ }
}
/**
* Inflates the layout.
* <p>
- * <b>THIS MUST BE INSIDE A SYNCHRONIZED BLOCK on the BRIDGE OBJECT.<b>
+ * {@link #acquire(long)} must have been called before this.
+ *
+ * @throws IllegalStateException if the current context is different than the one owned by
+ * the scene, or if {@link #init(long)} was not called.
*/
public SceneResult inflate() {
+ checkLock();
+
try {
mViewRoot = new FrameLayout(mContext);
@@ -247,10 +380,16 @@
/**
* Renders the scene.
* <p>
- * <b>THIS MUST BE INSIDE A SYNCHRONIZED BLOCK on the BRIDGE OBJECT.<b>
+ * {@link #acquire(long)} must have been called before this.
+ *
+ * @throws IllegalStateException if the current context is different than the one owned by
+ * the scene, or if {@link #acquire(long)} was not called.
*/
public SceneResult render() {
+ checkLock();
+
try {
+ long current = System.currentTimeMillis();
if (mViewRoot == null) {
return new SceneResult("Layout has not been inflated!");
}
@@ -329,6 +468,8 @@
mViewInfo = visit(((ViewGroup)mViewRoot).getChildAt(0), mContext);
+ System.out.println("rendering (ms): " + (System.currentTimeMillis() - current));
+
// success!
return SceneResult.SUCCESS;
} catch (Throwable e) {
@@ -346,6 +487,71 @@
}
/**
+ * Animate an object
+ * <p>
+ * {@link #acquire(long)} must have been called before this.
+ *
+ * @throws IllegalStateException if the current context is different than the one owned by
+ * the scene, or if {@link #acquire(long)} was not called.
+ */
+ public SceneResult animate(Object targetObject, String animationName,
+ boolean isFrameworkAnimation, IAnimationListener listener) {
+ checkLock();
+
+ // find the animation file.
+ IResourceValue animationResource = null;
+ int animationId = 0;
+ if (isFrameworkAnimation) {
+ animationResource = mContext.getFrameworkResource("anim", animationName);
+ if (animationResource != null) {
+ animationId = Bridge.getResourceValue("anim", animationName);
+ }
+ } else {
+ animationResource = mContext.getProjectResource("anim", animationName);
+ if (animationResource != null) {
+ animationId = mContext.getProjectCallback().getResourceValue("anim", animationName);
+ }
+ }
+
+ if (animationResource != null) {
+ try {
+ ObjectAnimator anim = (ObjectAnimator) AnimatorInflater.loadAnimator(
+ mContext, animationId);
+ if (anim != null) {
+ anim.setTarget(targetObject);
+
+ new AnimationThread(this, anim, listener).start();
+
+ return SceneResult.SUCCESS;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ return new SceneResult("", e);
+ }
+ }
+
+ return new SceneResult("Failed to find animation");
+ }
+
+ /**
+ * Checks that the lock is owned by the current thread and that the current context is the one
+ * from this scene.
+ *
+ * @throws IllegalStateException if the current context is different than the one owned by
+ * the scene, or if {@link #acquire(long)} was not called.
+ */
+ private void checkLock() {
+ ReentrantLock lock = Bridge.getLock();
+ if (lock.isHeldByCurrentThread() == false) {
+ throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
+ }
+ if (sCurrentContext != mContext) {
+ throw new IllegalStateException("Thread acquired a scene but is rendering a different one");
+ }
+ }
+
+
+ /**
* Compute style information from the given list of style for the project and framework.
* @param themeName the name of the current theme. In order to differentiate project and
* platform themes sharing the same name, all project themes must be prepended with
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index bb2e6b3..4440685 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -95,6 +95,7 @@
*/
private final static String[] DELEGATE_METHODS = new String[] {
"android.app.Fragment#instantiate", //(Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)Landroid/app/Fragment;",
+ "android.os.Handler#sendMessageAtTime",
"android.view.View#isInEditMode",
// TODO: comment out once DelegateClass is working
// "android.content.res.Resources$Theme#obtainStyledAttributes",
@@ -118,6 +119,7 @@
"android.graphics.SweepGradient",
"android.graphics.Typeface",
"android.graphics.Xfermode",
+ "android.os.SystemClock",
"android.util.FloatMath",
};