diff options
26 files changed, 631 insertions, 223 deletions
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java index 6baf1c2..e403ac2 100644 --- a/core/java/android/content/res/CompatibilityInfo.java +++ b/core/java/android/content/res/CompatibilityInfo.java @@ -141,10 +141,16 @@ public class CompatibilityInfo { appFlags = appInfo.flags; if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) { - mCompatibilityFlags |= LARGE_SCREENS | CONFIGURED_LARGE_SCREENS; + // Saying you support large screens also implies you support xlarge + // screens; there is no compatibility mode for a large app on an + // xlarge screen. + mCompatibilityFlags |= LARGE_SCREENS | CONFIGURED_LARGE_SCREENS + | XLARGE_SCREENS | CONFIGURED_XLARGE_SCREENS + | EXPANDABLE | CONFIGURED_EXPANDABLE; } if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) { - mCompatibilityFlags |= XLARGE_SCREENS | CONFIGURED_XLARGE_SCREENS; + mCompatibilityFlags |= XLARGE_SCREENS | CONFIGURED_XLARGE_SCREENS + | EXPANDABLE | CONFIGURED_EXPANDABLE; } if ((appInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) { mCompatibilityFlags |= EXPANDABLE | CONFIGURED_EXPANDABLE; diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java index 0492fce..d143243 100644 --- a/core/java/android/net/TrafficStats.java +++ b/core/java/android/net/TrafficStats.java @@ -219,7 +219,7 @@ public class TrafficStats { public static native long getUidTcpTxSegments(int uid); /** - * Get the number of TCP payload bytes received for this UID. + * Get the number of TCP segments received for this UID. * Does not include TCP control packets (SYN/ACKs/FIN/..). * The statistics are across all interfaces. * diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java index ff9fe1c..1fea65a 100644 --- a/core/java/android/webkit/CookieManager.java +++ b/core/java/android/webkit/CookieManager.java @@ -294,6 +294,8 @@ public final class CookieManager { */ public void setCookie(String url, String value) { if (JniUtil.useChromiumHttpStack()) { + if (url.indexOf("://") == -1) + url = "http://" + url; nativeSetCookie(url, value); return; } @@ -425,6 +427,8 @@ public final class CookieManager { */ public String getCookie(String url) { if (JniUtil.useChromiumHttpStack()) { + if (url.indexOf("://") == -1) + url = "http://" + url; return nativeGetCookie(url); } diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index 4a34b45..cd3862f 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -229,36 +229,34 @@ public class DatePicker extends FrameLayout { } else { setSpinnersShown(spinnersShown); setCalendarViewShown(calendarViewShown); + } - // set the min date giving priority of the minDate over startYear - mTempDate.clear(); - if (!TextUtils.isEmpty(minDate)) { - if (!parseDate(minDate, mTempDate)) { - mTempDate.set(startYear, 0, 1); - } - } else { + // set the min date giving priority of the minDate over startYear + mTempDate.clear(); + if (!TextUtils.isEmpty(minDate)) { + if (!parseDate(minDate, mTempDate)) { mTempDate.set(startYear, 0, 1); } - mMinDate.clear(); - setMinDate(mTempDate.getTimeInMillis()); - - // set the max date giving priority of the minDate over startYear - mTempDate.clear(); - if (!TextUtils.isEmpty(maxDate)) { - if (!parseDate(maxDate, mTempDate)) { - mTempDate.set(endYear, 11, 31); - } - } else { + } else { + mTempDate.set(startYear, 0, 1); + } + setMinDate(mTempDate.getTimeInMillis()); + + // set the max date giving priority of the maxDate over endYear + mTempDate.clear(); + if (!TextUtils.isEmpty(maxDate)) { + if (!parseDate(maxDate, mTempDate)) { mTempDate.set(endYear, 11, 31); } - mMaxDate.clear(); - setMaxDate(mTempDate.getTimeInMillis()); - - // initialize to current date - mCurrentDate.setTimeInMillis(System.currentTimeMillis()); - init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate - .get(Calendar.DAY_OF_MONTH), null); + } else { + mTempDate.set(endYear, 11, 31); } + setMaxDate(mTempDate.getTimeInMillis()); + + // initialize to current date + mCurrentDate.setTimeInMillis(System.currentTimeMillis()); + init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate + .get(Calendar.DAY_OF_MONTH), null); // re-order the number spinners to match the current date format reorderSpinners(); diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java index 1a4ff29..22f6f4e 100644 --- a/core/java/android/widget/TabWidget.java +++ b/core/java/android/widget/TabWidget.java @@ -66,6 +66,10 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { private final Rect mBounds = new Rect(); + // When positive, the widths and heights of tabs will be imposed so that they fit in parent + private int mImposedTabsHeight = -1; + private int[] mImposedTabWidths; + public TabWidget(Context context) { this(context, null); } @@ -150,52 +154,62 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { setOnFocusChangeListener(this); } - /** - * {@inheritDoc} - */ + @Override + void measureChildBeforeLayout(View child, int childIndex, + int widthMeasureSpec, int totalWidth, + int heightMeasureSpec, int totalHeight) { + + if (mImposedTabsHeight >= 0) { + widthMeasureSpec = MeasureSpec.makeMeasureSpec( + totalWidth + mImposedTabWidths[childIndex], MeasureSpec.EXACTLY); + heightMeasureSpec = MeasureSpec.makeMeasureSpec(mImposedTabsHeight, + MeasureSpec.EXACTLY); + } + + super.measureChildBeforeLayout(child, childIndex, + widthMeasureSpec, totalWidth, heightMeasureSpec, totalHeight); + } + @Override void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) { - // First measure with no constraint + // First, measure with no constraint final int unspecifiedWidth = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); super.measureHorizontal(unspecifiedWidth, heightMeasureSpec); + mImposedTabsHeight = -1; - final int count = getChildCount(); - int totalWidth = 0; - int totalCount = 0; - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - if (child.getVisibility() == GONE) { - continue; - } - final int childWidth = child.getMeasuredWidth(); - totalWidth += childWidth; - totalCount++; - } + int extraWidth = getMeasuredWidth() - MeasureSpec.getSize(widthMeasureSpec); + if (extraWidth > 0) { + final int count = getChildCount(); - final int width = MeasureSpec.getSize(widthMeasureSpec); - if (totalWidth > width && totalCount > 0) { - int extraWidth = totalWidth - width; + int childCount = 0; for (int i = 0; i < count; i++) { final View child = getChildAt(i); - if (child.getVisibility() == GONE) { - continue; - } - final int childWidth = child.getMeasuredWidth(); - final int delta = extraWidth / totalCount; - final int tabWidth = Math.max(0, childWidth - delta); - - final int tabWidthMeasureSpec = MeasureSpec.makeMeasureSpec( - tabWidth, MeasureSpec.EXACTLY); - final int tabHeightMeasureSpec = MeasureSpec.makeMeasureSpec( - child.getMeasuredHeight(), MeasureSpec.EXACTLY); - - child.measure(tabWidthMeasureSpec, tabHeightMeasureSpec); + if (child.getVisibility() == GONE) continue; + childCount++; + } - // Make sure the extra width is evenly distributed, avoiding int division remainder - extraWidth -= delta; - totalCount--; + if (childCount > 0) { + if (mImposedTabWidths == null || mImposedTabWidths.length != count) { + mImposedTabWidths = new int[count]; + } + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() == GONE) continue; + final int childWidth = child.getMeasuredWidth(); + final int delta = extraWidth / childCount; + final int newWidth = Math.max(0, childWidth - delta); + mImposedTabWidths[i] = newWidth; + // Make sure the extra width is evenly distributed, no int division remainder + extraWidth -= childWidth - newWidth; // delta may have been clamped + childCount--; + mImposedTabsHeight = Math.max(mImposedTabsHeight, child.getMeasuredHeight()); + } } - setMeasuredDimension(width, getMeasuredHeight()); + } + + // Measure again, this time with imposed tab widths and respecting initial spec request + if (mImposedTabsHeight >= 0 || unspecifiedWidth != widthMeasureSpec) { + super.measureHorizontal(widthMeasureSpec, heightMeasureSpec); } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index e7c33ab..8cb725a 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -5779,11 +5779,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); int unpaddedWidth = want; - int hintWant = want; - if (mHorizontallyScrolling) - want = VERY_WIDE; + if (mHorizontallyScrolling) want = VERY_WIDE; + int hintWant = want; int hintWidth = mHintLayout == null ? hintWant : mHintLayout.getWidth(); if (mLayout == null) { @@ -7365,7 +7364,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener stopSelectionActionMode(); boolean selectAllGotFocus = mSelectAllOnFocus && mTouchFocusSelected; - if (hasInsertionController() && !selectAllGotFocus) { + if (hasInsertionController() && !selectAllGotFocus && mText.length() > 0) { getInsertionController().show(); } } @@ -8888,6 +8887,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mLastParentX = coords[0]; mLastParentY = coords[1]; mIsDragging = true; + if (mIsInsertionHandle) { + mTouchTimer = SystemClock.uptimeMillis(); + } break; } @@ -9036,6 +9038,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (offset != previousOffset) { updateOffset(handle, offset); + removePastePopupCallback(); } hideDelayed(); } diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java index 4b37beb..18d1825 100644 --- a/core/java/android/widget/TimePicker.java +++ b/core/java/android/widget/TimePicker.java @@ -132,12 +132,8 @@ public class TimePicker extends FrameLayout { mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { if (!is24HourView()) { - int minValue = mHourSpinner.getMinValue(); - int maxValue = mHourSpinner.getMaxValue(); - // toggle AM/PM if the spinner has wrapped and not in 24 - // format - if ((oldVal == maxValue && newVal == minValue) - || (oldVal == minValue && newVal == maxValue)) { + if ((oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) + || (oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) { mIsAm = !mIsAm; updateAmPmControl(); } @@ -163,21 +159,19 @@ public class TimePicker extends FrameLayout { int minValue = mMinuteSpinner.getMinValue(); int maxValue = mMinuteSpinner.getMaxValue(); if (oldVal == maxValue && newVal == minValue) { - int currentHour = mHourSpinner.getValue(); - // toggle AM/PM if the spinner is about to wrap - if (!is24HourView() && currentHour == mHourSpinner.getMaxValue()) { + int newHour = mHourSpinner.getValue() + 1; + if (!is24HourView() && newHour == HOURS_IN_HALF_DAY) { mIsAm = !mIsAm; updateAmPmControl(); } - mHourSpinner.setValue(currentHour + 1); + mHourSpinner.setValue(newHour); } else if (oldVal == minValue && newVal == maxValue) { - int currentHour = mHourSpinner.getValue(); - // toggle AM/PM if the spinner is about to wrap - if (!is24HourView() && currentHour == mHourSpinner.getMinValue()) { + int newHour = mHourSpinner.getValue() - 1; + if (!is24HourView() && newHour == HOURS_IN_HALF_DAY - 1) { mIsAm = !mIsAm; updateAmPmControl(); } - mHourSpinner.setValue(currentHour - 1); + mHourSpinner.setValue(newHour); } onTimeChanged(); } @@ -330,10 +324,12 @@ public class TimePicker extends FrameLayout { */ public Integer getCurrentHour() { int currentHour = mHourSpinner.getValue(); - if (is24HourView() || mIsAm) { + if (is24HourView()) { return currentHour; + } else if (mIsAm) { + return currentHour % HOURS_IN_HALF_DAY; } else { - return (currentHour == HOURS_IN_HALF_DAY) ? 0 : currentHour + HOURS_IN_HALF_DAY; + return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY; } } @@ -347,14 +343,16 @@ public class TimePicker extends FrameLayout { } if (!is24HourView()) { // convert [0,23] ordinal to wall clock display - if (currentHour > HOURS_IN_HALF_DAY) { - currentHour -= HOURS_IN_HALF_DAY; + if (currentHour >= HOURS_IN_HALF_DAY) { mIsAm = false; + if (currentHour > HOURS_IN_HALF_DAY) { + currentHour = currentHour - HOURS_IN_HALF_DAY; + } } else { + mIsAm = true; if (currentHour == 0) { currentHour = HOURS_IN_HALF_DAY; } - mIsAm = true; } updateAmPmControl(); } diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java index b1b5d71..471a5a9 100644 --- a/core/java/com/android/internal/app/ActionBarImpl.java +++ b/core/java/com/android/internal/app/ActionBarImpl.java @@ -480,12 +480,12 @@ public class ActionBarImpl extends ActionBar { @Override public void show() { - if (mContainerView.getVisibility() == View.VISIBLE) { - return; - } if (mCurrentAnim != null) { mCurrentAnim.end(); } + if (mContainerView.getVisibility() == View.VISIBLE) { + return; + } mContainerView.setVisibility(View.VISIBLE); mContainerView.setAlpha(0); AnimatorSet anim = new AnimatorSet(); diff --git a/core/res/res/raw/fallbackring.ogg b/core/res/res/raw/fallbackring.ogg Binary files differindex 0cbf55d..a9adeb8 100644..100755 --- a/core/res/res/raw/fallbackring.ogg +++ b/core/res/res/raw/fallbackring.ogg diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 1c9b587..8802003 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3146,11 +3146,6 @@ <attr name="layout" /> </declare-styleable> - <declare-styleable name="DatePicker"> - <!-- @hide The layout of the time picker. --> - <attr name="layout" /> - </declare-styleable> - <!-- ========================= --> <!-- Drawable class attributes --> <!-- ========================= --> diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java index 4c659d4..1fc2722 100644 --- a/graphics/java/android/graphics/SurfaceTexture.java +++ b/graphics/java/android/graphics/SurfaceTexture.java @@ -32,6 +32,17 @@ import android.os.Message; * updated to contain the most recent image from the image stream. This may cause some frames of * the stream to be skipped. * + * <p>When sampling from the texture one should first transform the texture coordinates using the + * matrix queried via {@link #getTransformMatrix}. The transform matrix may change each time {@link + * #updateTexImage} is called, so it should be re-queried each time the texture image is updated. + * This matrix transforms traditional 2D OpenGL ES texture coordinate column vectors of the form (s, + * t, 0, 1) where s and t are on the inclusive interval [0, 1] to the proper sampling location in + * the streamed texture. This transform compensates for any properties of the image stream source + * that cause it to appear different from a traditional OpenGL ES texture. For example, sampling + * from the bottom left corner of the image can be accomplished by transforming the column vector + * (0, 0, 0, 1) using the queried matrix, while sampling from the top right corner of the image can + * be done by transforming (1, 1, 0, 1). + * * <p>The texture object uses the GL_TEXTURE_EXTERNAL_OES texture target, which is defined by the * OES_EGL_image_external OpenGL ES extension. This limits how the texture may be used. * diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp index 1dadd53..236ff4f 100644 --- a/libs/gui/SurfaceTexture.cpp +++ b/libs/gui/SurfaceTexture.cpp @@ -219,11 +219,19 @@ status_t SurfaceTexture::updateTexImage() { mSlots[mLastQueued].mEglImage = image; mSlots[mLastQueued].mEglDisplay = dpy; } + + GLint error; + while ((error = glGetError()) != GL_NO_ERROR) { + LOGE("GL error cleared before updating SurfaceTexture: %#04x", error); + } glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, (GLeglImageOES)image); - GLint error = glGetError(); - if (error != GL_NO_ERROR) { + bool failed = false; + while ((error = glGetError()) != GL_NO_ERROR) { LOGE("error binding external texture image %p (slot %d): %#04x", image, mLastQueued, error); + failed = true; + } + if (failed) { return -EINVAL; } diff --git a/libs/rs/rsScriptC.cpp b/libs/rs/rsScriptC.cpp index fc673a2..445a4e4 100644 --- a/libs/rs/rsScriptC.cpp +++ b/libs/rs/rsScriptC.cpp @@ -485,12 +485,7 @@ bool ScriptCState::runCompiler(Context *rsc, } #if 1 - if (bccLinkBC(s->mBccScript, - resName, - NULL /*rs_runtime_lib_bc*/, - 1 /*rs_runtime_lib_bc_size*/ - /*"1" means skip buffer here, and let libbcc decide*/, - 0) != 0) { + if (bccLinkFile(s->mBccScript, "/system/lib/libclcore.bc", 0) != 0) { LOGE("bcc: FAILS to link bitcode"); return false; } diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp index c6c36e3..0bed3ca 100644 --- a/media/libstagefright/httplive/LiveSession.cpp +++ b/media/libstagefright/httplive/LiveSession.cpp @@ -51,6 +51,7 @@ LiveSession::LiveSession() mNumRetries(0), mDurationUs(-1), mSeekDone(false), + mDisconnectPending(false), mMonitorQueueGeneration(0) { } @@ -68,6 +69,11 @@ void LiveSession::connect(const char *url) { } void LiveSession::disconnect() { + Mutex::Autolock autoLock(mLock); + mDisconnectPending = true; + + mHTTPDataSource->disconnect(); + (new AMessage(kWhatDisconnect, id()))->post(); } @@ -138,7 +144,13 @@ void LiveSession::onConnect(const sp<AMessage> &msg) { mMasterURL = url; sp<M3UParser> playlist = fetchPlaylist(url.c_str()); - CHECK(playlist != NULL); + + if (playlist == NULL) { + LOGE("unable to fetch master playlist '%s'.", url.c_str()); + + mDataSource->queueEOS(ERROR_IO); + return; + } if (playlist->isVariantPlaylist()) { for (size_t i = 0; i < playlist->size(); ++i) { @@ -177,6 +189,9 @@ void LiveSession::onDisconnect() { LOGI("onDisconnect"); mDataSource->queueEOS(ERROR_END_OF_STREAM); + + Mutex::Autolock autoLock(mLock); + mDisconnectPending = false; } status_t LiveSession::fetchFile(const char *url, sp<ABuffer> *out) { @@ -189,6 +204,14 @@ status_t LiveSession::fetchFile(const char *url, sp<ABuffer> *out) { } else if (strncasecmp(url, "http://", 7)) { return ERROR_UNSUPPORTED; } else { + { + Mutex::Autolock autoLock(mLock); + + if (mDisconnectPending) { + return ERROR_IO; + } + } + status_t err = mHTTPDataSource->connect(url); if (err != OK) { diff --git a/media/libstagefright/include/LiveSession.h b/media/libstagefright/include/LiveSession.h index 41f5ad0..f1188c4 100644 --- a/media/libstagefright/include/LiveSession.h +++ b/media/libstagefright/include/LiveSession.h @@ -87,6 +87,7 @@ private: Condition mCondition; int64_t mDurationUs; bool mSeekDone; + bool mDisconnectPending; int32_t mMonitorQueueGeneration; diff --git a/opengl/libagl/egl.cpp b/opengl/libagl/egl.cpp index 7ac6f92..a1cb23a 100644 --- a/opengl/libagl/egl.cpp +++ b/opengl/libagl/egl.cpp @@ -82,6 +82,11 @@ static GLint getError() { if (ggl_unlikely(gEGLErrorKey == -1)) return EGL_SUCCESS; GLint error = (GLint)pthread_getspecific(gEGLErrorKey); + if (error == 0) { + // The TLS key has been created by another thread, but the value for + // this thread has not been initialized. + return EGL_SUCCESS; + } pthread_setspecific(gEGLErrorKey, (void*)EGL_SUCCESS); return error; } diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp index 8977fbf..3dc8c03 100644 --- a/opengl/libs/EGL/egl.cpp +++ b/opengl/libs/EGL/egl.cpp @@ -389,10 +389,9 @@ static tls_t* getTLS() } static inline void clearError() { - if (gEGLThreadLocalStorageKey != -1) { - tls_t* tls = getTLS(); - tls->error = EGL_SUCCESS; - } + // This must clear the error from all the underlying EGL implementations as + // well as the EGL wrapper layer. + eglGetError(); } template<typename T> diff --git a/packages/SystemUI/res/layout-xlarge/status_bar_notification_panel_title.xml b/packages/SystemUI/res/layout-xlarge/status_bar_notification_panel_title.xml index 2d34635..15b2b70 100644 --- a/packages/SystemUI/res/layout-xlarge/status_bar_notification_panel_title.xml +++ b/packages/SystemUI/res/layout-xlarge/status_bar_notification_panel_title.xml @@ -98,7 +98,7 @@ android:layout_alignBaseline="@id/battery" android:paddingRight="16dp" android:visibility="invisible" - android:src="@drawable/status_bar_veto" + android:src="@drawable/ic_notification_open" android:baseline="21dp" /> diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 586d222..6636fb7 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -60,6 +60,7 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.SystemProperties; import android.provider.Settings; import android.provider.Settings.Secure; import android.provider.Settings.SettingNotFoundException; @@ -314,6 +315,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT; int mImeWindowVis; + long mOldSystemSettingsVersion; AlertDialog.Builder mDialogBuilder; AlertDialog mSwitchingDialog; @@ -486,6 +488,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub handleMessage(msg); } }); + // Initialize the system settings version to undefined. + mOldSystemSettingsVersion = -1; (new MyPackageMonitor()).register(mContext, true); @@ -1007,7 +1011,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + // TODO: Investigate and fix why are settings changes getting processed before the settings seq + // number is updated? + // TODO: Change this stuff to not rely on modifying settings for normal user interactions. void updateFromSettingsLocked() { + long newSystemSettingsVersion = getSystemSettingsVersion(); + // This is a workaround to avoid a situation that old cached value in Settings.Secure + // will be handled. + if (newSystemSettingsVersion == mOldSystemSettingsVersion) return; + // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and // ENABLED_INPUT_METHODS is taking care of keeping them correctly in // sync, so we will never have a DEFAULT_INPUT_METHOD that is not @@ -1958,6 +1970,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId, boolean setSubtypeOnly) { + mOldSystemSettingsVersion = getSystemSettingsVersion(); // Update the history of InputMethod and Subtype saveCurrentInputMethodAndSubtypeToHistory(); @@ -2207,6 +2220,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + private static long getSystemSettingsVersion() { + return SystemProperties.getLong(Settings.Secure.SYS_PROP_SETTING_VERSION, 0); + } + /** * @return Return the current subtype of this input method. */ diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java index c7968a4..8d7f016 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java @@ -27,6 +27,8 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; +import java.util.ArrayList; + /** * This method adapter rewrites a method by discarding the original code and generating * a call to a delegate. Original annotations are passed along unchanged. @@ -124,7 +126,7 @@ class DelegateMethodAdapter implements MethodVisitor { public void generateCode() { /* * The goal is to generate a call to a static delegate method. - * If this method is not-static, the first parameter will be this. + * If this method is non-static, the first parameter will be 'this'. * All the parameters must be passed and then the eventual return type returned. * * Example, let's say we have a method such as @@ -133,9 +135,19 @@ class DelegateMethodAdapter implements MethodVisitor { * We'll want to create a body that calls a delegate method like this: * TheClass_Delegate.method_1(this, a, b, c); * + * If the method is non-static and the class name is an inner class (e.g. has $ in its + * last segment), we want to push the 'this' of the outer class first: + * OuterClass_InnerClass_Delegate.method_1( + * OuterClass.this, + * OuterClass$InnerClass.this, + * a, b, c); + * + * Only one level of inner class is supported right now, for simplicity and because + * we don't need more. + * * The generated class name is the current class name with "_Delegate" appended to it. * One thing to realize is that we don't care about generics -- since generic types - * are erased at runtime, they have no influence on the method being called. + * are erased at runtime, they have no influence on the method name being called. */ // Add our annotation @@ -151,34 +163,61 @@ class DelegateMethodAdapter implements MethodVisitor { mVisitCodeCalled = true; } - int numVars = 0; + ArrayList<Type> paramTypes = new ArrayList<Type>(); + String delegateClassName = mClassName + DELEGATE_SUFFIX; + boolean pushedArg0 = false; + int maxStack = 0; - // Push "this" for an instance method, which is always ALOAD 0 + // For an instance method (e.g. non-static), push the 'this' preceded + // by the 'this' of any outer class, if any. if (!mIsStatic) { - mParentVisitor.visitVarInsn(Opcodes.ALOAD, numVars++); + // Check if the last segment of the class name has inner an class. + // Right now we only support one level of inner classes. + int slash = mClassName.lastIndexOf('/'); + int dol = mClassName.lastIndexOf('$'); + if (dol != -1 && dol > slash && dol == mClassName.indexOf('$')) { + String outerClass = mClassName.substring(0, dol); + Type outerType = Type.getObjectType(outerClass); + + // Change a delegate class name to "com/foo/Outer_Inner_Delegate" + delegateClassName = delegateClassName.replace('$', '_'); + + // The first-level inner class has a package-protected member called 'this$0' + // that points to the outer class. + + // Push this.getField("this$0") on the call stack. + mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0); // var 0 = this + mParentVisitor.visitFieldInsn(Opcodes.GETFIELD, + mClassName, // class where the field is defined + "this$0", // field name + outerType.getDescriptor()); // type of the field + maxStack++; + paramTypes.add(outerType); + } + + // Push "this" for the instance method, which is always ALOAD 0 + mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0); + maxStack++; + pushedArg0 = true; + paramTypes.add(Type.getObjectType(mClassName)); } - // Push all other arguments + // Push all other arguments. Start at arg 1 if we already pushed 'this' above. Type[] argTypes = Type.getArgumentTypes(mDesc); + int maxLocals = pushedArg0 ? 1 : 0; for (Type t : argTypes) { int size = t.getSize(); - mParentVisitor.visitVarInsn(t.getOpcode(Opcodes.ILOAD), numVars); - numVars += size; - } - - // Construct the descriptor of the delegate. For a static method, it's the same - // however for an instance method we need to pass the 'this' reference first - String desc = mDesc; - if (!mIsStatic) { - Type[] argTypes2 = new Type[argTypes.length + 1]; - - argTypes2[0] = Type.getObjectType(mClassName); - System.arraycopy(argTypes, 0, argTypes2, 1, argTypes.length); - - desc = Type.getMethodDescriptor(Type.getReturnType(mDesc), argTypes2); + mParentVisitor.visitVarInsn(t.getOpcode(Opcodes.ILOAD), maxLocals); + maxLocals += size; + maxStack += size; + paramTypes.add(t); } - String delegateClassName = mClassName + DELEGATE_SUFFIX; + // Construct the descriptor of the delegate based on the parameters + // we pushed on the call stack. The return type remains unchanged. + String desc = Type.getMethodDescriptor( + Type.getReturnType(mDesc), + paramTypes.toArray(new Type[paramTypes.size()])); // Invoke the static delegate mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, @@ -189,7 +228,7 @@ class DelegateMethodAdapter implements MethodVisitor { Type returnType = Type.getReturnType(mDesc); mParentVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN)); - mParentVisitor.visitMaxs(numVars, numVars); + mParentVisitor.visitMaxs(maxStack, maxLocals); mParentVisitor.visitEnd(); // For debugging now. Maybe we should collect these and store them in diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java index 7d80796..e8b3ea8 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java @@ -24,25 +24,38 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import com.android.tools.layoutlib.create.dataclass.ClassWithNative; +import com.android.tools.layoutlib.create.dataclass.OuterClass; +import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass; + import org.junit.Before; import org.junit.Test; import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; public class DelegateClassAdapterTest { private MockLog mLog; - private static final String CLASS_NAME = - DelegateClassAdapterTest.class.getCanonicalName() + "$" + - ClassWithNative.class.getSimpleName(); + private static final String NATIVE_CLASS_NAME = ClassWithNative.class.getCanonicalName(); + private static final String OUTER_CLASS_NAME = OuterClass.class.getCanonicalName(); + private static final String INNER_CLASS_NAME = OuterClass.class.getCanonicalName() + "$" + + InnerClass.class.getSimpleName(); @Before public void setUp() throws Exception { @@ -55,11 +68,11 @@ public class DelegateClassAdapterTest { */ @SuppressWarnings("unchecked") @Test - public void testNoOp() throws Exception { + public void testNoOp() throws Throwable { // create an instance of the class that will be modified // (load the class in a distinct class loader so that we can trash its definition later) ClassLoader cl1 = new ClassLoader(this.getClass().getClassLoader()) { }; - Class<ClassWithNative> clazz1 = (Class<ClassWithNative>) cl1.loadClass(CLASS_NAME); + Class<ClassWithNative> clazz1 = (Class<ClassWithNative>) cl1.loadClass(NATIVE_CLASS_NAME); ClassWithNative instance1 = clazz1.newInstance(); assertEquals(42, instance1.add(20, 22)); try { @@ -73,42 +86,47 @@ public class DelegateClassAdapterTest { ClassWriter cw = new ClassWriter(0 /*flags*/); HashSet<String> delegateMethods = new HashSet<String>(); - String internalClassName = CLASS_NAME.replace('.', '/'); + String internalClassName = NATIVE_CLASS_NAME.replace('.', '/'); DelegateClassAdapter cv = new DelegateClassAdapter( mLog, cw, internalClassName, delegateMethods); - ClassReader cr = new ClassReader(CLASS_NAME); + ClassReader cr = new ClassReader(NATIVE_CLASS_NAME); cr.accept(cv, 0 /* flags */); // Load the generated class in a different class loader and try it again - final byte[] bytes = cw.toByteArray(); - ClassLoader2 cl2 = new ClassLoader2(bytes) { - @Override - public void testModifiedInstance() throws Exception { - Class<?> clazz2 = loadClass(CLASS_NAME); - Object i2 = clazz2.newInstance(); - assertNotNull(i2); - assertEquals(42, callAdd(i2, 20, 22)); + ClassLoader2 cl2 = null; + try { + cl2 = new ClassLoader2() { + @Override + public void testModifiedInstance() throws Exception { + Class<?> clazz2 = loadClass(NATIVE_CLASS_NAME); + Object i2 = clazz2.newInstance(); + assertNotNull(i2); + assertEquals(42, callAdd(i2, 20, 22)); - try { - callCallNativeInstance(i2, 10, 3.1415, new Object[0]); - fail("Test should have failed to invoke callTheNativeMethod [2]"); - } catch (InvocationTargetException e) { - // This is expected to fail since the native method has NOT been - // overridden here. - assertEquals(UnsatisfiedLinkError.class, e.getCause().getClass()); + try { + callCallNativeInstance(i2, 10, 3.1415, new Object[0]); + fail("Test should have failed to invoke callTheNativeMethod [2]"); + } catch (InvocationTargetException e) { + // This is expected to fail since the native method has NOT been + // overridden here. + assertEquals(UnsatisfiedLinkError.class, e.getCause().getClass()); + } + + // Check that the native method does NOT have the new annotation + Method[] m = clazz2.getDeclaredMethods(); + assertEquals("native_instance", m[2].getName()); + assertTrue(Modifier.isNative(m[2].getModifiers())); + Annotation[] a = m[2].getAnnotations(); + assertEquals(0, a.length); } - - // Check that the native method does NOT have the new annotation - Method[] m = clazz2.getDeclaredMethods(); - assertEquals("native_instance", m[2].getName()); - assertTrue(Modifier.isNative(m[2].getModifiers())); - Annotation[] a = m[2].getAnnotations(); - assertEquals(0, a.length); - } - }; - cl2.testModifiedInstance(); + }; + cl2.add(NATIVE_CLASS_NAME, cw); + cl2.testModifiedInstance(); + } catch (Throwable t) { + throw dumpGeneratedClass(t, cl2); + } } /** @@ -122,38 +140,37 @@ public class DelegateClassAdapterTest { public void testConstructorsNotSupported() throws IOException { ClassWriter cw = new ClassWriter(0 /*flags*/); - String internalClassName = CLASS_NAME.replace('.', '/'); + String internalClassName = NATIVE_CLASS_NAME.replace('.', '/'); HashSet<String> delegateMethods = new HashSet<String>(); delegateMethods.add("<init>"); DelegateClassAdapter cv = new DelegateClassAdapter( mLog, cw, internalClassName, delegateMethods); - ClassReader cr = new ClassReader(CLASS_NAME); + ClassReader cr = new ClassReader(NATIVE_CLASS_NAME); cr.accept(cv, 0 /* flags */); } @Test - public void testDelegateNative() throws Exception { + public void testDelegateNative() throws Throwable { ClassWriter cw = new ClassWriter(0 /*flags*/); - String internalClassName = CLASS_NAME.replace('.', '/'); + String internalClassName = NATIVE_CLASS_NAME.replace('.', '/'); HashSet<String> delegateMethods = new HashSet<String>(); delegateMethods.add(DelegateClassAdapter.ALL_NATIVES); DelegateClassAdapter cv = new DelegateClassAdapter( mLog, cw, internalClassName, delegateMethods); - ClassReader cr = new ClassReader(CLASS_NAME); + ClassReader cr = new ClassReader(NATIVE_CLASS_NAME); cr.accept(cv, 0 /* flags */); // Load the generated class in a different class loader and try it - final byte[] bytes = cw.toByteArray(); - + ClassLoader2 cl2 = null; try { - ClassLoader2 cl2 = new ClassLoader2(bytes) { + cl2 = new ClassLoader2() { @Override public void testModifiedInstance() throws Exception { - Class<?> clazz2 = loadClass(CLASS_NAME); + Class<?> clazz2 = loadClass(NATIVE_CLASS_NAME); Object i2 = clazz2.newInstance(); assertNotNull(i2); @@ -173,48 +190,105 @@ public class DelegateClassAdapterTest { assertEquals("LayoutlibDelegate", a[0].annotationType().getSimpleName()); } }; + cl2.add(NATIVE_CLASS_NAME, cw); cl2.testModifiedInstance(); + } catch (Throwable t) { + throw dumpGeneratedClass(t, cl2); + } + } + + @Test + public void testDelegateInner() throws Throwable { + // We'll delegate the "get" method of both the inner and outer class. + HashSet<String> delegateMethods = new HashSet<String>(); + delegateMethods.add("get"); + + // Generate the delegate for the outer class. + ClassWriter cwOuter = new ClassWriter(0 /*flags*/); + String outerClassName = OUTER_CLASS_NAME.replace('.', '/'); + DelegateClassAdapter cvOuter = new DelegateClassAdapter( + mLog, cwOuter, outerClassName, delegateMethods); + ClassReader cr = new ClassReader(OUTER_CLASS_NAME); + cr.accept(cvOuter, 0 /* flags */); + + // Generate the delegate for the inner class. + ClassWriter cwInner = new ClassWriter(0 /*flags*/); + String innerClassName = INNER_CLASS_NAME.replace('.', '/'); + DelegateClassAdapter cvInner = new DelegateClassAdapter( + mLog, cwInner, innerClassName, delegateMethods); + cr = new ClassReader(INNER_CLASS_NAME); + cr.accept(cvInner, 0 /* flags */); + + // Load the generated classes in a different class loader and try them + ClassLoader2 cl2 = null; + try { + cl2 = new ClassLoader2() { + @Override + public void testModifiedInstance() throws Exception { + + // Check the outer class + Class<?> outerClazz2 = loadClass(OUTER_CLASS_NAME); + Object o2 = outerClazz2.newInstance(); + assertNotNull(o2); + + // The original Outer.get returns 1+10+20, + // but the delegate makes it return 4+10+20 + assertEquals(4+10+20, callGet(o2, 10, 20)); + + // Check the inner class. Since it's not a static inner class, we need + // to use the hidden constructor that takes the outer class as first parameter. + Class<?> innerClazz2 = loadClass(INNER_CLASS_NAME); + Constructor<?> innerCons = innerClazz2.getConstructor( + new Class<?>[] { outerClazz2 }); + Object i2 = innerCons.newInstance(new Object[] { o2 }); + assertNotNull(i2); - // This code block is useful for debugging. However to make it work you need to - // pull in the org.objectweb.asm.util.TraceClassVisitor class and associated - // utilities which are found in the ASM source jar. - // - // } catch (Throwable t) { - // For debugging, dump the bytecode of the class in case of unexpected error. - // StringWriter sw = new StringWriter(); - // PrintWriter pw = new PrintWriter(sw); - // TraceClassVisitor tcv = new TraceClassVisitor(pw); - // ClassReader cr2 = new ClassReader(bytes); - // cr2.accept(tcv, 0 /* flags */); - // String msg = "\n" + t.getClass().getCanonicalName(); - // if (t.getMessage() != null) { - // msg += ": " + t.getMessage(); - // } - // msg = msg + "\nBytecode dump:\n" + sw.toString(); - // // Re-throw exception with new message - // RuntimeException ex = new RuntimeException(msg, t); - // throw ex; - } finally { + // The original Inner.get returns 3+10+20, + // but the delegate makes it return 6+10+20 + assertEquals(6+10+20, callGet(i2, 10, 20)); + } + }; + cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray()); + cl2.add(INNER_CLASS_NAME, cwInner.toByteArray()); + cl2.testModifiedInstance(); + } catch (Throwable t) { + throw dumpGeneratedClass(t, cl2); } } //------- /** - * A class loader than can define and instantiate our dummy {@link ClassWithNative}. + * A class loader than can define and instantiate our modified classes. + * <p/> + * The trick here is that this class loader will test our <em>modified</em> version + * of the classes, the one with the delegate calls. * <p/> - * The trick here is that this class loader will test our modified version of ClassWithNative. * Trying to do so in the original class loader generates all sort of link issues because * there are 2 different definitions of the same class name. This class loader will * define and load the class when requested by name and provide helpers to access the * instance methods via reflection. */ private abstract class ClassLoader2 extends ClassLoader { - private final byte[] mClassWithNative; - public ClassLoader2(byte[] classWithNative) { + private final Map<String, byte[]> mClassDefs = new HashMap<String, byte[]>(); + + public ClassLoader2() { super(null); - mClassWithNative = classWithNative; + } + + public ClassLoader2 add(String className, byte[] definition) { + mClassDefs.put(className, definition); + return this; + } + + public ClassLoader2 add(String className, ClassWriter rewrittenClass) { + mClassDefs.put(className, rewrittenClass.toByteArray()); + return this; + } + + private Set<Entry<String, byte[]>> getByteCode() { + return mClassDefs.entrySet(); } @SuppressWarnings("unused") @@ -224,9 +298,10 @@ public class DelegateClassAdapterTest { return super.findClass(name); } catch (ClassNotFoundException e) { - if (CLASS_NAME.equals(name)) { + byte[] def = mClassDefs.get(name); + if (def != null) { // Load the modified ClassWithNative from its bytes representation. - return defineClass(CLASS_NAME, mClassWithNative, 0, mClassWithNative.length); + return defineClass(name, def, 0, def.length); } try { @@ -244,6 +319,17 @@ public class DelegateClassAdapterTest { } /** + * Accesses {@link OuterClass#get()} or {@link InnerClass#get() }via reflection. + */ + public int callGet(Object instance, int a, long b) throws Exception { + Method m = instance.getClass().getMethod("get", + new Class<?>[] { int.class, long.class } ); + + Object result = m.invoke(instance, new Object[] { a, b }); + return ((Integer) result).intValue(); + } + + /** * Accesses {@link ClassWithNative#add(int, int)} via reflection. */ public int callAdd(Object instance, int a, int b) throws Exception { @@ -271,34 +357,53 @@ public class DelegateClassAdapterTest { } /** - * Dummy test class with a native method. - * The native method is not defined and any attempt to invoke it will - * throw an {@link UnsatisfiedLinkError}. + * For debugging, it's useful to dump the content of the generated classes + * along with the exception that was generated. + * + * However to make it work you need to pull in the org.objectweb.asm.util.TraceClassVisitor + * class and associated utilities which are found in the ASM source jar. Since we don't + * want that dependency in the source code, we only put it manually for development and + * access the TraceClassVisitor via reflection if present. + * + * @param t The exception thrown by {@link ClassLoader2#testModifiedInstance()} + * @param cl2 The {@link ClassLoader2} instance with the generated bytecode. + * @return Either original {@code t} or a new wrapper {@link Throwable} */ - public static class ClassWithNative { - public ClassWithNative() { - } - - public int add(int a, int b) { - return a + b; - } + private Throwable dumpGeneratedClass(Throwable t, ClassLoader2 cl2) { + try { + // For debugging, dump the bytecode of the class in case of unexpected error + // if we can find the TraceClassVisitor class. + Class<?> tcvClass = Class.forName("org.objectweb.asm.util.TraceClassVisitor"); + + StringBuilder sb = new StringBuilder(); + sb.append('\n').append(t.getClass().getCanonicalName()); + if (t.getMessage() != null) { + sb.append(": ").append(t.getMessage()); + } + + for (Entry<String, byte[]> entry : cl2.getByteCode()) { + String className = entry.getKey(); + byte[] bytes = entry.getValue(); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + // next 2 lines do: TraceClassVisitor tcv = new TraceClassVisitor(pw); + Constructor<?> cons = tcvClass.getConstructor(new Class<?>[] { pw.getClass() }); + Object tcv = cons.newInstance(new Object[] { pw }); + ClassReader cr2 = new ClassReader(bytes); + cr2.accept((ClassVisitor) tcv, 0 /* flags */); + + sb.append("\nBytecode dump: <").append(className).append(">:\n") + .append(sw.toString()); + } - public int callNativeInstance(int a, double d, Object[] o) { - return native_instance(a, d, o); + // Re-throw exception with new message + RuntimeException ex = new RuntimeException(sb.toString(), t); + return ex; + } catch (Throwable ignore) { + // In case of problem, just throw the original exception as-is. + return t; } - - private native int native_instance(int a, double d, Object[] o); } - /** - * The delegate that receives the call to {@link ClassWithNative}'s overridden methods. - */ - public static class ClassWithNative_Delegate { - public static int native_instance(ClassWithNative instance, int a, double d, Object[] o) { - if (o != null && o.length > 0) { - o[0] = instance; - } - return (int)(a + d); - } - } } diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java new file mode 100644 index 0000000..c314853 --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java @@ -0,0 +1,45 @@ +/* + * 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.tools.layoutlib.create.dataclass; + +import com.android.tools.layoutlib.create.DelegateClassAdapterTest; + +/** + * Dummy test class with a native method. + * The native method is not defined and any attempt to invoke it will + * throw an {@link UnsatisfiedLinkError}. + * + * Used by {@link DelegateClassAdapterTest}. + */ +public class ClassWithNative { + public ClassWithNative() { + } + + public int add(int a, int b) { + return a + b; + } + + // Note: it's good to have a long or double for testing parameters since they take + // 2 slots in the stack/locals maps. + + public int callNativeInstance(int a, double d, Object[] o) { + return native_instance(a, d, o); + } + + private native int native_instance(int a, double d, Object[] o); +} + diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java new file mode 100644 index 0000000..a3d4dc6 --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java @@ -0,0 +1,34 @@ +/* + * 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.tools.layoutlib.create.dataclass; + +import com.android.tools.layoutlib.create.DelegateClassAdapterTest; + +/** + * The delegate that receives the call to {@link ClassWithNative_Delegate}'s overridden methods. + * + * Used by {@link DelegateClassAdapterTest}. + */ +public class ClassWithNative_Delegate { + public static int native_instance(ClassWithNative instance, int a, double d, Object[] o) { + if (o != null && o.length > 0) { + o[0] = instance; + } + return (int)(a + d); + } +} + diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java new file mode 100644 index 0000000..9dc2f69 --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.layoutlib.create.dataclass; + +import com.android.tools.layoutlib.create.DelegateClassAdapterTest; + +/** + * Test class with an inner class. + * + * Used by {@link DelegateClassAdapterTest}. + */ +public class OuterClass { + private int mOuterValue = 1; + public OuterClass() { + } + + // Outer.get returns 1 + a + b + // Note: it's good to have a long or double for testing parameters since they take + // 2 slots in the stack/locals maps. + public int get(int a, long b) { + return mOuterValue + a + (int) b; + } + + public class InnerClass { + public InnerClass() { + } + + // Inner.get returns 1+2=3 + a + b + public int get(int a, long b) { + return 2 + mOuterValue + a + (int) b; + } + } +} + diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java new file mode 100644 index 0000000..3252d87 --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.layoutlib.create.dataclass; + +import com.android.tools.layoutlib.create.DelegateClassAdapterTest; + +/** + * Used by {@link DelegateClassAdapterTest}. + */ +public class OuterClass_Delegate { + // The delegate override of Outer.get returns 4 + a + b + public static int get(OuterClass instance, int a, long b) { + return 4 + a + (int) b; + } +} + diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java new file mode 100644 index 0000000..b472220 --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.layoutlib.create.dataclass; + +import com.android.tools.layoutlib.create.DelegateClassAdapterTest; +import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass; + +/** + * Used by {@link DelegateClassAdapterTest}. + */ +public class OuterClass_InnerClass_Delegate { + // The delegate override of Inner.get return 6 + a + b + public static int get(OuterClass outer, InnerClass inner, int a, long b) { + return 6 + a + (int) b; + } +} |
