Merge "Revert "Remove permission android.permission.WRITE_MEDIA_STORAGE""
diff --git a/Android.mk b/Android.mk
index cd9ae7d..898d7e2 100644
--- a/Android.mk
+++ b/Android.mk
@@ -158,6 +158,7 @@
core/java/com/android/internal/view/IInputMethodSession.aidl \
core/java/com/android/internal/widget/IRemoteViewsFactory.aidl \
core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl \
+ keystore/java/android/security/IKeyChainService.aidl \
location/java/android/location/ICountryDetector.aidl \
location/java/android/location/ICountryListener.aidl \
location/java/android/location/IGeocodeProvider.aidl \
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/Display.java b/core/java/android/view/Display.java
index 980239b..8e839c0 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -200,8 +200,27 @@
* @param outMetrics
*/
public void getMetrics(DisplayMetrics outMetrics) {
- outMetrics.widthPixels = getWidth();
- outMetrics.heightPixels = getHeight();
+ synchronized (mTmpPoint) {
+ getSize(mTmpPoint);
+ outMetrics.widthPixels = mTmpPoint.x;
+ outMetrics.heightPixels = mTmpPoint.y;
+ }
+ getNonSizeMetrics(outMetrics);
+ }
+
+ /**
+ * Initialize a DisplayMetrics object from this display's data.
+ *
+ * @param outMetrics
+ * @hide
+ */
+ public void getRealMetrics(DisplayMetrics outMetrics) {
+ outMetrics.widthPixels = getRealWidth();
+ outMetrics.heightPixels = getRealHeight();
+ getNonSizeMetrics(outMetrics);
+ }
+
+ private void getNonSizeMetrics(DisplayMetrics outMetrics) {
outMetrics.density = mDensity;
outMetrics.densityDpi = (int)((mDensity*DisplayMetrics.DENSITY_DEFAULT)+.5f);
outMetrics.scaledDensity= outMetrics.density;
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/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 8f7bb8c..3d19380 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -400,7 +400,7 @@
* display dimensions.
*/
public void setInitialDisplaySize(int width, int height);
-
+
/**
* Check permissions when adding a window.
*
@@ -816,6 +816,13 @@
boolean displayEnabled);
/**
+ * Return the currently locked screen rotation, if any. Return
+ * Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_180, or
+ * Surface.ROTATION_270 if locked; return -1 if not locked.
+ */
+ public int getLockedRotationLw();
+
+ /**
* Called when the system is mostly done booting to determine whether
* the system should go into safe mode.
*/
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 537709d..4d3aa68 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -81,8 +81,8 @@
import android.text.method.TimeKeyListener;
import android.text.method.TransformationMethod;
import android.text.style.ClickableSpan;
-import android.text.style.SuggestionSpan;
import android.text.style.ParagraphStyle;
+import android.text.style.SuggestionSpan;
import android.text.style.URLSpan;
import android.text.style.UpdateAppearance;
import android.text.util.Linkify;
@@ -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,
@@ -8038,7 +8037,7 @@
case ID_SELECTION_MODE:
if (mSelectionActionMode != null) {
// Selection mode is already started, simply change selected part.
- updateSelectedRegion();
+ selectCurrentWord();
} else {
startSelectionActionMode();
}
@@ -8188,8 +8187,7 @@
startDrag(data, getTextThumbnailBuilder(selectedText), localState, 0);
stopSelectionActionMode();
} else {
- // New selection at touch position
- updateSelectedRegion();
+ selectCurrentWord();
}
handled = true;
}
@@ -8205,17 +8203,6 @@
return handled;
}
- /**
- * When selection mode is already started, this method simply updates the selected part of text
- * to the text under the finger.
- */
- private void updateSelectedRegion() {
- // Start a new selection at current position, keep selectionAction mode on
- selectCurrentWord();
- // Updates handles' positions
- getSelectionController().show();
- }
-
private boolean touchPositionIsInSelection() {
int selectionStart = getSelectionStart();
int selectionEnd = getSelectionEnd();
@@ -8783,7 +8770,7 @@
private float mTouchOffsetY;
// Where the touch position should be on the handle to ensure a maximum cursor visibility
private float mIdealVerticalOffset;
- // Parent's (TextView) position in window
+ // Parent's (TextView) previous position in window
private int mLastParentX, mLastParentY;
// PopupWindow container absolute position with respect to the enclosing window
private int mContainerPositionX, mContainerPositionY;
@@ -8857,12 +8844,9 @@
}
public void show() {
- updateContainerPosition();
if (isShowing()) {
mContainer.update(mContainerPositionX, mContainerPositionY,
mRight - mLeft, mBottom - mTop);
-
- hideAssociatedPopupWindow();
} else {
mContainer.showAtLocation(TextView.this, 0,
mContainerPositionX, mContainerPositionY);
@@ -8877,7 +8861,6 @@
protected void dismiss() {
mIsDragging = false;
mContainer.dismiss();
- hideAssociatedPopupWindow();
}
public void hide() {
@@ -8908,22 +8891,22 @@
final int compoundPaddingLeft = getCompoundPaddingLeft();
final int compoundPaddingRight = getCompoundPaddingRight();
- final TextView hostView = TextView.this;
+ final TextView textView = TextView.this;
if (mTempRect == null) mTempRect = new Rect();
final Rect clip = mTempRect;
clip.left = compoundPaddingLeft;
clip.top = extendedPaddingTop;
- clip.right = hostView.getWidth() - compoundPaddingRight;
- clip.bottom = hostView.getHeight() - extendedPaddingBottom;
+ clip.right = textView.getWidth() - compoundPaddingRight;
+ clip.bottom = textView.getHeight() - extendedPaddingBottom;
- final ViewParent parent = hostView.getParent();
- if (parent == null || !parent.getChildVisibleRect(hostView, clip, null)) {
+ final ViewParent parent = textView.getParent();
+ if (parent == null || !parent.getChildVisibleRect(textView, clip, null)) {
return false;
}
final int[] coords = mTempCoords;
- hostView.getLocationInWindow(coords);
+ textView.getLocationInWindow(coords);
final int posX = coords[0] + mPositionX + (int) mHotspotX;
final int posY = coords[1] + mPositionY;
@@ -8932,23 +8915,6 @@
posY >= clip.top && posY <= clip.bottom;
}
- private void moveTo(int x, int y) {
- mPositionX = x - TextView.this.mScrollX;
- mPositionY = y - TextView.this.mScrollY;
-
- if (mIsDragging) {
- TextView.this.getLocationInWindow(mTempCoords);
- if (mTempCoords[0] != mLastParentX || mTempCoords[1] != mLastParentY) {
- mTouchToWindowOffsetX += mTempCoords[0] - mLastParentX;
- mTouchToWindowOffsetY += mTempCoords[1] - mLastParentY;
- mLastParentX = mTempCoords[0];
- mLastParentY = mTempCoords[1];
- }
-
- hideAssociatedPopupWindow();
- }
- }
-
public abstract int getCurrentCursorOffset();
public abstract void updateOffset(int offset);
@@ -8957,44 +8923,44 @@
protected void positionAtCursorOffset(int offset) {
addPositionToTouchUpFilter(offset);
- final int width = mDrawable.getIntrinsicWidth();
- final int height = mDrawable.getIntrinsicHeight();
final int line = mLayout.getLineForOffset(offset);
final int lineBottom = mLayout.getLineBottom(line);
- final Rect bounds = sCursorControllerTempRect;
- bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX) +
- TextView.this.mScrollX;
- bounds.top = lineBottom + TextView.this.mScrollY;
+ mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX);
+ mPositionY = lineBottom;
- bounds.right = bounds.left + width;
- bounds.bottom = bounds.top + height;
-
- convertFromViewportToContentCoordinates(bounds);
- moveTo(bounds.left, bounds.top);
+ // Take TextView's padding into account.
+ mPositionX += viewportToContentHorizontalOffset();
+ mPositionY += viewportToContentVerticalOffset();
}
- /**
- * Updates the global container's position.
- * @return whether or not the position has actually changed
- */
- private boolean updateContainerPosition() {
+ protected boolean updateContainerPosition() {
positionAtCursorOffset(getCurrentCursorOffset());
- TextView.this.getLocationInWindow(mTempCoords);
- final int containerPositionX = mTempCoords[0] + mPositionX;
- final int containerPositionY = mTempCoords[1] + mPositionY;
- if (containerPositionX != mContainerPositionX ||
- containerPositionY != mContainerPositionY) {
- mContainerPositionX = containerPositionX;
- mContainerPositionY = containerPositionY;
- return true;
- }
- return false;
+ final int previousContainerPositionX = mContainerPositionX;
+ final int previousContainerPositionY = mContainerPositionY;
+
+ TextView.this.getLocationInWindow(mTempCoords);
+ mContainerPositionX = mTempCoords[0] + mPositionX;
+ mContainerPositionY = mTempCoords[1] + mPositionY;
+
+ return (previousContainerPositionX != mContainerPositionX ||
+ previousContainerPositionY != mContainerPositionY);
}
public boolean onPreDraw() {
if (updateContainerPosition()) {
+ if (mIsDragging) {
+ if (mTempCoords[0] != mLastParentX || mTempCoords[1] != mLastParentY) {
+ mTouchToWindowOffsetX += mTempCoords[0] - mLastParentX;
+ mTouchToWindowOffsetY += mTempCoords[1] - mLastParentY;
+ mLastParentX = mTempCoords[0];
+ mLastParentY = mTempCoords[1];
+ }
+ }
+
+ onHandleMoved();
+
if (isPositionVisible()) {
mContainer.update(mContainerPositionX, mContainerPositionY,
mRight - mLeft, mBottom - mTop);
@@ -9007,9 +8973,6 @@
dismiss();
}
}
-
- // Hide paste popup as soon as the view is scrolled or moved
- hideAssociatedPopupWindow();
}
return true;
}
@@ -9076,8 +9039,8 @@
return mIsDragging;
}
- void hideAssociatedPopupWindow() {
- // No associated popup window by default
+ void onHandleMoved() {
+ // Does nothing by default
}
public void onDetached() {
@@ -9096,15 +9059,11 @@
private Runnable mHider;
private Runnable mPastePopupShower;
- public InsertionHandleView() {
- super();
- }
-
@Override
public void show() {
super.show();
hideDelayed();
- removePastePopupCallback();
+ hidePastePopupWindow();
}
public void show(int delayBeforePaste) {
@@ -9118,11 +9077,11 @@
if (mPastePopupShower == null) {
mPastePopupShower = new Runnable() {
public void run() {
- showAssociatedPopupWindow();
+ showPastePopupWindow();
}
};
}
- postDelayed(mPastePopupShower, delayBeforePaste);
+ TextView.this.postDelayed(mPastePopupShower, delayBeforePaste);
}
}
@@ -9132,11 +9091,6 @@
onDetached();
}
- @Override
- public void hide() {
- super.hide();
- }
-
private void hideDelayed() {
removeHiderCallback();
if (mHider == null) {
@@ -9146,18 +9100,12 @@
}
};
}
- postDelayed(mHider, DELAY_BEFORE_FADE_OUT);
- }
-
- private void removePastePopupCallback() {
- if (mPastePopupShower != null) {
- removeCallbacks(mPastePopupShower);
- }
+ TextView.this.postDelayed(mHider, DELAY_BEFORE_FADE_OUT);
}
private void removeHiderCallback() {
if (mHider != null) {
- removeCallbacks(mHider);
+ TextView.this.removeCallbacks(mHider);
}
}
@@ -9197,6 +9145,11 @@
}
}
}
+ hideDelayed();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ hideDelayed();
break;
default:
@@ -9214,31 +9167,30 @@
@Override
public void updateOffset(int offset) {
Selection.setSelection((Spannable) mText, offset);
- positionAtCursorOffset(offset);
}
@Override
public void updatePosition(int x, int y) {
- final int previousOffset = getCurrentCursorOffset();
- final int newOffset = getOffset(x, y);
-
- if (newOffset != previousOffset) {
- updateOffset(newOffset);
- removePastePopupCallback();
- }
- hideDelayed();
+ updateOffset(getOffset(x, y));
}
- void showAssociatedPopupWindow() {
+ void showPastePopupWindow() {
if (mPastePopupWindow == null) {
- // Lazy initialisation: create when actually shown only.
mPastePopupWindow = new PastePopupWindow();
}
mPastePopupWindow.show();
}
@Override
- void hideAssociatedPopupWindow() {
+ void onHandleMoved() {
+ removeHiderCallback();
+ hidePastePopupWindow();
+ }
+
+ void hidePastePopupWindow() {
+ if (mPastePopupShower != null) {
+ TextView.this.removeCallbacks(mPastePopupShower);
+ }
if (mPastePopupWindow != null) {
mPastePopupWindow.hide();
}
@@ -9247,15 +9199,11 @@
@Override
public void onDetached() {
removeHiderCallback();
- removePastePopupCallback();
+ hidePastePopupWindow();
}
}
private class SelectionStartHandleView extends HandleView {
- public SelectionStartHandleView() {
- super();
- }
-
@Override
protected void initDrawable() {
if (mSelectHandleLeft == null) {
@@ -9274,7 +9222,6 @@
@Override
public void updateOffset(int offset) {
Selection.setSelection((Spannable) mText, offset, getSelectionEnd());
- positionAtCursorOffset(offset);
}
@Override
@@ -9290,15 +9237,10 @@
if (offset >= selectionEnd) offset = selectionEnd - 1;
Selection.setSelection((Spannable) mText, offset, selectionEnd);
- positionAtCursorOffset(offset);
}
}
private class SelectionEndHandleView extends HandleView {
- public SelectionEndHandleView() {
- super();
- }
-
@Override
protected void initDrawable() {
if (mSelectHandleRight == null) {
@@ -9317,7 +9259,6 @@
@Override
public void updateOffset(int offset) {
Selection.setSelection((Spannable) mText, getSelectionStart(), offset);
- positionAtCursorOffset(offset);
}
@Override
@@ -9333,7 +9274,6 @@
if (offset <= selectionStart) offset = selectionStart + 1;
Selection.setSelection((Spannable) mText, selectionStart, offset);
- positionAtCursorOffset(offset);
}
}
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/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 33825c7..05a4810 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -68,7 +68,7 @@
<string name="serviceNotProvisioned" msgid="8614830180508686666">"無法提供此服務。"</string>
<string name="CLIRPermanent" msgid="5460892159398802465">"本機號碼顯示設定無法變更。"</string>
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"受限存取已變更"</string>
- <string name="RestrictedOnData" msgid="8653794784690065540">"已封鎖資料傳輸服務。"</string>
+ <string name="RestrictedOnData" msgid="8653794784690065540">"已封鎖數據傳輸服務。"</string>
<string name="RestrictedOnEmergency" msgid="6581163779072833665">"已封鎖緊急服務。"</string>
<string name="RestrictedOnNormal" msgid="4953867011389750673">"已封鎖語音服務。"</string>
<string name="RestrictedOnAllVoice" msgid="1459318899842232234">"已封鎖所有語音服務。"</string>
@@ -700,15 +700,15 @@
<string name="beforeOneMonthDurationPast" msgid="909134546836499826">"1 個月前"</string>
<plurals name="num_seconds_ago">
<item quantity="one" msgid="4869870056547896011">"1 秒以前"</item>
- <item quantity="other" msgid="3903706804349556379">"<xliff:g id="COUNT">%d</xliff:g> 秒以前"</item>
+ <item quantity="other" msgid="3903706804349556379">"<xliff:g id="COUNT">%d</xliff:g> 秒前"</item>
</plurals>
<plurals name="num_minutes_ago">
<item quantity="one" msgid="3306787433088810191">"1 分鐘以前"</item>
- <item quantity="other" msgid="2176942008915455116">"<xliff:g id="COUNT">%d</xliff:g> 分鐘以前"</item>
+ <item quantity="other" msgid="2176942008915455116">"<xliff:g id="COUNT">%d</xliff:g> 分鐘前"</item>
</plurals>
<plurals name="num_hours_ago">
<item quantity="one" msgid="9150797944610821849">"1 小時以前"</item>
- <item quantity="other" msgid="2467273239587587569">"<xliff:g id="COUNT">%d</xliff:g> 小時以前"</item>
+ <item quantity="other" msgid="2467273239587587569">"<xliff:g id="COUNT">%d</xliff:g> 小時前"</item>
</plurals>
<plurals name="last_num_days">
<item quantity="other" msgid="3069992808164318268">"最近 <xliff:g id="COUNT">%d</xliff:g> 天"</item>
@@ -717,7 +717,7 @@
<string name="older" msgid="5211975022815554840">"較舊"</string>
<plurals name="num_days_ago">
<item quantity="one" msgid="861358534398115820">"昨天"</item>
- <item quantity="other" msgid="2479586466153314633">"<xliff:g id="COUNT">%d</xliff:g> 天以前"</item>
+ <item quantity="other" msgid="2479586466153314633">"<xliff:g id="COUNT">%d</xliff:g> 天前"</item>
</plurals>
<plurals name="in_num_seconds">
<item quantity="one" msgid="2729745560954905102">"1 秒內"</item>
@@ -745,11 +745,11 @@
</plurals>
<plurals name="abbrev_num_hours_ago">
<item quantity="one" msgid="4796212039724722116">"1 小時以前"</item>
- <item quantity="other" msgid="6889970745748538901">"<xliff:g id="COUNT">%d</xliff:g> 小時以前"</item>
+ <item quantity="other" msgid="6889970745748538901">"<xliff:g id="COUNT">%d</xliff:g> 小時前"</item>
</plurals>
<plurals name="abbrev_num_days_ago">
<item quantity="one" msgid="8463161711492680309">"昨天"</item>
- <item quantity="other" msgid="3453342639616481191">"<xliff:g id="COUNT">%d</xliff:g> 天以前"</item>
+ <item quantity="other" msgid="3453342639616481191">"<xliff:g id="COUNT">%d</xliff:g> 天前"</item>
</plurals>
<plurals name="abbrev_in_num_seconds">
<item quantity="one" msgid="5842225370795066299">"1 秒內"</item>
@@ -922,14 +922,14 @@
<string name="ext_media_unmountable_notification_title" product="nosdcard" msgid="2090046769532713563">"USB 儲存裝置已毀損"</string>
<string name="ext_media_unmountable_notification_title" product="default" msgid="6410723906019100189">"SD 卡已損壞"</string>
<string name="ext_media_unmountable_notification_message" product="nosdcard" msgid="529021299294450667">"USB 儲存裝置已損壞,您可能必須重新格式化。"</string>
- <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"SD 卡已毀損,您可能必須予以重新格式化。"</string>
- <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="1661683031330951073">"USB 儲存裝置已意外移除"</string>
+ <string name="ext_media_unmountable_notification_message" product="default" msgid="6902531775948238989">"SD 卡已毀損,您可能必須重新格式化。"</string>
+ <string name="ext_media_badremoval_notification_title" product="nosdcard" msgid="1661683031330951073">"USB 儲存裝置未正常移除"</string>
<string name="ext_media_badremoval_notification_title" product="default" msgid="6872152882604407837">"SD 卡未正常移除"</string>
<string name="ext_media_badremoval_notification_message" product="nosdcard" msgid="4329848819865594241">"請先卸載 USB 儲存裝置,再將其移除,以免資料遺失。"</string>
<string name="ext_media_badremoval_notification_message" product="default" msgid="7260183293747448241">"請先卸載 SD 卡,再將其移除,以免資料遺失。"</string>
<string name="ext_media_safe_unmount_notification_title" product="nosdcard" msgid="3967973893270360230">"USB 儲存裝置已可安全移除"</string>
<string name="ext_media_safe_unmount_notification_title" product="default" msgid="6729801130790616200">"可安全移除 SD 卡"</string>
- <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="6142195361606493530">"您可安全移除 USB 儲存裝置了。"</string>
+ <string name="ext_media_safe_unmount_notification_message" product="nosdcard" msgid="6142195361606493530">"您現在可以安全地移除 USB 儲存裝置。"</string>
<string name="ext_media_safe_unmount_notification_message" product="default" msgid="568841278138377604">"您現在可以安全地移除 SD 卡。"</string>
<string name="ext_media_nomedia_notification_title" product="nosdcard" msgid="4486377230140227651">"USB 儲存裝置已移除"</string>
<string name="ext_media_nomedia_notification_title" product="default" msgid="8902518030404381318">"已移除 SD 卡"</string>
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/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
new file mode 100644
index 0000000..64f5a48
--- /dev/null
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.security;
+
+import android.os.Bundle;
+
+/**
+ * Caller is required to ensure that {@link KeyStore#unlock
+ * KeyStore.unlock} was successful.
+ *
+ * @hide
+ */
+interface IKeyChainService {
+ byte[] getPrivate(String alias, String authToken);
+ byte[] getCertificate(String alias, String authToken);
+ byte[] getCaCertificate(String alias, String authToken);
+ String findIssuer(in Bundle cert);
+}
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
new file mode 100644
index 0000000..69847bf
--- /dev/null
+++ b/keystore/java/android/security/KeyChain.java
@@ -0,0 +1,372 @@
+/*
+ * 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.security;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import dalvik.system.CloseGuard;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.TrustAnchor;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import org.apache.harmony.xnet.provider.jsse.IndexedPKIXParameters;
+import org.apache.harmony.xnet.provider.jsse.SSLParametersImpl;
+
+/**
+ * @hide
+ */
+public final class KeyChain {
+
+ private static final String TAG = "KeyChain";
+
+ /**
+ * @hide Also used by KeyChainService implementation
+ */
+ public static final String ACCOUNT_TYPE = "com.android.keychain";
+
+ /**
+ * @hide Also used by KeyChainService implementation
+ */
+ // TODO This non-localized CA string to be removed when CAs moved out of keystore
+ public static final String CA_SUFFIX = " CA";
+
+ public static final String KEY_INTENT = "intent";
+
+ /**
+ * Intentionally not public to leave open the future possibility
+ * of hardware based keys. Callers should use {@link #toPrivateKey
+ * toPrivateKey} in order to convert a bundle to a {@code
+ * PrivateKey}
+ */
+ private static final String KEY_PKCS8 = "pkcs8";
+
+ /**
+ * Intentionally not public to leave open the future possibility
+ * of hardware based certs. Callers should use {@link
+ * #toCertificate toCertificate} in order to convert a bundle to a
+ * {@code PrivateKey}
+ */
+ private static final String KEY_X509 = "x509";
+
+ /**
+ * Returns an {@code Intent} for use with {@link
+ * android.app.Activity#startActivityForResult
+ * startActivityForResult}. The result will be returned via {@link
+ * android.app.Activity#onActivityResult onActivityResult} with
+ * {@link android.app.Activity#RESULT_OK RESULT_OK} and the alias
+ * in the returned {@code Intent}'s extra data with key {@link
+ * android.content.Intent#EXTRA_TEXT Intent.EXTRA_TEXT}.
+ */
+ public static Intent chooseAlias() {
+ return new Intent("com.android.keychain.CHOOSER");
+ }
+
+ /**
+ * Returns a new {@code KeyChain} instance. When the caller is
+ * done using the {@code KeyChain}, it must be closed with {@link
+ * #close()} or resource leaks will occur.
+ */
+ public static KeyChain getInstance(Context context) throws InterruptedException {
+ return new KeyChain(context);
+ }
+
+ private final AccountManager mAccountManager;
+
+ private final Object mServiceLock = new Object();
+ private IKeyChainService mService;
+ private boolean mIsBound;
+
+ private Account mAccount;
+
+ private ServiceConnection mServiceConnection = new ServiceConnection() {
+ @Override public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (mServiceLock) {
+ mService = IKeyChainService.Stub.asInterface(service);
+ mServiceLock.notifyAll();
+
+ // Account is created if necessary during binding of the IKeyChainService
+ mAccount = mAccountManager.getAccountsByType(ACCOUNT_TYPE)[0];
+ }
+ }
+
+ @Override public void onServiceDisconnected(ComponentName name) {
+ synchronized (mServiceLock) {
+ mService = null;
+ }
+ }
+ };
+
+ private final Context mContext;
+
+ private final CloseGuard mGuard = CloseGuard.get();
+
+ private KeyChain(Context context) throws InterruptedException {
+ if (context == null) {
+ throw new NullPointerException("context == null");
+ }
+ mContext = context;
+ ensureNotOnMainThread();
+ mAccountManager = AccountManager.get(mContext);
+ mIsBound = mContext.bindService(new Intent(IKeyChainService.class.getName()),
+ mServiceConnection,
+ Context.BIND_AUTO_CREATE);
+ if (!mIsBound) {
+ throw new AssertionError();
+ }
+ synchronized (mServiceLock) {
+ // there is a race between binding on this thread and the
+ // callback on the main thread. wait until binding is done
+ // to be sure we have the mAccount initialized.
+ if (mService == null) {
+ mServiceLock.wait();
+ }
+ }
+ mGuard.open("close");
+ }
+
+ /**
+ * {@code Bundle} will contain {@link #KEY_INTENT} if user needs
+ * to confirm application access to requested key. In the alias
+ * does not exist or there is an error, null is
+ * returned. Otherwise the {@code Bundle} contains information
+ * representing the private key which can be interpreted with
+ * {@link #toPrivateKey toPrivateKey}.
+ *
+ * non-null alias
+ */
+ public Bundle getPrivate(String alias) {
+ return get(alias, Credentials.USER_PRIVATE_KEY);
+ }
+
+ public Bundle getCertificate(String alias) {
+ return get(alias, Credentials.USER_CERTIFICATE);
+ }
+
+ public Bundle getCaCertificate(String alias) {
+ return get(alias, Credentials.CA_CERTIFICATE);
+ }
+
+ private Bundle get(String alias, String type) {
+ if (alias == null) {
+ throw new NullPointerException("alias == null");
+ }
+ ensureNotOnMainThread();
+
+ String authAlias = (type.equals(Credentials.CA_CERTIFICATE)) ? (alias + CA_SUFFIX) : alias;
+ AccountManagerFuture<Bundle> future = mAccountManager.getAuthToken(mAccount,
+ authAlias,
+ false,
+ null,
+ null);
+ Bundle bundle;
+ try {
+ bundle = future.getResult();
+ } catch (OperationCanceledException e) {
+ throw new AssertionError(e);
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ } catch (AuthenticatorException e) {
+ throw new AssertionError(e);
+ }
+ Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
+ if (intent != null) {
+ Bundle result = new Bundle();
+ // we don't want this Eclair compatability flag,
+ // it will prevent onActivityResult from being called
+ intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
+ result.putParcelable(KEY_INTENT, intent);
+ return result;
+ }
+ String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
+ if (authToken == null) {
+ throw new AssertionError("Invalid authtoken");
+ }
+
+ byte[] bytes;
+ try {
+ if (type.equals(Credentials.USER_PRIVATE_KEY)) {
+ bytes = mService.getPrivate(alias, authToken);
+ } else if (type.equals(Credentials.USER_CERTIFICATE)) {
+ bytes = mService.getCertificate(alias, authToken);
+ } else if (type.equals(Credentials.CA_CERTIFICATE)) {
+ bytes = mService.getCaCertificate(alias, authToken);
+ } else {
+ throw new AssertionError();
+ }
+ } catch (RemoteException e) {
+ throw new AssertionError(e);
+ }
+ if (bytes == null) {
+ throw new AssertionError();
+ }
+ Bundle result = new Bundle();
+ if (type.equals(Credentials.USER_PRIVATE_KEY)) {
+ result.putByteArray(KEY_PKCS8, bytes);
+ } else if (type.equals(Credentials.USER_CERTIFICATE)) {
+ result.putByteArray(KEY_X509, bytes);
+ } else if (type.equals(Credentials.CA_CERTIFICATE)) {
+ result.putByteArray(KEY_X509, bytes);
+ } else {
+ throw new AssertionError();
+ }
+ return result;
+ }
+
+ public static PrivateKey toPrivateKey(Bundle bundle) {
+ byte[] bytes = bundle.getByteArray(KEY_PKCS8);
+ if (bytes == null) {
+ throw new IllegalArgumentException("not a private key bundle");
+ }
+ try {
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes));
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError(e);
+ } catch (InvalidKeySpecException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ public static Bundle fromPrivateKey(PrivateKey privateKey) {
+ Bundle bundle = new Bundle();
+ String format = privateKey.getFormat();
+ if (!format.equals("PKCS#8")) {
+ throw new IllegalArgumentException("Unsupported private key format " + format);
+ }
+ bundle.putByteArray(KEY_PKCS8, privateKey.getEncoded());
+ return bundle;
+ }
+
+ public static X509Certificate toCertificate(Bundle bundle) {
+ byte[] bytes = bundle.getByteArray(KEY_X509);
+ if (bytes == null) {
+ throw new IllegalArgumentException("not a certificate bundle");
+ }
+ try {
+ CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes));
+ return (X509Certificate) cert;
+ } catch (CertificateException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ public static Bundle fromCertificate(Certificate cert) {
+ Bundle bundle = new Bundle();
+ String type = cert.getType();
+ if (!type.equals("X.509")) {
+ throw new IllegalArgumentException("Unsupported certificate type " + type);
+ }
+ try {
+ bundle.putByteArray(KEY_X509, cert.getEncoded());
+ } catch (CertificateEncodingException e) {
+ throw new AssertionError(e);
+ }
+ return bundle;
+ }
+
+ private void ensureNotOnMainThread() {
+ Looper looper = Looper.myLooper();
+ if (looper != null && looper == mContext.getMainLooper()) {
+ throw new IllegalStateException(
+ "calling this from your main thread can lead to deadlock");
+ }
+ }
+
+ public Bundle findIssuer(X509Certificate cert) {
+ if (cert == null) {
+ throw new NullPointerException("cert == null");
+ }
+ ensureNotOnMainThread();
+
+ // check and see if the issuer is already known to the default IndexedPKIXParameters
+ IndexedPKIXParameters index = SSLParametersImpl.getDefaultIndexedPKIXParameters();
+ try {
+ TrustAnchor anchor = index.findTrustAnchor(cert);
+ if (anchor != null && anchor.getTrustedCert() != null) {
+ X509Certificate ca = anchor.getTrustedCert();
+ return fromCertificate(ca);
+ }
+ } catch (CertPathValidatorException ignored) {
+ }
+
+ // otherwise, it might be a user installed CA in the keystore
+ String alias;
+ try {
+ alias = mService.findIssuer(fromCertificate(cert));
+ } catch (RemoteException e) {
+ throw new AssertionError(e);
+ }
+ if (alias == null) {
+ Log.w(TAG, "Lookup failed for issuer");
+ return null;
+ }
+
+ Bundle bundle = get(alias, Credentials.CA_CERTIFICATE);
+ Intent intent = bundle.getParcelable(KEY_INTENT);
+ if (intent != null) {
+ // permission still required
+ return bundle;
+ }
+ // add the found CA to the index for next time
+ X509Certificate ca = toCertificate(bundle);
+ index.index(new TrustAnchor(ca, null));
+ return bundle;
+ }
+
+ public void close() {
+ if (mIsBound) {
+ mContext.unbindService(mServiceConnection);
+ mIsBound = false;
+ mGuard.close();
+ }
+ }
+
+ protected void finalize() throws Throwable {
+ // note we don't close, we just warn.
+ // shouldn't be doing I/O in a finalizer,
+ // which the unbind would cause.
+ try {
+ if (mGuard != null) {
+ mGuard.warnIfOpen();
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+}
diff --git a/libs/rs/driver/rsdRuntimeMath.cpp b/libs/rs/driver/rsdRuntimeMath.cpp
index bd1fd0e..093e311 100644
--- a/libs/rs/driver/rsdRuntimeMath.cpp
+++ b/libs/rs/driver/rsdRuntimeMath.cpp
@@ -243,13 +243,15 @@
static float SC_randf(float max) {
float r = (float)rand();
r *= max;
- return r / RAND_MAX;
+ r /= RAND_MAX;
+ return r;
}
static float SC_randf2(float min, float max) {
float r = (float)rand();
+ r /= RAND_MAX;
r = r * (max - min) + min;
- return r / RAND_MAX;
+ return r;
}
static int SC_randi(int max) {
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/mediaeditor/VideoEditorClasses.cpp b/media/jni/mediaeditor/VideoEditorClasses.cpp
index bfbdd77..5696433 100755
--- a/media/jni/mediaeditor/VideoEditorClasses.cpp
+++ b/media/jni/mediaeditor/VideoEditorClasses.cpp
@@ -28,7 +28,6 @@
#include <M4OSA_FileWriter.h>
#include <M4OSA_Memory.h>
#include <M4OSA_Debug.h>
-#include <M4OSA_String.h>
#include <M4OSA_Thread.h>
#include <M4VSS3GPP_API.h>
#include <M4xVSS_API.h>
diff --git a/media/jni/mediaeditor/VideoEditorMain.cpp b/media/jni/mediaeditor/VideoEditorMain.cpp
index 0376806..c95a0c2 100755
--- a/media/jni/mediaeditor/VideoEditorMain.cpp
+++ b/media/jni/mediaeditor/VideoEditorMain.cpp
@@ -41,15 +41,12 @@
#include <M4OSA_FileCommon.h>
#include <M4OSA_FileReader.h>
#include <M4OSA_FileWriter.h>
-#include <M4OSA_FileExtra.h>
#include <M4OSA_Memory.h>
-#include <M4OSA_String.h>
#include <M4OSA_Thread.h>
#include <M4xVSS_API.h>
#include <M4VSS3GPP_ErrorCodes.h>
#include <M4MCS_API.h>
#include <M4MCS_ErrorCodes.h>
-#include <M4MDP_API.h>
#include <M4READER_Common.h>
#include <M4WRITER_common.h>
};
diff --git a/media/jni/mediaeditor/VideoEditorOsal.cpp b/media/jni/mediaeditor/VideoEditorOsal.cpp
index 616c2fe..53e7de1 100755
--- a/media/jni/mediaeditor/VideoEditorOsal.cpp
+++ b/media/jni/mediaeditor/VideoEditorOsal.cpp
@@ -25,7 +25,6 @@
#include <M4OSA_FileReader.h>
#include <M4OSA_FileWriter.h>
#include <M4OSA_Memory.h>
-#include <M4OSA_String.h>
#include <M4OSA_Thread.h>
#include <M4xVSS_API.h>
#include <M4VSS3GPP_ErrorCodes.h>
@@ -82,14 +81,6 @@
VIDEOEDIT_OSAL_RESULT_INIT(M4ERR_FILE_BAD_MODE_ACCESS ),
VIDEOEDIT_OSAL_RESULT_INIT(M4ERR_FILE_INVALID_POSITION ),
- // M4OSA_String.h
- VIDEOEDIT_OSAL_RESULT_INIT(M4ERR_STR_BAD_STRING ),
- VIDEOEDIT_OSAL_RESULT_INIT(M4ERR_STR_CONV_FAILED ),
- VIDEOEDIT_OSAL_RESULT_INIT(M4ERR_STR_OVERFLOW ),
- VIDEOEDIT_OSAL_RESULT_INIT(M4ERR_STR_BAD_ARGS ),
- VIDEOEDIT_OSAL_RESULT_INIT(M4WAR_STR_OVERFLOW ),
- VIDEOEDIT_OSAL_RESULT_INIT(M4WAR_STR_NOT_FOUND ),
-
// M4OSA_Thread.h
VIDEOEDIT_OSAL_RESULT_INIT(M4ERR_THREAD_NOT_STARTED ),
diff --git a/media/jni/mediaeditor/VideoEditorPropertiesMain.cpp b/media/jni/mediaeditor/VideoEditorPropertiesMain.cpp
index 39221f3..9de7207 100755
--- a/media/jni/mediaeditor/VideoEditorPropertiesMain.cpp
+++ b/media/jni/mediaeditor/VideoEditorPropertiesMain.cpp
@@ -34,13 +34,11 @@
#include <M4OSA_FileReader.h>
#include <M4OSA_FileWriter.h>
#include <M4OSA_Memory.h>
-#include <M4OSA_String.h>
#include <M4OSA_Thread.h>
#include <M4VSS3GPP_API.h>
#include <M4VSS3GPP_ErrorCodes.h>
#include <M4MCS_API.h>
#include <M4MCS_ErrorCodes.h>
-#include <M4MDP_API.h>
#include <M4READER_Common.h>
#include <M4WRITER_common.h>
#include <M4DECODER_Common.h>
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index f9db1a1..01d0a92 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -2028,14 +2028,18 @@
size_t dstOffset = 0;
while (srcOffset < size) {
- CHECK(srcOffset + mNALLengthSize <= size);
- size_t nalLength = parseNALSize(&mSrcBuffer[srcOffset]);
- srcOffset += mNALLengthSize;
+ bool isMalFormed = (srcOffset + mNALLengthSize > size);
+ size_t nalLength = 0;
+ if (!isMalFormed) {
+ nalLength = parseNALSize(&mSrcBuffer[srcOffset]);
+ srcOffset += mNALLengthSize;
+ isMalFormed = srcOffset + nalLength > size;
+ }
- if (srcOffset + nalLength > size) {
+ if (isMalFormed) {
+ LOGE("Video is malformed");
mBuffer->release();
mBuffer = NULL;
-
return ERROR_MALFORMED;
}
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/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 65321b7..a37ccc7 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -2615,6 +2615,26 @@
}
}
+ public int getLockedRotationLw() {
+ synchronized (mLock) {
+ if (false) {
+ // Not yet working.
+ if (mHdmiPlugged) {
+ return Surface.ROTATION_0;
+ } else if (mLidOpen == LID_OPEN) {
+ return mLidOpenRotation;
+ } else if (mDockMode == Intent.EXTRA_DOCK_STATE_CAR && mCarDockRotation >= 0) {
+ return mCarDockRotation;
+ } else if (mDockMode == Intent.EXTRA_DOCK_STATE_DESK && mDeskDockRotation >= 0) {
+ return mDeskDockRotation;
+ } else if (mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED) {
+ return mUserRotation;
+ }
+ }
+ return -1;
+ }
+ }
+
private int getCurrentLandscapeRotation(int lastRotation) {
// if the user has locked rotation, we ignore the sensor
if (mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED) {
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/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 538e38c..8b739a4 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -398,6 +398,7 @@
int mRotation = 0;
int mRequestedRotation = 0;
int mForcedAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ boolean mAltOrientation = false;
int mLastRotationFlags;
ArrayList<IRotationWatcher> mRotationWatchers
= new ArrayList<IRotationWatcher>();
@@ -585,7 +586,6 @@
}
final Configuration mTempConfiguration = new Configuration();
- int mScreenLayout = Configuration.SCREENLAYOUT_SIZE_UNDEFINED;
// The frame use to limit the size of the app running in compatibility mode.
Rect mCompatibleScreenFrame = new Rect();
@@ -4954,7 +4954,52 @@
rotation = mPolicy.rotationForOrientationLw(mForcedAppOrientation,
mRotation, mDisplayEnabled);
if (DEBUG_ORIENTATION) Slog.v(TAG, "new rotation is set to " + rotation);
+
+ int desiredRotation = rotation;
+ int lockedRotation = mPolicy.getLockedRotationLw();
+ if (lockedRotation >= 0 && rotation != lockedRotation) {
+ // We are locked in a rotation but something is requesting
+ // a different rotation... we will either keep the locked
+ // rotation if it results in the same orientation, or have to
+ // switch into an emulated orientation mode.
+
+ // First, we know that our rotation is actually going to be
+ // the locked rotation.
+ rotation = lockedRotation;
+
+ // Now the difference between the desired and lockedRotation
+ // may mean that the orientation is different... if that is
+ // not the case, we can just make the desired rotation be the
+ // same as the new locked rotation.
+ switch (lockedRotation) {
+ case Surface.ROTATION_0:
+ if (rotation == Surface.ROTATION_180) {
+ desiredRotation = lockedRotation;
+ }
+ break;
+ case Surface.ROTATION_90:
+ if (rotation == Surface.ROTATION_270) {
+ desiredRotation = lockedRotation;
+ }
+ break;
+ case Surface.ROTATION_180:
+ if (rotation == Surface.ROTATION_0) {
+ desiredRotation = lockedRotation;
+ }
+ break;
+ case Surface.ROTATION_270:
+ if (rotation == Surface.ROTATION_90) {
+ desiredRotation = lockedRotation;
+ }
+ break;
+ }
+ }
+
changed = mDisplayEnabled && mRotation != rotation;
+ if (mAltOrientation != (rotation != desiredRotation)) {
+ changed = true;
+ mAltOrientation = rotation != desiredRotation;
+ }
if (changed) {
if (DEBUG_ORIENTATION) Slog.v(TAG,
@@ -5000,6 +5045,7 @@
Surface.setOrientation(0, rotation, animFlags);
}
}
+
for (int i=mWindows.size()-1; i>=0; i--) {
WindowState w = mWindows.get(i);
if (w.mSurface != null) {
@@ -5440,8 +5486,32 @@
// Use the effective "visual" dimensions based on current rotation
final boolean rotated = (mRotation == Surface.ROTATION_90
|| mRotation == Surface.ROTATION_270);
- final int dw = rotated ? mInitialDisplayHeight : mInitialDisplayWidth;
- final int dh = rotated ? mInitialDisplayWidth : mInitialDisplayHeight;
+ final int realdw = rotated ? mInitialDisplayHeight : mInitialDisplayWidth;
+ final int realdh = rotated ? mInitialDisplayWidth : mInitialDisplayHeight;
+
+ if (mAltOrientation) {
+ mCurDisplayWidth = realdw;
+ mCurDisplayHeight = realdh;
+ if (realdw > realdh) {
+ // Turn landscape into portrait.
+ int maxw = (int)(realdh/1.3f);
+ if (maxw < realdw) {
+ mCurDisplayWidth = maxw;
+ }
+ } else {
+ // Turn portrait into landscape.
+ int maxh = (int)(realdw/1.3f);
+ if (maxh < realdh) {
+ mCurDisplayHeight = maxh;
+ }
+ }
+ } else {
+ mCurDisplayWidth = realdw;
+ mCurDisplayHeight = realdh;
+ }
+
+ final int dw = mCurDisplayWidth;
+ final int dh = mCurDisplayHeight;
int orientation = Configuration.ORIENTATION_SQUARE;
if (dw < dh) {
@@ -5452,66 +5522,69 @@
config.orientation = orientation;
DisplayMetrics dm = new DisplayMetrics();
- mDisplay.getMetrics(dm);
+ mDisplay.getRealMetrics(dm);
+
+ // Override display width and height with what we are computing,
+ // to be sure they remain consistent.
+ dm.widthPixels = dw;
+ dm.heightPixels = dh;
+
CompatibilityInfo.updateCompatibleScreenFrame(dm, orientation, mCompatibleScreenFrame);
config.screenWidthDp = (int)(dm.widthPixels / dm.density);
config.screenHeightDp = (int)(dm.heightPixels / dm.density);
- if (mScreenLayout == Configuration.SCREENLAYOUT_SIZE_UNDEFINED) {
- // Note we only do this once because at this point we don't
- // expect the screen to change in this way at runtime, and want
- // to avoid all of this computation for every config change.
- int longSize = dw;
- int shortSize = dh;
- if (longSize < shortSize) {
- int tmp = longSize;
- longSize = shortSize;
- shortSize = tmp;
- }
- longSize = (int)(longSize/dm.density);
- shortSize = (int)(shortSize/dm.density);
+ // Compute the screen layout size class.
+ int screenLayout;
+ int longSize = dw;
+ int shortSize = dh;
+ if (longSize < shortSize) {
+ int tmp = longSize;
+ longSize = shortSize;
+ shortSize = tmp;
+ }
+ longSize = (int)(longSize/dm.density);
+ shortSize = (int)(shortSize/dm.density);
- // These semi-magic numbers define our compatibility modes for
- // applications with different screens. These are guarantees to
- // app developers about the space they can expect for a particular
- // configuration. DO NOT CHANGE!
- if (longSize < 470) {
- // This is shorter than an HVGA normal density screen (which
- // is 480 pixels on its long side).
- mScreenLayout = Configuration.SCREENLAYOUT_SIZE_SMALL
- | Configuration.SCREENLAYOUT_LONG_NO;
+ // These semi-magic numbers define our compatibility modes for
+ // applications with different screens. These are guarantees to
+ // app developers about the space they can expect for a particular
+ // configuration. DO NOT CHANGE!
+ if (longSize < 470) {
+ // This is shorter than an HVGA normal density screen (which
+ // is 480 pixels on its long side).
+ screenLayout = Configuration.SCREENLAYOUT_SIZE_SMALL
+ | Configuration.SCREENLAYOUT_LONG_NO;
+ } else {
+ // What size is this screen screen?
+ if (longSize >= 960 && shortSize >= 720) {
+ // 1.5xVGA or larger screens at medium density are the point
+ // at which we consider it to be an extra large screen.
+ screenLayout = Configuration.SCREENLAYOUT_SIZE_XLARGE;
+ } else if (longSize >= 640 && shortSize >= 480) {
+ // VGA or larger screens at medium density are the point
+ // at which we consider it to be a large screen.
+ screenLayout = Configuration.SCREENLAYOUT_SIZE_LARGE;
} else {
- // What size is this screen screen?
- if (longSize >= 960 && shortSize >= 720) {
- // 1.5xVGA or larger screens at medium density are the point
- // at which we consider it to be an extra large screen.
- mScreenLayout = Configuration.SCREENLAYOUT_SIZE_XLARGE;
- } else if (longSize >= 640 && shortSize >= 480) {
- // VGA or larger screens at medium density are the point
- // at which we consider it to be a large screen.
- mScreenLayout = Configuration.SCREENLAYOUT_SIZE_LARGE;
- } else {
- mScreenLayout = Configuration.SCREENLAYOUT_SIZE_NORMAL;
- }
-
- // If this screen is wider than normal HVGA, or taller
- // than FWVGA, then for old apps we want to run in size
- // compatibility mode.
- if (shortSize > 321 || longSize > 570) {
- mScreenLayout |= Configuration.SCREENLAYOUT_COMPAT_NEEDED;
- }
+ screenLayout = Configuration.SCREENLAYOUT_SIZE_NORMAL;
+ }
- // Is this a long screen?
- if (((longSize*3)/5) >= (shortSize-1)) {
- // Anything wider than WVGA (5:3) is considering to be long.
- mScreenLayout |= Configuration.SCREENLAYOUT_LONG_YES;
- } else {
- mScreenLayout |= Configuration.SCREENLAYOUT_LONG_NO;
- }
+ // If this screen is wider than normal HVGA, or taller
+ // than FWVGA, then for old apps we want to run in size
+ // compatibility mode.
+ if (shortSize > 321 || longSize > 570) {
+ screenLayout |= Configuration.SCREENLAYOUT_COMPAT_NEEDED;
+ }
+
+ // Is this a long screen?
+ if (((longSize*3)/5) >= (shortSize-1)) {
+ // Anything wider than WVGA (5:3) is considering to be long.
+ screenLayout |= Configuration.SCREENLAYOUT_LONG_YES;
+ } else {
+ screenLayout |= Configuration.SCREENLAYOUT_LONG_NO;
}
}
- config.screenLayout = mScreenLayout;
+ config.screenLayout = screenLayout;
// Determine whether a hard keyboard is available and enabled.
boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS;
@@ -6722,8 +6795,8 @@
}
final long currentTime = SystemClock.uptimeMillis();
- final int dw = mCurDisplayWidth = mDisplay.getRealWidth();
- final int dh = mCurDisplayHeight = mDisplay.getRealHeight();
+ final int dw = mCurDisplayWidth;
+ final int dh = mCurDisplayHeight;
int i;
@@ -8709,8 +8782,9 @@
pw.print(" mAppsFreezingScreen="); pw.print(mAppsFreezingScreen);
pw.print(" mWaitingForConfig="); pw.println(mWaitingForConfig);
pw.print(" mRotation="); pw.print(mRotation);
- pw.print(", mForcedAppOrientation="); pw.print(mForcedAppOrientation);
- pw.print(", mRequestedRotation="); pw.println(mRequestedRotation);
+ pw.print(" mForcedAppOrientation="); pw.print(mForcedAppOrientation);
+ pw.print(" mRequestedRotation="); pw.print(mRequestedRotation);
+ pw.print(" mAltOrientation="); pw.println(mAltOrientation);
pw.print(" mDeferredRotation="); pw.print(mDeferredRotation);
pw.print(", mDeferredRotationAnimFlags="); pw.println(mDeferredRotationAnimFlags);
pw.print(" mAnimationPending="); pw.print(mAnimationPending);
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);
diff --git a/tests/RenderScriptTests/ImageProcessing/AndroidManifest.xml b/tests/RenderScriptTests/ImageProcessing/AndroidManifest.xml
index 69a33bc..174cc65 100644
--- a/tests/RenderScriptTests/ImageProcessing/AndroidManifest.xml
+++ b/tests/RenderScriptTests/ImageProcessing/AndroidManifest.xml
@@ -2,10 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.rs.image">
-
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-sdk android:minSdkVersion="11" />
- <application android:label="Image Processing">
+ <application android:label="Image Processing"
+ android:hardwareAccelerated="true">
<activity android:name="ImageProcessingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/tests/RenderScriptTests/ImageProcessing/res/drawable-hdpi/data.jpg b/tests/RenderScriptTests/ImageProcessing/res/drawable-hdpi/data.jpg
deleted file mode 100644
index 81a87b1..0000000
--- a/tests/RenderScriptTests/ImageProcessing/res/drawable-hdpi/data.jpg
+++ /dev/null
Binary files differ
diff --git a/tests/RenderScriptTests/ImageProcessing/res/drawable/city.png b/tests/RenderScriptTests/ImageProcessing/res/drawable/city.png
new file mode 100644
index 0000000..856eeff
--- /dev/null
+++ b/tests/RenderScriptTests/ImageProcessing/res/drawable/city.png
Binary files differ
diff --git a/tests/RenderScriptTests/ImageProcessing/res/drawable/data.jpg b/tests/RenderScriptTests/ImageProcessing/res/drawable/data.jpg
deleted file mode 100644
index 81a87b1..0000000
--- a/tests/RenderScriptTests/ImageProcessing/res/drawable/data.jpg
+++ /dev/null
Binary files differ
diff --git a/tests/RenderScriptTests/ImageProcessing/res/layout/main.xml b/tests/RenderScriptTests/ImageProcessing/res/layout/main.xml
index b271b43..08a010d 100644
--- a/tests/RenderScriptTests/ImageProcessing/res/layout/main.xml
+++ b/tests/RenderScriptTests/ImageProcessing/res/layout/main.xml
@@ -17,31 +17,12 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
- android:layout_height="fill_parent">
+ android:layout_height="fill_parent"
+ android:id="@+id/toplevel">
<SurfaceView
android:id="@+id/surface"
android:layout_width="1dip"
android:layout_height="1dip" />
- <ImageView
- android:id="@+id/display"
- android:layout_width="320dip"
- android:layout_height="266dip" />
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content">
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/benchmark"
- android:onClick="benchmark"/>
- <TextView
- android:id="@+id/benchmarkText"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textSize="8pt"
- android:text="@string/saturation"/>
- </LinearLayout>
<ScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent">
@@ -49,6 +30,26 @@
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
+ <ImageView
+ android:id="@+id/display"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/benchmark"
+ android:onClick="benchmark"/>
+ <TextView
+ android:id="@+id/benchmarkText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="8pt"
+ android:text="@string/saturation"/>
+ </LinearLayout>
<TextView
android:id="@+id/inSaturationText"
android:layout_width="match_parent"
diff --git a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java
index 4f2f52ab..7462701 100644
--- a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java
+++ b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java
@@ -40,7 +40,6 @@
SeekBar.OnSeekBarChangeListener {
private Bitmap mBitmapIn;
private Bitmap mBitmapOut;
- private Bitmap mBitmapScratch;
private ScriptC_threshold mScript;
private ScriptC_vertical_blur mScriptVBlur;
private ScriptC_horizontal_blur mScriptHBlur;
@@ -78,9 +77,20 @@
private SurfaceView mSurfaceView;
private ImageView mDisplayView;
+ private boolean mIsProcessing;
+
class FilterCallback extends RenderScript.RSMessageHandler {
private Runnable mAction = new Runnable() {
public void run() {
+
+ synchronized (mDisplayView) {
+ mIsProcessing = false;
+ }
+
+ // This is a hack to work around an invalidation bug
+ mBitmapOut = Bitmap.createBitmap(mBitmapOut);
+ mOutPixelsAllocation.copyTo(mBitmapOut);
+ mDisplayView.setImageBitmap(mBitmapOut);
mDisplayView.invalidate();
}
};
@@ -99,156 +109,6 @@
// Store our coefficients here
float gaussian[];
- private long javaFilter() {
- final int width = mBitmapIn.getWidth();
- final int height = mBitmapIn.getHeight();
- final int count = width * height;
-
- if (in == null) {
- in = new int[count];
- interm = new int[count];
- out = new int[count];
- gaussian = new float[MAX_RADIUS * 2 + 1];
- mBitmapIn.getPixels(in, 0, width, 0, 0, width, height);
- }
-
- long t = java.lang.System.currentTimeMillis();
-
- int w, h, r;
-
- float fRadius = (float)mRadius;
- int radius = (int)mRadius;
-
- // Compute gaussian weights for the blur
- // e is the euler's number
- float e = 2.718281828459045f;
- float pi = 3.1415926535897932f;
- // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 )
- // x is of the form [-radius .. 0 .. radius]
- // and sigma varies with radius.
- // Based on some experimental radius values and sigma's
- // we approximately fit sigma = f(radius) as
- // sigma = radius * 0.4 + 0.6
- // The larger the radius gets, the more our gaussian blur
- // will resemble a box blur since with large sigma
- // the gaussian curve begins to lose its shape
- float sigma = 0.4f * fRadius + 0.6f;
- // Now compute the coefficints
- // We will store some redundant values to save some math during
- // the blur calculations
- // precompute some values
- float coeff1 = 1.0f / (float)(Math.sqrt( 2.0f * pi ) * sigma);
- float coeff2 = - 1.0f / (2.0f * sigma * sigma);
- float normalizeFactor = 0.0f;
- float floatR = 0.0f;
- for (r = -radius; r <= radius; r ++) {
- floatR = (float)r;
- gaussian[r + radius] = coeff1 * (float)Math.pow(e, floatR * floatR * coeff2);
- normalizeFactor += gaussian[r + radius];
- }
-
- //Now we need to normalize the weights because all our coefficients need to add up to one
- normalizeFactor = 1.0f / normalizeFactor;
- for (r = -radius; r <= radius; r ++) {
- floatR = (float)r;
- gaussian[r + radius] *= normalizeFactor;
- }
-
- float blurredPixelR = 0.0f;
- float blurredPixelG = 0.0f;
- float blurredPixelB = 0.0f;
- float blurredPixelA = 0.0f;
-
- for (h = 0; h < height; h ++) {
- for (w = 0; w < width; w ++) {
-
- blurredPixelR = 0.0f;
- blurredPixelG = 0.0f;
- blurredPixelB = 0.0f;
- blurredPixelA = 0.0f;
-
- for (r = -radius; r <= radius; r ++) {
- // Stepping left and right away from the pixel
- int validW = w + r;
- // Clamp to zero and width max() isn't exposed for ints yet
- if (validW < 0) {
- validW = 0;
- }
- if (validW > width - 1) {
- validW = width - 1;
- }
-
- int input = in[h*width + validW];
-
- int R = ((input >> 24) & 0xff);
- int G = ((input >> 16) & 0xff);
- int B = ((input >> 8) & 0xff);
- int A = (input & 0xff);
-
- float weight = gaussian[r + radius];
-
- blurredPixelR += (float)(R)*weight;
- blurredPixelG += (float)(G)*weight;
- blurredPixelB += (float)(B)*weight;
- blurredPixelA += (float)(A)*weight;
- }
-
- int R = (int)blurredPixelR;
- int G = (int)blurredPixelG;
- int B = (int)blurredPixelB;
- int A = (int)blurredPixelA;
-
- interm[h*width + w] = (R << 24) | (G << 16) | (B << 8) | (A);
- }
- }
-
- for (h = 0; h < height; h ++) {
- for (w = 0; w < width; w ++) {
-
- blurredPixelR = 0.0f;
- blurredPixelG = 0.0f;
- blurredPixelB = 0.0f;
- blurredPixelA = 0.0f;
- for (r = -radius; r <= radius; r ++) {
- int validH = h + r;
- // Clamp to zero and width
- if (validH < 0) {
- validH = 0;
- }
- if (validH > height - 1) {
- validH = height - 1;
- }
-
- int input = interm[validH*width + w];
-
- int R = ((input >> 24) & 0xff);
- int G = ((input >> 16) & 0xff);
- int B = ((input >> 8) & 0xff);
- int A = (input & 0xff);
-
- float weight = gaussian[r + radius];
-
- blurredPixelR += (float)(R)*weight;
- blurredPixelG += (float)(G)*weight;
- blurredPixelB += (float)(B)*weight;
- blurredPixelA += (float)(A)*weight;
- }
-
- int R = (int)blurredPixelR;
- int G = (int)blurredPixelG;
- int B = (int)blurredPixelB;
- int A = (int)blurredPixelA;
-
- out[h*width + w] = (R << 24) | (G << 16) | (B << 8) | (A);
- }
- }
-
- t = java.lang.System.currentTimeMillis() - t;
- android.util.Log.v("Img", "Java frame time ms " + t);
- mBitmapOut.setPixels(out, 0, width, 0, 0, width, height);
- return t;
- }
-
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
@@ -280,17 +140,14 @@
mScriptVBlur.invoke_setSaturation(mSaturation);
}
- long t = java.lang.System.currentTimeMillis();
- if (true) {
- mScript.invoke_filter();
- mOutPixelsAllocation.copyTo(mBitmapOut);
- } else {
- javaFilter();
- mDisplayView.invalidate();
+ synchronized (mDisplayView) {
+ if (mIsProcessing) {
+ return;
+ }
+ mIsProcessing = true;
}
- t = java.lang.System.currentTimeMillis() - t;
- android.util.Log.v("Img", "Renderscript frame time core ms " + t);
+ mScript.invoke_filter();
}
}
@@ -305,9 +162,8 @@
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
- mBitmapIn = loadBitmap(R.drawable.data);
- mBitmapOut = loadBitmap(R.drawable.data);
- mBitmapScratch = loadBitmap(R.drawable.data);
+ mBitmapIn = loadBitmap(R.drawable.city);
+ mBitmapOut = loadBitmap(R.drawable.city);
mSurfaceView = (SurfaceView) findViewById(R.id.surface);
mSurfaceView.getHolder().addCallback(this);
diff --git a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/horizontal_blur.rs b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/horizontal_blur.rs
index 652ffd7..45eea5e 100644
--- a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/horizontal_blur.rs
+++ b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/horizontal_blur.rs
@@ -19,7 +19,7 @@
} else {
for (int r = -fs->radius; r <= fs->radius; r ++) {
// Stepping left and right away from the pixel
- int validW = rsClamp(x + r, (uint)0, (uint)(fs->width - 1));
+ int validW = rsClamp((int)x + r, (int)0, (int)(fs->width - 1));
blurredPixel += input[validW].xyz * gPtr[0];
gPtr++;
}
diff --git a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/vertical_blur.rs b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/vertical_blur.rs
index bd4ae4e..6b0cde0 100644
--- a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/vertical_blur.rs
+++ b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/vertical_blur.rs
@@ -74,7 +74,7 @@
}
} else {
for (int r = -fs->radius; r <= fs->radius; r ++) {
- int validH = rsClamp(y + r, (uint)0, (uint)(fs->height - 1));
+ int validH = rsClamp((int)y + r, (int)0, (int)(fs->height - 1));
const float4 *i = input + validH * fs->width;
blurredPixel += i->xyz * gPtr[0];
gPtr++;