Merge "Expose Credentials.UNLOCK_ACTION for callers that want to use startActivityForResult"
diff --git a/api/13.txt b/api/13.txt
index cbc2c40..a17b80c 100644
--- a/api/13.txt
+++ b/api/13.txt
@@ -21945,49 +21945,24 @@
package android.view.accessibility {
- public final class AccessibilityEvent implements android.os.Parcelable {
+ public final class AccessibilityEvent extends android.view.accessibility.AccessibilityRecord implements android.os.Parcelable {
+ method public void appendRecord(android.view.accessibility.AccessibilityRecord);
method public int describeContents();
- method public int getAddedCount();
- method public java.lang.CharSequence getBeforeText();
- method public java.lang.CharSequence getClassName();
- method public java.lang.CharSequence getContentDescription();
- method public int getCurrentItemIndex();
method public long getEventTime();
method public int getEventType();
- method public int getFromIndex();
- method public int getItemCount();
method public java.lang.CharSequence getPackageName();
- method public android.os.Parcelable getParcelableData();
- method public int getRemovedCount();
- method public java.util.List<java.lang.CharSequence> getText();
+ method public android.view.accessibility.AccessibilityRecord getRecord(int);
+ method public int getRecordCount();
method public void initFromParcel(android.os.Parcel);
- method public boolean isChecked();
- method public boolean isEnabled();
- method public boolean isFullScreen();
- method public boolean isPassword();
method public static android.view.accessibility.AccessibilityEvent obtain(int);
method public static android.view.accessibility.AccessibilityEvent obtain();
- method public void recycle();
- method public void setAddedCount(int);
- method public void setBeforeText(java.lang.CharSequence);
- method public void setChecked(boolean);
- method public void setClassName(java.lang.CharSequence);
- method public void setContentDescription(java.lang.CharSequence);
- method public void setCurrentItemIndex(int);
- method public void setEnabled(boolean);
method public void setEventTime(long);
method public void setEventType(int);
- method public void setFromIndex(int);
- method public void setFullScreen(boolean);
- method public void setItemCount(int);
method public void setPackageName(java.lang.CharSequence);
- method public void setParcelableData(android.os.Parcelable);
- method public void setPassword(boolean);
- method public void setRemovedCount(int);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
field public static final int INVALID_POSITION = -1; // 0xffffffff
- field public static final int MAX_TEXT_LENGTH = 500; // 0x1f4
+ field public static final deprecated int MAX_TEXT_LENGTH = 500; // 0x1f4
field public static final int TYPES_ALL_MASK = -1; // 0xffffffff
field public static final int TYPE_NOTIFICATION_STATE_CHANGED = 64; // 0x40
field public static final int TYPE_VIEW_CLICKED = 1; // 0x1
@@ -22010,6 +21985,51 @@
method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
}
+ public class AccessibilityRecord {
+ ctor protected AccessibilityRecord();
+ method protected void clear();
+ method public int getAddedCount();
+ method public java.lang.CharSequence getBeforeText();
+ method public boolean getBooleanProperty(int);
+ method public java.lang.CharSequence getClassName();
+ method public java.lang.CharSequence getContentDescription();
+ method public int getCurrentItemIndex();
+ method public int getFromIndex();
+ method public int getItemCount();
+ method public android.os.Parcelable getParcelableData();
+ method public int getRemovedCount();
+ method public java.util.List<java.lang.CharSequence> getText();
+ method public boolean isChecked();
+ method public boolean isEnabled();
+ method public boolean isFullScreen();
+ method public boolean isPassword();
+ method protected static android.view.accessibility.AccessibilityRecord obtain();
+ method public void recycle();
+ method public void setAddedCount(int);
+ method public void setBeforeText(java.lang.CharSequence);
+ method public void setChecked(boolean);
+ method public void setClassName(java.lang.CharSequence);
+ method public void setContentDescription(java.lang.CharSequence);
+ method public void setCurrentItemIndex(int);
+ method public void setEnabled(boolean);
+ method public void setFromIndex(int);
+ method public void setFullScreen(boolean);
+ method public void setItemCount(int);
+ method public void setParcelableData(android.os.Parcelable);
+ method public void setPassword(boolean);
+ method public void setRemovedCount(int);
+ field protected int mAddedCount;
+ field protected java.lang.CharSequence mBeforeText;
+ field protected int mBooleanProperties;
+ field protected java.lang.CharSequence mClassName;
+ field protected java.lang.CharSequence mContentDescription;
+ field protected int mCurrentItemIndex;
+ field protected int mFromIndex;
+ field protected int mItemCount;
+ field protected android.os.Parcelable mParcelableData;
+ field protected int mRemovedCount;
+ field protected final java.util.List mText;
+ }
}
package android.view.animation {
diff --git a/api/current.txt b/api/current.txt
index c7e6ee6..fd05e48 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -21174,6 +21174,7 @@
method protected void onLayout(boolean, int, int, int, int);
method protected void onMeasure(int, int);
method protected void onOverScrolled(int, int, boolean, boolean);
+ method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method protected void onRestoreInstanceState(android.os.Parcelable);
method protected android.os.Parcelable onSaveInstanceState();
method protected void onScrollChanged(int, int, int, int);
@@ -21585,6 +21586,7 @@
method public boolean onInterceptTouchEvent(android.view.MotionEvent);
method protected abstract void onLayout(boolean, int, int, int, int);
method protected boolean onRequestFocusInDescendants(int, android.graphics.Rect);
+ method public boolean onRequestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
method public void recomputeViewAttributes(android.view.View);
method public void removeAllViews();
method public void removeAllViewsInLayout();
@@ -21597,6 +21599,7 @@
method public void requestChildFocus(android.view.View, android.view.View);
method public boolean requestChildRectangleOnScreen(android.view.View, android.graphics.Rect, boolean);
method public void requestDisallowInterceptTouchEvent(boolean);
+ method public boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
method public void requestTransparentRegion(android.view.View);
method public void scheduleLayoutAnimation();
method public void setAddStatesFromChildren(boolean);
@@ -21683,6 +21686,7 @@
method public abstract boolean requestChildRectangleOnScreen(android.view.View, android.graphics.Rect, boolean);
method public abstract void requestDisallowInterceptTouchEvent(boolean);
method public abstract void requestLayout();
+ method public abstract boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
method public abstract void requestTransparentRegion(android.view.View);
method public abstract boolean showContextMenuForChild(android.view.View);
method public abstract android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback);
@@ -22028,53 +22032,32 @@
package android.view.accessibility {
- public final class AccessibilityEvent implements android.os.Parcelable {
+ public final class AccessibilityEvent extends android.view.accessibility.AccessibilityRecord implements android.os.Parcelable {
+ method public void appendRecord(android.view.accessibility.AccessibilityRecord);
method public int describeContents();
- method public int getAddedCount();
- method public java.lang.CharSequence getBeforeText();
- method public java.lang.CharSequence getClassName();
- method public java.lang.CharSequence getContentDescription();
- method public int getCurrentItemIndex();
method public long getEventTime();
method public int getEventType();
- method public int getFromIndex();
- method public int getItemCount();
method public java.lang.CharSequence getPackageName();
- method public android.os.Parcelable getParcelableData();
- method public int getRemovedCount();
- method public java.util.List<java.lang.CharSequence> getText();
+ method public android.view.accessibility.AccessibilityRecord getRecord(int);
+ method public int getRecordCount();
method public void initFromParcel(android.os.Parcel);
- method public boolean isChecked();
- method public boolean isEnabled();
- method public boolean isFullScreen();
- method public boolean isPassword();
method public static android.view.accessibility.AccessibilityEvent obtain(int);
method public static android.view.accessibility.AccessibilityEvent obtain();
- method public void recycle();
- method public void setAddedCount(int);
- method public void setBeforeText(java.lang.CharSequence);
- method public void setChecked(boolean);
- method public void setClassName(java.lang.CharSequence);
- method public void setContentDescription(java.lang.CharSequence);
- method public void setCurrentItemIndex(int);
- method public void setEnabled(boolean);
method public void setEventTime(long);
method public void setEventType(int);
- method public void setFromIndex(int);
- method public void setFullScreen(boolean);
- method public void setItemCount(int);
method public void setPackageName(java.lang.CharSequence);
- method public void setParcelableData(android.os.Parcelable);
- method public void setPassword(boolean);
- method public void setRemovedCount(int);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
field public static final int INVALID_POSITION = -1; // 0xffffffff
field public static final deprecated int MAX_TEXT_LENGTH = 500; // 0x1f4
field public static final int TYPES_ALL_MASK = -1; // 0xffffffff
field public static final int TYPE_NOTIFICATION_STATE_CHANGED = 64; // 0x40
+ field public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 1024; // 0x400
+ field public static final int TYPE_TOUCH_EXPLORATION_GESTURE_START = 512; // 0x200
field public static final int TYPE_VIEW_CLICKED = 1; // 0x1
field public static final int TYPE_VIEW_FOCUSED = 8; // 0x8
+ field public static final int TYPE_VIEW_HOVER_ENTER = 128; // 0x80
+ field public static final int TYPE_VIEW_HOVER_EXIT = 256; // 0x100
field public static final int TYPE_VIEW_LONG_CLICKED = 2; // 0x2
field public static final int TYPE_VIEW_SELECTED = 4; // 0x4
field public static final int TYPE_VIEW_TEXT_CHANGED = 16; // 0x10
@@ -22088,11 +22071,58 @@
public final class AccessibilityManager {
method public java.util.List<android.content.pm.ServiceInfo> getAccessibilityServiceList();
+ method public java.util.List<android.content.pm.ServiceInfo> getEnabledAccessibilityServiceList(int);
method public void interrupt();
method public boolean isEnabled();
method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
}
+ public class AccessibilityRecord {
+ ctor protected AccessibilityRecord();
+ method protected void clear();
+ method public int getAddedCount();
+ method public java.lang.CharSequence getBeforeText();
+ method public boolean getBooleanProperty(int);
+ method public java.lang.CharSequence getClassName();
+ method public java.lang.CharSequence getContentDescription();
+ method public int getCurrentItemIndex();
+ method public int getFromIndex();
+ method public int getItemCount();
+ method public android.os.Parcelable getParcelableData();
+ method public int getRemovedCount();
+ method public java.util.List<java.lang.CharSequence> getText();
+ method public boolean isChecked();
+ method public boolean isEnabled();
+ method public boolean isFullScreen();
+ method public boolean isPassword();
+ method protected static android.view.accessibility.AccessibilityRecord obtain();
+ method public void recycle();
+ method public void setAddedCount(int);
+ method public void setBeforeText(java.lang.CharSequence);
+ method public void setChecked(boolean);
+ method public void setClassName(java.lang.CharSequence);
+ method public void setContentDescription(java.lang.CharSequence);
+ method public void setCurrentItemIndex(int);
+ method public void setEnabled(boolean);
+ method public void setFromIndex(int);
+ method public void setFullScreen(boolean);
+ method public void setItemCount(int);
+ method public void setParcelableData(android.os.Parcelable);
+ method public void setPassword(boolean);
+ method public void setRemovedCount(int);
+ field protected int mAddedCount;
+ field protected java.lang.CharSequence mBeforeText;
+ field protected int mBooleanProperties;
+ field protected java.lang.CharSequence mClassName;
+ field protected java.lang.CharSequence mContentDescription;
+ field protected int mCurrentItemIndex;
+ field protected int mFromIndex;
+ field protected int mItemCount;
+ field protected android.os.Parcelable mParcelableData;
+ field protected int mRemovedCount;
+ field protected final java.util.List mText;
+ }
+
}
package android.view.animation {
diff --git a/core/java/android/preference/MultiSelectListPreference.java b/core/java/android/preference/MultiSelectListPreference.java
index 42d555cf..2e8d551 100644
--- a/core/java/android/preference/MultiSelectListPreference.java
+++ b/core/java/android/preference/MultiSelectListPreference.java
@@ -169,9 +169,9 @@
new DialogInterface.OnMultiChoiceClickListener() {
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
if (isChecked) {
- mPreferenceChanged |= mNewValues.add(mEntries[which].toString());
+ mPreferenceChanged |= mNewValues.add(mEntryValues[which].toString());
} else {
- mPreferenceChanged |= mNewValues.remove(mEntries[which].toString());
+ mPreferenceChanged |= mNewValues.remove(mEntryValues[which].toString());
}
}
});
@@ -180,7 +180,7 @@
}
private boolean[] getSelectedItems() {
- final CharSequence[] entries = mEntries;
+ final CharSequence[] entries = mEntryValues;
final int entryCount = entries.length;
final Set<String> values = mValues;
boolean[] result = new boolean[entryCount];
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2f4a4bb..570b801 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3721,6 +3721,22 @@
"setup_prepaid_data_service_url";
/**
+ * URL to attempt a GET on to see if this is a prepay device
+ * @hide
+ */
+ public static final String SETUP_PREPAID_DETECTION_TARGET_URL =
+ "setup_prepaid_detection_target_url";
+
+ /**
+ * Host to check for a redirect to after an attempt to GET
+ * SETUP_PREPAID_DETECTION_TARGET_URL. (If we redirected there,
+ * this is a prepaid device with zero balance.)
+ * @hide
+ */
+ public static final String SETUP_PREPAID_DETECTION_REDIR_HOST =
+ "setup_prepaid_detection_redir_host";
+
+ /**
* @hide
*/
public static final String[] SETTINGS_TO_BACKUP = {
diff --git a/core/java/android/text/GraphicsOperations.java b/core/java/android/text/GraphicsOperations.java
index 6e2168b..831ccc5 100644
--- a/core/java/android/text/GraphicsOperations.java
+++ b/core/java/android/text/GraphicsOperations.java
@@ -61,8 +61,8 @@
* Just like {@link Paint#getTextRunAdvances}.
* @hide
*/
- float getTextRunAdvancesICU(int start, int end, int contextStart, int contextEnd,
- int flags, float[] advances, int advancesIndex, Paint paint);
+ float getTextRunAdvances(int start, int end, int contextStart, int contextEnd,
+ int flags, float[] advances, int advancesIndex, Paint paint, int reserved);
/**
* Just like {@link Paint#getTextRunCursor}.
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index ff6a4cd..6b2d8e4 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -1173,8 +1173,8 @@
* Don't call this yourself -- exists for Paint to use internally.
* {@hide}
*/
- public float getTextRunAdvancesICU(int start, int end, int contextStart, int contextEnd, int flags,
- float[] advances, int advancesPos, Paint p) {
+ public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
+ float[] advances, int advancesPos, Paint p, int reserved) {
float ret;
@@ -1182,16 +1182,16 @@
int len = end - start;
if (end <= mGapStart) {
- ret = p.getTextRunAdvancesICU(mText, start, len, contextStart, contextLen,
- flags, advances, advancesPos);
+ ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
+ flags, advances, advancesPos, reserved);
} else if (start >= mGapStart) {
- ret = p.getTextRunAdvancesICU(mText, start + mGapLength, len,
- contextStart + mGapLength, contextLen, flags, advances, advancesPos);
+ ret = p.getTextRunAdvances(mText, start + mGapLength, len,
+ contextStart + mGapLength, contextLen, flags, advances, advancesPos, reserved);
} else {
char[] buf = TextUtils.obtain(contextLen);
getChars(contextStart, contextEnd, buf, 0);
- ret = p.getTextRunAdvancesICU(buf, start - contextStart, len,
- 0, contextLen, flags, advances, advancesPos);
+ ret = p.getTextRunAdvances(buf, start - contextStart, len,
+ 0, contextLen, flags, advances, advancesPos, reserved);
TextUtils.recycle(buf);
}
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
index b5ca2c2..e14b975 100644
--- a/core/java/android/view/InputEventConsistencyVerifier.java
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -30,7 +30,6 @@
* @hide
*/
public final class InputEventConsistencyVerifier {
- private static final String TAG = "InputEventConsistencyVerifier";
private static final boolean IS_ENG_BUILD = "eng".equals(Build.TYPE);
// The number of recent events to log when a problem is detected.
@@ -44,6 +43,11 @@
// Consistency verifier flags.
private final int mFlags;
+ // Tag for logging which a client can set to help distinguish the output
+ // from different verifiers since several can be active at the same time.
+ // If not provided defaults to the simple class name.
+ private final String mLogTag;
+
// The most recently checked event and the nesting level at which it was checked.
// This is only set when the verifier is called from a nesting level greater than 0
// so that the verifier can detect when it has been asked to verify the same event twice.
@@ -103,8 +107,19 @@
* @param flags Flags to the verifier, or 0 if none.
*/
public InputEventConsistencyVerifier(Object caller, int flags) {
+ this(caller, flags, InputEventConsistencyVerifier.class.getSimpleName());
+ }
+
+ /**
+ * Creates an input consistency verifier.
+ * @param caller The object to which the verifier is attached.
+ * @param flags Flags to the verifier, or 0 if none.
+ * @param logTag Tag for logging. If null defaults to the short class name.
+ */
+ public InputEventConsistencyVerifier(Object caller, int flags, String logTag) {
this.mCaller = caller;
this.mFlags = flags;
+ this.mLogTag = (logTag != null) ? logTag : "InputEventConsistencyVerifier";
}
/**
@@ -596,7 +611,7 @@
}
}
- Log.d(TAG, mViolationMessage.toString());
+ Log.d(mLogTag, mViolationMessage.toString());
mViolationMessage.setLength(0);
tainted = true;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 4a62892..4bc7f39 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3455,6 +3455,10 @@
if (!isShown()) {
return;
}
+
+ // Populate these here since they are related to the View that
+ // sends the event and should not be modified while dispatching
+ // to descendants.
event.setClassName(getClass().getName());
event.setPackageName(getContext().getPackageName());
event.setEnabled(isEnabled());
@@ -3470,22 +3474,38 @@
dispatchPopulateAccessibilityEvent(event);
- AccessibilityManager.getInstance(mContext).sendAccessibilityEvent(event);
+ // In the beginning we called #isShown(), so we know that getParent() is not null.
+ getParent().requestSendAccessibilityEvent(this, event);
}
/**
- * Dispatches an {@link AccessibilityEvent} to the {@link View} children
- * to be populated.
+ * Dispatches an {@link AccessibilityEvent} to the {@link View} children to be populated.
+ * This method first calls {@link #onPopulateAccessibilityEvent(AccessibilityEvent)}
+ * on this view allowing it to populate information about itself and also decide
+ * whether to intercept the population i.e. to prevent its children from populating
+ * the event.
*
* @param event The event.
*
* @return True if the event population was completed.
*/
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ onPopulateAccessibilityEvent(event);
return false;
}
/**
+ * Called from {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)}
+ * giving a chance to this View to populate the accessibility evnet with
+ * information about itself.
+ *
+ * @param event The accessibility event which to populate.
+ */
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+
+ }
+
+ /**
* Gets the {@link View} description. It briefly describes the view and is
* primarily used for accessibility support. Set this property to enable
* better accessibility support for your application. This is especially
@@ -5390,20 +5410,6 @@
* to receive the hover event.
*/
public boolean onHoverEvent(MotionEvent event) {
- final int viewFlags = mViewFlags;
-
- if (((viewFlags & CLICKABLE) != CLICKABLE &&
- (viewFlags & LONG_CLICKABLE) != LONG_CLICKABLE)) {
- // Nothing to do if the view is not clickable.
- return false;
- }
-
- if ((viewFlags & ENABLED_MASK) == DISABLED) {
- // A disabled view that is clickable still consumes the hover events, it just doesn't
- // respond to them.
- return true;
- }
-
switch (event.getAction()) {
case MotionEvent.ACTION_HOVER_ENTER:
setHovered(true);
@@ -5414,7 +5420,7 @@
break;
}
- return true;
+ return false;
}
/**
@@ -5436,11 +5442,13 @@
if ((mPrivateFlags & HOVERED) == 0) {
mPrivateFlags |= HOVERED;
refreshDrawableState();
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
}
} else {
if ((mPrivateFlags & HOVERED) != 0) {
mPrivateFlags &= ~HOVERED;
refreshDrawableState();
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
}
}
}
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 739758c..94eb429 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -19,7 +19,6 @@
import android.app.AppGlobals;
import android.content.Context;
import android.content.res.Configuration;
-import android.os.Bundle;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.SparseArray;
@@ -156,6 +155,13 @@
private static final int MAXIMUM_FLING_VELOCITY = 8000;
/**
+ * Distance between a touch up event denoting the end of a touch exploration
+ * gesture and the touch up event of a subsequent tap for the latter tap to be
+ * considered as a tap i.e. to perform a click.
+ */
+ private static final int TOUCH_EXPLORATION_TAP_SLOP = 80;
+
+ /**
* The maximum size of View's drawing cache, expressed in bytes. This size
* should be at least equal to the size of the screen in ARGB888 format.
*/
@@ -185,6 +191,7 @@
private final int mTouchSlop;
private final int mPagingTouchSlop;
private final int mDoubleTapSlop;
+ private final int mScaledTouchExplorationTapSlop;
private final int mWindowTouchSlop;
private final int mMaximumDrawingCacheSize;
private final int mOverscrollDistance;
@@ -206,6 +213,7 @@
mTouchSlop = TOUCH_SLOP;
mPagingTouchSlop = PAGING_TOUCH_SLOP;
mDoubleTapSlop = DOUBLE_TAP_SLOP;
+ mScaledTouchExplorationTapSlop = TOUCH_EXPLORATION_TAP_SLOP;
mWindowTouchSlop = WINDOW_TOUCH_SLOP;
//noinspection deprecation
mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE;
@@ -242,6 +250,7 @@
mTouchSlop = (int) (sizeAndDensity * TOUCH_SLOP + 0.5f);
mPagingTouchSlop = (int) (sizeAndDensity * PAGING_TOUCH_SLOP + 0.5f);
mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_TAP_SLOP + 0.5f);
+ mScaledTouchExplorationTapSlop = (int) (density * TOUCH_EXPLORATION_TAP_SLOP + 0.5f);
mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f);
// Size of the screen in bytes, in ARGB_8888 format
@@ -444,6 +453,17 @@
}
/**
+ * @return Distance between a touch up event denoting the end of a touch exploration
+ * gesture and the touch up event of a subsequent tap for the latter tap to be
+ * considered as a tap i.e. to perform a click.
+ *
+ * @hide
+ */
+ public int getScaledTouchExplorationTapSlop() {
+ return mScaledTouchExplorationTapSlop;
+ }
+
+ /**
* @return Distance a touch must be outside the bounds of a window for it
* to be counted as outside the window for purposes of dismissing that
* window.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 08daa28..7b404b4 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -586,6 +586,35 @@
/**
* {@inheritDoc}
*/
+ public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+ ViewParent parent = getParent();
+ if (parent == null) {
+ return false;
+ }
+ final boolean propagate = onRequestSendAccessibilityEvent(child, event);
+ if (!propagate) {
+ return false;
+ }
+ return parent.requestSendAccessibilityEvent(this, event);
+ }
+
+ /**
+ * Called when a child has requested sending an {@link AccessibilityEvent} and
+ * gives an opportunity to its parent to augment the event.
+ *
+ * @param child The child which requests sending the event.
+ * @param event The event to be sent.
+ * @return True if the event should be sent.
+ *
+ * @see #requestSendAccessibilityEvent(View, AccessibilityEvent)
+ */
+ public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
@Override
public boolean dispatchUnhandledMove(View focused, int direction) {
return mFocused != null &&
@@ -1216,9 +1245,8 @@
eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT);
handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, mHoveredChild);
eventNoHistory.setAction(action);
-
mHoveredChild = null;
- } else if (action == MotionEvent.ACTION_HOVER_MOVE) {
+ } else {
// Pointer is still within the child.
handled |= dispatchTransformedGenericPointerEvent(event, mHoveredChild);
}
@@ -1278,6 +1306,17 @@
return handled;
}
+ @Override
+ public boolean onHoverEvent(MotionEvent event) {
+ // Handle the event only if leaf. This guarantees that
+ // the leafs (or any custom class that returns true from
+ // this method) will get a change to process the hover.
+ if (getChildCount() == 0) {
+ return super.onHoverEvent(event);
+ }
+ return false;
+ }
+
private static MotionEvent obtainMotionEventNoHistoryOrSelf(MotionEvent event) {
if (event.getHistorySize() == 0) {
return event;
@@ -2091,11 +2130,16 @@
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- boolean populated = false;
+ // We first get a chance to populate the event.
+ onPopulateAccessibilityEvent(event);
+ // Let our children have a shot in populating the event.
for (int i = 0, count = getChildCount(); i < count; i++) {
- populated |= getChildAt(i).dispatchPopulateAccessibilityEvent(event);
+ boolean handled = getChildAt(i).dispatchPopulateAccessibilityEvent(event);
+ if (handled) {
+ return handled;
+ }
}
- return populated;
+ return false;
}
/**
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index d7d4c3f..655df39 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -17,6 +17,7 @@
package android.view;
import android.graphics.Rect;
+import android.view.accessibility.AccessibilityEvent;
/**
* Defines the responsibilities for a class that will be a parent of a View.
@@ -222,4 +223,22 @@
*/
public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
boolean immediate);
+
+ /**
+ * Called by a child to request from its parent to send an {@link AccessibilityEvent}.
+ * The child has already populated a record for itself in the event and is delegating
+ * to its parent to send the event. The parent can optionally add a record for itself.
+ * <p>
+ * Note: An accessibility event is fired by an individual view which populates the
+ * event with a record for its state and requests from its parent to perform
+ * the sending. The parent can optionally add a record for itself before
+ * dispatching the request to its parent. A parent can also choose not to
+ * respect the request for sending the event. The accessibility event is sent
+ * by the topmost view in the view tree.
+ *
+ * @param child The child which requests sending the event.
+ * @param event The event to be sent.
+ * @return True if the event was sent.
+ */
+ public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event);
}
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 4104b07..f02daba 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -3530,6 +3530,14 @@
public void childDrawableStateChanged(View child) {
}
+ public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+ if (mView == null) {
+ return false;
+ }
+ AccessibilityManager.getInstance(child.mContext).sendAccessibilityEvent(event);
+ return true;
+ }
+
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 9af19b8..11c9392 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -21,13 +21,26 @@
import android.text.TextUtils;
import java.util.ArrayList;
-import java.util.List;
/**
* This class represents accessibility events that are sent by the system when
* something notable happens in the user interface. For example, when a
* {@link android.widget.Button} is clicked, a {@link android.view.View} is focused, etc.
* <p>
+ * An accessibility event is fired by an individual view which populates the event with
+ * a record for its state and requests from its parent to send the event to interested
+ * parties. The parent can optionally add a record for itself before dispatching a similar
+ * request to its parent. A parent can also choose not to respect the request for sending
+ * an event. The accessibility event is sent by the topmost view in the view tree.
+ * Therefore, an {@link android.accessibilityservice.AccessibilityService} can explore
+ * all records in an accessibility event to obtain more information about the context
+ * in which the event was fired.
+ * <p>
+ * A client can add, remove, and modify records. The getters and setters for individual
+ * properties operate on the current record which can be explicitly set by the client. By
+ * default current is the first record. Thus, querying a record would require setting
+ * it as the current one and interacting with the property getters and setters.
+ * <p>
* This class represents various semantically different accessibility event
* types. Each event type has associated a set of related properties. In other
* words, each event type is characterized via a subset of the properties exposed
@@ -145,7 +158,7 @@
* @see android.view.accessibility.AccessibilityManager
* @see android.accessibilityservice.AccessibilityService
*/
-public final class AccessibilityEvent implements Parcelable {
+public final class AccessibilityEvent extends AccessibilityRecord implements Parcelable {
/**
* Invalid selection/focus position.
@@ -207,6 +220,26 @@
public static final int TYPE_NOTIFICATION_STATE_CHANGED = 0x00000040;
/**
+ * Represents the event of a hover enter over a {@link android.view.View}.
+ */
+ public static final int TYPE_VIEW_HOVER_ENTER = 0x00000080;
+
+ /**
+ * Represents the event of a hover exit over a {@link android.view.View}.
+ */
+ public static final int TYPE_VIEW_HOVER_EXIT = 0x00000100;
+
+ /**
+ * Represents the event of starting a touch exploration gesture.
+ */
+ public static final int TYPE_TOUCH_EXPLORATION_GESTURE_START = 0x00000200;
+
+ /**
+ * Represents the event of ending a touch exploration gesture.
+ */
+ public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 0x00000400;
+
+ /**
* Mask for {@link AccessibilityEvent} all types.
*
* @see #TYPE_VIEW_CLICKED
@@ -219,116 +252,53 @@
*/
public static final int TYPES_ALL_MASK = 0xFFFFFFFF;
- private static final int MAX_POOL_SIZE = 2;
+ private static final int MAX_POOL_SIZE = 10;
private static final Object mPoolLock = new Object();
private static AccessibilityEvent sPool;
private static int sPoolSize;
- private static final int CHECKED = 0x00000001;
- private static final int ENABLED = 0x00000002;
- private static final int PASSWORD = 0x00000004;
- private static final int FULL_SCREEN = 0x00000080;
-
private AccessibilityEvent mNext;
+ private boolean mIsInPool;
private int mEventType;
- private int mBooleanProperties;
- private int mCurrentItemIndex;
- private int mItemCount;
- private int mFromIndex;
- private int mAddedCount;
- private int mRemovedCount;
-
+ private CharSequence mPackageName;
private long mEventTime;
- private CharSequence mClassName;
- private CharSequence mPackageName;
- private CharSequence mContentDescription;
- private CharSequence mBeforeText;
-
- private Parcelable mParcelableData;
-
- private final List<CharSequence> mText = new ArrayList<CharSequence>();
-
- private boolean mIsInPool;
+ private final ArrayList<AccessibilityRecord> mRecords = new ArrayList<AccessibilityRecord>();
/*
* Hide constructor from clients.
*/
private AccessibilityEvent() {
- mCurrentItemIndex = INVALID_POSITION;
+
}
/**
- * Gets if the source is checked.
+ * Gets the number of records contained in the event.
*
- * @return True if the view is checked, false otherwise.
+ * @return The number of records.
*/
- public boolean isChecked() {
- return getBooleanProperty(CHECKED);
+ public int getRecordCount() {
+ return mRecords.size();
}
/**
- * Sets if the source is checked.
+ * Appends an {@link AccessibilityRecord} to the end of event records.
*
- * @param isChecked True if the view is checked, false otherwise.
+ * @param record The record to append.
*/
- public void setChecked(boolean isChecked) {
- setBooleanProperty(CHECKED, isChecked);
+ public void appendRecord(AccessibilityRecord record) {
+ mRecords.add(record);
}
/**
- * Gets if the source is enabled.
+ * Gets the records at a given index.
*
- * @return True if the view is enabled, false otherwise.
+ * @param index The index.
+ * @return The records at the specified index.
*/
- public boolean isEnabled() {
- return getBooleanProperty(ENABLED);
- }
-
- /**
- * Sets if the source is enabled.
- *
- * @param isEnabled True if the view is enabled, false otherwise.
- */
- public void setEnabled(boolean isEnabled) {
- setBooleanProperty(ENABLED, isEnabled);
- }
-
- /**
- * Gets if the source is a password field.
- *
- * @return True if the view is a password field, false otherwise.
- */
- public boolean isPassword() {
- return getBooleanProperty(PASSWORD);
- }
-
- /**
- * Sets if the source is a password field.
- *
- * @param isPassword True if the view is a password field, false otherwise.
- */
- public void setPassword(boolean isPassword) {
- setBooleanProperty(PASSWORD, isPassword);
- }
-
- /**
- * Sets if the source is taking the entire screen.
- *
- * @param isFullScreen True if the source is full screen, false otherwise.
- */
- public void setFullScreen(boolean isFullScreen) {
- setBooleanProperty(FULL_SCREEN, isFullScreen);
- }
-
- /**
- * Gets if the source is taking the entire screen.
- *
- * @return True if the source is full screen, false otherwise.
- */
- public boolean isFullScreen() {
- return getBooleanProperty(FULL_SCREEN);
+ public AccessibilityRecord getRecord(int index) {
+ return mRecords.get(index);
}
/**
@@ -350,96 +320,6 @@
}
/**
- * Gets the number of items that can be visited.
- *
- * @return The number of items.
- */
- public int getItemCount() {
- return mItemCount;
- }
-
- /**
- * Sets the number of items that can be visited.
- *
- * @param itemCount The number of items.
- */
- public void setItemCount(int itemCount) {
- mItemCount = itemCount;
- }
-
- /**
- * Gets the index of the source in the list of items the can be visited.
- *
- * @return The current item index.
- */
- public int getCurrentItemIndex() {
- return mCurrentItemIndex;
- }
-
- /**
- * Sets the index of the source in the list of items that can be visited.
- *
- * @param currentItemIndex The current item index.
- */
- public void setCurrentItemIndex(int currentItemIndex) {
- mCurrentItemIndex = currentItemIndex;
- }
-
- /**
- * Gets the index of the first character of the changed sequence.
- *
- * @return The index of the first character.
- */
- public int getFromIndex() {
- return mFromIndex;
- }
-
- /**
- * Sets the index of the first character of the changed sequence.
- *
- * @param fromIndex The index of the first character.
- */
- public void setFromIndex(int fromIndex) {
- mFromIndex = fromIndex;
- }
-
- /**
- * Gets the number of added characters.
- *
- * @return The number of added characters.
- */
- public int getAddedCount() {
- return mAddedCount;
- }
-
- /**
- * Sets the number of added characters.
- *
- * @param addedCount The number of added characters.
- */
- public void setAddedCount(int addedCount) {
- mAddedCount = addedCount;
- }
-
- /**
- * Gets the number of removed characters.
- *
- * @return The number of removed characters.
- */
- public int getRemovedCount() {
- return mRemovedCount;
- }
-
- /**
- * Sets the number of removed characters.
- *
- * @param removedCount The number of removed characters.
- */
- public void setRemovedCount(int removedCount) {
- mRemovedCount = removedCount;
- }
-
- /**
* Gets the time in which this event was sent.
*
* @return The event time.
@@ -458,24 +338,6 @@
}
/**
- * Gets the class name of the source.
- *
- * @return The class name.
- */
- public CharSequence getClassName() {
- return mClassName;
- }
-
- /**
- * Sets the class name of the source.
- *
- * @param className The lass name.
- */
- public void setClassName(CharSequence className) {
- mClassName = className;
- }
-
- /**
* Gets the package name of the source.
*
* @return The package name.
@@ -494,70 +356,6 @@
}
/**
- * Gets the text of the event. The index in the list represents the priority
- * of the text. Specifically, the lower the index the higher the priority.
- *
- * @return The text.
- */
- public List<CharSequence> getText() {
- return mText;
- }
-
- /**
- * Sets the text before a change.
- *
- * @return The text before the change.
- */
- public CharSequence getBeforeText() {
- return mBeforeText;
- }
-
- /**
- * Sets the text before a change.
- *
- * @param beforeText The text before the change.
- */
- public void setBeforeText(CharSequence beforeText) {
- mBeforeText = beforeText;
- }
-
- /**
- * Gets the description of the source.
- *
- * @return The description.
- */
- public CharSequence getContentDescription() {
- return mContentDescription;
- }
-
- /**
- * Sets the description of the source.
- *
- * @param contentDescription The description.
- */
- public void setContentDescription(CharSequence contentDescription) {
- mContentDescription = contentDescription;
- }
-
- /**
- * Gets the {@link Parcelable} data.
- *
- * @return The parcelable data.
- */
- public Parcelable getParcelableData() {
- return mParcelableData;
- }
-
- /**
- * Sets the {@link Parcelable} data of the event.
- *
- * @param parcelableData The parcelable data.
- */
- public void setParcelableData(Parcelable parcelableData) {
- mParcelableData = parcelableData;
- }
-
- /**
* Returns a cached instance if such is available or a new one is
* instantiated with type property set.
*
@@ -595,11 +393,11 @@
* <p>
* <b>Note: You must not touch the object after calling this function.</b>
*/
+ @Override
public void recycle() {
if (mIsInPool) {
return;
}
-
clear();
synchronized (mPoolLock) {
if (sPoolSize <= MAX_POOL_SIZE) {
@@ -614,44 +412,15 @@
/**
* Clears the state of this instance.
*/
- private void clear() {
+ @Override
+ protected void clear() {
+ super.clear();
mEventType = 0;
- mBooleanProperties = 0;
- mCurrentItemIndex = INVALID_POSITION;
- mItemCount = 0;
- mFromIndex = 0;
- mAddedCount = 0;
- mRemovedCount = 0;
- mEventTime = 0;
- mClassName = null;
mPackageName = null;
- mContentDescription = null;
- mBeforeText = null;
- mParcelableData = null;
- mText.clear();
- }
-
- /**
- * Gets the value of a boolean property.
- *
- * @param property The property.
- * @return The value.
- */
- private boolean getBooleanProperty(int property) {
- return (mBooleanProperties & property) == property;
- }
-
- /**
- * Sets a boolean property.
- *
- * @param property The property.
- * @param value The value.
- */
- private void setBooleanProperty(int property, boolean value) {
- if (value) {
- mBooleanProperties |= property;
- } else {
- mBooleanProperties &= ~property;
+ mEventTime = 0;
+ while (!mRecords.isEmpty()) {
+ AccessibilityRecord record = mRecords.remove(0);
+ record.recycle();
}
}
@@ -662,38 +431,82 @@
*/
public void initFromParcel(Parcel parcel) {
mEventType = parcel.readInt();
- mBooleanProperties = parcel.readInt();
- mCurrentItemIndex = parcel.readInt();
- mItemCount = parcel.readInt();
- mFromIndex = parcel.readInt();
- mAddedCount = parcel.readInt();
- mRemovedCount = parcel.readInt();
- mEventTime = parcel.readLong();
- mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
- mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
- mBeforeText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
- mParcelableData = parcel.readParcelable(null);
- parcel.readList(mText, null);
+ mEventTime = parcel.readLong();
+ readAccessibilityRecordFromParcel(this, parcel);
+
+ // Read the records.
+ final int recordCount = parcel.readInt();
+ for (int i = 0; i < recordCount; i++) {
+ AccessibilityRecord record = AccessibilityRecord.obtain();
+ readAccessibilityRecordFromParcel(record, parcel);
+ mRecords.add(record);
+ }
}
+ /**
+ * Reads an {@link AccessibilityRecord} from a parcel.
+ *
+ * @param record The record to initialize.
+ * @param parcel The parcel to read from.
+ */
+ private void readAccessibilityRecordFromParcel(AccessibilityRecord record,
+ Parcel parcel) {
+ record.mBooleanProperties = parcel.readInt();
+ record.mCurrentItemIndex = parcel.readInt();
+ record.mItemCount = parcel.readInt();
+ record.mFromIndex = parcel.readInt();
+ record.mAddedCount = parcel.readInt();
+ record.mRemovedCount = parcel.readInt();
+ record.mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ record.mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ record.mBeforeText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ record.mParcelableData = parcel.readParcelable(null);
+ parcel.readList(record.mText, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(mEventType);
- parcel.writeInt(mBooleanProperties);
- parcel.writeInt(mCurrentItemIndex);
- parcel.writeInt(mItemCount);
- parcel.writeInt(mFromIndex);
- parcel.writeInt(mAddedCount);
- parcel.writeInt(mRemovedCount);
- parcel.writeLong(mEventTime);
- TextUtils.writeToParcel(mClassName, parcel, 0);
TextUtils.writeToParcel(mPackageName, parcel, 0);
- TextUtils.writeToParcel(mContentDescription, parcel, 0);
- TextUtils.writeToParcel(mBeforeText, parcel, 0);
- parcel.writeParcelable(mParcelableData, flags);
- parcel.writeList(mText);
+ parcel.writeLong(mEventTime);
+ writeAccessibilityRecordToParcel(this, parcel, flags);
+
+ // Write the records.
+ final int recordCount = getRecordCount();
+ parcel.writeInt(recordCount);
+ for (int i = 0; i < recordCount; i++) {
+ AccessibilityRecord record = mRecords.get(i);
+ writeAccessibilityRecordToParcel(record, parcel, flags);
+ }
}
+ /**
+ * Writes an {@link AccessibilityRecord} to a parcel.
+ *
+ * @param record The record to write.
+ * @param parcel The parcel to which to write.
+ */
+ private void writeAccessibilityRecordToParcel(AccessibilityRecord record, Parcel parcel,
+ int flags) {
+ parcel.writeInt(record.mBooleanProperties);
+ parcel.writeInt(record.mCurrentItemIndex);
+ parcel.writeInt(record.mItemCount);
+ parcel.writeInt(record.mFromIndex);
+ parcel.writeInt(record.mAddedCount);
+ parcel.writeInt(record.mRemovedCount);
+ TextUtils.writeToParcel(record.mClassName, parcel, flags);
+ TextUtils.writeToParcel(record.mContentDescription, parcel, flags);
+ TextUtils.writeToParcel(record.mBeforeText, parcel, flags);
+ parcel.writeParcelable(record.mParcelableData, flags);
+ parcel.writeList(record.mText);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
public int describeContents() {
return 0;
}
@@ -701,24 +514,21 @@
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- builder.append(super.toString());
builder.append("; EventType: " + mEventType);
builder.append("; EventTime: " + mEventTime);
- builder.append("; ClassName: " + mClassName);
builder.append("; PackageName: " + mPackageName);
- builder.append("; Text: " + mText);
- builder.append("; ContentDescription: " + mContentDescription);
- builder.append("; ItemCount: " + mItemCount);
- builder.append("; CurrentItemIndex: " + mCurrentItemIndex);
- builder.append("; IsEnabled: " + isEnabled());
- builder.append("; IsPassword: " + isPassword());
- builder.append("; IsChecked: " + isChecked());
- builder.append("; IsFullScreen: " + isFullScreen());
- builder.append("; BeforeText: " + mBeforeText);
- builder.append("; FromIndex: " + mFromIndex);
- builder.append("; AddedCount: " + mAddedCount);
- builder.append("; RemovedCount: " + mRemovedCount);
- builder.append("; ParcelableData: " + mParcelableData);
+ builder.append(" \n{\n");
+ builder.append(super.toString());
+ builder.append("\n");
+ for (int i = 0; i < mRecords.size(); i++) {
+ AccessibilityRecord record = mRecords.get(i);
+ builder.append(" Record ");
+ builder.append(i);
+ builder.append(":");
+ builder.append(record.toString());
+ builder.append("\n");
+ }
+ builder.append("}\n");
return builder.toString();
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 22cb0d4..dd77193 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -16,6 +16,8 @@
package android.view.accessibility;
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context;
import android.content.pm.ServiceInfo;
import android.os.Binder;
@@ -44,6 +46,8 @@
* @see android.content.Context#getSystemService
*/
public final class AccessibilityManager {
+ private static final boolean DEBUG = false;
+
private static final String LOG_TAG = "AccessibilityManager";
static final Object sInstanceSync = new Object();
@@ -164,7 +168,7 @@
long identityToken = Binder.clearCallingIdentity();
doRecycle = mService.sendAccessibilityEvent(event);
Binder.restoreCallingIdentity(identityToken);
- if (false) {
+ if (DEBUG) {
Log.i(LOG_TAG, event + " sent");
}
} catch (RemoteException re) {
@@ -185,7 +189,7 @@
}
try {
mService.interrupt();
- if (false) {
+ if (DEBUG) {
Log.i(LOG_TAG, "Requested interrupt from all services");
}
} catch (RemoteException re) {
@@ -202,7 +206,33 @@
List<ServiceInfo> services = null;
try {
services = mService.getAccessibilityServiceList();
- if (false) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
+ }
+ return Collections.unmodifiableList(services);
+ }
+
+ /**
+ * Returns the {@link ServiceInfo}s of the enabled accessibility services
+ * for a given feedback type.
+ *
+ * @param feedbackType The type of feedback.
+ * @return An unmodifiable list with {@link ServiceInfo}s.
+ *
+ * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE
+ * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
+ * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
+ * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
+ * @see AccessibilityServiceInfo#FEEDBACK_GENERIC
+ */
+ public List<ServiceInfo> getEnabledAccessibilityServiceList(int feedbackType) {
+ List<ServiceInfo> services = null;
+ try {
+ services = mService.getEnabledAccessibilityServiceList(feedbackType);
+ if (DEBUG) {
Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
}
} catch (RemoteException re) {
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
new file mode 100644
index 0000000..e095f43
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -0,0 +1,415 @@
+/*
+ * 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 android.view.accessibility;
+
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a record in an accessibility event. This class encapsulates
+ * the information for a {@link android.view.View}. Note that not all properties
+ * are applicable to all view types. For detailed information please refer to
+ * {@link AccessibilityEvent}.
+ *
+ * @see AccessibilityEvent
+ */
+public class AccessibilityRecord {
+
+ private static final int INVALID_POSITION = -1;
+
+ private static final int PROPERTY_CHECKED = 0x00000001;
+ private static final int PROPERTY_ENABLED = 0x00000002;
+ private static final int PROPERTY_PASSWORD = 0x00000004;
+ private static final int PROPERTY_FULL_SCREEN = 0x00000080;
+
+ private static final int MAX_POOL_SIZE = 10;
+ private static final Object mPoolLock = new Object();
+ private static AccessibilityRecord sPool;
+ private static int sPoolSize;
+
+ private AccessibilityRecord mNext;
+ private boolean mIsInPool;
+
+ protected int mBooleanProperties;
+ protected int mCurrentItemIndex;
+ protected int mItemCount;
+ protected int mFromIndex;
+ protected int mAddedCount;
+ protected int mRemovedCount;
+
+ protected CharSequence mClassName;
+ protected CharSequence mContentDescription;
+ protected CharSequence mBeforeText;
+ protected Parcelable mParcelableData;
+
+ protected final List<CharSequence> mText = new ArrayList<CharSequence>();
+
+ /*
+ * Hide constructor.
+ */
+ protected AccessibilityRecord() {
+
+ }
+
+ /**
+ * Gets if the source is checked.
+ *
+ * @return True if the view is checked, false otherwise.
+ */
+ public boolean isChecked() {
+ return getBooleanProperty(PROPERTY_CHECKED);
+ }
+
+ /**
+ * Sets if the source is checked.
+ *
+ * @param isChecked True if the view is checked, false otherwise.
+ */
+ public void setChecked(boolean isChecked) {
+ setBooleanProperty(PROPERTY_CHECKED, isChecked);
+ }
+
+ /**
+ * Gets if the source is enabled.
+ *
+ * @return True if the view is enabled, false otherwise.
+ */
+ public boolean isEnabled() {
+ return getBooleanProperty(PROPERTY_ENABLED);
+ }
+
+ /**
+ * Sets if the source is enabled.
+ *
+ * @param isEnabled True if the view is enabled, false otherwise.
+ */
+ public void setEnabled(boolean isEnabled) {
+ setBooleanProperty(PROPERTY_ENABLED, isEnabled);
+ }
+
+ /**
+ * Gets if the source is a password field.
+ *
+ * @return True if the view is a password field, false otherwise.
+ */
+ public boolean isPassword() {
+ return getBooleanProperty(PROPERTY_PASSWORD);
+ }
+
+ /**
+ * Sets if the source is a password field.
+ *
+ * @param isPassword True if the view is a password field, false otherwise.
+ */
+ public void setPassword(boolean isPassword) {
+ setBooleanProperty(PROPERTY_PASSWORD, isPassword);
+ }
+
+ /**
+ * Sets if the source is taking the entire screen.
+ *
+ * @param isFullScreen True if the source is full screen, false otherwise.
+ */
+ public void setFullScreen(boolean isFullScreen) {
+ setBooleanProperty(PROPERTY_FULL_SCREEN, isFullScreen);
+ }
+
+ /**
+ * Gets if the source is taking the entire screen.
+ *
+ * @return True if the source is full screen, false otherwise.
+ */
+ public boolean isFullScreen() {
+ return getBooleanProperty(PROPERTY_FULL_SCREEN);
+ }
+
+ /**
+ * Gets the number of items that can be visited.
+ *
+ * @return The number of items.
+ */
+ public int getItemCount() {
+ return mItemCount;
+ }
+
+ /**
+ * Sets the number of items that can be visited.
+ *
+ * @param itemCount The number of items.
+ */
+ public void setItemCount(int itemCount) {
+ mItemCount = itemCount;
+ }
+
+ /**
+ * Gets the index of the source in the list of items the can be visited.
+ *
+ * @return The current item index.
+ */
+ public int getCurrentItemIndex() {
+ return mCurrentItemIndex;
+ }
+
+ /**
+ * Sets the index of the source in the list of items that can be visited.
+ *
+ * @param currentItemIndex The current item index.
+ */
+ public void setCurrentItemIndex(int currentItemIndex) {
+ mCurrentItemIndex = currentItemIndex;
+ }
+
+ /**
+ * Gets the index of the first character of the changed sequence.
+ *
+ * @return The index of the first character.
+ */
+ public int getFromIndex() {
+ return mFromIndex;
+ }
+
+ /**
+ * Sets the index of the first character of the changed sequence.
+ *
+ * @param fromIndex The index of the first character.
+ */
+ public void setFromIndex(int fromIndex) {
+ mFromIndex = fromIndex;
+ }
+
+ /**
+ * Gets the number of added characters.
+ *
+ * @return The number of added characters.
+ */
+ public int getAddedCount() {
+ return mAddedCount;
+ }
+
+ /**
+ * Sets the number of added characters.
+ *
+ * @param addedCount The number of added characters.
+ */
+ public void setAddedCount(int addedCount) {
+ mAddedCount = addedCount;
+ }
+
+ /**
+ * Gets the number of removed characters.
+ *
+ * @return The number of removed characters.
+ */
+ public int getRemovedCount() {
+ return mRemovedCount;
+ }
+
+ /**
+ * Sets the number of removed characters.
+ *
+ * @param removedCount The number of removed characters.
+ */
+ public void setRemovedCount(int removedCount) {
+ mRemovedCount = removedCount;
+ }
+
+ /**
+ * Gets the class name of the source.
+ *
+ * @return The class name.
+ */
+ public CharSequence getClassName() {
+ return mClassName;
+ }
+
+ /**
+ * Sets the class name of the source.
+ *
+ * @param className The lass name.
+ */
+ public void setClassName(CharSequence className) {
+ mClassName = className;
+ }
+
+ /**
+ * Gets the text of the event. The index in the list represents the priority
+ * of the text. Specifically, the lower the index the higher the priority.
+ *
+ * @return The text.
+ */
+ public List<CharSequence> getText() {
+ return mText;
+ }
+
+ /**
+ * Sets the text before a change.
+ *
+ * @return The text before the change.
+ */
+ public CharSequence getBeforeText() {
+ return mBeforeText;
+ }
+
+ /**
+ * Sets the text before a change.
+ *
+ * @param beforeText The text before the change.
+ */
+ public void setBeforeText(CharSequence beforeText) {
+ mBeforeText = beforeText;
+ }
+
+ /**
+ * Gets the description of the source.
+ *
+ * @return The description.
+ */
+ public CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ /**
+ * Sets the description of the source.
+ *
+ * @param contentDescription The description.
+ */
+ public void setContentDescription(CharSequence contentDescription) {
+ mContentDescription = contentDescription;
+ }
+
+ /**
+ * Gets the {@link Parcelable} data.
+ *
+ * @return The parcelable data.
+ */
+ public Parcelable getParcelableData() {
+ return mParcelableData;
+ }
+
+ /**
+ * Sets the {@link Parcelable} data of the event.
+ *
+ * @param parcelableData The parcelable data.
+ */
+ public void setParcelableData(Parcelable parcelableData) {
+ mParcelableData = parcelableData;
+ }
+
+ /**
+ * Gets the value of a boolean property.
+ *
+ * @param property The property.
+ * @return The value.
+ */
+ public boolean getBooleanProperty(int property) {
+ return (mBooleanProperties & property) == property;
+ }
+
+ /**
+ * Sets a boolean property.
+ *
+ * @param property The property.
+ * @param value The value.
+ */
+ private void setBooleanProperty(int property, boolean value) {
+ if (value) {
+ mBooleanProperties |= property;
+ } else {
+ mBooleanProperties &= ~property;
+ }
+ }
+
+ /**
+ * Returns a cached instance if such is available or a new one is
+ * instantiated.
+ *
+ * @return An instance.
+ */
+ protected static AccessibilityRecord obtain() {
+ synchronized (mPoolLock) {
+ if (sPool != null) {
+ AccessibilityRecord record = sPool;
+ sPool = sPool.mNext;
+ sPoolSize--;
+ record.mNext = null;
+ record.mIsInPool = false;
+ return record;
+ }
+ return new AccessibilityRecord();
+ }
+ }
+
+ /**
+ * Return an instance back to be reused.
+ * <p>
+ * <b>Note: You must not touch the object after calling this function.</b>
+ */
+ public void recycle() {
+ if (mIsInPool) {
+ return;
+ }
+ clear();
+ synchronized (mPoolLock) {
+ if (sPoolSize <= MAX_POOL_SIZE) {
+ mNext = sPool;
+ sPool = this;
+ mIsInPool = true;
+ sPoolSize++;
+ }
+ }
+ }
+
+ /**
+ * Clears the state of this instance.
+ */
+ protected void clear() {
+ mBooleanProperties = 0;
+ mCurrentItemIndex = INVALID_POSITION;
+ mItemCount = 0;
+ mFromIndex = 0;
+ mAddedCount = 0;
+ mRemovedCount = 0;
+ mClassName = null;
+ mContentDescription = null;
+ mBeforeText = null;
+ mParcelableData = null;
+ mText.clear();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(" [ ClassName: " + mClassName);
+ builder.append("; Text: " + mText);
+ builder.append("; ContentDescription: " + mContentDescription);
+ builder.append("; ItemCount: " + mItemCount);
+ builder.append("; CurrentItemIndex: " + mCurrentItemIndex);
+ builder.append("; IsEnabled: " + getBooleanProperty(PROPERTY_ENABLED));
+ builder.append("; IsPassword: " + getBooleanProperty(PROPERTY_PASSWORD));
+ builder.append("; IsChecked: " + getBooleanProperty(PROPERTY_CHECKED));
+ builder.append("; IsFullScreen: " + getBooleanProperty(PROPERTY_FULL_SCREEN));
+ builder.append("; BeforeText: " + mBeforeText);
+ builder.append("; FromIndex: " + mFromIndex);
+ builder.append("; AddedCount: " + mAddedCount);
+ builder.append("; RemovedCount: " + mRemovedCount);
+ builder.append("; ParcelableData: " + mParcelableData);
+ builder.append(" ]");
+ return builder.toString();
+ }
+}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 7633569d..aaaae32 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -35,5 +35,7 @@
List<ServiceInfo> getAccessibilityServiceList();
+ List<ServiceInfo> getEnabledAccessibilityServiceList(int feedbackType);
+
void interrupt();
}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 6cb5c35..d63d421 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -55,6 +55,7 @@
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
@@ -2556,6 +2557,17 @@
}
@Override
+ public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+ // Add a record for ourselves as well.
+ AccessibilityEvent record = AccessibilityEvent.obtain();
+ // Set the class since it is not populated in #dispatchPopulateAccessibilityEvent
+ record.setClassName(getClass().getName());
+ child.dispatchPopulateAccessibilityEvent(record);
+ event.appendRecord(record);
+ return true;
+ }
+
+ @Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return false;
}
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index f16efbd..060f1a9 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -876,7 +876,6 @@
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- boolean populated = false;
// This is an exceptional case which occurs when a window gets the
// focus and sends a focus event via its focused child to announce
// current focus/selection. AdapterView fires selection but not focus
@@ -885,22 +884,27 @@
event.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED);
}
- // we send selection events only from AdapterView to avoid
- // generation of such event for each child
+ // We first get a chance to populate the event.
+ onPopulateAccessibilityEvent(event);
+
+ // We send selection events only from AdapterView to avoid
+ // generation of such event for each child.
View selectedView = getSelectedView();
if (selectedView != null) {
- populated = selectedView.dispatchPopulateAccessibilityEvent(event);
+ return selectedView.dispatchPopulateAccessibilityEvent(event);
}
- if (!populated) {
- if (selectedView != null) {
- event.setEnabled(selectedView.isEnabled());
- }
- event.setItemCount(getCount());
- event.setCurrentItemIndex(getSelectedItemPosition());
- }
+ return false;
+ }
- return populated;
+ @Override
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ View selectedView = getSelectedView();
+ if (selectedView != null) {
+ event.setEnabled(selectedView.isEnabled());
+ }
+ event.setItemCount(getCount());
+ event.setCurrentItemIndex(getSelectedItemPosition());
}
@Override
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index bf63607..bd595a5 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -199,11 +199,8 @@
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- boolean populated = super.dispatchPopulateAccessibilityEvent(event);
- if (!populated) {
- event.setChecked(mChecked);
- }
- return populated;
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(event);
+ event.setChecked(mChecked);
}
}
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 0df45cc..f050d41 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -208,22 +208,18 @@
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- boolean populated = super.dispatchPopulateAccessibilityEvent(event);
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(event);
- if (!populated) {
- int resourceId = 0;
- if (mChecked) {
- resourceId = R.string.accessibility_compound_button_selected;
- } else {
- resourceId = R.string.accessibility_compound_button_unselected;
- }
- String state = getResources().getString(resourceId);
- event.getText().add(state);
- event.setChecked(mChecked);
+ int resourceId = 0;
+ if (mChecked) {
+ resourceId = R.string.accessibility_compound_button_selected;
+ } else {
+ resourceId = R.string.accessibility_compound_button_unselected;
}
-
- return populated;
+ String state = getResources().getString(resourceId);
+ event.getText().add(state);
+ event.setChecked(mChecked);
}
@Override
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 7210e21..30fb927 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -353,13 +353,14 @@
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(event);
+
+ final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY
| DateUtils.FORMAT_SHOW_YEAR;
String selectedDateUtterance = DateUtils.formatDateTime(mContext,
mCurrentDate.getTimeInMillis(), flags);
event.getText().add(selectedDateUtterance);
- return true;
}
/**
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index d76a956..5618dbe 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1998,36 +1998,32 @@
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- boolean populated = super.dispatchPopulateAccessibilityEvent(event);
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(event);
// If the item count is less than 15 then subtract disabled items from the count and
// position. Otherwise ignore disabled items.
- if (!populated) {
- int itemCount = 0;
- int currentItemIndex = getSelectedItemPosition();
+ int itemCount = 0;
+ int currentItemIndex = getSelectedItemPosition();
- ListAdapter adapter = getAdapter();
- if (adapter != null) {
- final int count = adapter.getCount();
- if (count < 15) {
- for (int i = 0; i < count; i++) {
- if (adapter.isEnabled(i)) {
- itemCount++;
- } else if (i <= currentItemIndex) {
- currentItemIndex--;
- }
+ ListAdapter adapter = getAdapter();
+ if (adapter != null) {
+ final int count = adapter.getCount();
+ if (count < 15) {
+ for (int i = 0; i < count; i++) {
+ if (adapter.isEnabled(i)) {
+ itemCount++;
+ } else if (i <= currentItemIndex) {
+ currentItemIndex--;
}
- } else {
- itemCount = count;
}
+ } else {
+ itemCount = count;
}
-
- event.setItemCount(itemCount);
- event.setCurrentItemIndex(currentItemIndex);
}
- return populated;
+ event.setItemCount(itemCount);
+ event.setCurrentItemIndex(currentItemIndex);
}
/**
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 8db34d9..96d41a0 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -1027,12 +1027,10 @@
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- if (!super.dispatchPopulateAccessibilityEvent(event)) {
- event.setItemCount(mMax);
- event.setCurrentItemIndex(mProgress);
- }
- return true;
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(event);
+ event.setItemCount(mMax);
+ event.setCurrentItemIndex(mProgress);
}
/**
diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java
index 6f76dd0..31ec785 100644
--- a/core/java/android/widget/TabWidget.java
+++ b/core/java/android/widget/TabWidget.java
@@ -427,12 +427,19 @@
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ onPopulateAccessibilityEvent(event);
+ // Dispatch only to the selected tab.
+ if (mSelectedTab != -1) {
+ return getChildTabViewAt(mSelectedTab).dispatchPopulateAccessibilityEvent(event);
+ }
+ return false;
+ }
+
+ @Override
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(event);
event.setItemCount(getTabCount());
event.setCurrentItemIndex(mSelectedTab);
- if (mSelectedTab != -1) {
- getChildTabViewAt(mSelectedTab).dispatchPopulateAccessibilityEvent(event);
- }
- return true;
}
/**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 8110d8e..4d3aa68 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -2967,14 +2967,14 @@
advancesIndex);
}
- public float getTextRunAdvancesICU(int start, int end, int contextStart,
+ public float getTextRunAdvances(int start, int end, int contextStart,
int contextEnd, int flags, float[] advances, int advancesIndex,
- Paint p) {
+ Paint p, int reserved) {
int count = end - start;
int contextCount = contextEnd - contextStart;
- return p.getTextRunAdvancesICU(mChars, start + mStart, count,
+ return p.getTextRunAdvances(mChars, start + mStart, count,
contextStart + mStart, contextCount, flags, advances,
- advancesIndex);
+ advancesIndex, reserved);
}
public int getTextRunCursor(int contextStart, int contextEnd, int flags,
@@ -7896,9 +7896,9 @@
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
if (!isShown()) {
- return false;
+ return;
}
final boolean isPassword = hasPasswordTransformationMethod();
@@ -7914,7 +7914,6 @@
} else {
event.setPassword(isPassword);
}
- return false;
}
void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 029d690..423e735c 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -409,7 +409,9 @@
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(event);
+
int flags = DateUtils.FORMAT_SHOW_TIME;
if (mIs24HourView) {
flags |= DateUtils.FORMAT_24HOUR;
@@ -421,7 +423,6 @@
String selectedDateUtterance = DateUtils.formatDateTime(mContext,
mTempCalendar.getTimeInMillis(), flags);
event.getText().add(selectedDateUtterance);
- return true;
}
private void updateHourControl() {
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 587d678..fa8eb51 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -457,11 +457,11 @@
mIconView.getPaddingTop() - mIconView.getPaddingBottom();
int iconSize = res.getDimensionPixelSize(android.R.dimen.app_icon_size);
- if (iconSize * DisplayMetrics.DENSITY_LOW > availableHeight) {
+ if (iconSize * DisplayMetrics.DENSITY_LOW >= availableHeight) {
return DisplayMetrics.DENSITY_LOW;
- } else if (iconSize * DisplayMetrics.DENSITY_MEDIUM > availableHeight) {
+ } else if (iconSize * DisplayMetrics.DENSITY_MEDIUM >= availableHeight) {
return DisplayMetrics.DENSITY_MEDIUM;
- } else if (iconSize * DisplayMetrics.DENSITY_HIGH > availableHeight) {
+ } else if (iconSize * DisplayMetrics.DENSITY_HIGH >= availableHeight) {
return DisplayMetrics.DENSITY_HIGH;
}
return DisplayMetrics.DENSITY_XHIGH;
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 0a54e17..768b836 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -482,44 +482,28 @@
return totalAdvance;
}
- static float getTextRunAdvances___CIIIII_FI(JNIEnv* env, jobject clazz, SkPaint* paint,
+ static float getTextRunAdvances___CIIIII_FII(JNIEnv* env, jobject clazz, SkPaint* paint,
jcharArray text, jint index, jint count, jint contextIndex, jint contextCount,
- jint flags, jfloatArray advances, jint advancesIndex) {
+ jint flags, jfloatArray advances, jint advancesIndex, jint reserved) {
jchar* textArray = env->GetCharArrayElements(text, NULL);
- jfloat result = doTextRunAdvances(env, paint, textArray + contextIndex,
- index - contextIndex, count, contextCount, flags, advances, advancesIndex);
+ jfloat result = (reserved == 0) ?
+ doTextRunAdvances(env, paint, textArray + contextIndex, index - contextIndex,
+ count, contextCount, flags, advances, advancesIndex) :
+ doTextRunAdvancesICU(env, paint, textArray + contextIndex, index - contextIndex,
+ count, contextCount, flags, advances, advancesIndex);
env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
return result;
}
- static float getTextRunAdvances__StringIIIII_FI(JNIEnv* env, jobject clazz, SkPaint* paint,
+ static float getTextRunAdvances__StringIIIII_FII(JNIEnv* env, jobject clazz, SkPaint* paint,
jstring text, jint start, jint end, jint contextStart, jint contextEnd, jint flags,
- jfloatArray advances, jint advancesIndex) {
+ jfloatArray advances, jint advancesIndex, jint reserved) {
const jchar* textArray = env->GetStringChars(text, NULL);
- jfloat result = doTextRunAdvances(env, paint, textArray + contextStart,
- start - contextStart, end - start, contextEnd - contextStart, flags, advances,
- advancesIndex);
- env->ReleaseStringChars(text, textArray);
- return result;
- }
-
- static float getTextRunAdvancesICU___CIIIII_FI(JNIEnv* env, jobject clazz, SkPaint* paint,
- jcharArray text, jint index, jint count, jint contextIndex, jint contextCount,
- jint flags, jfloatArray advances, jint advancesIndex) {
- jchar* textArray = env->GetCharArrayElements(text, NULL);
- jfloat result = doTextRunAdvancesICU(env, paint, textArray + contextIndex,
- index - contextIndex, count, contextCount, flags, advances, advancesIndex);
- env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
- return result;
- }
-
- static float getTextRunAdvancesICU__StringIIIII_FI(JNIEnv* env, jobject clazz, SkPaint* paint,
- jstring text, jint start, jint end, jint contextStart, jint contextEnd, jint flags,
- jfloatArray advances, jint advancesIndex) {
- const jchar* textArray = env->GetStringChars(text, NULL);
- jfloat result = doTextRunAdvancesICU(env, paint, textArray + contextStart,
- start - contextStart, end - start, contextEnd - contextStart, flags, advances,
- advancesIndex);
+ jfloat result = (reserved == 0) ?
+ doTextRunAdvances(env, paint, textArray + contextStart, start - contextStart,
+ end - start, contextEnd - contextStart, flags, advances, advancesIndex) :
+ doTextRunAdvancesICU(env, paint, textArray + contextStart, start - contextStart,
+ end - start, contextEnd - contextStart, flags, advances, advancesIndex);
env->ReleaseStringChars(text, textArray);
return result;
}
@@ -816,14 +800,12 @@
{"native_breakText","(Ljava/lang/String;ZF[F)I", (void*) SkPaintGlue::breakTextS},
{"native_getTextWidths","(I[CII[F)I", (void*) SkPaintGlue::getTextWidths___CII_F},
{"native_getTextWidths","(ILjava/lang/String;II[F)I", (void*) SkPaintGlue::getTextWidths__StringII_F},
- {"native_getTextRunAdvances","(I[CIIIII[FI)F",
- (void*) SkPaintGlue::getTextRunAdvances___CIIIII_FI},
- {"native_getTextRunAdvances","(ILjava/lang/String;IIIII[FI)F",
- (void*) SkPaintGlue::getTextRunAdvances__StringIIIII_FI},
- {"native_getTextRunAdvancesICU","(I[CIIIII[FI)F",
- (void*) SkPaintGlue::getTextRunAdvancesICU___CIIIII_FI},
- {"native_getTextRunAdvancesICU","(ILjava/lang/String;IIIII[FI)F",
- (void*) SkPaintGlue::getTextRunAdvancesICU__StringIIIII_FI},
+ {"native_getTextRunAdvances","(I[CIIIII[FII)F",
+ (void*) SkPaintGlue::getTextRunAdvances___CIIIII_FII},
+ {"native_getTextRunAdvances","(ILjava/lang/String;IIIII[FII)F",
+ (void*) SkPaintGlue::getTextRunAdvances__StringIIIII_FII},
+
+
{"native_getTextGlyphs","(ILjava/lang/String;IIIII[C)I",
(void*) SkPaintGlue::getTextGlyphs__StringIIIII_C},
{"native_getTextRunCursor", "(I[CIIIII)I", (void*) SkPaintGlue::getTextRunCursor___C},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7f18121..2ed39e4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -550,6 +550,14 @@
android:description="@string/permdesc_sdcardWrite"
android:protectionLevel="dangerous" />
+ <!-- Allows an application to write to internal media storage
+ @hide -->
+ <permission android:name="android.permission.WRITE_MEDIA_STORAGE"
+ android:permissionGroup="android.permission-group.STORAGE"
+ android:label="@string/permlab_mediaStorageWrite"
+ android:description="@string/permdesc_mediaStorageWrite"
+ android:protectionLevel="signatureOrSystem" />
+
<!-- ============================================ -->
<!-- Permissions for low-level system interaction -->
<!-- ============================================ -->
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index df80546..1870a4a 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -58,6 +58,10 @@
<group gid="sdcard_rw" />
</permission>
+ <permission name="android.permission.WRITE_MEDIA_STORAGE" >
+ <group gid="media_rw" />
+ </permission>
+
<permission name="android.permission.ACCESS_MTP" >
<group gid="mtp" />
</permission>
diff --git a/docs/html/guide/topics/ui/actionbar.jd b/docs/html/guide/topics/ui/actionbar.jd
index d8898ae3e..7e13569 100644
--- a/docs/html/guide/topics/ui/actionbar.jd
+++ b/docs/html/guide/topics/ui/actionbar.jd
@@ -429,21 +429,18 @@
private TabContentFragment mFragment;
// Called to create an instance of the listener when adding a new tab
- public TabListener(TabContentFragment fragment) {
+ public MyTabListener(TabContentFragment fragment) {
mFragment = fragment;
}
- @Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
ft.add(R.id.fragment_content, mFragment, null);
}
- @Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
ft.remove(mFragment);
}
- @Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
// do nothing
}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 0949beb..962f22c 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1501,6 +1501,20 @@
public float getTextRunAdvances(char[] chars, int index, int count,
int contextIndex, int contextCount, int flags, float[] advances,
int advancesIndex) {
+ return getTextRunAdvances(chars, index, count, contextIndex, contextCount, flags,
+ advances, advancesIndex, 0 /* use Harfbuzz*/);
+ }
+
+ /**
+ * Convenience overload that takes a char array instead of a
+ * String.
+ *
+ * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int, int)
+ * @hide
+ */
+ public float getTextRunAdvances(char[] chars, int index, int count,
+ int contextIndex, int contextCount, int flags, float[] advances,
+ int advancesIndex, int reserved) {
if ((index | count | contextIndex | contextCount | advancesIndex
| (index - contextIndex)
@@ -1516,55 +1530,13 @@
if (!mHasCompatScaling) {
return native_getTextRunAdvances(mNativePaint, chars, index, count,
- contextIndex, contextCount, flags, advances, advancesIndex);
+ contextIndex, contextCount, flags, advances, advancesIndex, reserved);
}
final float oldSize = getTextSize();
setTextSize(oldSize * mCompatScaling);
float res = native_getTextRunAdvances(mNativePaint, chars, index, count,
- contextIndex, contextCount, flags, advances, advancesIndex);
- setTextSize(oldSize);
-
- if (advances != null) {
- for (int i = advancesIndex, e = i + count; i < e; i++) {
- advances[i] *= mInvCompatScaling;
- }
- }
- return res * mInvCompatScaling; // assume errors are not significant
- }
-
- /**
- * Convenience overload that takes a char array instead of a
- * String.
- *
- * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int)
- * @hide
- */
- public float getTextRunAdvancesICU(char[] chars, int index, int count,
- int contextIndex, int contextCount, int flags, float[] advances,
- int advancesIndex) {
-
- if ((index | count | contextIndex | contextCount | advancesIndex
- | (index - contextIndex)
- | ((contextIndex + contextCount) - (index + count))
- | (chars.length - (contextIndex + contextCount))
- | (advances == null ? 0 :
- (advances.length - (advancesIndex + count)))) < 0) {
- throw new IndexOutOfBoundsException();
- }
- if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) {
- throw new IllegalArgumentException("unknown flags value: " + flags);
- }
-
- if (!mHasCompatScaling) {
- return native_getTextRunAdvancesICU(mNativePaint, chars, index, count,
- contextIndex, contextCount, flags, advances, advancesIndex);
- }
-
- final float oldSize = getTextSize();
- setTextSize(oldSize * mCompatScaling);
- float res = native_getTextRunAdvancesICU(mNativePaint, chars, index, count,
- contextIndex, contextCount, flags, advances, advancesIndex);
+ contextIndex, contextCount, flags, advances, advancesIndex, reserved);
setTextSize(oldSize);
if (advances != null) {
@@ -1585,15 +1557,29 @@
public float getTextRunAdvances(CharSequence text, int start, int end,
int contextStart, int contextEnd, int flags, float[] advances,
int advancesIndex) {
+ return getTextRunAdvances(text, start, end, contextStart, contextEnd, flags,
+ advances, advancesIndex, 0 /* use Harfbuzz */);
+ }
+
+ /**
+ * Convenience overload that takes a CharSequence instead of a
+ * String.
+ *
+ * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int)
+ * @hide
+ */
+ public float getTextRunAdvances(CharSequence text, int start, int end,
+ int contextStart, int contextEnd, int flags, float[] advances,
+ int advancesIndex, int reserved) {
if (text instanceof String) {
return getTextRunAdvances((String) text, start, end,
- contextStart, contextEnd, flags, advances, advancesIndex);
+ contextStart, contextEnd, flags, advances, advancesIndex, reserved);
}
if (text instanceof SpannedString ||
text instanceof SpannableString) {
return getTextRunAdvances(text.toString(), start, end,
- contextStart, contextEnd, flags, advances, advancesIndex);
+ contextStart, contextEnd, flags, advances, advancesIndex, reserved);
}
if (text instanceof GraphicsOperations) {
return ((GraphicsOperations) text).getTextRunAdvances(start, end,
@@ -1605,42 +1591,7 @@
char[] buf = TemporaryBuffer.obtain(contextLen);
TextUtils.getChars(text, start, end, buf, 0);
float result = getTextRunAdvances(buf, start - contextStart, len,
- 0, contextLen, flags, advances, advancesIndex);
- TemporaryBuffer.recycle(buf);
- return result;
- }
-
- /**
- * Convenience overload that takes a CharSequence instead of a
- * String.
- *
- * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int)
- * @hide
- */
- public float getTextRunAdvancesICU(CharSequence text, int start, int end,
- int contextStart, int contextEnd, int flags, float[] advances,
- int advancesIndex) {
-
- if (text instanceof String) {
- return getTextRunAdvancesICU((String) text, start, end,
- contextStart, contextEnd, flags, advances, advancesIndex);
- }
- if (text instanceof SpannedString ||
- text instanceof SpannableString) {
- return getTextRunAdvancesICU(text.toString(), start, end,
- contextStart, contextEnd, flags, advances, advancesIndex);
- }
- if (text instanceof GraphicsOperations) {
- return ((GraphicsOperations) text).getTextRunAdvancesICU(start, end,
- contextStart, contextEnd, flags, advances, advancesIndex, this);
- }
-
- int contextLen = contextEnd - contextStart;
- int len = end - start;
- char[] buf = TemporaryBuffer.obtain(contextLen);
- TextUtils.getChars(text, start, end, buf, 0);
- float result = getTextRunAdvancesICU(buf, start - contextStart, len,
- 0, contextLen, flags, advances, advancesIndex);
+ 0, contextLen, flags, advances, advancesIndex, reserved);
TemporaryBuffer.recycle(buf);
return result;
}
@@ -1689,6 +1640,55 @@
*/
public float getTextRunAdvances(String text, int start, int end, int contextStart,
int contextEnd, int flags, float[] advances, int advancesIndex) {
+ return getTextRunAdvances(text, start, end, contextStart, contextEnd, flags,
+ advances, advancesIndex, 0 /* use Harfbuzz*/);
+ }
+
+ /**
+ * Returns the total advance width for the characters in the run
+ * between start and end, and if advances is not null, the advance
+ * assigned to each of these characters (java chars).
+ *
+ * <p>The trailing surrogate in a valid surrogate pair is assigned
+ * an advance of 0. Thus the number of returned advances is
+ * always equal to count, not to the number of unicode codepoints
+ * represented by the run.
+ *
+ * <p>In the case of conjuncts or combining marks, the total
+ * advance is assigned to the first logical character, and the
+ * following characters are assigned an advance of 0.
+ *
+ * <p>This generates the sum of the advances of glyphs for
+ * characters in a reordered cluster as the width of the first
+ * logical character in the cluster, and 0 for the widths of all
+ * other characters in the cluster. In effect, such clusters are
+ * treated like conjuncts.
+ *
+ * <p>The shaping bounds limit the amount of context available
+ * outside start and end that can be used for shaping analysis.
+ * These bounds typically reflect changes in bidi level or font
+ * metrics across which shaping does not occur.
+ *
+ * @param text the text to measure
+ * @param start the index of the first character to measure
+ * @param end the index past the last character to measure
+ * @param contextStart the index of the first character to use for shaping context,
+ * must be <= start
+ * @param contextEnd the index past the last character to use for shaping context,
+ * must be >= end
+ * @param flags the flags to control the advances, either {@link #DIRECTION_LTR}
+ * or {@link #DIRECTION_RTL}
+ * @param advances array to receive the advances, must have room for all advances,
+ * can be null if only total advance is needed
+ * @param advancesIndex the position in advances at which to put the
+ * advance corresponding to the character at start
+ * @param reserved int reserved value
+ * @return the total advance
+ *
+ * @hide
+ */
+ public float getTextRunAdvances(String text, int start, int end, int contextStart,
+ int contextEnd, int flags, float[] advances, int advancesIndex, int reserved) {
if ((start | end | contextStart | contextEnd | advancesIndex | (end - start)
| (start - contextStart) | (contextEnd - end)
@@ -1703,51 +1703,13 @@
if (!mHasCompatScaling) {
return native_getTextRunAdvances(mNativePaint, text, start, end,
- contextStart, contextEnd, flags, advances, advancesIndex);
+ contextStart, contextEnd, flags, advances, advancesIndex, reserved);
}
final float oldSize = getTextSize();
setTextSize(oldSize * mCompatScaling);
float totalAdvance = native_getTextRunAdvances(mNativePaint, text, start, end,
- contextStart, contextEnd, flags, advances, advancesIndex);
- setTextSize(oldSize);
-
- if (advances != null) {
- for (int i = advancesIndex, e = i + (end - start); i < e; i++) {
- advances[i] *= mInvCompatScaling;
- }
- }
- return totalAdvance * mInvCompatScaling; // assume errors are insignificant
- }
-
- /**
- * Temporary - DO NOT USE
- *
- * @hide
- */
- public float getTextRunAdvancesICU(String text, int start, int end, int contextStart,
- int contextEnd, int flags, float[] advances, int advancesIndex) {
-
- if ((start | end | contextStart | contextEnd | advancesIndex | (end - start)
- | (start - contextStart) | (contextEnd - end)
- | (text.length() - contextEnd)
- | (advances == null ? 0 :
- (advances.length - advancesIndex - (end - start)))) < 0) {
- throw new IndexOutOfBoundsException();
- }
- if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) {
- throw new IllegalArgumentException("unknown flags value: " + flags);
- }
-
- if (!mHasCompatScaling) {
- return native_getTextRunAdvancesICU(mNativePaint, text, start, end,
- contextStart, contextEnd, flags, advances, advancesIndex);
- }
-
- final float oldSize = getTextSize();
- setTextSize(oldSize * mCompatScaling);
- float totalAdvance = native_getTextRunAdvances(mNativePaint, text, start, end,
- contextStart, contextEnd, flags, advances, advancesIndex);
+ contextStart, contextEnd, flags, advances, advancesIndex, reserved);
setTextSize(oldSize);
if (advances != null) {
@@ -2017,17 +1979,10 @@
private static native float native_getTextRunAdvances(int native_object,
char[] text, int index, int count, int contextIndex, int contextCount,
- int flags, float[] advances, int advancesIndex);
+ int flags, float[] advances, int advancesIndex, int reserved);
private static native float native_getTextRunAdvances(int native_object,
String text, int start, int end, int contextStart, int contextEnd,
- int flags, float[] advances, int advancesIndex);
-
- private static native float native_getTextRunAdvancesICU(int native_object,
- char[] text, int index, int count, int contextIndex, int contextCount,
- int flags, float[] advances, int advancesIndex);
- private static native float native_getTextRunAdvancesICU(int native_object,
- String text, int start, int end, int contextStart, int contextEnd,
- int flags, float[] advances, int advancesIndex);
+ int flags, float[] advances, int advancesIndex, int reserved);
private native int native_getTextRunCursor(int native_object, char[] text,
int contextStart, int contextLength, int flags, int offset, int cursorOpt);
diff --git a/libs/rs/rsMatrix4x4.cpp b/libs/rs/rsMatrix4x4.cpp
index 2d90a98..f34af47 100644
--- a/libs/rs/rsMatrix4x4.cpp
+++ b/libs/rs/rsMatrix4x4.cpp
@@ -305,3 +305,10 @@
out[2] = (m[2] * in[0]) + (m[6] * in[1]) + (m[10] * in[2]) + m[14];
out[3] = (m[3] * in[0]) + (m[7] * in[1]) + (m[11] * in[2]) + m[15];
}
+
+void Matrix4x4::logv(const char *s) const {
+ LOGV("%s {%f, %f, %f, %f", s, m[0], m[4], m[8], m[12]);
+ LOGV("%s %f, %f, %f, %f", s, m[1], m[5], m[9], m[13]);
+ LOGV("%s %f, %f, %f, %f", s, m[2], m[6], m[10], m[14]);
+ LOGV("%s %f, %f, %f, %f}", s, m[3], m[7], m[11], m[15]);
+}
diff --git a/libs/rs/rsMatrix4x4.h b/libs/rs/rsMatrix4x4.h
index abf34a3..d30184f 100644
--- a/libs/rs/rsMatrix4x4.h
+++ b/libs/rs/rsMatrix4x4.h
@@ -54,6 +54,7 @@
bool inverseTranspose();
void transpose();
+ void logv(const char *s) const;
void multiply(const rs_matrix4x4 *rhs) {
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index 06708da..585cd30 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -29,6 +29,7 @@
#include "MtpDatabase.h"
#include "MtpDataPacket.h"
+#include "MtpObjectInfo.h"
#include "MtpProperty.h"
#include "MtpStringBuffer.h"
#include "MtpUtils.h"
@@ -138,7 +139,7 @@
MtpDataPacket& packet);
virtual MtpResponseCode getObjectInfo(MtpObjectHandle handle,
- MtpDataPacket& packet);
+ MtpObjectInfo& info);
virtual MtpResponseCode getObjectFilePath(MtpObjectHandle handle,
MtpString& outFilePath,
@@ -744,7 +745,7 @@
}
MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle,
- MtpDataPacket& packet) {
+ MtpObjectInfo& info) {
char date[20];
JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -754,46 +755,27 @@
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
jint* intValues = env->GetIntArrayElements(mIntBuffer, 0);
- MtpStorageID storageID = intValues[0];
- MtpObjectFormat format = intValues[1];
- MtpObjectHandle parent = intValues[2];
+ info.mStorageID = intValues[0];
+ info.mFormat = intValues[1];
+ info.mParent = intValues[2];
env->ReleaseIntArrayElements(mIntBuffer, intValues, 0);
jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0);
uint64_t size = longValues[0];
- uint64_t modified = longValues[1];
+ info.mCompressedSize = (size > 0xFFFFFFFFLL ? 0xFFFFFFFF : size);
+ info.mDateModified = longValues[1];
env->ReleaseLongArrayElements(mLongBuffer, longValues, 0);
-// int associationType = (format == MTP_FORMAT_ASSOCIATION ?
+// info.mAssociationType = (format == MTP_FORMAT_ASSOCIATION ?
// MTP_ASSOCIATION_TYPE_GENERIC_FOLDER :
// MTP_ASSOCIATION_TYPE_UNDEFINED);
- int associationType = MTP_ASSOCIATION_TYPE_UNDEFINED;
-
- packet.putUInt32(storageID);
- packet.putUInt16(format);
- packet.putUInt16(0); // protection status
- packet.putUInt32((size > 0xFFFFFFFFLL ? 0xFFFFFFFF : size));
- packet.putUInt16(0); // thumb format
- packet.putUInt32(0); // thumb compressed size
- packet.putUInt32(0); // thumb pix width
- packet.putUInt32(0); // thumb pix height
- packet.putUInt32(0); // image pix width
- packet.putUInt32(0); // image pix height
- packet.putUInt32(0); // image bit depth
- packet.putUInt32(parent);
- packet.putUInt16(associationType);
- packet.putUInt32(0); // association desc
- packet.putUInt32(0); // sequence number
+ info.mAssociationType = MTP_ASSOCIATION_TYPE_UNDEFINED;
jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
- packet.putString(str); // file name
+ MtpString temp(str);
+ info.mName = strdup((const char *)temp);
env->ReleaseCharArrayElements(mStringBuffer, str, 0);
- packet.putEmptyString();
- formatDateTime(modified, date, sizeof(date));
- packet.putString(date); // date modified
- packet.putEmptyString(); // keywords
-
checkAndClearExceptionFromCallback(env, __FUNCTION__);
return MTP_RESPONSE_OK;
}
diff --git a/media/mtp/MtpDatabase.h b/media/mtp/MtpDatabase.h
index 4d9a1ae..d7bde00 100644
--- a/media/mtp/MtpDatabase.h
+++ b/media/mtp/MtpDatabase.h
@@ -23,6 +23,7 @@
class MtpDataPacket;
class MtpProperty;
+class MtpObjectInfo;
class MtpDatabase {
public:
@@ -81,7 +82,7 @@
MtpDataPacket& packet) = 0;
virtual MtpResponseCode getObjectInfo(MtpObjectHandle handle,
- MtpDataPacket& packet) = 0;
+ MtpObjectInfo& info) = 0;
virtual MtpResponseCode getObjectFilePath(MtpObjectHandle handle,
MtpString& outFilePath,
diff --git a/media/mtp/MtpDebug.cpp b/media/mtp/MtpDebug.cpp
index 1668ecf..9f3037d 100644
--- a/media/mtp/MtpDebug.cpp
+++ b/media/mtp/MtpDebug.cpp
@@ -63,6 +63,12 @@
{ "MTP_OPERATION_GET_OBJECT_REFERENCES", 0x9810 },
{ "MTP_OPERATION_SET_OBJECT_REFERENCES", 0x9811 },
{ "MTP_OPERATION_SKIP", 0x9820 },
+ // android extensions
+ { "MTP_OPERATION_GET_PARTIAL_OBJECT_64", 0x95C1 },
+ { "MTP_OPERATION_SEND_PARTIAL_OBJECT", 0x95C2 },
+ { "MTP_OPERATION_TRUNCATE_OBJECT", 0x95C3 },
+ { "MTP_OPERATION_BEGIN_EDIT_OBJECT", 0x95C4 },
+ { "MTP_OPERATION_END_EDIT_OBJECT", 0x95C5 },
{ 0, 0 },
};
diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp
index 37e02a3..ff4009c 100644
--- a/media/mtp/MtpServer.cpp
+++ b/media/mtp/MtpServer.cpp
@@ -30,6 +30,7 @@
#include "MtpDebug.h"
#include "MtpDatabase.h"
+#include "MtpObjectInfo.h"
#include "MtpProperty.h"
#include "MtpServer.h"
#include "MtpStorage.h"
@@ -79,6 +80,12 @@
MTP_OPERATION_GET_OBJECT_REFERENCES,
MTP_OPERATION_SET_OBJECT_REFERENCES,
// MTP_OPERATION_SKIP,
+ // Android extension for direct file IO
+ MTP_OPERATION_GET_PARTIAL_OBJECT_64,
+ MTP_OPERATION_SEND_PARTIAL_OBJECT,
+ MTP_OPERATION_TRUNCATE_OBJECT,
+ MTP_OPERATION_BEGIN_EDIT_OBJECT,
+ MTP_OPERATION_END_EDIT_OBJECT,
};
static const MtpEventCode kSupportedEventCodes[] = {
@@ -218,6 +225,15 @@
}
}
+ // commit any open edits
+ int count = mObjectEditList.size();
+ for (int i = 0; i < count; i++) {
+ ObjectEdit* edit = mObjectEditList[i];
+ commitEdit(edit);
+ delete edit;
+ }
+ mObjectEditList.clear();
+
if (mSessionOpen)
mDatabase->sessionEnded();
}
@@ -252,6 +268,44 @@
}
}
+void MtpServer::addEditObject(MtpObjectHandle handle, MtpString& path,
+ uint64_t size, MtpObjectFormat format, int fd) {
+ ObjectEdit* edit = new ObjectEdit;
+ edit->handle = handle;
+ edit->path = path;
+ edit->size = size;
+ edit->format = format;
+ edit->fd = fd;
+ mObjectEditList.add(edit);
+}
+
+MtpServer::ObjectEdit* MtpServer::getEditObject(MtpObjectHandle handle) {
+ int count = mObjectEditList.size();
+ for (int i = 0; i < count; i++) {
+ ObjectEdit* edit = mObjectEditList[i];
+ if (edit->handle == handle) return edit;
+ }
+ return NULL;
+}
+
+void MtpServer::removeEditObject(MtpObjectHandle handle) {
+ int count = mObjectEditList.size();
+ for (int i = 0; i < count; i++) {
+ ObjectEdit* edit = mObjectEditList[i];
+ if (edit->handle == handle) {
+ delete edit;
+ mObjectEditList.removeAt(i);
+ return;
+ }
+ }
+ LOGE("ObjectEdit not found in removeEditObject");
+}
+
+void MtpServer::commitEdit(ObjectEdit* edit) {
+ mDatabase->endSendObject((const char *)edit->path, edit->handle, edit->format, true);
+}
+
+
bool MtpServer::handleRequest() {
Mutex::Autolock autoLock(mMutex);
@@ -322,7 +376,8 @@
response = doGetObject();
break;
case MTP_OPERATION_GET_PARTIAL_OBJECT:
- response = doGetPartialObject();
+ case MTP_OPERATION_GET_PARTIAL_OBJECT_64:
+ response = doGetPartialObject(operation);
break;
case MTP_OPERATION_SEND_OBJECT_INFO:
response = doSendObjectInfo();
@@ -339,6 +394,18 @@
case MTP_OPERATION_GET_DEVICE_PROP_DESC:
response = doGetDevicePropDesc();
break;
+ case MTP_OPERATION_SEND_PARTIAL_OBJECT:
+ response = doSendPartialObject();
+ break;
+ case MTP_OPERATION_TRUNCATE_OBJECT:
+ response = doTruncateObject();
+ break;
+ case MTP_OPERATION_BEGIN_EDIT_OBJECT:
+ response = doBeginEditObject();
+ break;
+ case MTP_OPERATION_END_EDIT_OBJECT:
+ response = doEndEditObject();
+ break;
default:
LOGE("got unsupported command %s", MtpDebug::getOperationCodeName(operation));
response = MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
@@ -363,7 +430,7 @@
mData.putUInt16(MTP_STANDARD_VERSION);
mData.putUInt32(6); // MTP Vendor Extension ID
mData.putUInt16(MTP_STANDARD_VERSION);
- string.set("microsoft.com: 1.0;");
+ string.set("microsoft.com: 1.0; android.com: 1.0;");
mData.putString(string); // MTP Extensions
mData.putUInt16(0); //Functional Mode
mData.putAUInt16(kSupportedOperationCodes,
@@ -601,7 +668,40 @@
if (!hasStorage())
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
MtpObjectHandle handle = mRequest.getParameter(1);
- return mDatabase->getObjectInfo(handle, mData);
+ MtpObjectInfo info(handle);
+ MtpResponseCode result = mDatabase->getObjectInfo(handle, info);
+ if (result == MTP_RESPONSE_OK) {
+ char date[20];
+
+ mData.putUInt32(info.mStorageID);
+ mData.putUInt16(info.mFormat);
+ mData.putUInt16(info.mProtectionStatus);
+
+ // if object is being edited the database size may be out of date
+ uint32_t size = info.mCompressedSize;
+ ObjectEdit* edit = getEditObject(handle);
+ if (edit)
+ size = (edit->size > 0xFFFFFFFFLL ? 0xFFFFFFFF : (uint32_t)edit->size);
+ mData.putUInt32(size);
+
+ mData.putUInt16(info.mThumbFormat);
+ mData.putUInt32(info.mThumbCompressedSize);
+ mData.putUInt32(info.mThumbPixWidth);
+ mData.putUInt32(info.mThumbPixHeight);
+ mData.putUInt32(info.mImagePixWidth);
+ mData.putUInt32(info.mImagePixHeight);
+ mData.putUInt32(info.mImagePixDepth);
+ mData.putUInt32(info.mParent);
+ mData.putUInt16(info.mAssociationType);
+ mData.putUInt32(info.mAssociationDesc);
+ mData.putUInt32(info.mSequenceNumber);
+ mData.putString(info.mName);
+ mData.putEmptyString(); // date created
+ formatDateTime(info.mDateModified, date, sizeof(date));
+ mData.putString(date); // date modified
+ mData.putEmptyString(); // keywords
+ }
+ return result;
}
MtpResponseCode MtpServer::doGetObject() {
@@ -641,12 +741,22 @@
return MTP_RESPONSE_OK;
}
-MtpResponseCode MtpServer::doGetPartialObject() {
+MtpResponseCode MtpServer::doGetPartialObject(MtpOperationCode operation) {
if (!hasStorage())
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
MtpObjectHandle handle = mRequest.getParameter(1);
- uint32_t offset = mRequest.getParameter(2);
- uint32_t length = mRequest.getParameter(3);
+ uint64_t offset;
+ uint32_t length;
+ offset = mRequest.getParameter(2);
+ if (operation == MTP_OPERATION_GET_PARTIAL_OBJECT_64) {
+ // android extension with 64 bit offset
+ uint64_t offset2 = mRequest.getParameter(3);
+ offset = offset | (offset2 << 32);
+ length = mRequest.getParameter(4);
+ } else {
+ // standard GetPartialObject
+ length = mRequest.getParameter(3);
+ }
MtpString pathBuf;
int64_t fileLength;
MtpObjectFormat format;
@@ -933,4 +1043,113 @@
return MTP_RESPONSE_OK;
}
+MtpResponseCode MtpServer::doSendPartialObject() {
+ if (!hasStorage())
+ return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+ MtpObjectHandle handle = mRequest.getParameter(1);
+ uint64_t offset = mRequest.getParameter(2);
+ uint64_t offset2 = mRequest.getParameter(3);
+ offset = offset | (offset2 << 32);
+ uint32_t length = mRequest.getParameter(4);
+
+ ObjectEdit* edit = getEditObject(handle);
+ if (!edit) {
+ LOGE("object not open for edit in doSendPartialObject");
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+
+ // can't start writing past the end of the file
+ if (offset > edit->size) {
+ LOGD("writing past end of object, offset: %lld, edit->size: %lld", offset, edit->size);
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+
+ // read the header
+ int ret = mData.readDataHeader(mFD);
+ // FIXME - check for errors here.
+
+ // reset so we don't attempt to send this back
+ mData.reset();
+
+ const char* filePath = (const char *)edit->path;
+ LOGV("receiving partial %s %lld %ld\n", filePath, offset, length);
+ mtp_file_range mfr;
+ mfr.fd = edit->fd;
+ mfr.offset = offset;
+ mfr.length = length;
+
+ // transfer the file
+ ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr);
+ LOGV("MTP_RECEIVE_FILE returned %d", ret);
+ if (ret < 0) {
+ mResponse.setParameter(1, 0);
+ if (errno == ECANCELED)
+ return MTP_RESPONSE_TRANSACTION_CANCELLED;
+ else
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+ mResponse.setParameter(1, length);
+ uint64_t end = offset + length;
+ if (end > edit->size) {
+ edit->size = end;
+ }
+ return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doTruncateObject() {
+ MtpObjectHandle handle = mRequest.getParameter(1);
+ ObjectEdit* edit = getEditObject(handle);
+ if (!edit) {
+ LOGE("object not open for edit in doTruncateObject");
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+
+ uint64_t offset = mRequest.getParameter(2);
+ uint64_t offset2 = mRequest.getParameter(3);
+ offset |= (offset2 << 32);
+ if (ftruncate(edit->fd, offset) != 0) {
+ return MTP_RESPONSE_GENERAL_ERROR;
+ } else {
+ edit->size = offset;
+ return MTP_RESPONSE_OK;
+ }
+}
+
+MtpResponseCode MtpServer::doBeginEditObject() {
+ MtpObjectHandle handle = mRequest.getParameter(1);
+ if (getEditObject(handle)) {
+ LOGE("object already open for edit in doBeginEditObject");
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+
+ MtpString path;
+ int64_t fileLength;
+ MtpObjectFormat format;
+ int result = mDatabase->getObjectFilePath(handle, path, fileLength, format);
+ if (result != MTP_RESPONSE_OK)
+ return result;
+
+ int fd = open((const char *)path, O_RDWR | O_EXCL);
+ if (fd < 0) {
+ LOGE("open failed for %s in doBeginEditObject (%d)", (const char *)path, errno);
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+
+ addEditObject(handle, path, fileLength, format, fd);
+ return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doEndEditObject() {
+ MtpObjectHandle handle = mRequest.getParameter(1);
+ ObjectEdit* edit = getEditObject(handle);
+ if (!edit) {
+ LOGE("object not open for edit in doEndEditObject");
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+
+ commitEdit(edit);
+ removeEditObject(handle);
+ return MTP_RESPONSE_OK;
+}
+
} // namespace android
diff --git a/media/mtp/MtpServer.h b/media/mtp/MtpServer.h
index fa729a8..35a38e7 100644
--- a/media/mtp/MtpServer.h
+++ b/media/mtp/MtpServer.h
@@ -65,6 +65,17 @@
Mutex mMutex;
+ // represents an MTP object that is being edited using the android extensions
+ // for direct editing (BeginEditObject, SendPartialObject, TruncateObject and EndEditObject)
+ struct ObjectEdit {
+ MtpObjectHandle handle;
+ MtpString path;
+ uint64_t size;
+ MtpObjectFormat format;
+ int fd;
+ };
+ Vector<ObjectEdit*> mObjectEditList;
+
public:
MtpServer(int fd, MtpDatabase* database,
int fileGroup, int filePerm, int directoryPerm);
@@ -86,6 +97,12 @@
void sendStoreRemoved(MtpStorageID id);
void sendEvent(MtpEventCode code, uint32_t param1);
+ void addEditObject(MtpObjectHandle handle, MtpString& path,
+ uint64_t size, MtpObjectFormat format, int fd);
+ ObjectEdit* getEditObject(MtpObjectHandle handle);
+ void removeEditObject(MtpObjectHandle handle);
+ void commitEdit(ObjectEdit* edit);
+
bool handleRequest();
MtpResponseCode doGetDeviceInfo();
@@ -106,12 +123,16 @@
MtpResponseCode doGetObjectPropList();
MtpResponseCode doGetObjectInfo();
MtpResponseCode doGetObject();
- MtpResponseCode doGetPartialObject();
+ MtpResponseCode doGetPartialObject(MtpOperationCode operation);
MtpResponseCode doSendObjectInfo();
MtpResponseCode doSendObject();
MtpResponseCode doDeleteObject();
MtpResponseCode doGetObjectPropDesc();
MtpResponseCode doGetDevicePropDesc();
+ MtpResponseCode doSendPartialObject();
+ MtpResponseCode doTruncateObject();
+ MtpResponseCode doBeginEditObject();
+ MtpResponseCode doEndEditObject();
};
}; // namespace android
diff --git a/media/mtp/mtp.h b/media/mtp/mtp.h
index 8bc2e22..d270df5 100644
--- a/media/mtp/mtp.h
+++ b/media/mtp/mtp.h
@@ -391,6 +391,19 @@
#define MTP_OPERATION_SET_OBJECT_REFERENCES 0x9811
#define MTP_OPERATION_SKIP 0x9820
+// Android extensions for direct file IO
+
+// Same as GetPartialObject, but with 64 bit offset
+#define MTP_OPERATION_GET_PARTIAL_OBJECT_64 0x95C1
+// Same as GetPartialObject64, but copying host to device
+#define MTP_OPERATION_SEND_PARTIAL_OBJECT 0x95C2
+// Truncates file to 64 bit length
+#define MTP_OPERATION_TRUNCATE_OBJECT 0x95C3
+// Must be called before using SendPartialObject and TruncateObject
+#define MTP_OPERATION_BEGIN_EDIT_OBJECT 0x95C4
+// Called to commit changes made by SendPartialObject and TruncateObject
+#define MTP_OPERATION_END_EDIT_OBJECT 0x95C5
+
// MTP Response Codes
#define MTP_RESPONSE_UNDEFINED 0x2000
#define MTP_RESPONSE_OK 0x2001
diff --git a/opengl/libs/GLES2_dbg/generate_api_cpp.py b/opengl/libs/GLES2_dbg/generate_api_cpp.py
index aeba213..96cde57 100755
--- a/opengl/libs/GLES2_dbg/generate_api_cpp.py
+++ b/opengl/libs/GLES2_dbg/generate_api_cpp.py
@@ -38,7 +38,7 @@
"glShaderSource", "glTexImage2D", "glTexSubImage2D"]
# these also needs to be forwarded to DbgContext
- contextFunctions = ["glUseProgram", "glEnableVertexAttribArray", "glDisableVertexAttribArray",
+ contextFunctions = ["glUseProgram", "glEnableVertexAttribArray", "glDisableVertexAttribArray",
"glVertexAttribPointer", "glBindBuffer", "glBufferData", "glBufferSubData", "glDeleteBuffers",]
for line in lines:
@@ -114,10 +114,10 @@
else:
getData += " msg.mutable_data()->assign(reinterpret_cast<const char *>(%s), %s * sizeof(%s));" % (paramName, annotation, paramType)
paramType += "*"
- else:
+ else:
if paramType == "GLfloat" or paramType == "GLclampf" or paramType.find("*") >= 0:
setMsgParameters += " msg.set_arg%d(ToInt(%s));\n" % (paramIndex, paramName)
- else:
+ else:
setMsgParameters += " msg.set_arg%d(%s);\n" % (paramIndex, paramName)
if paramIndex < len(parameters) - 1:
arguments += ', '
@@ -156,7 +156,7 @@
} caller;"""
print setCallerMembers
print setMsgParameters
-
+
if line.find("*") >= 0 or line.find(":") >= 0:
print " // FIXME: check for pointer usage"
if inout in ["in", "inout"]:
diff --git a/opengl/libs/GLES2_dbg/generate_debugger_message_proto.py b/opengl/libs/GLES2_dbg/generate_debugger_message_proto.py
index 57e008c..535b13e 100755
--- a/opengl/libs/GLES2_dbg/generate_debugger_message_proto.py
+++ b/opengl/libs/GLES2_dbg/generate_debugger_message_proto.py
@@ -25,7 +25,7 @@
line = line.split(",")[1].strip() #extract EGL function name
output.write(" %s = %d;\n" % (line, i))
i += 1
- return i
+ return i
def generate_gl_entries(output,lines,i):
@@ -118,7 +118,7 @@
optional int32 arg4 = 16;
optional int32 arg5 = 17;
optional int32 arg6 = 18;
- optional int32 arg7 = 19;
+ optional int32 arg7 = 19; // glDrawArrays/Elements sets this to active number of attributes
optional int32 arg8 = 20;
optional bytes data = 10; // variable length data used for GL call
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 879f1a8..73003c8 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -511,7 +511,7 @@
}
// This will populate st.shownPanelView
- if (!initializePanelContent(st) || (st.shownPanelView == null)) {
+ if (!initializePanelContent(st) || !st.hasPanelItems()) {
return;
}
@@ -2976,6 +2976,16 @@
refreshDecorView = false;
}
+ public boolean hasPanelItems() {
+ if (shownPanelView == null) return false;
+
+ if (isInExpandedMode) {
+ return expandedMenuPresenter.getAdapter().getCount() > 0;
+ } else {
+ return ((ViewGroup) shownPanelView).getChildCount() > 0;
+ }
+ }
+
/**
* Unregister and free attached MenuPresenters. They will be recreated as needed.
*/
diff --git a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
index ced8feb..8ba0a0b 100644
--- a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -20,8 +20,8 @@
import android.content.Context;
import android.util.Slog;
+import android.view.InputDevice;
import android.view.InputEvent;
-import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.WindowManagerPolicy;
@@ -32,10 +32,35 @@
*/
public class AccessibilityInputFilter extends InputFilter {
private static final String TAG = "AccessibilityInputFilter";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
private final Context mContext;
+ /**
+ * This is an interface for explorers that take a {@link MotionEvent}
+ * stream and perform touch exploration of the screen content.
+ */
+ public interface Explorer {
+ /**
+ * Handles a {@link MotionEvent}.
+ *
+ * @param event The event to handle.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ public void onMotionEvent(MotionEvent event, int policyFlags);
+
+ /**
+ * Requests that the explorer clears its internal state.
+ *
+ * @param event The last received event.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ public void clear(MotionEvent event, int policyFlags);
+ }
+
+ private TouchExplorer mTouchExplorer;
+ private int mTouchscreenSourceDeviceId;
+
public AccessibilityInputFilter(Context context) {
super(context.getMainLooper());
mContext = context;
@@ -60,27 +85,27 @@
@Override
public void onInputEvent(InputEvent event, int policyFlags) {
if (DEBUG) {
- Slog.d(TAG, "Accessibility input filter received input event: "
- + event + ", policyFlags=0x" + Integer.toHexString(policyFlags));
+ Slog.d(TAG, "Received event: " + event + ", policyFlags=0x"
+ + Integer.toHexString(policyFlags));
}
-
- // To prove that this is working as intended, we will silently transform
- // Q key presses into non-repeating Z's as part of this stub implementation.
- // TODO: Replace with the real thing.
- if (event instanceof KeyEvent) {
- final KeyEvent keyEvent = (KeyEvent)event;
- if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_Q) {
- if (keyEvent.getRepeatCount() == 0) {
- sendInputEvent(new KeyEvent(keyEvent.getDownTime(), keyEvent.getEventTime(),
- keyEvent.getAction(), KeyEvent.KEYCODE_Z, keyEvent.getRepeatCount(),
- keyEvent.getMetaState(), keyEvent.getDeviceId(), keyEvent.getScanCode(),
- keyEvent.getFlags(), keyEvent.getSource()),
- policyFlags | WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
+ if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
+ MotionEvent motionEvent = (MotionEvent) event;
+ int deviceId = event.getDeviceId();
+ if (mTouchscreenSourceDeviceId != deviceId) {
+ mTouchscreenSourceDeviceId = deviceId;
+ if (mTouchExplorer != null) {
+ mTouchExplorer.clear(motionEvent, policyFlags);
+ } else {
+ mTouchExplorer = new TouchExplorer(this, mContext);
}
- return;
}
+ if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) != 0) {
+ mTouchExplorer.onMotionEvent(motionEvent, policyFlags);
+ } else {
+ mTouchExplorer.clear(motionEvent, policyFlags);
+ }
+ } else {
+ super.onInputEvent(event, policyFlags);
}
-
- super.onInputEvent(event, policyFlags);
}
}
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index 7a483aa..1ad8047 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -56,6 +56,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -74,6 +75,8 @@
public class AccessibilityManagerService extends IAccessibilityManager.Stub
implements HandlerCaller.Callback {
+ private static final boolean DEBUG = false;
+
private static final String LOG_TAG = "AccessibilityManagerService";
private static int sIdCounter = 0;
@@ -102,6 +105,9 @@
private final SimpleStringSplitter mStringColonSplitter = new SimpleStringSplitter(':');
+ private final SparseArray<List<ServiceInfo>> mFeedbackTypeToEnabledServicesMap =
+ new SparseArray<List<ServiceInfo>>();
+
private PackageManager mPackageManager;
private int mHandledFeedbackTypes = 0;
@@ -211,7 +217,6 @@
}
manageServicesLocked();
- updateInputFilterLocked();
}
return;
@@ -252,7 +257,6 @@
unbindAllServicesLocked();
}
updateClientsLocked();
- updateInputFilterLocked();
}
}
});
@@ -300,6 +304,16 @@
}
}
+ public List<ServiceInfo> getEnabledAccessibilityServiceList(int feedbackType) {
+ synchronized (mLock) {
+ List<ServiceInfo> enabledServices = mFeedbackTypeToEnabledServicesMap.get(feedbackType);
+ if (enabledServices == null) {
+ return Collections.emptyList();
+ }
+ return enabledServices;
+ }
+ }
+
public void interrupt() {
synchronized (mLock) {
for (int i = 0, count = mServices.size(); i < count; i++) {
@@ -339,6 +353,8 @@
}
service.mNotificationTimeout = info.notificationTimeout;
service.mIsDefault = (info.flags & AccessibilityServiceInfo.DEFAULT) != 0;
+
+ updateStateOnEnabledService(service);
}
return;
default:
@@ -449,7 +465,7 @@
try {
listener.onAccessibilityEvent(event);
- if (false) {
+ if (DEBUG) {
Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
}
} catch (RemoteException re) {
@@ -469,10 +485,11 @@
* @return True if the service was removed, false otherwise.
*/
private boolean removeDeadServiceLocked(Service service) {
- if (false) {
+ if (DEBUG) {
Slog.i(LOG_TAG, "Dead service " + service.mService + " removed");
}
mHandler.removeMessages(service.mId);
+ updateStateOnDisabledService(service);
return mServices.remove(service);
}
@@ -593,7 +610,7 @@
if (isEnabled) {
if (enabledServices.contains(componentName)) {
if (service == null) {
- service = new Service(componentName);
+ service = new Service(componentName, intalledService);
}
service.bind();
} else if (!enabledServices.contains(componentName)) {
@@ -644,6 +661,47 @@
}
/**
+ * Updates the set of enabled services for a given feedback type and
+ * if more than one of them provides spoken feedback enables touch
+ * exploration.
+ *
+ * @param service An enable service.
+ */
+ private void updateStateOnEnabledService(Service service) {
+ int feedbackType = service.mFeedbackType;
+ List<ServiceInfo> enabledServices = mFeedbackTypeToEnabledServicesMap.get(feedbackType);
+ if (enabledServices == null) {
+ enabledServices = new ArrayList<ServiceInfo>();
+ mFeedbackTypeToEnabledServicesMap.put(feedbackType, enabledServices);
+ }
+ enabledServices.add(service.mServiceInfo);
+
+ // We enable touch exploration if at least one
+ // enabled service provides spoken feedback.
+ if (enabledServices.size() > 0
+ && service.mFeedbackType == AccessibilityServiceInfo.FEEDBACK_SPOKEN) {
+ updateClientsLocked();
+ updateInputFilterLocked();
+ }
+ }
+
+ private void updateStateOnDisabledService(Service service) {
+ List<ServiceInfo> enabledServices =
+ mFeedbackTypeToEnabledServicesMap.get(service.mFeedbackType);
+ if (enabledServices == null) {
+ return;
+ }
+ enabledServices.remove(service.mServiceInfo);
+ // We disable touch exploration if no
+ // enabled service provides spoken feedback.
+ if (enabledServices.isEmpty()
+ && service.mFeedbackType == AccessibilityServiceInfo.FEEDBACK_SPOKEN) {
+ updateClientsLocked();
+ updateInputFilterLocked();
+ }
+ }
+
+ /**
* This class represents an accessibility service. It stores all per service
* data required for the service management, provides API for starting/stopping the
* service and is responsible for adding/removing the service in the data structures
@@ -654,6 +712,8 @@
class Service extends IAccessibilityServiceConnection.Stub implements ServiceConnection {
int mId = 0;
+ ServiceInfo mServiceInfo;
+
IBinder mService;
IEventListener mServiceInterface;
@@ -678,9 +738,10 @@
final SparseArray<AccessibilityEvent> mPendingEvents =
new SparseArray<AccessibilityEvent>();
- Service(ComponentName componentName) {
+ Service(ComponentName componentName, ServiceInfo serviceInfo) {
mId = sIdCounter++;
mComponentName = componentName;
+ mServiceInfo = serviceInfo;
mIntent = new Intent().setComponent(mComponentName);
mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.accessibility_binding_label);
@@ -712,6 +773,7 @@
mContext.unbindService(this);
mComponentNameToServiceMap.remove(mComponentName);
mServices.remove(this);
+ updateStateOnDisabledService(this);
return true;
}
return false;
diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java
new file mode 100644
index 0000000..4ba6060
--- /dev/null
+++ b/services/java/com/android/server/accessibility/TouchExplorer.java
@@ -0,0 +1,1540 @@
+/*
+ ** Copyright 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.server.accessibility;
+
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START;
+
+import com.android.server.accessibility.AccessibilityInputFilter.Explorer;
+import com.android.server.wm.InputFilter;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import android.view.WindowManagerPolicy;
+import android.view.MotionEvent.PointerCoords;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import java.util.Arrays;
+
+/**
+ * This class is a strategy for performing touch exploration. It
+ * transforms the motion event stream by modifying, adding, replacing,
+ * and consuming certain events. The interaction model is:
+ *
+ * <ol>
+ * <li>1. One finger moving around performs touch exploration.</li>
+ * <li>2. Two close fingers moving in the same direction perform a drag.</li>
+ * <li>3. Multi-finger gestures are delivered to view hierarchy.</li>
+ * <li>4. Pointers that have not moved more than a specified distance after they
+ * went down are considered inactive.</li>
+ * <li>5. Two fingers moving too far from each other or in different directions
+ * are considered a multi-finger gesture.</li>
+ * <li>6. Tapping on the last touch explored location within given time and
+ * distance slop performs a click.</li>
+ * <li>7. Tapping and holding for a while on the last touch explored location within
+ * given time and distance slop performs a long press.</li>
+ * <ol>
+ *
+ * @hide
+ */
+public class TouchExplorer implements Explorer {
+ private static final boolean DEBUG = false;
+
+ // Tag for logging received events.
+ private static final String LOG_TAG_RECEIVED = "TouchExplorer-RECEIVED";
+ // Tag for logging injected events.
+ private static final String LOG_TAG_INJECTED = "TouchExplorer-INJECTED";
+ // Tag for logging the current state.
+ private static final String LOG_TAG_STATE = "TouchExplorer-STATE";
+
+ // States this explorer can be in.
+ private static final int STATE_TOUCH_EXPLORING = 0x00000001;
+ private static final int STATE_DRAGGING = 0x00000002;
+ private static final int STATE_DELEGATING = 0x00000004;
+
+ // Human readable symbolic names for the states of the explorer.
+ private static final SparseArray<String> sStateSymbolicNames = new SparseArray<String>();
+ static {
+ SparseArray<String> symbolicNames = sStateSymbolicNames;
+ symbolicNames.append(STATE_TOUCH_EXPLORING, "STATE_TOUCH_EXPLORING");
+ symbolicNames.append(STATE_DRAGGING, "STATE_DRAGING");
+ symbolicNames.append(STATE_DELEGATING, "STATE_DELEGATING");
+ }
+
+ // Invalid pointer ID.
+ private static final int INVALID_POINTER_ID = -1;
+
+ // The coefficient by which to multiply
+ // ViewConfiguration.#getScaledTouchExplorationTapSlop()
+ // to compute #mDraggingDistance.
+ private static final int COEFFICIENT_DRAGGING_DISTANCE = 2;
+
+ // The time slop in milliseconds for activating an item after it has
+ // been touch explored. Tapping on an item within this slop will perform
+ // a click and tapping and holding down a long press.
+ private static final long ACTIVATION_TIME_SLOP = 2000;
+
+ // This constant captures the current implementation detail that
+ // pointer IDs are between 0 and 31 inclusive (subject to change).
+ // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
+ private static final int MAX_POINTER_COUNT = 32;
+
+ // The minimum of the cosine between the vectors of two moving
+ // pointers so they can be considered moving in the same direction.
+ private static final float MIN_ANGLE_COS = 0.866025404f; // cos(pi/6)
+
+ // The delay for sending a hover enter event.
+ private static final long DELAY_SEND_HOVER_MOVE = 200;
+
+ // Temporary array for storing pointer IDs.
+ private final int[] mTempPointerIds = new int[MAX_POINTER_COUNT];
+
+ // Temporary array for mapping new to old pointer IDs while filtering inactive pointers.
+ private final int [] mTempNewToOldPointerIndexMap = new int[MAX_POINTER_COUNT];
+
+ // Temporary array for storing PointerCoords
+ private final PointerCoords[] mTempPointerCoords= new PointerCoords[MAX_POINTER_COUNT];
+
+ // The maximal distance between two pointers so they are
+ // considered to be performing a drag operation.
+ private final float mDraggingDistance;
+
+ // The distance from the last touch explored location tapping within
+ // which would perform a click and tapping and holding a long press.
+ private final int mTouchExplorationTapSlop;
+
+ // Context handle for accessing resources.
+ private final Context mContext;
+
+ // The InputFilter this tracker is associated with i.e. the filter
+ // which delegates event processing to this touch explorer.
+ private final InputFilter mInputFilter;
+
+ // Helper class for tracking pointers on the screen, for example which
+ // pointers are down, which are active, etc.
+ private final PointerTracker mPointerTracker;
+
+ // Handle to the accessibility manager for firing accessibility events
+ // announcing touch exploration gesture start and end.
+ private final AccessibilityManager mAccessibilityManager;
+
+ // The last event that was received while performing touch exploration.
+ private MotionEvent mLastTouchExploreEvent;
+
+ // The current state of the touch explorer.
+ private int mCurrentState = STATE_TOUCH_EXPLORING;
+
+ // Flag whether a touch exploration gesture is in progress.
+ private boolean mTouchExploreGestureInProgress;
+
+ // The ID of the pointer used for dragging.
+ private int mDraggingPointerId;
+
+ // Handler for performing asynchronous operations.
+ private final Handler mHandler;
+
+ // Command for delayed sending of a hover event.
+ private final SendHoverDelayed mSendHoverDelayed;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param inputFilter The input filter associated with this explorer.
+ * @param context A context handle for accessing resources.
+ */
+ public TouchExplorer(InputFilter inputFilter, Context context) {
+ mInputFilter = inputFilter;
+ mTouchExplorationTapSlop =
+ ViewConfiguration.get(context).getScaledTouchExplorationTapSlop();
+ mDraggingDistance = mTouchExplorationTapSlop * COEFFICIENT_DRAGGING_DISTANCE;
+ mPointerTracker = new PointerTracker(context);
+ mContext = context;
+ mHandler = new Handler(context.getMainLooper());
+ mSendHoverDelayed = new SendHoverDelayed();
+ mAccessibilityManager = AccessibilityManager.getInstance(context);
+
+ // Populate the temporary array with PointerCorrds to be reused.
+ for (int i = 0, count = mTempPointerCoords.length; i < count; i++) {
+ mTempPointerCoords[i] = new PointerCoords();
+ }
+ }
+
+ public void clear(MotionEvent event, int policyFlags) {
+ sendUpForInjectedDownPointers(event, policyFlags);
+ clear();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void onMotionEvent(MotionEvent event, int policyFlags) {
+ if (DEBUG) {
+ Slog.d(LOG_TAG_RECEIVED, "Received event: " + event + ", policyFlags=0x"
+ + Integer.toHexString(policyFlags));
+ Slog.d(LOG_TAG_STATE, sStateSymbolicNames.get(mCurrentState));
+ }
+
+ // Keep track of the pointers's state.
+ mPointerTracker.onReceivedMotionEvent(event);
+
+ switch(mCurrentState) {
+ case STATE_TOUCH_EXPLORING: {
+ handleMotionEventStateTouchExploring(event, policyFlags);
+ } break;
+ case STATE_DRAGGING: {
+ handleMotionEventStateDragging(event, policyFlags);
+ } break;
+ case STATE_DELEGATING: {
+ handleMotionEventStateDelegating(event, policyFlags);
+ } break;
+ default: {
+ throw new IllegalStateException("Illegal state: " + mCurrentState);
+ }
+ }
+ }
+
+ /**
+ * Handles a motion event in touch exploring state.
+ *
+ * @param event The event to be handled.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void handleMotionEventStateTouchExploring(MotionEvent event, int policyFlags) {
+ PointerTracker pointerTracker = mPointerTracker;
+ final int activePointerCount = pointerTracker.getActivePointerCount();
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN: {
+ // Send a hover for every finger down so the user gets feedback
+ // where she is currently touching.
+ mSendHoverDelayed.forceSendAndRemove();
+ mSendHoverDelayed.post(event, MotionEvent.ACTION_HOVER_ENTER, 0, policyFlags,
+ DELAY_SEND_HOVER_MOVE);
+ } break;
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ switch (activePointerCount) {
+ case 0: {
+ throw new IllegalStateException("The must always be one active pointer in"
+ + "touch exploring state!");
+ }
+ case 1: {
+ // Schedule a hover event which will lead to firing an
+ // accessibility event from the hovered view.
+ mSendHoverDelayed.remove();
+ final int pointerId = pointerTracker.getPrimaryActivePointerId();
+ final int pointerIndex = event.findPointerIndex(pointerId);
+ final int lastAction = pointerTracker.getLastInjectedHoverAction();
+ // If a schedules hover enter for another pointer is delivered we send move.
+ final int action = (lastAction == MotionEvent.ACTION_HOVER_ENTER)
+ ? MotionEvent.ACTION_HOVER_MOVE
+ : MotionEvent.ACTION_HOVER_ENTER;
+ mSendHoverDelayed.post(event, action, pointerIndex, policyFlags,
+ DELAY_SEND_HOVER_MOVE);
+
+ if (mLastTouchExploreEvent == null) {
+ break;
+ }
+
+ // If more pointers down on the screen since the last touch
+ // exploration we discard the last cached touch explore event.
+ if (event.getPointerCount() != mLastTouchExploreEvent.getPointerCount()) {
+ mLastTouchExploreEvent = null;
+ }
+ } break;
+ default: {
+ /* do nothing - let the code for ACTION_MOVE decide what to do */
+ } break;
+ }
+ } break;
+ case MotionEvent.ACTION_MOVE: {
+ switch (activePointerCount) {
+ case 0: {
+ /* do nothing - no active pointers so we swallow the event */
+ } break;
+ case 1: {
+ final int pointerId = pointerTracker.getPrimaryActivePointerId();
+ final int pointerIndex = event.findPointerIndex(pointerId);
+
+ // Detect touch exploration gesture start by having one active pointer
+ // that moved more than a given distance.
+ if (!mTouchExploreGestureInProgress) {
+ final float deltaX = pointerTracker.getReceivedPointerDownX(pointerId)
+ - event.getX(pointerIndex);
+ final float deltaY = pointerTracker.getReceivedPointerDownY(pointerId)
+ - event.getY(pointerIndex);
+ final double moveDelta = Math.hypot(deltaX, deltaY);
+
+ if (moveDelta > mTouchExplorationTapSlop) {
+ mTouchExploreGestureInProgress = true;
+ sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START);
+ // Make sure the scheduled down/move event is sent.
+ mSendHoverDelayed.forceSendAndRemove();
+ sendHoverEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIndex,
+ policyFlags);
+ }
+ } else {
+ // Touch exploration gesture in progress so send a hover event.
+ sendHoverEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIndex,
+ policyFlags);
+ }
+
+ // Detect long press on the last touch explored position.
+ if (!mTouchExploreGestureInProgress && mLastTouchExploreEvent != null) {
+
+ // If the down was not in the time slop => nothing else to do.
+ final long pointerDownTime =
+ pointerTracker.getReceivedPointerDownTime(pointerId);
+ final long lastExploreTime = mLastTouchExploreEvent.getEventTime();
+ final long deltaTimeExplore = pointerDownTime - lastExploreTime;
+ if (deltaTimeExplore > ACTIVATION_TIME_SLOP) {
+ mLastTouchExploreEvent = null;
+ break;
+ }
+
+ // If the pointer moved more than the tap slop => nothing else to do.
+ final float deltaX = mLastTouchExploreEvent.getX(pointerIndex)
+ - event.getX(pointerIndex);
+ final float deltaY = mLastTouchExploreEvent.getY(pointerIndex)
+ - event.getY(pointerIndex);
+ final float moveDelta = (float) Math.hypot(deltaX, deltaY);
+ if (moveDelta > mTouchExplorationTapSlop) {
+ mLastTouchExploreEvent = null;
+ break;
+ }
+
+ // If down for long enough we get a long press.
+ final long deltaTimeMove = event.getEventTime() - pointerDownTime;
+ if (deltaTimeMove > ViewConfiguration.getLongPressTimeout()) {
+ mCurrentState = STATE_DELEGATING;
+ // Make sure the scheduled hover exit is delivered.
+ mSendHoverDelayed.forceSendAndRemove();
+ sendDownForAllActiveNotInjectedPointers(event, policyFlags);
+ sendMotionEvent(event, policyFlags);
+ mTouchExploreGestureInProgress = false;
+ mLastTouchExploreEvent = null;
+ }
+ }
+ } break;
+ case 2: {
+ // Make sure the scheduled hover enter is delivered.
+ mSendHoverDelayed.forceSendAndRemove();
+ // We want to no longer hover over the location so subsequent
+ // touch at the same spot will generate a hover enter.
+ final int pointerId = pointerTracker.getPrimaryActivePointerId();
+ final int pointerIndex = event.findPointerIndex(pointerId);
+ sendHoverEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIndex,
+ policyFlags);
+
+ if (isDraggingGesture(event)) {
+ // Two pointers moving in the same direction within
+ // a given distance perform a drag.
+ mCurrentState = STATE_DRAGGING;
+ if (mTouchExploreGestureInProgress) {
+ sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
+ mTouchExploreGestureInProgress = false;
+ }
+ mDraggingPointerId = pointerTracker.getPrimaryActivePointerId();
+ sendDragEvent(event, MotionEvent.ACTION_DOWN, policyFlags);
+ } else {
+ // Two pointers moving arbitrary are delegated to the view hierarchy.
+ mCurrentState = STATE_DELEGATING;
+ if (mTouchExploreGestureInProgress) {
+ sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
+ mTouchExploreGestureInProgress = false;
+ }
+ sendDownForAllActiveNotInjectedPointers(event, policyFlags);
+ }
+ } break;
+ default: {
+ // Make sure the scheduled hover enter is delivered.
+ mSendHoverDelayed.forceSendAndRemove();
+ // We want to no longer hover over the location so subsequent
+ // touch at the same spot will generate a hover enter.
+ final int pointerId = pointerTracker.getPrimaryActivePointerId();
+ final int pointerIndex = event.findPointerIndex(pointerId);
+ sendHoverEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIndex,
+ policyFlags);
+
+ // More than two pointers are delegated to the view hierarchy.
+ mCurrentState = STATE_DELEGATING;
+ mSendHoverDelayed.remove();
+ if (mTouchExploreGestureInProgress) {
+ sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
+ mTouchExploreGestureInProgress = false;
+ }
+ sendDownForAllActiveNotInjectedPointers(event, policyFlags);
+ }
+ }
+ } break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_UP: {
+ switch (activePointerCount) {
+ case 0: {
+ // If the pointer that went up was not active we have nothing to do.
+ if (!pointerTracker.wasLastReceivedUpPointerActive()) {
+ break;
+ }
+
+ // If touch exploring announce the end of the gesture.
+ if (mTouchExploreGestureInProgress) {
+ sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
+ mTouchExploreGestureInProgress = false;
+ }
+
+ // Detect whether to activate i.e. click on the last explored location.
+ if (mLastTouchExploreEvent != null) {
+ final int pointerId = pointerTracker.getLastReceivedUpPointerId();
+
+ // If the down was not in the time slop => nothing else to do.
+ final long eventTime =
+ pointerTracker.getLastReceivedUpPointerDownTime();
+ final long exploreTime = mLastTouchExploreEvent.getEventTime();
+ final long deltaTime = eventTime - exploreTime;
+ if (deltaTime > ACTIVATION_TIME_SLOP) {
+ mSendHoverDelayed.forceSendAndRemove();
+ scheduleHoverExit(event, policyFlags);
+ mLastTouchExploreEvent = MotionEvent.obtain(event);
+ break;
+ }
+
+ // If the pointer moved more than the tap slop => nothing else to do.
+ final int pointerIndex = event.findPointerIndex(pointerId);
+ final float deltaX = pointerTracker.getLastReceivedUpPointerDownX()
+ - event.getX(pointerIndex);
+ final float deltaY = pointerTracker.getLastReceivedUpPointerDownY()
+ - event.getY(pointerIndex);
+ final float deltaMove = (float) Math.hypot(deltaX, deltaY);
+ if (deltaMove > mTouchExplorationTapSlop) {
+ mSendHoverDelayed.forceSendAndRemove();
+ scheduleHoverExit(event, policyFlags);
+ mLastTouchExploreEvent = MotionEvent.obtain(event);
+ break;
+ }
+
+ // All preconditions are met, so click the last explored location.
+ mSendHoverDelayed.forceSendAndRemove();
+ sendActionDownAndUp(mLastTouchExploreEvent, policyFlags);
+ mLastTouchExploreEvent = null;
+ } else {
+ mSendHoverDelayed.forceSendAndRemove();
+ scheduleHoverExit(event, policyFlags);
+ mLastTouchExploreEvent = MotionEvent.obtain(event);
+ }
+ } break;
+ }
+ } break;
+ case MotionEvent.ACTION_CANCEL: {
+ final int lastAction = pointerTracker.getLastInjectedHoverAction();
+ if (lastAction != MotionEvent.ACTION_HOVER_EXIT) {
+ final int pointerId = pointerTracker.getPrimaryActivePointerId();
+ final int pointerIndex = event.findPointerIndex(pointerId);
+ sendHoverEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIndex,
+ policyFlags);
+ }
+ clear();
+ } break;
+ }
+ }
+
+ /**
+ * Handles a motion event in dragging state.
+ *
+ * @param event The event to be handled.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void handleMotionEventStateDragging(MotionEvent event, int policyFlags) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN: {
+ throw new IllegalStateException("Dragging state can be reached only if two "
+ + "pointers are already down");
+ }
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ // We are in dragging state so we have two pointers and another one
+ // goes down => delegate the three pointers to the view hierarchy
+ mCurrentState = STATE_DELEGATING;
+ sendDragEvent(event, MotionEvent.ACTION_UP, policyFlags);
+ sendDownForAllActiveNotInjectedPointers(event, policyFlags);
+ } break;
+ case MotionEvent.ACTION_MOVE: {
+ final int activePointerCount = mPointerTracker.getActivePointerCount();
+ switch (activePointerCount) {
+ case 2: {
+ if (isDraggingGesture(event)) {
+ // If still dragging send a drag event.
+ sendDragEvent(event, MotionEvent.ACTION_MOVE, policyFlags);
+ } else {
+ // The two pointers are moving either in different directions or
+ // no close enough => delegate the gesture to the view hierarchy.
+ mCurrentState = STATE_DELEGATING;
+ // Send an event to the end of the drag gesture.
+ sendDragEvent(event, MotionEvent.ACTION_UP, policyFlags);
+ // Deliver all active pointers to the view hierarchy.
+ sendDownForAllActiveNotInjectedPointers(event, policyFlags);
+ }
+ } break;
+ default: {
+ mCurrentState = STATE_DELEGATING;
+ // Send an event to the end of the drag gesture.
+ sendDragEvent(event, MotionEvent.ACTION_UP, policyFlags);
+ // Deliver all active pointers to the view hierarchy.
+ sendDownForAllActiveNotInjectedPointers(event, policyFlags);
+ }
+ }
+ } break;
+ case MotionEvent.ACTION_POINTER_UP: {
+ mCurrentState = STATE_TOUCH_EXPLORING;
+ // Send an event to the end of the drag gesture.
+ sendDragEvent(event, MotionEvent.ACTION_UP, policyFlags);
+ } break;
+ case MotionEvent.ACTION_CANCEL: {
+ clear();
+ } break;
+ }
+ }
+
+ /**
+ * Handles a motion event in delegating state.
+ *
+ * @param event The event to be handled.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ public void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN: {
+ throw new IllegalStateException("Delegating state can only be reached if "
+ + "there is at least one pointer down!");
+ }
+ case MotionEvent.ACTION_UP: {
+ mCurrentState = STATE_TOUCH_EXPLORING;
+ } break;
+ case MotionEvent.ACTION_MOVE: {
+ // Check whether some other pointer became active because they have moved
+ // a given distance and if such exist send them to the view hierarchy
+ final int notInjectedCount = mPointerTracker.getNotInjectedActivePointerCount();
+ if (notInjectedCount > 0) {
+ sendDownForAllActiveNotInjectedPointers(event, policyFlags);
+ }
+ } break;
+ case MotionEvent.ACTION_POINTER_UP: {
+ // No active pointers => go to initial state.
+ if (mPointerTracker.getActivePointerCount() == 0) {
+ mCurrentState = STATE_TOUCH_EXPLORING;
+ }
+ } break;
+ case MotionEvent.ACTION_CANCEL: {
+ clear();
+ } break;
+ }
+ // Deliver the event striping out inactive pointers.
+ sendMotionEventStripInactivePointers(event, policyFlags);
+ }
+
+ /**
+ * Schedules a hover up event so subsequent poking on the same location after
+ * the scheduled delay will perform exploration.
+ *
+ * @param prototype The prototype from which to create the injected events.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void scheduleHoverExit(MotionEvent prototype,
+ int policyFlags) {
+ final int pointerId = mPointerTracker.getLastReceivedUpPointerId();
+ final int pointerIndex = prototype.findPointerIndex(pointerId);
+ // We want to no longer hover over the location so subsequent
+ // touch at the same spot will generate a hover enter.
+ mSendHoverDelayed.post(prototype, MotionEvent.ACTION_HOVER_EXIT,
+ pointerIndex, policyFlags, ACTIVATION_TIME_SLOP);
+ }
+
+ /**
+ * Sends down events to the view hierarchy for all active pointers which are
+ * not already being delivered i.e. pointers that are not yet injected.
+ *
+ * @param prototype The prototype from which to create the injected events.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void sendDownForAllActiveNotInjectedPointers(MotionEvent prototype, int policyFlags) {
+ PointerCoords[] pointerCoords = mTempPointerCoords;
+ PointerTracker pointerTracker = mPointerTracker;
+ int[] pointerIds = mTempPointerIds;
+ int pointerDataIndex = 0;
+
+ final int pinterCount = prototype.getPointerCount();
+ for (int i = 0; i < pinterCount; i++) {
+ final int pointerId = prototype.getPointerId(i);
+
+ // Skip inactive pointers.
+ if (!pointerTracker.isActivePointer(pointerId)) {
+ continue;
+ }
+ // Skip already delivered pointers.
+ if (pointerTracker.isInjectedPointerDown(pointerId)) {
+ continue;
+ }
+
+ // Populate and inject an event for the current pointer.
+ pointerIds[pointerDataIndex] = pointerId;
+ prototype.getPointerCoords(i, pointerCoords[pointerDataIndex]);
+
+ final long downTime = pointerTracker.getLastInjectedDownEventTime();
+ final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, pointerDataIndex);
+ final int pointerCount = pointerDataIndex + 1;
+ final long pointerDownTime = SystemClock.uptimeMillis();
+
+ MotionEvent event = MotionEvent.obtain(downTime, pointerDownTime,
+ action, pointerCount, pointerIds, pointerCoords, prototype.getMetaState(),
+ prototype.getXPrecision(), prototype.getYPrecision(), prototype.getDeviceId(),
+ prototype.getEdgeFlags(), prototype.getSource(), prototype.getFlags());
+ sendMotionEvent(event, policyFlags);
+ event.recycle();
+
+ pointerDataIndex++;
+ }
+ }
+
+ /**
+ * Sends up events to the view hierarchy for all active pointers which are
+ * already being delivered i.e. pointers that are injected.
+ *
+ * @param prototype The prototype from which to create the injected events.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) {
+ PointerTracker pointerTracker = mPointerTracker;
+ PointerCoords[] pointerCoords = mTempPointerCoords;
+ int[] pointerIds = mTempPointerIds;
+ int pointerDataIndex = 0;
+
+ final int pointerCount = prototype.getPointerCount();
+ for (int i = 0; i < pointerCount; i++) {
+ final int pointerId = prototype.getPointerId(i);
+
+ // Skip non injected down pointers.
+ if (!pointerTracker.isInjectedPointerDown(pointerId)) {
+ continue;
+ }
+
+ // Populate and inject event.
+ pointerIds[pointerDataIndex] = pointerId;
+ prototype.getPointerCoords(i, pointerCoords[pointerDataIndex]);
+
+ final long downTime = pointerTracker.getLastInjectedDownEventTime();
+ final int action = computeInjectionAction(MotionEvent.ACTION_UP, pointerDataIndex);
+ final int newPointerCount = pointerDataIndex + 1;
+ final long eventTime = SystemClock.uptimeMillis();
+
+ MotionEvent event = MotionEvent.obtain(downTime, eventTime, action,
+ newPointerCount, pointerIds, pointerCoords, prototype.getMetaState(),
+ prototype.getXPrecision(), prototype.getYPrecision(), prototype.getDeviceId(),
+ prototype.getEdgeFlags(), prototype.getSource(), prototype.getFlags());
+
+ sendMotionEvent(event, policyFlags);
+ event.recycle();
+
+ pointerDataIndex++;
+ }
+ }
+
+ /**
+ * Sends a motion event by first stripping the inactive pointers.
+ *
+ * @param prototype The prototype from which to create the injected event.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void sendMotionEventStripInactivePointers(MotionEvent prototype, int policyFlags) {
+ PointerTracker pointerTracker = mPointerTracker;
+
+ // All pointers active therefore we just inject the event as is.
+ if (prototype.getPointerCount() == pointerTracker.getActivePointerCount()) {
+ sendMotionEvent(prototype, policyFlags);
+ return;
+ }
+
+ // No active pointers and the one that just went up was not
+ // active, therefore we have nothing to do.
+ if (pointerTracker.getActivePointerCount() == 0
+ && !pointerTracker.wasLastReceivedUpPointerActive()) {
+ return;
+ }
+
+ // Filter out inactive pointers from the event and inject it.
+ PointerCoords[] pointerCoords = mTempPointerCoords;
+ int[] pointerIds = mTempPointerIds;
+ int [] newToOldPointerIndexMap = mTempNewToOldPointerIndexMap;
+ int newPointerIndex = 0;
+ int actionIndex = prototype.getActionIndex();
+
+ final int oldPointerCount = prototype.getPointerCount();
+ for (int oldPointerIndex = 0; oldPointerIndex < oldPointerCount; oldPointerIndex++) {
+ final int pointerId = prototype.getPointerId(oldPointerIndex);
+
+ // If the pointer is inactive or the pointer that just went up
+ // was inactive we strip the pointer data from the event.
+ if (!pointerTracker.isActiveOrWasLastActiveUpPointer(pointerId)) {
+ if (oldPointerIndex <= prototype.getActionIndex()) {
+ actionIndex--;
+ }
+ continue;
+ }
+
+ newToOldPointerIndexMap[newPointerIndex] = oldPointerIndex;
+ pointerIds[newPointerIndex] = pointerId;
+ prototype.getPointerCoords(oldPointerIndex, pointerCoords[newPointerIndex]);
+
+ newPointerIndex++;
+ }
+
+ // If we skipped all pointers => nothing to do.
+ if (newPointerIndex == 0) {
+ return;
+ }
+
+ // Populate and inject the event.
+ final long downTime = pointerTracker.getLastInjectedDownEventTime();
+ final int action = computeInjectionAction(prototype.getActionMasked(), actionIndex);
+ final int newPointerCount = newPointerIndex;
+ MotionEvent prunedEvent = MotionEvent.obtain(downTime, prototype.getEventTime(), action,
+ newPointerCount, pointerIds, pointerCoords, prototype.getMetaState(),
+ prototype.getXPrecision(), prototype.getYPrecision(), prototype.getDeviceId(),
+ prototype.getEdgeFlags(), prototype.getSource(),prototype.getFlags());
+
+ // Add the filtered history.
+ final int historySize = prototype.getHistorySize();
+ for (int historyIndex = 0; historyIndex < historySize; historyIndex++) {
+ for (int pointerIndex = 0; pointerIndex < newPointerCount; pointerIndex++) {
+ final int oldPointerIndex = newToOldPointerIndexMap[pointerIndex];
+ prototype.getPointerCoords(oldPointerIndex, pointerCoords[pointerIndex]);
+ }
+ final long historicalTime = prototype.getHistoricalEventTime(historyIndex);
+ prunedEvent.addBatch(historicalTime, pointerCoords, 0);
+ }
+
+ sendMotionEvent(prunedEvent, policyFlags);
+ prunedEvent.recycle();
+ }
+
+ /**
+ * Sends a dragging event from a two pointer event. The two pointers are
+ * merged into one and delivered to the view hierarchy. Through the entire
+ * drag gesture the pointer id delivered to the view hierarchy is the same.
+ *
+ * @param prototype The prototype from which to create the injected event.
+ * @param action The dragging action that is to be injected.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void sendDragEvent(MotionEvent prototype, int action, int policyFlags) {
+ PointerCoords[] pointerCoords = mTempPointerCoords;
+ int[] pointerIds = mTempPointerIds;
+ final int pointerId = mDraggingPointerId;
+ final int pointerIndex = prototype.findPointerIndex(pointerId);
+
+ // Populate the event with the date of the dragging pointer and inject it.
+ pointerIds[0] = pointerId;
+ prototype.getPointerCoords(pointerIndex, pointerCoords[0]);
+
+ MotionEvent event = MotionEvent.obtain(prototype.getDownTime(),
+ prototype.getEventTime(), action, 1, pointerIds, pointerCoords,
+ prototype.getMetaState(), prototype.getXPrecision(), prototype.getYPrecision(),
+ prototype.getDeviceId(), prototype.getEdgeFlags(), prototype.getSource(),
+ prototype.getFlags());
+
+ sendMotionEvent(event, policyFlags);
+ event.recycle();
+ }
+
+ /**
+ * Sends an up and down events.
+ *
+ * @param prototype The prototype from which to create the injected events.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void sendActionDownAndUp(MotionEvent prototype, int policyFlags) {
+ PointerCoords[] pointerCoords = mTempPointerCoords;
+ int[] pointerIds = mTempPointerIds;
+ final int pointerId = mPointerTracker.getLastReceivedUpPointerId();
+ final int pointerIndex = prototype.findPointerIndex(pointerId);
+
+ // Send down.
+ pointerIds[0] = pointerId;
+ prototype.getPointerCoords(pointerIndex, pointerCoords[0]);
+
+ final long downTime = SystemClock.uptimeMillis();
+
+ MotionEvent downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN,
+ 1, mTempPointerIds, mTempPointerCoords, prototype.getMetaState(),
+ prototype.getXPrecision(), prototype.getYPrecision(), prototype.getDeviceId(),
+ prototype.getEdgeFlags(), prototype.getSource(), prototype.getFlags());
+
+ // Clone the down event before recycling it.
+ MotionEvent upEvent = MotionEvent.obtain(downEvent);
+
+ sendMotionEvent(downEvent, policyFlags);
+ downEvent.recycle();
+
+ // Send up.
+ upEvent.setAction(MotionEvent.ACTION_UP);
+ sendMotionEvent(upEvent, policyFlags);
+ upEvent.recycle();
+ }
+
+ /**
+ * Sends a hover event.
+ *
+ * @param prototype The prototype from which to create the injected event.
+ * @param action The hover action.
+ * @param pointerIndex The action pointer index.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void sendHoverEvent(MotionEvent prototype, int action, int pointerIndex, int
+ policyFlags) {
+ PointerCoords[] pointerCoords = mTempPointerCoords;
+ int[] pointerIds = mTempPointerIds;
+
+ // Keep only data relevant to a hover event.
+ pointerIds[0] = prototype.getPointerId(pointerIndex);
+ pointerCoords[0].clear();
+ pointerCoords[0].x = prototype.getX(pointerIndex);
+ pointerCoords[0].y = prototype.getY(pointerIndex);
+
+ final long downTime = mPointerTracker.getLastInjectedDownEventTime();
+
+ // Populate and inject a hover event.
+ MotionEvent hoverEvent = MotionEvent.obtain(downTime, prototype.getEventTime(), action,
+ 1, pointerIds, pointerCoords, 0, 0, 0, prototype.getDeviceId(), 0,
+ prototype.getSource(), 0);
+
+ sendMotionEvent(hoverEvent, policyFlags);
+ hoverEvent.recycle();
+ }
+
+ /**
+ * Computes the action for an injected event based on a masked action
+ * and a pointer index.
+ *
+ * @param actionMasked The masked action.
+ * @param pointerIndex The index of the pointer which has changed.
+ * @return The action to be used for injection.
+ */
+ private int computeInjectionAction(int actionMasked, int pointerIndex) {
+ switch (actionMasked) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ PointerTracker pointerTracker = mPointerTracker;
+ // Compute the action based on how many down pointers are injected.
+ if (pointerTracker.getInjectedPointerDownCount() == 0) {
+ return MotionEvent.ACTION_DOWN;
+ } else {
+ return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
+ | MotionEvent.ACTION_POINTER_DOWN;
+ }
+ }
+ case MotionEvent.ACTION_POINTER_UP: {
+ PointerTracker pointerTracker = mPointerTracker;
+ // Compute the action based on how many down pointers are injected.
+ if (pointerTracker.getInjectedPointerDownCount() == 1) {
+ return MotionEvent.ACTION_UP;
+ } else {
+ return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
+ | MotionEvent.ACTION_POINTER_UP;
+ }
+ }
+ default:
+ return actionMasked;
+ }
+ }
+
+ /**
+ * Determines whether a two pointer gesture is a dragging one.
+ *
+ * @param event The event with the pointer data.
+ * @return True if the gesture is a dragging one.
+ */
+ private boolean isDraggingGesture(MotionEvent event) {
+ PointerTracker pointerTracker = mPointerTracker;
+ int[] pointerIds = mTempPointerIds;
+ pointerTracker.populateActivePointerIds(pointerIds);
+
+ final int firstPtrIndex = event.findPointerIndex(pointerIds[0]);
+ final int secondPtrIndex = event.findPointerIndex(pointerIds[1]);
+
+ final float firstPtrX = event.getX(firstPtrIndex);
+ final float firstPtrY = event.getY(firstPtrIndex);
+ final float secondPtrX = event.getX(secondPtrIndex);
+ final float secondPtrY = event.getY(secondPtrIndex);
+
+ // Check if the pointers are close enough.
+ final float deltaX = firstPtrX - secondPtrX;
+ final float deltaY = firstPtrY - secondPtrY;
+ final float deltaMove = (float) Math.hypot(deltaX, deltaY);
+ if (deltaMove > mDraggingDistance) {
+ return false;
+ }
+
+ // Check if the pointers are moving in the same direction.
+ final float firstDeltaX =
+ firstPtrX - pointerTracker.getReceivedPointerDownX(firstPtrIndex);
+ final float firstDeltaY =
+ firstPtrY - pointerTracker.getReceivedPointerDownY(firstPtrIndex);
+ final float firstMagnitude =
+ (float) Math.sqrt(firstDeltaX * firstDeltaX + firstDeltaY * firstDeltaY);
+ final float firstXNormalized =
+ (firstMagnitude > 0) ? firstDeltaX / firstMagnitude : firstDeltaX;
+ final float firstYNormalized =
+ (firstMagnitude > 0) ? firstDeltaY / firstMagnitude : firstDeltaY;
+
+ final float secondDeltaX =
+ secondPtrX - pointerTracker.getReceivedPointerDownX(secondPtrIndex);
+ final float secondDeltaY =
+ secondPtrY - pointerTracker.getReceivedPointerDownY(secondPtrIndex);
+ final float secondMagnitude =
+ (float) Math.sqrt(secondDeltaX * secondDeltaX + secondDeltaY * secondDeltaY);
+ final float secondXNormalized =
+ (secondMagnitude > 0) ? secondDeltaX / secondMagnitude : secondDeltaX;
+ final float secondYNormalized =
+ (secondMagnitude > 0) ? secondDeltaY / secondMagnitude : secondDeltaY;
+
+ final float angleCos =
+ firstXNormalized * secondXNormalized + firstYNormalized * secondYNormalized;
+
+ if (angleCos < MIN_ANGLE_COS) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Sends an event announcing the start/end of a touch exploration gesture.
+ *
+ * @param eventType The type of the event to send.
+ */
+ private void sendAccessibilityEvent(int eventType) {
+ AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+ event.setPackageName(mContext.getPackageName());
+ event.setClassName(getClass().getName());
+ mAccessibilityManager.sendAccessibilityEvent(event);
+ }
+
+ /**
+ * Sends a motion event to the input filter for injection.
+ *
+ * @param event The event to send.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void sendMotionEvent(MotionEvent event, int policyFlags) {
+ if (DEBUG) {
+ Slog.d(LOG_TAG_INJECTED, "Injecting event: " + event + ", policyFlags=0x"
+ + Integer.toHexString(policyFlags));
+ }
+ // Make sure that the user will see the event.
+ policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
+ mPointerTracker.onInjectedMotionEvent(event);
+ mInputFilter.sendInputEvent(event, policyFlags);
+ }
+
+ /**
+ * Clears the internal state of this explorer.
+ */
+ private void clear() {
+ mSendHoverDelayed.remove();
+ mPointerTracker.clear();
+ mLastTouchExploreEvent = null;
+ mCurrentState = STATE_TOUCH_EXPLORING;
+ mTouchExploreGestureInProgress = false;
+ mDraggingPointerId = INVALID_POINTER_ID;
+ }
+
+ /**
+ * Helper class for tracking pointers and more specifically which of
+ * them are currently down, which are active, and which are delivered
+ * to the view hierarchy. The enclosing {@link TouchExplorer} uses the
+ * pointer state reported by this class to perform touch exploration.
+ * <p>
+ * The main purpose of this class is to allow the touch explorer to
+ * disregard pointers put down by accident by the user and not being
+ * involved in the interaction. For example, a blind user grabs the
+ * device with her left hand such that she touches the screen and she
+ * uses her right hand's index finger to explore the screen content.
+ * In this scenario the touches generated by the left hand are to be
+ * ignored.
+ */
+ class PointerTracker {
+ private static final String LOG_TAG = "PointerTracker";
+
+ // The coefficient by which to multiply
+ // ViewConfiguration.#getScaledTouchSlop()
+ // to compute #mThresholdActivePointer.
+ private static final int COEFFICIENT_ACTIVE_POINTER = 2;
+
+ // Pointers that moved less than mThresholdActivePointer
+ // are considered active i.e. are ignored.
+ private final double mThresholdActivePointer;
+
+ // Keep track of where and when a pointer went down.
+ private final float[] mReceivedPointerDownX = new float[MAX_POINTER_COUNT];
+ private final float[] mReceivedPointerDownY = new float[MAX_POINTER_COUNT];
+ private final long[] mReceivedPointerDownTime = new long[MAX_POINTER_COUNT];
+
+ // Which pointers are down.
+ private int mReceivedPointersDown;
+
+ // Which down pointers are active.
+ private int mActivePointers;
+
+ // Primary active pointer which is either the first that went down
+ // or if it goes up the next active that most recently went down.
+ private int mPrimaryActivePointerId;
+
+ // Flag indicating that there is at least one active pointer moving.
+ private boolean mHasMovingActivePointer;
+
+ // Keep track of which pointers sent to the system are down.
+ private int mInjectedPointersDown;
+
+ // Keep track of the last up pointer data.
+ private float mLastReceivedUpPointerDownX;
+ private float mLastReveivedUpPointerDownY;
+ private long mLastReceivedUpPointerDownTime;
+ private int mLastReceivedUpPointerId;
+ private boolean mLastReceivedUpPointerActive;
+
+ // The time of the last injected down.
+ private long mLastInjectedDownEventTime;
+
+ // The action of the last injected hover event.
+ private int mLastInjectedHoverEventAction = MotionEvent.ACTION_HOVER_EXIT;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param context Context for looking up resources.
+ */
+ public PointerTracker(Context context) {
+ mThresholdActivePointer =
+ ViewConfiguration.get(context).getScaledTouchSlop() * COEFFICIENT_ACTIVE_POINTER;
+ }
+
+ /**
+ * Clears the internals state.
+ */
+ public void clear() {
+ Arrays.fill(mReceivedPointerDownX, 0);
+ Arrays.fill(mReceivedPointerDownY, 0);
+ Arrays.fill(mReceivedPointerDownTime, 0);
+ mReceivedPointersDown = 0;
+ mActivePointers = 0;
+ mPrimaryActivePointerId = 0;
+ mHasMovingActivePointer = false;
+ mInjectedPointersDown = 0;
+ mLastReceivedUpPointerDownX = 0;
+ mLastReveivedUpPointerDownY = 0;
+ mLastReceivedUpPointerDownTime = 0;
+ mLastReceivedUpPointerId = 0;
+ mLastReceivedUpPointerActive = false;
+ }
+
+ /**
+ * Processes a received {@link MotionEvent} event.
+ *
+ * @param event The event to process.
+ */
+ public void onReceivedMotionEvent(MotionEvent event) {
+ final int action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ // New gesture so restart tracking injected down pointers.
+ mInjectedPointersDown = 0;
+ handleReceivedPointerDown(0, event);
+ } break;
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ handleReceivedPointerDown(event.getActionIndex(), event);
+ } break;
+ case MotionEvent.ACTION_MOVE: {
+ handleReceivedPointerMove(event);
+ } break;
+ case MotionEvent.ACTION_UP: {
+ handleReceivedPointerUp(0, event);
+ } break;
+ case MotionEvent.ACTION_POINTER_UP: {
+ handleReceivedPointerUp(event.getActionIndex(), event);
+ } break;
+ }
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Received pointer: " + toString());
+ }
+ }
+
+ /**
+ * Processes an injected {@link MotionEvent} event.
+ *
+ * @param event The event to process.
+ */
+ public void onInjectedMotionEvent(MotionEvent event) {
+ final int action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ handleInjectedPointerDown(0, event);
+ } break;
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ handleInjectedPointerDown(event.getActionIndex(), event);
+ } break;
+ case MotionEvent.ACTION_UP: {
+ handleInjectedPointerUp(0, event);
+ } break;
+ case MotionEvent.ACTION_POINTER_UP: {
+ handleInjectedPointerUp(event.getActionIndex(), event);
+ } break;
+ case MotionEvent.ACTION_HOVER_ENTER:
+ case MotionEvent.ACTION_HOVER_MOVE:
+ case MotionEvent.ACTION_HOVER_EXIT: {
+ mLastInjectedHoverEventAction = event.getActionMasked();
+ } break;
+ }
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Injected pointer: " + toString());
+ }
+ }
+
+ /**
+ * @return The number of received pointers that are down.
+ */
+ public int getReceivedPointerDownCount() {
+ return Integer.bitCount(mReceivedPointersDown);
+ }
+
+ /**
+ * @return The number of down input pointers that are active.
+ */
+ public int getActivePointerCount() {
+ return Integer.bitCount(mActivePointers);
+ }
+
+ /**
+ * Whether an received pointer is down.
+ *
+ * @param pointerId The unique pointer id.
+ * @return True if the pointer is down.
+ */
+ public boolean isReceivedPointerDown(int pointerId) {
+ final int pointerFlag = (1 << pointerId);
+ return (mReceivedPointersDown & pointerFlag) != 0;
+ }
+
+ /**
+ * Whether an injected pointer is down.
+ *
+ * @param pointerId The unique pointer id.
+ * @return True if the pointer is down.
+ */
+ public boolean isInjectedPointerDown(int pointerId) {
+ final int pointerFlag = (1 << pointerId);
+ return (mInjectedPointersDown & pointerFlag) != 0;
+ }
+
+ /**
+ * @return The number of down pointers injected to the view hierarchy.
+ */
+ public int getInjectedPointerDownCount() {
+ return Integer.bitCount(mInjectedPointersDown);
+ }
+
+ /**
+ * Whether an input pointer is active.
+ *
+ * @param pointerId The unique pointer id.
+ * @return True if the pointer is active.
+ */
+ public boolean isActivePointer(int pointerId) {
+ final int pointerFlag = (1 << pointerId);
+ return (mActivePointers & pointerFlag) != 0;
+ }
+
+ /**
+ * @param pointerId The unique pointer id.
+ * @return The X coordinate where the pointer went down.
+ */
+ public float getReceivedPointerDownX(int pointerId) {
+ return mReceivedPointerDownX[pointerId];
+ }
+
+ /**
+ * @param pointerId The unique pointer id.
+ * @return The Y coordinate where the pointer went down.
+ */
+ public float getReceivedPointerDownY(int pointerId) {
+ return mReceivedPointerDownY[pointerId];
+ }
+
+ /**
+ * @param pointerId The unique pointer id.
+ * @return The time when the pointer went down.
+ */
+ public long getReceivedPointerDownTime(int pointerId) {
+ return mReceivedPointerDownTime[pointerId];
+ }
+
+ /**
+ * @return The id of the primary pointer.
+ */
+ public int getPrimaryActivePointerId() {
+ if (mPrimaryActivePointerId == INVALID_POINTER_ID) {
+ mPrimaryActivePointerId = findPrimaryActivePointer();
+ }
+ return mPrimaryActivePointerId;
+ }
+
+ /**
+ * @return The X coordinate where the last up received pointer went down.
+ */
+ public float getLastReceivedUpPointerDownX() {
+ return mLastReceivedUpPointerDownX;
+ }
+
+ /**
+ * @return The Y coordinate where the last up received pointer went down.
+ */
+ public float getLastReceivedUpPointerDownY() {
+ return mLastReveivedUpPointerDownY;
+ }
+
+ /**
+ * @return The time when the last up received pointer went down.
+ */
+ public long getLastReceivedUpPointerDownTime() {
+ return mLastReceivedUpPointerDownTime;
+ }
+
+ /**
+ * @return The id of the last received pointer that went up.
+ */
+ public int getLastReceivedUpPointerId() {
+ return mLastReceivedUpPointerId;
+ }
+
+ /**
+ * @return Whether the last received pointer that went up was active.
+ */
+ public boolean wasLastReceivedUpPointerActive() {
+ return mLastReceivedUpPointerActive;
+ }
+
+ /**
+ * @return The time of the last injected down event.
+ */
+ public long getLastInjectedDownEventTime() {
+ return mLastInjectedDownEventTime;
+ }
+
+ /**
+ * @return The action of the last injected hover event.
+ */
+ public int getLastInjectedHoverAction() {
+ return mLastInjectedHoverEventAction;
+ }
+
+ /**
+ * Populates the active pointer IDs to the given array.
+ * <p>
+ * Note: The client is responsible for providing large enough array.
+ *
+ * @param outPointerIds The array to which to write the active pointers.
+ */
+ public void populateActivePointerIds(int[] outPointerIds) {
+ int index = 0;
+ for (int idBits = mActivePointers; idBits != 0; ) {
+ final int id = Integer.numberOfTrailingZeros(idBits);
+ idBits &= ~(1 << id);
+ outPointerIds[index] = id;
+ index++;
+ }
+ }
+
+ /**
+ * @return The number of non injected active pointers.
+ */
+ public int getNotInjectedActivePointerCount() {
+ final int pointerState = mActivePointers & ~mInjectedPointersDown;
+ return Integer.bitCount(pointerState);
+ }
+
+ /**
+ * @param pointerId The unique pointer id.
+ * @return Whether the pointer is active or was the last active than went up.
+ */
+ private boolean isActiveOrWasLastActiveUpPointer(int pointerId) {
+ return (isActivePointer(pointerId)
+ || (mLastReceivedUpPointerId == pointerId
+ && mLastReceivedUpPointerActive));
+ }
+
+ /**
+ * Handles a received pointer down event.
+ *
+ * @param pointerIndex The index of the pointer that has changed.
+ * @param event The event to be handled.
+ */
+ private void handleReceivedPointerDown(int pointerIndex, MotionEvent event) {
+ final int pointerId = event.getPointerId(pointerIndex);
+ final int pointerFlag = (1 << pointerId);
+
+ mLastReceivedUpPointerId = 0;
+ mLastReceivedUpPointerDownX = 0;
+ mLastReveivedUpPointerDownY = 0;
+ mLastReceivedUpPointerDownTime = 0;
+ mLastReceivedUpPointerActive = false;
+
+ mReceivedPointersDown |= pointerFlag;
+ mReceivedPointerDownX[pointerId] = event.getX(pointerIndex);
+ mReceivedPointerDownY[pointerId] = event.getY(pointerIndex);
+ mReceivedPointerDownTime[pointerId] = event.getEventTime();
+
+ if (!mHasMovingActivePointer) {
+ // If still no moving active pointers every
+ // down pointer is the only active one.
+ mActivePointers = pointerFlag;
+ mPrimaryActivePointerId = pointerId;
+ } else {
+ // If at least one moving active pointer every
+ // subsequent down pointer is active.
+ mActivePointers |= pointerFlag;
+ }
+ }
+
+ /**
+ * Handles a received pointer move event.
+ *
+ * @param event The event to be handled.
+ */
+ private void handleReceivedPointerMove(MotionEvent event) {
+ detectActivePointers(event);
+ }
+
+ /**
+ * Handles a received pointer up event.
+ *
+ * @param pointerIndex The index of the pointer that has changed.
+ * @param event The event to be handled.
+ */
+ private void handleReceivedPointerUp(int pointerIndex, MotionEvent event) {
+ final int pointerId = event.getPointerId(pointerIndex);
+ final int pointerFlag = (1 << pointerId);
+
+ mLastReceivedUpPointerId = pointerId;
+ mLastReceivedUpPointerDownX = getReceivedPointerDownX(pointerId);
+ mLastReveivedUpPointerDownY = getReceivedPointerDownY(pointerId);
+ mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId);
+ mLastReceivedUpPointerActive = isActivePointer(pointerId);
+
+ mReceivedPointersDown &= ~pointerFlag;
+ mActivePointers &= ~pointerFlag;
+ mReceivedPointerDownX[pointerId] = 0;
+ mReceivedPointerDownY[pointerId] = 0;
+ mReceivedPointerDownTime[pointerId] = 0;
+
+ if (mActivePointers == 0) {
+ mHasMovingActivePointer = false;
+ }
+ if (mPrimaryActivePointerId == pointerId) {
+ mPrimaryActivePointerId = INVALID_POINTER_ID;
+ }
+ }
+
+ /**
+ * Handles a injected pointer down event.
+ *
+ * @param pointerIndex The index of the pointer that has changed.
+ * @param event The event to be handled.
+ */
+ private void handleInjectedPointerDown(int pointerIndex, MotionEvent event) {
+ final int pointerId = event.getPointerId(pointerIndex);
+ final int pointerFlag = (1 << pointerId);
+ mInjectedPointersDown |= pointerFlag;
+ mLastInjectedDownEventTime = event.getEventTime();
+ }
+
+ /**
+ * Handles a injected pointer up event.
+ *
+ * @param pointerIndex The index of the pointer that has changed.
+ * @param event The event to be handled.
+ */
+ private void handleInjectedPointerUp(int pointerIndex, MotionEvent event) {
+ final int pointerId = event.getPointerId(pointerIndex);
+ final int pointerFlag = (1 << pointerId);
+ mInjectedPointersDown &= ~pointerFlag;
+ }
+
+ /**
+ * Detects the active pointers in an event.
+ *
+ * @param event The event to examine.
+ */
+ private void detectActivePointers(MotionEvent event) {
+ for (int i = 0, count = event.getPointerCount(); i < count; i++) {
+ final int pointerId = event.getPointerId(i);
+ if (mHasMovingActivePointer) {
+ // If already active => nothing to do.
+ if (isActivePointer(pointerId)) {
+ continue;
+ }
+ }
+ // Active pointers are ones that moved more than a given threshold.
+ final float pointerDeltaMove = computePointerDeltaMove(i, event);
+ if (pointerDeltaMove > mThresholdActivePointer) {
+ final int pointerFlag = (1 << pointerId);
+ mActivePointers |= pointerFlag;
+ mHasMovingActivePointer = true;
+ }
+ }
+ }
+
+ /**
+ * @return The primary active pointer.
+ */
+ private int findPrimaryActivePointer() {
+ int primaryActivePointerId = INVALID_POINTER_ID;
+ long minDownTime = Long.MAX_VALUE;
+ // Find the active pointer that went down first.
+ for (int i = 0, count = mReceivedPointerDownTime.length; i < count; i++) {
+ if (isActivePointer(i)) {
+ final long downPointerTime = mReceivedPointerDownTime[i];
+ if (downPointerTime < minDownTime) {
+ minDownTime = downPointerTime;
+ primaryActivePointerId = i;
+ }
+ }
+ }
+ return primaryActivePointerId;
+ }
+
+ /**
+ * Computes the move for a given action pointer index since the
+ * corresponding pointer went down.
+ *
+ * @param pointerIndex The action pointer index.
+ * @param event The event to examine.
+ * @return The distance the pointer has moved.
+ */
+ private float computePointerDeltaMove(int pointerIndex, MotionEvent event) {
+ final int pointerId = event.getPointerId(pointerIndex);
+ final float deltaX = event.getX(pointerIndex) - mReceivedPointerDownX[pointerId];
+ final float deltaY = event.getY(pointerIndex) - mReceivedPointerDownY[pointerId];
+ return (float) Math.hypot(deltaX, deltaY);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("=========================");
+ builder.append("\nDown pointers #");
+ builder.append(getReceivedPointerDownCount());
+ builder.append(" [ ");
+ for (int i = 0; i < MAX_POINTER_COUNT; i++) {
+ if (isReceivedPointerDown(i)) {
+ builder.append(i);
+ builder.append(" ");
+ }
+ }
+ builder.append("]");
+ builder.append("\nActive pointers #");
+ builder.append(getActivePointerCount());
+ builder.append(" [ ");
+ for (int i = 0; i < MAX_POINTER_COUNT; i++) {
+ if (isActivePointer(i)) {
+ builder.append(i);
+ builder.append(" ");
+ }
+ }
+ builder.append("]");
+ builder.append("\nPrimary active pointer id [ ");
+ builder.append(getPrimaryActivePointerId());
+ builder.append(" ]");
+ builder.append("\n=========================");
+ return builder.toString();
+ }
+ }
+
+ /**
+ * Class for delayed sending of hover events.
+ */
+ private final class SendHoverDelayed implements Runnable {
+ private static final String LOG_TAG = "SendHoverEnterOrExitDelayed";
+
+ private MotionEvent mEvent;
+ private int mAction;
+ private int mPointerIndex;
+ private int mPolicyFlags;
+
+ public void post(MotionEvent prototype, int action, int pointerIndex, int policyFlags,
+ long delay) {
+ remove();
+ mEvent = MotionEvent.obtain(prototype);
+ mAction = action;
+ mPointerIndex = pointerIndex;
+ mPolicyFlags = policyFlags;
+ mHandler.postDelayed(this, delay);
+ }
+
+ public void remove() {
+ mHandler.removeCallbacks(this);
+ clear();
+ }
+
+ private boolean isPenidng() {
+ return (mEvent != null);
+ }
+
+ private void clear() {
+ if (!isPenidng()) {
+ return;
+ }
+ mEvent.recycle();
+ mEvent = null;
+ mAction = 0;
+ mPointerIndex = -1;
+ mPolicyFlags = 0;
+ }
+
+ public void forceSendAndRemove() {
+ if (isPenidng()) {
+ run();
+ remove();
+ }
+ }
+
+ public void run() {
+ if (DEBUG) {
+ if (mAction == MotionEvent.ACTION_HOVER_ENTER) {
+ Slog.d(LOG_TAG, "Injecting: " + MotionEvent.ACTION_HOVER_ENTER);
+ } else if (mAction == MotionEvent.ACTION_HOVER_MOVE) {
+ Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_MOVE");
+ } else if (mAction == MotionEvent.ACTION_HOVER_EXIT) {
+ Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_EXIT");
+ }
+ }
+
+ sendHoverEvent(mEvent, mAction, mPointerIndex, mPolicyFlags);
+ clear();
+ }
+ }
+}
diff --git a/services/java/com/android/server/wm/InputFilter.java b/services/java/com/android/server/wm/InputFilter.java
index 7e1ab07..8f0001a 100644
--- a/services/java/com/android/server/wm/InputFilter.java
+++ b/services/java/com/android/server/wm/InputFilter.java
@@ -105,11 +105,13 @@
private final InputEventConsistencyVerifier mInboundInputEventConsistencyVerifier =
InputEventConsistencyVerifier.isInstrumentationEnabled() ?
new InputEventConsistencyVerifier(this,
- InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT) : null;
+ InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT,
+ "InputFilter#InboundInputEventConsistencyVerifier") : null;
private final InputEventConsistencyVerifier mOutboundInputEventConsistencyVerifier =
InputEventConsistencyVerifier.isInstrumentationEnabled() ?
new InputEventConsistencyVerifier(this,
- InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT) : null;
+ InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT,
+ "InputFilter#OutboundInputEventConsistencyVerifier") : null;
/**
* Creates the input filter.
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java
index 2f9b026..76031a8 100644
--- a/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java
@@ -153,7 +153,8 @@
float[] advances = new float[length];
float textWidthHB = paint.getTextRunAdvances(text, 0, length, 0, length, dir, advances, 0);
setPaintDir(paint, dir);
- float textWidthICU = paint.getTextRunAdvancesICU(text, 0, length, 0, length, dir, advances, 0);
+ float textWidthICU = paint.getTextRunAdvances(text, 0, length, 0, length, dir, advances, 0,
+ 1 /* use ICU */);
logAdvances(text, textWidthHB, textWidthICU, advances);
drawMetricsAroundText(canvas, x, y, textWidthHB, textWidthICU, textSize, Color.RED, Color.GREEN);