aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardView.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/PointerTracker.java35
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java131
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java41
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java23
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FormatSpec.java20
6 files changed, 176 insertions, 80 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index cf89567f8..a9856e121 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -781,9 +781,6 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
public void cancelAllMessages() {
mDrawingHandler.cancelAllMessages();
- if (mPreviewPlacerView != null) {
- mPreviewPlacerView.cancelAllMessages();
- }
}
private TextView getKeyPreviewText(final int pointerId) {
@@ -829,7 +826,8 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
}
final int[] viewOrigin = new int[2];
getLocationInWindow(viewOrigin);
- mPreviewPlacerView.setOrigin(viewOrigin[0], viewOrigin[1]);
+ mPreviewPlacerView.setKeyboardViewGeometry(
+ viewOrigin[0], viewOrigin[1], getWidth(), getHeight());
final View rootView = getRootView();
if (rootView == null) {
Log.w(TAG, "Cannot find root view");
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index d6c567ef7..ec8f65994 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -560,10 +560,13 @@ public class PointerTracker implements PointerTrackerQueue.Element {
return (sPointerTrackerQueue == null) ? 1 : sPointerTrackerQueue.size();
}
- private void mayStartBatchInput() {
+ private void mayStartBatchInput(final Key key) {
if (sInGesture || !mGestureStrokeWithPreviewPoints.isStartOfAGesture()) {
return;
}
+ if (key == null || !Character.isLetter(key.mCode)) {
+ return;
+ }
if (DEBUG_LISTENER) {
Log.d(TAG, "onStartBatchInput");
}
@@ -573,18 +576,20 @@ public class PointerTracker implements PointerTrackerQueue.Element {
mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker);
}
- private void updateBatchInput(final long eventTime) {
- synchronized (sAggregratedPointers) {
- mGestureStrokeWithPreviewPoints.appendIncrementalBatchPoints(sAggregratedPointers);
- final int size = sAggregratedPointers.getPointerSize();
- if (size > sLastRecognitionPointSize
- && GestureStroke.hasRecognitionTimePast(eventTime, sLastRecognitionTime)) {
- sLastRecognitionPointSize = size;
- sLastRecognitionTime = eventTime;
- if (DEBUG_LISTENER) {
- Log.d(TAG, "onUpdateBatchInput: batchPoints=" + size);
+ private void mayUpdateBatchInput(final long eventTime, final Key key) {
+ if (key != null) {
+ synchronized (sAggregratedPointers) {
+ mGestureStrokeWithPreviewPoints.appendIncrementalBatchPoints(sAggregratedPointers);
+ final int size = sAggregratedPointers.getPointerSize();
+ if (size > sLastRecognitionPointSize
+ && GestureStroke.hasRecognitionTimePast(eventTime, sLastRecognitionTime)) {
+ sLastRecognitionPointSize = size;
+ sLastRecognitionTime = eventTime;
+ if (DEBUG_LISTENER) {
+ Log.d(TAG, "onUpdateBatchInput: batchPoints=" + size);
+ }
+ mListener.onUpdateBatchInput(sAggregratedPointers);
}
- mListener.onUpdateBatchInput(sAggregratedPointers);
}
}
final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this;
@@ -742,9 +747,9 @@ public class PointerTracker implements PointerTrackerQueue.Element {
final int gestureTime = (int)(eventTime - sGestureFirstDownTime);
if (mIsDetectingGesture) {
mGestureStrokeWithPreviewPoints.addPoint(x, y, gestureTime, isMajorEvent);
- mayStartBatchInput();
- if (sInGesture && key != null) {
- updateBatchInput(eventTime);
+ mayStartBatchInput(key);
+ if (sInGesture) {
+ mayUpdateBatchInput(eventTime, key);
}
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
index 15170e040..72be7fc59 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
@@ -40,6 +40,10 @@ import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
public class PreviewPlacerView extends RelativeLayout {
+ // The height of extra area above the keyboard to draw gesture trails.
+ // Proportional to the keyboard height.
+ private static final float EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO = 0.25f;
+
private final int mGestureFloatingPreviewTextColor;
private final int mGestureFloatingPreviewTextOffset;
private final int mGestureFloatingPreviewColor;
@@ -47,14 +51,17 @@ public class PreviewPlacerView extends RelativeLayout {
private final float mGestureFloatingPreviewVerticalPadding;
private final float mGestureFloatingPreviewRoundRadius;
- private int mXOrigin;
- private int mYOrigin;
+ private int mKeyboardViewOriginX;
+ private int mKeyboardViewOriginY;
private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails =
CollectionUtils.newSparseArray();
private final Params mGesturePreviewTrailParams;
private final Paint mGesturePaint;
private boolean mDrawsGesturePreviewTrail;
+ private int mOffscreenWidth;
+ private int mOffscreenHeight;
+ private int mOffscreenOffsetY;
private Bitmap mOffscreenBuffer;
private final Canvas mOffscreenCanvas = new Canvas();
private final Rect mOffscreenDirtyRect = new Rect();
@@ -101,29 +108,17 @@ public class PreviewPlacerView extends RelativeLayout {
}
}
- private void cancelDismissGestureFloatingPreviewText() {
- removeMessages(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT);
- }
-
public void dismissGestureFloatingPreviewText() {
- cancelDismissGestureFloatingPreviewText();
+ removeMessages(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT);
sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT),
mGestureFloatingPreviewTextLingerTimeout);
}
- private void cancelUpdateGestureTrailPreview() {
- removeMessages(MSG_UPDATE_GESTURE_PREVIEW_TRAIL);
- }
-
public void postUpdateGestureTrailPreview() {
- cancelUpdateGestureTrailPreview();
+ removeMessages(MSG_UPDATE_GESTURE_PREVIEW_TRAIL);
sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_PREVIEW_TRAIL),
mGesturePreviewTrailParams.mUpdateInterval);
}
-
- public void cancelAllMessages() {
- cancelUpdateGestureTrailPreview();
- }
}
public PreviewPlacerView(final Context context, final AttributeSet attrs) {
@@ -177,9 +172,12 @@ public class PreviewPlacerView extends RelativeLayout {
setLayerType(LAYER_TYPE_HARDWARE, layerPaint);
}
- public void setOrigin(final int x, final int y) {
- mXOrigin = x;
- mYOrigin = y;
+ public void setKeyboardViewGeometry(final int x, final int y, final int w, final int h) {
+ mKeyboardViewOriginX = x;
+ mKeyboardViewOriginY = y;
+ mOffscreenOffsetY = (int)(h * EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO);
+ mOffscreenWidth = w;
+ mOffscreenHeight = mOffscreenOffsetY + h;
}
public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail,
@@ -216,45 +214,42 @@ public class PreviewPlacerView extends RelativeLayout {
@Override
protected void onDetachedFromWindow() {
+ freeOffscreenBuffer();
+ }
+
+ private void freeOffscreenBuffer() {
if (mOffscreenBuffer != null) {
mOffscreenBuffer.recycle();
mOffscreenBuffer = null;
}
}
+ private void mayAllocateOffscreenBuffer() {
+ if (mOffscreenBuffer != null && mOffscreenBuffer.getWidth() == mOffscreenWidth
+ && mOffscreenBuffer.getHeight() == mOffscreenHeight) {
+ return;
+ }
+ freeOffscreenBuffer();
+ mOffscreenBuffer = Bitmap.createBitmap(
+ mOffscreenWidth, mOffscreenHeight, Bitmap.Config.ARGB_8888);
+ mOffscreenCanvas.setBitmap(mOffscreenBuffer);
+ }
+
@Override
public void onDraw(final Canvas canvas) {
super.onDraw(canvas);
- canvas.translate(mXOrigin, mYOrigin);
if (mDrawsGesturePreviewTrail) {
- if (mOffscreenBuffer == null) {
- mOffscreenBuffer = Bitmap.createBitmap(
- getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
- mOffscreenCanvas.setBitmap(mOffscreenBuffer);
- }
- if (!mOffscreenDirtyRect.isEmpty()) {
- // Clear previous dirty rectangle.
- mGesturePaint.setColor(Color.TRANSPARENT);
- mGesturePaint.setStyle(Paint.Style.FILL);
- mOffscreenCanvas.drawRect(mOffscreenDirtyRect, mGesturePaint);
- mOffscreenDirtyRect.setEmpty();
- }
- boolean needsUpdatingGesturePreviewTrail = false;
- synchronized (mGesturePreviewTrails) {
- // Trails count == fingers count that have ever been active.
- final int trailsCount = mGesturePreviewTrails.size();
- for (int index = 0; index < trailsCount; index++) {
- final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index);
- needsUpdatingGesturePreviewTrail |=
- trail.drawGestureTrail(mOffscreenCanvas, mGesturePaint,
- mGesturePreviewTrailBoundsRect, mGesturePreviewTrailParams);
- // {@link #mGesturePreviewTrailBoundsRect} has bounding box of the trail.
- mOffscreenDirtyRect.union(mGesturePreviewTrailBoundsRect);
- }
- }
+ mayAllocateOffscreenBuffer();
+ // Draw gesture trails to offscreen buffer.
+ final boolean needsUpdatingGesturePreviewTrail = drawGestureTrails(
+ mOffscreenCanvas, mGesturePaint, mOffscreenDirtyRect);
+ // Transfer offscreen buffer to screen.
if (!mOffscreenDirtyRect.isEmpty()) {
+ final int offsetY = mKeyboardViewOriginY - mOffscreenOffsetY;
+ canvas.translate(mKeyboardViewOriginX, offsetY);
canvas.drawBitmap(mOffscreenBuffer, mOffscreenDirtyRect, mOffscreenDirtyRect,
mGesturePaint);
+ canvas.translate(-mKeyboardViewOriginX, -offsetY);
// Note: Defer clearing the dirty rectangle here because we will get cleared
// rectangle on the canvas.
}
@@ -263,9 +258,49 @@ public class PreviewPlacerView extends RelativeLayout {
}
}
if (mDrawsGestureFloatingPreviewText) {
+ canvas.translate(mKeyboardViewOriginX, mKeyboardViewOriginY);
drawGestureFloatingPreviewText(canvas, mGestureFloatingPreviewText);
+ canvas.translate(-mKeyboardViewOriginX, -mKeyboardViewOriginY);
+ }
+ }
+
+ private boolean drawGestureTrails(final Canvas offscreenCanvas, final Paint paint,
+ final Rect dirtyRect) {
+ // Clear previous dirty rectangle.
+ if (!dirtyRect.isEmpty()) {
+ paint.setColor(Color.TRANSPARENT);
+ paint.setStyle(Paint.Style.FILL);
+ offscreenCanvas.drawRect(dirtyRect, paint);
+ }
+ dirtyRect.setEmpty();
+
+ // Draw gesture trails to offscreen buffer.
+ offscreenCanvas.translate(0, mOffscreenOffsetY);
+ boolean needsUpdatingGesturePreviewTrail = false;
+ synchronized (mGesturePreviewTrails) {
+ // Trails count == fingers count that have ever been active.
+ final int trailsCount = mGesturePreviewTrails.size();
+ for (int index = 0; index < trailsCount; index++) {
+ final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index);
+ needsUpdatingGesturePreviewTrail |=
+ trail.drawGestureTrail(offscreenCanvas, paint,
+ mGesturePreviewTrailBoundsRect, mGesturePreviewTrailParams);
+ // {@link #mGesturePreviewTrailBoundsRect} has bounding box of the trail.
+ dirtyRect.union(mGesturePreviewTrailBoundsRect);
+ }
}
- canvas.translate(-mXOrigin, -mYOrigin);
+ offscreenCanvas.translate(0, -mOffscreenOffsetY);
+
+ // Clip dirty rectangle with offscreen buffer width/height.
+ dirtyRect.offset(0, mOffscreenOffsetY);
+ clipRect(dirtyRect, 0, 0, mOffscreenWidth, mOffscreenHeight);
+ return needsUpdatingGesturePreviewTrail;
+ }
+
+ private static void clipRect(final Rect out, final int left, final int top, final int right,
+ final int bottom) {
+ out.set(Math.max(out.left, left), Math.max(out.top, top), Math.min(out.right, right),
+ Math.min(out.bottom, bottom));
}
public void setGestureFloatingPreviewText(final String gestureFloatingPreviewText) {
@@ -278,10 +313,6 @@ public class PreviewPlacerView extends RelativeLayout {
mDrawingHandler.dismissGestureFloatingPreviewText();
}
- public void cancelAllMessages() {
- mDrawingHandler.cancelAllMessages();
- }
-
private void drawGestureFloatingPreviewText(final Canvas canvas,
final String gestureFloatingPreviewText) {
if (TextUtils.isEmpty(gestureFloatingPreviewText)) {
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index 19da5124a..b97be0543 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -85,7 +85,10 @@ public class BinaryDictIOUtils {
}
p.mPosition++;
- if (info.mFrequency != FusionDictionary.CharGroup.NOT_A_TERMINAL) { // found word
+ final boolean isMovedGroup = BinaryDictInputOutput.isMovedGroup(info.mFlags,
+ formatOptions);
+ if (!isMovedGroup
+ && info.mFrequency != FusionDictionary.CharGroup.NOT_A_TERMINAL) {// found word
words.put(info.mOriginalAddress, new String(pushedChars, 0, index));
frequencies.put(info.mOriginalAddress, info.mFrequency);
if (info.mBigrams != null) bigrams.put(info.mOriginalAddress, info.mBigrams);
@@ -109,7 +112,7 @@ public class BinaryDictIOUtils {
p.mAddress = buffer.position();
}
- if (BinaryDictInputOutput.hasChildrenAddress(info.mChildrenAddress)) {
+ if (!isMovedGroup && BinaryDictInputOutput.hasChildrenAddress(info.mChildrenAddress)) {
Position childrenPos = new Position(info.mChildrenAddress + headerSize, index);
stack.push(childrenPos);
}
@@ -168,6 +171,10 @@ public class BinaryDictIOUtils {
final int charGroupPos = buffer.position();
final CharGroupInfo currentInfo = BinaryDictInputOutput.readCharGroup(buffer,
buffer.position(), header.mFormatOptions);
+ if (BinaryDictInputOutput.isMovedGroup(currentInfo.mFlags,
+ header.mFormatOptions)) {
+ continue;
+ }
boolean same = true;
for (int p = 0, j = word.offsetByCodePoints(0, wordPos);
p < currentInfo.mCharacters.length;
@@ -239,4 +246,34 @@ public class BinaryDictIOUtils {
buffer.position(wordPosition);
buffer.put((byte)newFlags);
}
+
+ private static void putSInt24(final FusionDictionaryBufferInterface buffer,
+ final int value) {
+ final int absValue = Math.abs(value);
+ buffer.put((byte)(((value < 0 ? 0x80 : 0) | (absValue >> 16)) & 0xFF));
+ buffer.put((byte)((absValue >> 8) & 0xFF));
+ buffer.put((byte)(absValue & 0xFF));
+ }
+
+ /**
+ * Update a parent address in a CharGroup that is addressed by groupOriginAddress.
+ *
+ * @param buffer the buffer to write.
+ * @param groupOriginAddress the address of the group.
+ * @param newParentAddress the absolute address of the parent.
+ * @param formatOptions file format options.
+ */
+ public static void updateParentAddress(final FusionDictionaryBufferInterface buffer,
+ final int groupOriginAddress, final int newParentAddress,
+ final FormatOptions formatOptions) {
+ final int originalPosition = buffer.position();
+ buffer.position(groupOriginAddress);
+ if (!formatOptions.mSupportsDynamicUpdate) {
+ throw new RuntimeException("this file format does not support parent addresses");
+ }
+ final int flags = buffer.readUnsignedByte();
+ final int parentOffset = newParentAddress - groupOriginAddress;
+ putSInt24(buffer, parentOffset);
+ buffer.position(originalPosition);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index f9339de08..9fc694218 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -53,6 +53,7 @@ public class BinaryDictInputOutput {
// If the number of passes exceeds this number, makedict bails with an exception on
// suspicion that a bug might be causing an infinite loop.
private static final int MAX_PASSES = 24;
+ private static final int MAX_JUMPS = 12;
public interface FusionDictionaryBufferInterface {
public int readUnsignedByte();
@@ -395,6 +396,13 @@ public class BinaryDictInputOutput {
}
/**
+ * Helper method to check whether the group is moved.
+ */
+ public static boolean isMovedGroup(final int flags, final FormatOptions options) {
+ return options.mSupportsDynamicUpdate && ((flags & FormatSpec.FLAG_IS_MOVED) == 1);
+ }
+
+ /**
* Helper method to check whether the dictionary can be updated dynamically.
*/
public static boolean supportsDynamicUpdate(final FormatOptions options) {
@@ -1374,8 +1382,18 @@ public class BinaryDictInputOutput {
int index = FormatSpec.MAX_WORD_LENGTH - 1;
// the length of the path from the root to the leaf is limited by MAX_WORD_LENGTH
for (int count = 0; count < FormatSpec.MAX_WORD_LENGTH; ++count) {
- buffer.position(currentAddress + headerSize);
- final CharGroupInfo currentInfo = readCharGroup(buffer, currentAddress, options);
+ CharGroupInfo currentInfo;
+ int loopCounter = 0;
+ do {
+ buffer.position(currentAddress + headerSize);
+ currentInfo = readCharGroup(buffer, currentAddress, options);
+ if (isMovedGroup(currentInfo.mFlags, options)) {
+ currentAddress = currentInfo.mParentAddress + currentInfo.mOriginalAddress;
+ }
+ if (DBG && loopCounter++ > MAX_JUMPS) {
+ MakedictLog.d("Too many jumps - probably a bug");
+ }
+ } while (isMovedGroup(currentInfo.mFlags, options));
for (int i = 0; i < currentInfo.mCharacters.length; ++i) {
sGetWordBuffer[index--] =
currentInfo.mCharacters[currentInfo.mCharacters.length - i - 1];
@@ -1457,6 +1475,7 @@ public class BinaryDictInputOutput {
int groupOffset = nodeHeadPosition + getGroupCountSize(count);
for (int i = count; i > 0; --i) { // Scan the array of CharGroup.
CharGroupInfo info = readCharGroup(buffer, groupOffset, options);
+ if (isMovedGroup(info.mFlags, options)) continue;
ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets;
ArrayList<WeightedString> bigrams = null;
if (null != info.mBigrams) {
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index cab0661f6..35311f0c2 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -52,13 +52,18 @@ public final class FormatSpec {
*/
/* Node(CharGroup) layout is as follows:
- * | addressType xx : mask with MASK_GROUP_ADDRESS_TYPE
- * 2 bits, 00 = no children : FLAG_GROUP_ADDRESS_TYPE_NOADDRESS
- * f | 01 = 1 byte : FLAG_GROUP_ADDRESS_TYPE_ONEBYTE
- * l | 10 = 2 bytes : FLAG_GROUP_ADDRESS_TYPE_TWOBYTES
- * a | 11 = 3 bytes : FLAG_GROUP_ADDRESS_TYPE_THREEBYTES
- * g | has several chars ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_MULTIPLE_CHARS
- * s | has a terminal ? 1 bit, 1 = yes, 0 = no : FLAG_IS_TERMINAL
+ * | IF !SUPPORTS_DYNAMIC_UPDATE
+ * | addressType xx : mask with MASK_GROUP_ADDRESS_TYPE
+ * | 2 bits, 00 = no children : FLAG_GROUP_ADDRESS_TYPE_NOADDRESS
+ * f | 01 = 1 byte : FLAG_GROUP_ADDRESS_TYPE_ONEBYTE
+ * l | 10 = 2 bytes : FLAG_GROUP_ADDRESS_TYPE_TWOBYTES
+ * a | 11 = 3 bytes : FLAG_GROUP_ADDRESS_TYPE_THREEBYTES
+ * g | ELSE
+ * s | is moved ? 2 bits, 11 = no
+ * | 01 = yes
+ * | the new address is stored in the same place as the parent address
+ * | has several chars ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_MULTIPLE_CHARS
+ * | has a terminal ? 1 bit, 1 = yes, 0 = no : FLAG_IS_TERMINAL
* | has shortcut targets ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_SHORTCUT_TARGETS
* | has bigrams ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_BIGRAMS
* | is not a word ? 1 bit, 1 = yes, 0 = no : FLAG_IS_NOT_A_WORD
@@ -178,6 +183,7 @@ public final class FormatSpec {
static final int FLAG_HAS_BIGRAMS = 0x04;
static final int FLAG_IS_NOT_A_WORD = 0x02;
static final int FLAG_IS_BLACKLISTED = 0x01;
+ static final int FLAG_IS_MOVED = 0x40;
static final int FLAG_ATTRIBUTE_HAS_NEXT = 0x80;
static final int FLAG_ATTRIBUTE_OFFSET_NEGATIVE = 0x40;