diff options
40 files changed, 1945 insertions, 481 deletions
diff --git a/api/14.txt b/api/14.txt index 5c4732e0e87f..050540bd4a82 100644 --- a/api/14.txt +++ b/api/14.txt @@ -22239,7 +22239,6 @@ package android.view.accessibility { } public class AccessibilityRecord { - ctor protected AccessibilityRecord(); method public int getAddedCount(); method public java.lang.CharSequence getBeforeText(); method public java.lang.CharSequence getClassName(); @@ -22269,17 +22268,6 @@ package android.view.accessibility { 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; } } diff --git a/api/current.txt b/api/current.txt index 89654b8e6257..f641c96552cf 100644 --- a/api/current.txt +++ b/api/current.txt @@ -22721,13 +22721,11 @@ package android.view.accessibility { method public void appendRecord(android.view.accessibility.AccessibilityRecord); method public int describeContents(); method public static java.lang.String eventTypeToString(int); - method public int getAccessibilityWindowId(); method public long getEventTime(); method public int getEventType(); method public java.lang.CharSequence getPackageName(); method public android.view.accessibility.AccessibilityRecord getRecord(int); method public int getRecordCount(); - method public android.view.accessibility.AccessibilityNodeInfo getSource(); method public void initFromParcel(android.os.Parcel); method public static android.view.accessibility.AccessibilityEvent obtain(int); method public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent); @@ -22735,7 +22733,6 @@ package android.view.accessibility { method public void setEventTime(long); method public void setEventType(int); method public void setPackageName(java.lang.CharSequence); - method public void setSource(android.view.View); 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 @@ -22751,6 +22748,7 @@ package android.view.accessibility { 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 + field public static final int TYPE_WINDOW_CONTENT_CHANGED = 2048; // 0x800 field public static final int TYPE_WINDOW_STATE_CHANGED = 32; // 0x20 } @@ -22778,9 +22776,10 @@ package android.view.accessibility { method public void addAction(int); method public void addChild(android.view.View); method public int describeContents(); - method public int getAccessibilityWindowId(); + method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(java.lang.String); method public int getActions(); - method public void getBounds(android.graphics.Rect); + method public void getBoundsInParent(android.graphics.Rect); + method public void getBoundsInScreen(android.graphics.Rect); method public android.view.accessibility.AccessibilityNodeInfo getChild(int); method public int getChildCount(); method public java.lang.CharSequence getClassName(); @@ -22788,6 +22787,7 @@ package android.view.accessibility { method public java.lang.CharSequence getPackageName(); method public android.view.accessibility.AccessibilityNodeInfo getParent(); method public java.lang.CharSequence getText(); + method public int getWindowId(); method public boolean isCheckable(); method public boolean isChecked(); method public boolean isClickable(); @@ -22801,7 +22801,8 @@ package android.view.accessibility { method public static android.view.accessibility.AccessibilityNodeInfo obtain(); method public boolean performAction(int); method public void recycle(); - method public void setBounds(android.graphics.Rect); + method public void setBoundsInParent(android.graphics.Rect); + method public void setBoundsInScreen(android.graphics.Rect); method public void setCheckable(boolean); method public void setChecked(boolean); method public void setClassName(java.lang.CharSequence); @@ -22826,7 +22827,6 @@ package android.view.accessibility { } public class AccessibilityRecord { - ctor protected AccessibilityRecord(); method public int getAddedCount(); method public java.lang.CharSequence getBeforeText(); method public java.lang.CharSequence getClassName(); @@ -22836,7 +22836,9 @@ package android.view.accessibility { method public int getItemCount(); method public android.os.Parcelable getParcelableData(); method public int getRemovedCount(); + method public android.view.accessibility.AccessibilityNodeInfo getSource(); method public java.util.List<java.lang.CharSequence> getText(); + method public int getWindowId(); method public boolean isChecked(); method public boolean isEnabled(); method public boolean isFullScreen(); @@ -22857,17 +22859,7 @@ package android.view.accessibility { 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; + method public void setSource(android.view.View); } } diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 8bb305d4d55e..164acbcdf71f 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -190,9 +190,9 @@ public abstract class AccessibilityService extends Service { * <li> * Register for all event types with no notification timeout and keep track * for the active window by calling - * {@link AccessibilityEvent#getAccessibilityWindowId()} of the last received + * {@link AccessibilityEvent#getWindowId()} of the last received * event and compare this with the - * {@link AccessibilityNodeInfo#getAccessibilityWindowId()} before calling + * {@link AccessibilityNodeInfo#getWindowId()} before calling * retrieval methods on the latter. * </li> * <li> diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 19f0bf033a78..8b4e7aee7398 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -47,7 +47,9 @@ interface IAccessibilityServiceConnection { /** * Finds {@link AccessibilityNodeInfo}s by View text. The match is case - * insensitive containment. + * insensitive containment. The search is performed in the window whose + * id is specified and starts from the View whose accessibility id is + * specified. * <p> * <strong> * It is a client responsibility to recycle the received infos by @@ -57,12 +59,35 @@ interface IAccessibilityServiceConnection { * </p> * * @param text The searched text. + * @param accessibilityId The id of the view from which to start searching. + * Use {@link android.view.View#NO_ID} to start from the root. * @return A list of node info. */ - List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewText(String text); + List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewText(String text, + int accessibilityWindowId, int accessibilityViewId); /** - * Finds an {@link AccessibilityNodeInfo} by View id. + * Finds {@link AccessibilityNodeInfo}s by View text. The match is case + * insensitive containment. The search is performed in the currently + * active window and start from the root View in the window. + * <p> + * <strong> + * It is a client responsibility to recycle the received infos by + * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating + * of multiple instances. + * </strong> + * </p> + * + * @param text The searched text. + * @param accessibilityId The id of the view from which to start searching. + * Use {@link android.view.View#NO_ID} to start from the root. + * @return A list of node info. + */ + List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewTextInActiveWindow(String text); + + /** + * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed + * in the currently active window and start from the root View in the window. * <p> * <strong> * It is a client responsibility to recycle the received info by @@ -74,7 +99,7 @@ interface IAccessibilityServiceConnection { * @param id The id of the node. * @return The node info. */ - AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int viewId); + AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId); /** * Performs an accessibility action on an {@link AccessibilityNodeInfo}. diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 906a5649880f..9bd45d347e0f 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -277,24 +277,24 @@ public final class Configuration implements Parcelable, Comparable<Configuration public int compatSmallestScreenWidthDp; /** - * @hide + * @hide Do not use. Implementation not finished. */ - public static final int LAYOUT_DIRECTION_UNDEFINED = -1; + public static final int TEXT_LAYOUT_DIRECTION_UNDEFINED_DO_NOT_USE = -1; /** - * @hide + * @hide Do not use. Implementation not finished. */ - public static final int LAYOUT_DIRECTION_LTR = 0; + public static final int TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE = 0; /** - * @hide + * @hide Do not use. Implementation not finished. */ - public static final int LAYOUT_DIRECTION_RTL = 1; + public static final int TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE = 1; /** - * @hide The layout direction associated to the current Locale + * @hide The text layout direction associated to the current Locale */ - public int layoutDirection; + public int textLayoutDirection; /** * @hide Internal book-keeping. @@ -322,7 +322,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration mnc = o.mnc; if (o.locale != null) { locale = (Locale) o.locale.clone(); - layoutDirection = o.layoutDirection; + textLayoutDirection = o.textLayoutDirection; } userSetLocale = o.userSetLocale; touchscreen = o.touchscreen; @@ -358,6 +358,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration } else { sb.append(" (no locale)"); } + switch (textLayoutDirection) { + case TEXT_LAYOUT_DIRECTION_UNDEFINED_DO_NOT_USE: sb.append(" ?layoutdir"); break; + case TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE: sb.append(" rtl"); break; + default: sb.append(" layoutdir="); sb.append(textLayoutDirection); break; + } if (smallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) { sb.append(" sw"); sb.append(smallestScreenWidthDp); sb.append("dp"); } else { @@ -450,11 +455,6 @@ public final class Configuration implements Parcelable, Comparable<Configuration case NAVIGATIONHIDDEN_YES: sb.append("/h"); break; default: sb.append("/"); sb.append(navigationHidden); break; } - switch (layoutDirection) { - case LAYOUT_DIRECTION_UNDEFINED: sb.append(" ?layoutdir"); break; - case LAYOUT_DIRECTION_LTR: sb.append(" ltr"); break; - case LAYOUT_DIRECTION_RTL: sb.append(" rtl"); break; - } if (seq != 0) { sb.append(" s."); sb.append(seq); @@ -483,8 +483,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration screenWidthDp = compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED; screenHeightDp = compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED; smallestScreenWidthDp = compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; + textLayoutDirection = TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE; seq = 0; - layoutDirection = LAYOUT_DIRECTION_LTR; } /** {@hide} */ @@ -519,7 +519,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration changed |= ActivityInfo.CONFIG_LOCALE; locale = delta.locale != null ? (Locale) delta.locale.clone() : null; - layoutDirection = getLayoutDirectionFromLocale(locale); + textLayoutDirection = getLayoutDirectionFromLocale(locale); } if (delta.userSetLocale && (!userSetLocale || ((changed & ActivityInfo.CONFIG_LOCALE) != 0))) { @@ -611,23 +611,25 @@ public final class Configuration implements Parcelable, Comparable<Configuration /** * Return the layout direction for a given Locale * @param locale the Locale for which we want the layout direction. Can be null. - * @return the layout direction. This may be one of {@link #LAYOUT_DIRECTION_UNDEFINED}, - * {@link #LAYOUT_DIRECTION_LTR} or {@link #LAYOUT_DIRECTION_RTL}. + * @return the layout direction. This may be one of: + * {@link #TEXT_LAYOUT_DIRECTION_UNDEFINED_DO_NOT_USE} or + * {@link #TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE} or + * {@link #TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE}. * * @hide */ public static int getLayoutDirectionFromLocale(Locale locale) { - if (locale == null || locale.equals(Locale.ROOT)) return LAYOUT_DIRECTION_UNDEFINED; + if (locale == null || locale.equals(Locale.ROOT)) return TEXT_LAYOUT_DIRECTION_UNDEFINED_DO_NOT_USE; // Be careful: this code will need to be changed when vertical scripts will be supported // OR if ICU4C is updated to have the "likelySubtags" file switch(Character.getDirectionality(locale.getDisplayName(locale).charAt(0))) { case Character.DIRECTIONALITY_LEFT_TO_RIGHT: - return LAYOUT_DIRECTION_LTR; + return TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE; case Character.DIRECTIONALITY_RIGHT_TO_LEFT: case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC: - return LAYOUT_DIRECTION_RTL; + return TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE; default: - return LAYOUT_DIRECTION_UNDEFINED; + return TEXT_LAYOUT_DIRECTION_UNDEFINED_DO_NOT_USE; } } @@ -810,7 +812,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration dest.writeInt(compatScreenWidthDp); dest.writeInt(compatScreenHeightDp); dest.writeInt(compatSmallestScreenWidthDp); - dest.writeInt(layoutDirection); + dest.writeInt(textLayoutDirection); dest.writeInt(seq); } @@ -838,7 +840,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration compatScreenWidthDp = source.readInt(); compatScreenHeightDp = source.readInt(); compatSmallestScreenWidthDp = source.readInt(); - layoutDirection = source.readInt(); + textLayoutDirection = source.readInt(); seq = source.readInt(); } diff --git a/core/java/android/nfc/INfcTag.aidl b/core/java/android/nfc/INfcTag.aidl index b66035ff881e..aa5937ee9deb 100644 --- a/core/java/android/nfc/INfcTag.aidl +++ b/core/java/android/nfc/INfcTag.aidl @@ -43,7 +43,6 @@ interface INfcTag int formatNdef(int nativeHandle, in byte[] key); Tag rediscover(int nativehandle); - void setIsoDepTimeout(int timeout); - void setFelicaTimeout(int timeout); + int setTimeout(int technology, int timeout); void resetTimeouts(); } diff --git a/core/java/android/nfc/tech/IsoDep.java b/core/java/android/nfc/tech/IsoDep.java index 38b2bbd9e7ea..d02086ffcf1f 100644 --- a/core/java/android/nfc/tech/IsoDep.java +++ b/core/java/android/nfc/tech/IsoDep.java @@ -16,6 +16,7 @@ package android.nfc.tech; +import android.nfc.ErrorCodes; import android.nfc.Tag; import android.os.Bundle; import android.os.RemoteException; @@ -90,7 +91,10 @@ public final class IsoDep extends BasicTagTechnology { */ public void setTimeout(int timeout) { try { - mTag.getTagService().setIsoDepTimeout(timeout); + int err = mTag.getTagService().setTimeout(TagTechnology.ISO_DEP, timeout); + if (err != ErrorCodes.SUCCESS) { + throw new IllegalArgumentException("The supplied timeout is not valid"); + } } catch (RemoteException e) { Log.e(TAG, "NFC service dead", e); } diff --git a/core/java/android/nfc/tech/MifareClassic.java b/core/java/android/nfc/tech/MifareClassic.java index c4b771878ce2..5cafe5ba6126 100644 --- a/core/java/android/nfc/tech/MifareClassic.java +++ b/core/java/android/nfc/tech/MifareClassic.java @@ -16,9 +16,11 @@ package android.nfc.tech; +import android.nfc.ErrorCodes; import android.nfc.Tag; import android.nfc.TagLostException; import android.os.RemoteException; +import android.util.Log; import java.io.IOException; import java.nio.ByteBuffer; @@ -69,6 +71,8 @@ import java.nio.ByteOrder; * require the {@link android.Manifest.permission#NFC} permission. */ public final class MifareClassic extends BasicTagTechnology { + private static final String TAG = "NFC"; + /** * The default factory key. */ @@ -568,6 +572,31 @@ public final class MifareClassic extends BasicTagTechnology { return transceive(data, true); } + /** + * Set the timeout of {@link #transceive} in milliseconds. + * <p>The timeout only applies to MifareUltralight {@link #transceive}, + * and is reset to a default value when {@link #close} is called. + * <p>Setting a longer timeout may be useful when performing + * transactions that require a long processing time on the tag + * such as key generation. + * + * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. + * + * @param timeout timeout value in milliseconds + * @hide + */ + // TODO Unhide for ICS + public void setTimeout(int timeout) { + try { + int err = mTag.getTagService().setTimeout(TagTechnology.MIFARE_CLASSIC, timeout); + if (err != ErrorCodes.SUCCESS) { + throw new IllegalArgumentException("The supplied timeout is not valid"); + } + } catch (RemoteException e) { + Log.e(TAG, "NFC service dead", e); + } + } + private static void validateSector(int sector) { // Do not be too strict on upper bounds checking, since some cards // have more addressable memory than they report. For example, diff --git a/core/java/android/nfc/tech/MifareUltralight.java b/core/java/android/nfc/tech/MifareUltralight.java index 6c2754bcc05d..3d4cdd1a8dff 100644 --- a/core/java/android/nfc/tech/MifareUltralight.java +++ b/core/java/android/nfc/tech/MifareUltralight.java @@ -16,10 +16,12 @@ package android.nfc.tech; +import android.nfc.ErrorCodes; import android.nfc.Tag; import android.nfc.TagLostException; import android.os.Bundle; import android.os.RemoteException; +import android.util.Log; import java.io.IOException; @@ -57,6 +59,8 @@ import java.io.IOException; * require the {@link android.Manifest.permission#NFC} permission. */ public final class MifareUltralight extends BasicTagTechnology { + private static final String TAG = "NFC"; + /** A MIFARE Ultralight compatible tag of unknown type */ public static final int TYPE_UNKNOWN = -1; /** A MIFARE Ultralight tag */ @@ -208,6 +212,32 @@ public final class MifareUltralight extends BasicTagTechnology { return transceive(data, true); } + /** + * Set the timeout of {@link #transceive} in milliseconds. + * <p>The timeout only applies to MifareUltralight {@link #transceive}, + * and is reset to a default value when {@link #close} is called. + * <p>Setting a longer timeout may be useful when performing + * transactions that require a long processing time on the tag + * such as key generation. + * + * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. + * + * @param timeout timeout value in milliseconds + * @hide + */ + // TODO Unhide for ICS + public void setTimeout(int timeout) { + try { + int err = mTag.getTagService().setTimeout( + TagTechnology.MIFARE_ULTRALIGHT, timeout); + if (err != ErrorCodes.SUCCESS) { + throw new IllegalArgumentException("The supplied timeout is not valid"); + } + } catch (RemoteException e) { + Log.e(TAG, "NFC service dead", e); + } + } + private static void validatePageIndex(int pageIndex) { // Do not be too strict on upper bounds checking, since some cards // may have more addressable memory than they report. diff --git a/core/java/android/nfc/tech/NfcA.java b/core/java/android/nfc/tech/NfcA.java index 1843eaed24db..08095e6cb163 100644 --- a/core/java/android/nfc/tech/NfcA.java +++ b/core/java/android/nfc/tech/NfcA.java @@ -16,9 +16,11 @@ package android.nfc.tech; +import android.nfc.ErrorCodes; import android.nfc.Tag; import android.os.Bundle; import android.os.RemoteException; +import android.util.Log; import java.io.IOException; @@ -33,6 +35,8 @@ import java.io.IOException; * require the {@link android.Manifest.permission#NFC} permission. */ public final class NfcA extends BasicTagTechnology { + private static final String TAG = "NFC"; + /** @hide */ public static final String EXTRA_SAK = "sak"; /** @hide */ @@ -112,4 +116,29 @@ public final class NfcA extends BasicTagTechnology { public byte[] transceive(byte[] data) throws IOException { return transceive(data, true); } + + /** + * Set the timeout of {@link #transceive} in milliseconds. + * <p>The timeout only applies to NfcA {@link #transceive}, and is + * reset to a default value when {@link #close} is called. + * <p>Setting a longer timeout may be useful when performing + * transactions that require a long processing time on the tag + * such as key generation. + * + * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. + * + * @param timeout timeout value in milliseconds + * @hide + */ + // TODO Unhide for ICS + public void setTimeout(int timeout) { + try { + int err = mTag.getTagService().setTimeout(TagTechnology.NFC_A, timeout); + if (err != ErrorCodes.SUCCESS) { + throw new IllegalArgumentException("The supplied timeout is not valid"); + } + } catch (RemoteException e) { + Log.e(TAG, "NFC service dead", e); + } + } } diff --git a/core/java/android/nfc/tech/NfcF.java b/core/java/android/nfc/tech/NfcF.java index 250c9b31cf0d..85abf89e6dfd 100644 --- a/core/java/android/nfc/tech/NfcF.java +++ b/core/java/android/nfc/tech/NfcF.java @@ -16,6 +16,7 @@ package android.nfc.tech; +import android.nfc.ErrorCodes; import android.nfc.Tag; import android.os.Bundle; import android.os.RemoteException; @@ -131,7 +132,10 @@ public final class NfcF extends BasicTagTechnology { // TODO Unhide for ICS public void setTimeout(int timeout) { try { - mTag.getTagService().setFelicaTimeout(timeout); + int err = mTag.getTagService().setTimeout(TagTechnology.NFC_F, timeout); + if (err != ErrorCodes.SUCCESS) { + throw new IllegalArgumentException("The supplied timeout is not valid"); + } } catch (RemoteException e) { Log.e(TAG, "NFC service dead", e); } diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java index bb6ed9c82b7c..3b2e6b644084 100644 --- a/core/java/android/provider/Calendar.java +++ b/core/java/android/provider/Calendar.java @@ -178,9 +178,8 @@ public final class Calendar { * have specific uses which are expected to be consistent by the app and * sync adapter. * - * @hide */ - public interface SyncColumns extends CalendarSyncColumns { + protected interface SyncColumns extends CalendarSyncColumns { /** * The account that was used to sync the entry to the device. If the * account_type is not {@link #ACCOUNT_TYPE_LOCAL} then the name and @@ -223,7 +222,7 @@ public final class Calendar { /** * If set to 1 this causes events on this calendar to be duplicated with - * {@link EventsColumns#LAST_SYNCED} set to 1 whenever the event + * {@link Events#LAST_SYNCED} set to 1 whenever the event * transitions from non-dirty to dirty. The duplicated event will not be * expanded in the instances table and will only show up in sync adapter * queries of the events table. It will also be deleted when the @@ -236,7 +235,7 @@ public final class Calendar { /** * Columns specific to the Calendars Uri that other Uris can query. */ - private interface CalendarsColumns { + protected interface CalendarsColumns { /** * The color of the calendar * <P>Type: INTEGER (color value)</P> @@ -549,7 +548,7 @@ public final class Calendar { /** * Columns from the Attendees table that other tables join into themselves. */ - private interface AttendeesColumns { + protected interface AttendeesColumns { /** * The id of the event. Column name. @@ -639,7 +638,7 @@ public final class Calendar { /** * Columns from the Events table that other tables join into themselves. */ - private interface EventsColumns { + protected interface EventsColumns { /** * The {@link Calendars#_ID} of the calendar the event belongs to. @@ -1525,7 +1524,7 @@ public final class Calendar { * time zone for the instaces. These settings are stored using a key/value * scheme. */ - private interface CalendarCacheColumns { + protected interface CalendarCacheColumns { /** * The key for the setting. Keys are defined in {@link CalendarCache}. */ @@ -1597,7 +1596,7 @@ public final class Calendar { * the Instances table and these are all stored in the first (and only) * row of the CalendarMetaData table. */ - private interface CalendarMetaDataColumns { + protected interface CalendarMetaDataColumns { /** * The local timezone that was used for precomputing the fields * in the Instances table. @@ -1637,7 +1636,7 @@ public final class Calendar { public static final class CalendarMetaData implements CalendarMetaDataColumns, BaseColumns { } - private interface EventDaysColumns { + protected interface EventDaysColumns { /** * The Julian starting day number. Column name. * <P>Type: INTEGER (int)</P> @@ -1655,7 +1654,7 @@ public final class Calendar { * Fields and helpers for querying for a list of days that contain events. */ public static final class EventDays implements EventDaysColumns { - private static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/instances/groupbyday"); /** @@ -1690,7 +1689,7 @@ public final class Calendar { } } - private interface RemindersColumns { + protected interface RemindersColumns { /** * The event the reminder belongs to. Column name. * <P>Type: INTEGER (foreign key to the Events table)</P> @@ -1755,7 +1754,7 @@ public final class Calendar { } } - private interface CalendarAlertsColumns { + protected interface CalendarAlertsColumns { /** * The event that the alert belongs to. Column name. * <P>Type: INTEGER (foreign key to the Events table)</P> @@ -2069,7 +2068,7 @@ public final class Calendar { } } - private interface ExtendedPropertiesColumns { + protected interface ExtendedPropertiesColumns { /** * The event the extended property belongs to. Column name. * <P>Type: INTEGER (foreign key to the Events table)</P> @@ -2128,7 +2127,7 @@ public final class Calendar { /** * Columns from the EventsRawTimes table */ - private interface EventsRawTimesColumns { + protected interface EventsRawTimesColumns { /** * The corresponding event id. Column name. * <P>Type: INTEGER (long)</P> diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 441cdc14db8a..ee187222e622 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3704,7 +3704,8 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit * The base implementation sets: * <ul> * <li>{@link AccessibilityNodeInfo#setParent(View)},</li> - * <li>{@link AccessibilityNodeInfo#setBounds(Rect)},</li> + * <li>{@link AccessibilityNodeInfo#setBoundsInParent(Rect)},</li> + * <li>{@link AccessibilityNodeInfo#setBoundsInScreen(Rect)},</li> * <li>{@link AccessibilityNodeInfo#setPackageName(CharSequence)},</li> * <li>{@link AccessibilityNodeInfo#setClassName(CharSequence)},</li> * <li>{@link AccessibilityNodeInfo#setContentDescription(CharSequence)},</li> @@ -3724,7 +3725,12 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { Rect bounds = mAttachInfo.mTmpInvalRect; getDrawingRect(bounds); - info.setBounds(bounds); + info.setBoundsInParent(bounds); + + int[] locationOnScreen = mAttachInfo.mInvalidateChildLocation; + getLocationOnScreen(locationOnScreen); + bounds.offset(locationOnScreen[0], locationOnScreen[1]); + info.setBoundsInScreen(bounds); ViewParent parent = getParent(); if (parent instanceof View) { diff --git a/core/java/android/view/ViewAncestor.java b/core/java/android/view/ViewAncestor.java index 17d745485130..914973ed41b1 100644 --- a/core/java/android/view/ViewAncestor.java +++ b/core/java/android/view/ViewAncestor.java @@ -136,6 +136,13 @@ public final class ViewAncestor extends Handler implements ViewParent, static final ArrayList<ComponentCallbacks> sConfigCallbacks = new ArrayList<ComponentCallbacks>(); + /** + * Delay before dispatching an accessibility event that the window + * content has changed. The window content is considered changed + * after a layout pass. + */ + private static final long SEND_WINDOW_CONTENT_CHANGED_DELAY_MILLIS = 500; + long mLastTrackballTime = 0; final TrackballAxis mTrackballAxisX = new TrackballAxis(); final TrackballAxis mTrackballAxisY = new TrackballAxis(); @@ -277,6 +284,8 @@ public final class ViewAncestor extends Handler implements ViewParent, AccessibilityInteractionConnectionManager mAccessibilityInteractionConnectionManager; + SendWindowContentChanged mSendWindowContentChanged; + private final int mDensity; /** @@ -1427,6 +1436,10 @@ public final class ViewAncestor extends Handler implements ViewParent, if (triggerGlobalLayoutListener) { attachInfo.mRecomputeGlobalAttributes = false; attachInfo.mTreeObserver.dispatchOnGlobalLayout(); + + if (AccessibilityManager.getInstance(host.mContext).isEnabled()) { + postSendWindowContentChangedCallback(); + } } if (computesInternalInsets) { @@ -2086,6 +2099,7 @@ public final class ViewAncestor extends Handler implements ViewParent, mAccessibilityInteractionConnectionManager.ensureNoConnection(); mAccessibilityManager.removeAccessibilityStateChangeListener( mAccessibilityInteractionConnectionManager); + removeSendWindowContentChangedCallback(); mView = null; mAttachInfo.mRootView = null; @@ -3671,6 +3685,29 @@ public final class ViewAncestor extends Handler implements ViewParent, } } + /** + * Post a callback to send a + * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event. + */ + private void postSendWindowContentChangedCallback() { + if (mSendWindowContentChanged == null) { + mSendWindowContentChanged = new SendWindowContentChanged(); + } else { + removeCallbacks(mSendWindowContentChanged); + } + postDelayed(mSendWindowContentChanged, SEND_WINDOW_CONTENT_CHANGED_DELAY_MILLIS); + } + + /** + * Remove a posted callback to send a + * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event. + */ + private void removeSendWindowContentChangedCallback() { + if (mSendWindowContentChanged != null) { + removeCallbacks(mSendWindowContentChanged); + } + } + public boolean showContextMenuForChild(View originalView) { return false; } @@ -4268,12 +4305,12 @@ public final class ViewAncestor extends Handler implements ViewParent, } } - public void findAccessibilityNodeInfosByViewText(String text, int interactionId, - IAccessibilityInteractionConnectionCallback callback) { + public void findAccessibilityNodeInfosByViewText(String text, int accessibilityId, + int interactionId, IAccessibilityInteractionConnectionCallback callback) { if (mViewAncestor.get() != null) { getAccessibilityInteractionController() - .findAccessibilityNodeInfosByViewTextClientThread(text, interactionId, - callback); + .findAccessibilityNodeInfosByViewTextClientThread(text, accessibilityId, + interactionId, callback); } } } @@ -4414,13 +4451,15 @@ public final class ViewAncestor extends Handler implements ViewParent, } } - public void findAccessibilityNodeInfosByViewTextClientThread(String text, int interactionId, + public void findAccessibilityNodeInfosByViewTextClientThread(String text, + int accessibilityViewId, int interactionId, IAccessibilityInteractionConnectionCallback callback) { Message message = Message.obtain(); message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT; SomeArgs args = mPool.acquire(); args.arg1 = text; - args.argi1 = interactionId; + args.argi1 = accessibilityViewId; + args.argi2 = interactionId; args.arg2 = callback; message.obj = args; sendMessage(message); @@ -4429,18 +4468,28 @@ public final class ViewAncestor extends Handler implements ViewParent, public void findAccessibilityNodeInfosByViewTextUiThread(Message message) { SomeArgs args = (SomeArgs) message.obj; final String text = (String) args.arg1; - final int interactionId = args.argi1; + final int accessibilityViewId = args.argi1; + final int interactionId = args.argi2; final IAccessibilityInteractionConnectionCallback callback = (IAccessibilityInteractionConnectionCallback) args.arg2; mPool.release(args); List<AccessibilityNodeInfo> infos = null; try { - View root = ViewAncestor.this.mView; - ArrayList<View> foundViews = mAttachInfo.mFocusablesTempList; foundViews.clear(); + View root = null; + if (accessibilityViewId != View.NO_ID) { + root = findViewByAccessibilityId(accessibilityViewId); + } else { + root = ViewAncestor.this.mView; + } + + if (root == null) { + return; + } + root.findViewsWithText(foundViews, text); if (foundViews.isEmpty()) { return; @@ -4577,4 +4626,12 @@ public final class ViewAncestor extends Handler implements ViewParent, } } } + + private class SendWindowContentChanged implements Runnable { + public void run() { + if (mView != null) { + mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + } + } + } } diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index 06e4827ae371..5ef7763bff73 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -19,11 +19,10 @@ package android.view.accessibility; import android.accessibilityservice.IAccessibilityServiceConnection; import android.os.Parcel; import android.os.Parcelable; -import android.os.RemoteException; import android.text.TextUtils; -import android.view.View; import java.util.ArrayList; +import java.util.List; /** * This class represents accessibility events that are sent by the system when @@ -130,7 +129,7 @@ import java.util.ArrayList; * <p> * <b>TRANSITION TYPES</b> <br> * <p> - * <b>Window state changed</b> - represents the event of opening/closing a + * <b>Window state changed</b> - represents the event of opening a * {@link android.widget.PopupWindow}, {@link android.view.Menu}, * {@link android.app.Dialog}, etc. <br> * Type: {@link #TYPE_WINDOW_STATE_CHANGED} <br> @@ -140,6 +139,16 @@ import java.util.ArrayList; * {@link #getEventTime()}, * {@link #getText()} * <p> + * <b>Window content changed</b> - represents the event of change in the + * content of a window. This change can be adding/removing view, changing + * a view size, etc.<br> + * Type: {@link #TYPE_WINDOW_CONTENT_CHANGED} <br> + * Properties: + * {@link #getClassName()}, + * {@link #getPackageName()}, + * {@link #getEventTime()}, + * {@link #getText()} + * <p> * <b>NOTIFICATION TYPES</b> <br> * <p> * <b>Notification state changed</b> - represents the event showing/hiding @@ -244,6 +253,11 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 0x00000400; /** + * Represents the event of changing the content of a window. + */ + public static final int TYPE_WINDOW_CONTENT_CHANGED = 0x00000800; + + /** * Mask for {@link AccessibilityEvent} all types. * * @see #TYPE_VIEW_CLICKED @@ -264,15 +278,11 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par private boolean mIsInPool; private int mEventType; - private int mSourceAccessibilityViewId = View.NO_ID; - private int mSourceAccessibilityWindowId = View.NO_ID; private CharSequence mPackageName; private long mEventTime; private final ArrayList<AccessibilityRecord> mRecords = new ArrayList<AccessibilityRecord>(); - private IAccessibilityServiceConnection mConnection; - /* * Hide constructor from clients. */ @@ -288,10 +298,43 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par super.init(event); mEventType = event.mEventType; mEventTime = event.mEventTime; - mSourceAccessibilityWindowId = event.mSourceAccessibilityWindowId; - mSourceAccessibilityViewId = event.mSourceAccessibilityViewId; mPackageName = event.mPackageName; - mConnection = event.mConnection; + } + + /** + * Sets the connection for interacting with the AccessibilityManagerService. + * + * @param connection The connection. + * + * @hide + */ + @Override + public void setConnection(IAccessibilityServiceConnection connection) { + super.setConnection(connection); + List<AccessibilityRecord> records = mRecords; + final int recordCount = records.size(); + for (int i = 0; i < recordCount; i++) { + AccessibilityRecord record = records.get(i); + record.setConnection(connection); + } + } + + /** + * Sets if this instance is sealed. + * + * @param sealed Whether is sealed. + * + * @hide + */ + @Override + public void setSealed(boolean sealed) { + super.setSealed(sealed); + List<AccessibilityRecord> records = mRecords; + final int recordCount = records.size(); + for (int i = 0; i < recordCount; i++) { + AccessibilityRecord record = records.get(i); + record.setSealed(sealed); + } } /** @@ -335,81 +378,6 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par } /** - * Sets the event source. - * - * @param source The source. - * - * @throws IllegalStateException If called from an AccessibilityService. - */ - public void setSource(View source) { - enforceNotSealed(); - if (source != null) { - mSourceAccessibilityWindowId = source.getAccessibilityWindowId(); - mSourceAccessibilityViewId = source.getAccessibilityViewId(); - } else { - mSourceAccessibilityWindowId = View.NO_ID; - mSourceAccessibilityViewId = View.NO_ID; - } - } - - /** - * Gets the {@link AccessibilityNodeInfo} of the event source. - * <p> - * <strong> - * It is a client responsibility to recycle the received info by - * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating - * of multiple instances. - * </strong> - * </p> - * @return The info. - */ - public AccessibilityNodeInfo getSource() { - enforceSealed(); - if (mSourceAccessibilityWindowId == View.NO_ID - || mSourceAccessibilityViewId == View.NO_ID) { - return null; - } - try { - return mConnection.findAccessibilityNodeInfoByAccessibilityId( - mSourceAccessibilityWindowId, mSourceAccessibilityViewId); - } catch (RemoteException e) { - return null; - } - } - - /** - * Gets the id of the window from which the event comes from. - * - * @return The window id. - */ - public int getAccessibilityWindowId() { - return mSourceAccessibilityWindowId; - } - - /** - * Sets the client token for the accessibility service that - * provided this node info. - * - * @param connection The connection. - * - * @hide - */ - public final void setConnection(IAccessibilityServiceConnection connection) { - mConnection = connection; - } - - /** - * Gets the accessibility window id of the source window. - * - * @return The id. - * - * @hide - */ - public int getSourceAccessibilityWindowId() { - return mSourceAccessibilityWindowId; - } - - /** * Sets the event type. * * @param eventType The event type. @@ -548,10 +516,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par @Override protected void clear() { super.clear(); - mConnection = null; mEventType = 0; - mSourceAccessibilityViewId = View.NO_ID; - mSourceAccessibilityWindowId = View.NO_ID; mPackageName = null; mEventTime = 0; while (!mRecords.isEmpty()) { @@ -572,8 +537,6 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par } setSealed(parcel.readInt() == 1); mEventType = parcel.readInt(); - mSourceAccessibilityWindowId = parcel.readInt(); - mSourceAccessibilityViewId = parcel.readInt(); mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); mEventTime = parcel.readLong(); readAccessibilityRecordFromParcel(this, parcel); @@ -583,6 +546,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par for (int i = 0; i < recordCount; i++) { AccessibilityRecord record = AccessibilityRecord.obtain(); readAccessibilityRecordFromParcel(record, parcel); + // Do this to write the connection only once. + record.setConnection(mConnection); mRecords.add(record); } } @@ -606,6 +571,9 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par record.mBeforeText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); record.mParcelableData = parcel.readParcelable(null); parcel.readList(record.mText, null); + record.mSourceWindowId = parcel.readInt(); + record.mSourceViewId = parcel.readInt(); + record.mSealed = (parcel.readInt() == 1); } /** @@ -620,8 +588,6 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par } parcel.writeInt(isSealed() ? 1 : 0); parcel.writeInt(mEventType); - parcel.writeInt(mSourceAccessibilityWindowId); - parcel.writeInt(mSourceAccessibilityViewId); TextUtils.writeToParcel(mPackageName, parcel, 0); parcel.writeLong(mEventTime); writeAccessibilityRecordToParcel(this, parcel, flags); @@ -654,6 +620,9 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par TextUtils.writeToParcel(record.mBeforeText, parcel, flags); parcel.writeParcelable(record.mParcelableData, flags); parcel.writeList(record.mText); + parcel.writeInt(record.mSourceWindowId); + parcel.writeInt(record.mSourceViewId); + parcel.writeInt(record.mSealed ? 1 : 0); } /** @@ -672,8 +641,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par builder.append(super.toString()); if (DEBUG) { builder.append("\n"); - builder.append("; sourceAccessibilityWindowId: ").append(mSourceAccessibilityWindowId); - builder.append("; sourceAccessibilityViewId: ").append(mSourceAccessibilityViewId); + builder.append("; sourceWindowId: ").append(mSourceWindowId); + builder.append("; sourceViewId: ").append(mSourceViewId); for (int i = 0; i < mRecords.size(); i++) { AccessibilityRecord record = mRecords.get(i); builder.append(" Record "); @@ -733,6 +702,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par return "TYPE_TOUCH_EXPLORATION_GESTURE_START"; case TYPE_TOUCH_EXPLORATION_GESTURE_END: return "TYPE_TOUCH_EXPLORATION_GESTURE_END"; + case TYPE_WINDOW_CONTENT_CHANGED: + return "TYPE_WINDOW_CONTENT_CHANGED"; default: return null; } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 5fa65b450e42..18ef38af7476 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -26,6 +26,9 @@ import android.util.SparseArray; import android.util.SparseIntArray; import android.view.View; +import java.util.Collections; +import java.util.List; + /** * This class represents a node of the screen content. From the point of * view of an accessibility service the screen content is presented as tree @@ -97,7 +100,8 @@ public class AccessibilityNodeInfo implements Parcelable { private int mAccessibilityWindowId = View.NO_ID; private int mParentAccessibilityViewId = View.NO_ID; private int mBooleanProperties; - private final Rect mBounds = new Rect(); + private final Rect mBoundsInParent = new Rect(); + private final Rect mBoundsInScreen = new Rect(); private CharSequence mPackageName; private CharSequence mClassName; @@ -132,7 +136,7 @@ public class AccessibilityNodeInfo implements Parcelable { * * @return The window id. */ - public int getAccessibilityWindowId() { + public int getWindowId() { return mAccessibilityWindowId; } @@ -163,12 +167,16 @@ public class AccessibilityNodeInfo implements Parcelable { public AccessibilityNodeInfo getChild(int index) { enforceSealed(); final int childAccessibilityViewId = mChildAccessibilityIds.get(index); + if (!canPerformRequestOverConnection(childAccessibilityViewId)) { + return null; + } try { return mConnection.findAccessibilityNodeInfoByAccessibilityId(mAccessibilityWindowId, childAccessibilityViewId); - } catch (RemoteException e) { - return null; + } catch (RemoteException re) { + /* ignore*/ } + return null; } /** @@ -230,12 +238,37 @@ public class AccessibilityNodeInfo implements Parcelable { */ public boolean performAction(int action) { enforceSealed(); + if (!canPerformRequestOverConnection(mAccessibilityViewId)) { + return false; + } try { return mConnection.performAccessibilityAction(mAccessibilityWindowId, mAccessibilityViewId, action); } catch (RemoteException e) { - return false; + /* ignore */ } + return false; + } + + /** + * Finds {@link AccessibilityNodeInfo}s by text. The match is case + * insensitive containment. + * + * @param text The searched text. + * @return A list of node info. + */ + public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) { + enforceSealed(); + if (!canPerformRequestOverConnection(mAccessibilityViewId)) { + return null; + } + try { + return mConnection.findAccessibilityNodeInfosByViewText(text, mAccessibilityWindowId, + mAccessibilityViewId); + } catch (RemoteException e) { + /* ignore */ + } + return Collections.emptyList(); } /** @@ -251,12 +284,16 @@ public class AccessibilityNodeInfo implements Parcelable { */ public AccessibilityNodeInfo getParent() { enforceSealed(); + if (!canPerformRequestOverConnection(mAccessibilityViewId)) { + return null; + } try { - return mConnection.findAccessibilityNodeInfoByAccessibilityId(mAccessibilityWindowId, - mParentAccessibilityViewId); + return mConnection.findAccessibilityNodeInfoByAccessibilityId( + mAccessibilityWindowId, mParentAccessibilityViewId); } catch (RemoteException e) { - return null; + /* ignore */ } + return null; } /** @@ -279,8 +316,9 @@ public class AccessibilityNodeInfo implements Parcelable { * * @param outBounds The output node bounds. */ - public void getBounds(Rect outBounds) { - outBounds.set(mBounds.left, mBounds.top, mBounds.right, mBounds.bottom); + public void getBoundsInParent(Rect outBounds) { + outBounds.set(mBoundsInParent.left, mBoundsInParent.top, + mBoundsInParent.right, mBoundsInParent.bottom); } /** @@ -293,9 +331,34 @@ public class AccessibilityNodeInfo implements Parcelable { * * @throws IllegalStateException If called from an AccessibilityService. */ - public void setBounds(Rect bounds) { + public void setBoundsInParent(Rect bounds) { enforceNotSealed(); - mBounds.set(bounds.left, bounds.top, bounds.right, bounds.bottom); + mBoundsInParent.set(bounds.left, bounds.top, bounds.right, bounds.bottom); + } + + /** + * Gets the node bounds in screen coordinates. + * + * @param outBounds The output node bounds. + */ + public void getBoundsInScreen(Rect outBounds) { + outBounds.set(mBoundsInScreen.left, mBoundsInScreen.top, + mBoundsInScreen.right, mBoundsInScreen.bottom); + } + + /** + * Sets the node bounds in screen coordinates. + * <p> + * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * @param bounds The node bounds. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setBoundsInScreen(Rect bounds) { + enforceNotSealed(); + mBoundsInScreen.set(bounds.left, bounds.top, bounds.right, bounds.bottom); } /** @@ -636,6 +699,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @hide */ public final void setConnection(IAccessibilityServiceConnection connection) { + enforceNotSealed(); mConnection = connection; } @@ -777,10 +841,15 @@ public class AccessibilityNodeInfo implements Parcelable { parcel.writeInt(childIds.valueAt(i)); } - parcel.writeInt(mBounds.top); - parcel.writeInt(mBounds.bottom); - parcel.writeInt(mBounds.left); - parcel.writeInt(mBounds.right); + parcel.writeInt(mBoundsInParent.top); + parcel.writeInt(mBoundsInParent.bottom); + parcel.writeInt(mBoundsInParent.left); + parcel.writeInt(mBoundsInParent.right); + + parcel.writeInt(mBoundsInScreen.top); + parcel.writeInt(mBoundsInScreen.bottom); + parcel.writeInt(mBoundsInScreen.left); + parcel.writeInt(mBoundsInScreen.right); parcel.writeInt(mActions); @@ -818,10 +887,15 @@ public class AccessibilityNodeInfo implements Parcelable { childIds.put(i, childId); } - mBounds.top = parcel.readInt(); - mBounds.bottom = parcel.readInt(); - mBounds.left = parcel.readInt(); - mBounds.right = parcel.readInt(); + mBoundsInParent.top = parcel.readInt(); + mBoundsInParent.bottom = parcel.readInt(); + mBoundsInParent.left = parcel.readInt(); + mBoundsInParent.right = parcel.readInt(); + + mBoundsInScreen.top = parcel.readInt(); + mBoundsInScreen.bottom = parcel.readInt(); + mBoundsInScreen.left = parcel.readInt(); + mBoundsInScreen.right = parcel.readInt(); mActions = parcel.readInt(); @@ -842,7 +916,8 @@ public class AccessibilityNodeInfo implements Parcelable { mAccessibilityViewId = View.NO_ID; mParentAccessibilityViewId = View.NO_ID; mChildAccessibilityIds.clear(); - mBounds.set(0, 0, 0, 0); + mBoundsInParent.set(0, 0, 0, 0); + mBoundsInScreen.set(0, 0, 0, 0); mBooleanProperties = 0; mPackageName = null; mClassName = null; @@ -869,6 +944,12 @@ public class AccessibilityNodeInfo implements Parcelable { return actionSymbolicNames.get(action); } + private boolean canPerformRequestOverConnection(int accessibilityViewId) { + return (mAccessibilityWindowId != View.NO_ID + && accessibilityViewId != View.NO_ID + && mConnection != null); + } + @Override public boolean equals(Object object) { if (this == object) { @@ -918,7 +999,8 @@ public class AccessibilityNodeInfo implements Parcelable { builder.append("]"); } - builder.append("; bounds: " + mBounds); + builder.append("; boundsInParent: " + mBoundsInParent); + builder.append("; boundsInScreen: " + mBoundsInScreen); builder.append("; packageName: ").append(mPackageName); builder.append("; className: ").append(mClassName); diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java index 4bf03a7bed8e..9c495e2126cb 100644 --- a/core/java/android/view/accessibility/AccessibilityRecord.java +++ b/core/java/android/view/accessibility/AccessibilityRecord.java @@ -16,7 +16,10 @@ package android.view.accessibility; +import android.accessibilityservice.IAccessibilityServiceConnection; import android.os.Parcelable; +import android.os.RemoteException; +import android.view.View; import java.util.ArrayList; import java.util.List; @@ -45,26 +48,29 @@ public class AccessibilityRecord { private static int sPoolSize; private AccessibilityRecord mNext; private boolean mIsInPool; - private boolean mSealed; - protected int mBooleanProperties; - protected int mCurrentItemIndex; - protected int mItemCount; - protected int mFromIndex; - protected int mAddedCount; - protected int mRemovedCount; + boolean mSealed; + int mBooleanProperties; + int mCurrentItemIndex; + int mItemCount; + int mFromIndex; + int mAddedCount; + int mRemovedCount; + int mSourceViewId = View.NO_ID; + int mSourceWindowId = View.NO_ID; - protected CharSequence mClassName; - protected CharSequence mContentDescription; - protected CharSequence mBeforeText; - protected Parcelable mParcelableData; + CharSequence mClassName; + CharSequence mContentDescription; + CharSequence mBeforeText; + Parcelable mParcelableData; - protected final List<CharSequence> mText = new ArrayList<CharSequence>(); + final List<CharSequence> mText = new ArrayList<CharSequence>(); + IAccessibilityServiceConnection mConnection; /* * Hide constructor. */ - protected AccessibilityRecord() { + AccessibilityRecord() { } @@ -74,7 +80,7 @@ public class AccessibilityRecord { * @param record The to initialize from. */ void init(AccessibilityRecord record) { - mSealed = record.isSealed(); + mSealed = record.mSealed; mBooleanProperties = record.mBooleanProperties; mCurrentItemIndex = record.mCurrentItemIndex; mItemCount = record.mItemCount; @@ -86,6 +92,73 @@ public class AccessibilityRecord { mBeforeText = record.mBeforeText; mParcelableData = record.mParcelableData; mText.addAll(record.mText); + mSourceWindowId = record.mSourceWindowId; + mSourceViewId = record.mSourceViewId; + mConnection = record.mConnection; + } + + /** + * Sets the event source. + * + * @param source The source. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setSource(View source) { + enforceNotSealed(); + if (source != null) { + mSourceWindowId = source.getAccessibilityWindowId(); + mSourceViewId = source.getAccessibilityViewId(); + } else { + mSourceWindowId = View.NO_ID; + mSourceViewId = View.NO_ID; + } + } + + /** + * Gets the {@link AccessibilityNodeInfo} of the event source. + * <p> + * <strong> + * It is a client responsibility to recycle the received info by + * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating + * of multiple instances. + * </strong> + * </p> + * @return The info. + */ + public AccessibilityNodeInfo getSource() { + enforceSealed(); + if (mSourceWindowId == View.NO_ID || mSourceViewId == View.NO_ID || mConnection == null) { + return null; + } + try { + return mConnection.findAccessibilityNodeInfoByAccessibilityId(mSourceWindowId, + mSourceViewId); + } catch (RemoteException e) { + /* ignore */ + } + return null; + } + + /** + * Sets the connection for interacting with the AccessibilityManagerService. + * + * @param connection The connection. + * + * @hide + */ + public void setConnection(IAccessibilityServiceConnection connection) { + enforceNotSealed(); + mConnection = connection; + } + + /** + * Gets the id of the window from which the event comes from. + * + * @return The window id. + */ + public int getWindowId() { + return mSourceWindowId; } /** @@ -386,10 +459,8 @@ public class AccessibilityRecord { * Gets if this instance is sealed. * * @return Whether is sealed. - * - * @hide */ - public boolean isSealed() { + boolean isSealed() { return mSealed; } @@ -397,10 +468,8 @@ public class AccessibilityRecord { * Enforces that this instance is sealed. * * @throws IllegalStateException If this instance is not sealed. - * - * @hide */ - protected void enforceSealed() { + void enforceSealed() { if (!isSealed()) { throw new IllegalStateException("Cannot perform this " + "action on a not sealed instance."); @@ -411,10 +480,8 @@ public class AccessibilityRecord { * Enforces that this instance is not sealed. * * @throws IllegalStateException If this instance is sealed. - * - * @hide */ - protected void enforceNotSealed() { + void enforceNotSealed() { if (isSealed()) { throw new IllegalStateException("Cannot perform this " + "action on an sealed instance."); @@ -502,10 +569,8 @@ public class AccessibilityRecord { /** * Clears the state of this instance. - * - * @hide */ - protected void clear() { + void clear() { mSealed = false; mBooleanProperties = 0; mCurrentItemIndex = INVALID_POSITION; @@ -518,6 +583,8 @@ public class AccessibilityRecord { mBeforeText = null; mParcelableData = null; mText.clear(); + mSourceViewId = View.NO_ID; + mSourceWindowId = View.NO_ID; } @Override diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl index 77dcd0747ed8..d35186b823ce 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl @@ -33,8 +33,8 @@ oneway interface IAccessibilityInteractionConnection { void findAccessibilityNodeInfoByViewId(int id, int interactionId, IAccessibilityInteractionConnectionCallback callback); - void findAccessibilityNodeInfosByViewText(String text, int interactionId, - IAccessibilityInteractionConnectionCallback callback); + void findAccessibilityNodeInfosByViewText(String text, int accessibilityViewId, + int interactionId, IAccessibilityInteractionConnectionCallback callback); void performAccessibilityAction(int accessibilityId, int action, int interactionId, IAccessibilityInteractionConnectionCallback callback); diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java index 44229a42be9a..4cae9d80d614 100644 --- a/core/java/android/webkit/HTML5VideoFullScreen.java +++ b/core/java/android/webkit/HTML5VideoFullScreen.java @@ -253,18 +253,19 @@ public class HTML5VideoFullScreen extends HTML5VideoView mLayout.setVisibility(View.VISIBLE); WebChromeClient client = webView.getWebChromeClient(); - client.onShowCustomView(mLayout, mCallback); - // Plugins like Flash will draw over the video so hide - // them while we're playing. - if (webView.getViewManager() != null) - webView.getViewManager().hideAll(); - - mProgressView = client.getVideoLoadingProgressView(); - if (mProgressView != null) { - mLayout.addView(mProgressView, layoutParams); - mProgressView.setVisibility(View.VISIBLE); + if (client != null) { + client.onShowCustomView(mLayout, mCallback); + // Plugins like Flash will draw over the video so hide + // them while we're playing. + if (webView.getViewManager() != null) + webView.getViewManager().hideAll(); + + mProgressView = client.getVideoLoadingProgressView(); + if (mProgressView != null) { + mLayout.addView(mProgressView, layoutParams); + mProgressView.setVisibility(View.VISIBLE); + } } - } /** diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 95e48808665d..10c7390d0da2 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -5605,8 +5605,8 @@ public class WebView extends AbsoluteLayout * Adjustable parameters. Angle is the radians on a unit circle, limited * to quadrant 1. Values range from 0f (horizontal) to PI/2 (vertical) */ - private static final float HSLOPE_TO_START_SNAP = .1f; - private static final float HSLOPE_TO_BREAK_SNAP = .2f; + private static final float HSLOPE_TO_START_SNAP = .25f; + private static final float HSLOPE_TO_BREAK_SNAP = .4f; private static final float VSLOPE_TO_START_SNAP = 1.25f; private static final float VSLOPE_TO_BREAK_SNAP = .95f; /* @@ -5617,6 +5617,11 @@ public class WebView extends AbsoluteLayout */ private static final float ANGLE_VERT = 2f; private static final float ANGLE_HORIZ = 0f; + /* + * The modified moving average weight. + * Formula: MAV[t]=MAV[t-1] + (P[t]-MAV[t-1])/n + */ + private static final float MMA_WEIGHT_N = 5; private boolean hitFocusedPlugin(int contentX, int contentY) { if (DebugFlags.WEB_VIEW) { @@ -6003,8 +6008,9 @@ public class WebView extends AbsoluteLayout if (deltaX == 0 && deltaY == 0) { keepScrollBarsVisible = done = true; } else { - mAverageAngle = (mAverageAngle + - calculateDragAngle(deltaX, deltaY)) / 2; + mAverageAngle += + (calculateDragAngle(deltaX, deltaY) - mAverageAngle) + / MMA_WEIGHT_N; if (mSnapScrollMode != SNAP_NONE) { if (mSnapScrollMode == SNAP_Y) { // radical change means getting out of snap mode diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 82dd5dbba4b2..3fe81498aad9 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -55,7 +55,6 @@ import android.view.ViewConfiguration; 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; diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java index c4d05e91662b..755d4e09c039 100644 --- a/core/java/android/widget/AdapterView.java +++ b/core/java/android/widget/AdapterView.java @@ -904,8 +904,10 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { // Add a record for ourselves as well. AccessibilityEvent record = AccessibilityEvent.obtain(); + record.setSource(this); // Set the class since it is not populated in #dispatchPopulateAccessibilityEvent record.setClassName(getClass().getName()); + child.onInitializeAccessibilityEvent(record); child.dispatchPopulateAccessibilityEvent(record); event.appendRecord(record); return true; diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index bda82a384254..092c2f75d66a 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -494,6 +494,14 @@ public class GridLayout extends ViewGroup { requestLayout(); } + private static int max2(int[] a, int valueIfEmpty) { + int result = valueIfEmpty; + for (int i = 0, N = a.length; i < N; i++) { + result = Math.max(result, a[i]); + } + return result; + } + private static int sum(float[] a) { int result = 0; for (int i = 0, length = a.length; i < length; i++) { @@ -1409,7 +1417,7 @@ public class GridLayout extends ViewGroup { // External entry points private int size(int[] locations) { - return locations[locations.length - 1] - locations[0]; + return max2(locations, 0) - locations[0]; } private int getMin() { @@ -1878,21 +1886,13 @@ public class GridLayout extends ViewGroup { return result; } - private static int max(int[] a, int valueIfEmpty) { - int result = valueIfEmpty; - for (int i = 0, length = a.length; i < length; i++) { - result = Math.max(result, a[i]); - } - return result; - } - /* Create a compact array of keys or values using the supplied index. */ private static <K> K[] compact(K[] a, int[] index) { int size = a.length; Class<?> componentType = a.getClass().getComponentType(); - K[] result = (K[]) Array.newInstance(componentType, max(index, -1) + 1); + K[] result = (K[]) Array.newInstance(componentType, max2(index, -1) + 1); // this overwrite duplicates, retaining the last equivalent entry for (int i = 0; i < size; i++) { diff --git a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java index 32606168c61e..99d534c7950d 100644 --- a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java +++ b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java @@ -29,6 +29,7 @@ import android.os.SystemClock; import android.provider.Settings; import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.LargeTest; +import android.util.Log; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -49,6 +50,9 @@ import java.util.Queue; */ public class InterrogationActivityTest extends ActivityInstrumentationTestCase2<InterrogationActivity> { + private static final boolean DEBUG = true; + + private static String LOG_TAG = "InterrogationActivityTest"; // Timeout before give up wait for the system to process an accessibility setting change. private static final int TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING = 2000; @@ -62,7 +66,7 @@ public class InterrogationActivityTest private static IAccessibilityServiceConnection sConnection; // The last received accessibility event - private static volatile AccessibilityEvent sLastAccessibilityEvent; + private static volatile AccessibilityEvent sLastFocusAccessibilityEvent; public InterrogationActivityTest() { super(InterrogationActivity.class); @@ -72,18 +76,19 @@ public class InterrogationActivityTest @LargeTest public void testFindAccessibilityNodeInfoByViewId() throws Exception { beforeClassIfNeeded(); + final long startTimeMillis = SystemClock.uptimeMillis(); try { // bring up the activity getActivity(); - AccessibilityNodeInfo button = getConnection().findAccessibilityNodeInfoByViewId( - R.id.button5); + AccessibilityNodeInfo button = + getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertNotNull(button); assertEquals(0, button.getChildCount()); // bounds Rect bounds = new Rect(); - button.getBounds(bounds); + button.getBoundsInParent(bounds); assertEquals(0, bounds.left); assertEquals(0, bounds.top); assertEquals(73, bounds.right); @@ -111,28 +116,40 @@ public class InterrogationActivityTest button.getActions()); } finally { afterClassIfNeeded(); + if (DEBUG) { + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + Log.i(LOG_TAG, "testFindAccessibilityNodeInfoByViewId: " + + elapsedTimeMillis + "ms"); + } } } @LargeTest public void testFindAccessibilityNodeInfoByViewText() throws Exception { beforeClassIfNeeded(); + final long startTimeMillis = SystemClock.uptimeMillis(); try { // bring up the activity getActivity(); // find a view by text List<AccessibilityNodeInfo> buttons = - getConnection().findAccessibilityNodeInfosByViewText("butto"); + getConnection().findAccessibilityNodeInfosByViewTextInActiveWindow("butto"); assertEquals(9, buttons.size()); } finally { afterClassIfNeeded(); + if (DEBUG) { + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + Log.i(LOG_TAG, "testFindAccessibilityNodeInfoByViewText: " + + elapsedTimeMillis + "ms"); + } } } @LargeTest public void testTraverseAllViews() throws Exception { beforeClassIfNeeded(); + final long startTimeMillis = SystemClock.uptimeMillis(); try { // bring up the activity getActivity(); @@ -153,8 +170,8 @@ public class InterrogationActivityTest classNameAndTextList.add("android.widget.ButtonButton8"); classNameAndTextList.add("android.widget.ButtonButton9"); - AccessibilityNodeInfo root = getConnection().findAccessibilityNodeInfoByViewId( - R.id.root); + AccessibilityNodeInfo root = + getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.root); assertNotNull("We must find the existing root.", root); Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>(); @@ -181,125 +198,152 @@ public class InterrogationActivityTest } } finally { afterClassIfNeeded(); + if (DEBUG) { + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + Log.i(LOG_TAG, "testTraverseAllViews: " + elapsedTimeMillis + "ms"); + } } } @LargeTest public void testPerformAccessibilityActionFocus() throws Exception { beforeClassIfNeeded(); + final long startTimeMillis = SystemClock.uptimeMillis(); try { // bring up the activity getActivity(); // find a view and make sure it is not focused - AccessibilityNodeInfo button = getConnection().findAccessibilityNodeInfoByViewId( - R.id.button5); + AccessibilityNodeInfo button = + getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertFalse(button.isFocused()); // focus the view assertTrue(button.performAction(ACTION_FOCUS)); // find the view again and make sure it is focused - button = getConnection().findAccessibilityNodeInfoByViewId(R.id.button5); + button = getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertTrue(button.isFocused()); } finally { afterClassIfNeeded(); + if (DEBUG) { + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + Log.i(LOG_TAG, "testPerformAccessibilityActionFocus: " + elapsedTimeMillis + "ms"); + } } } @LargeTest public void testPerformAccessibilityActionClearFocus() throws Exception { beforeClassIfNeeded(); + final long startTimeMillis = SystemClock.uptimeMillis(); try { // bring up the activity getActivity(); // find a view and make sure it is not focused - AccessibilityNodeInfo button = getConnection().findAccessibilityNodeInfoByViewId( - R.id.button5); + AccessibilityNodeInfo button = + getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertFalse(button.isFocused()); // focus the view assertTrue(button.performAction(ACTION_FOCUS)); // find the view again and make sure it is focused - button = getConnection().findAccessibilityNodeInfoByViewId(R.id.button5); + button = getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertTrue(button.isFocused()); // unfocus the view assertTrue(button.performAction(ACTION_CLEAR_FOCUS)); // find the view again and make sure it is not focused - button = getConnection().findAccessibilityNodeInfoByViewId(R.id.button5); + button = getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertFalse(button.isFocused()); } finally { afterClassIfNeeded(); + if (DEBUG) { + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + Log.i(LOG_TAG, "testPerformAccessibilityActionClearFocus: " + + elapsedTimeMillis + "ms"); + } } } @LargeTest public void testPerformAccessibilityActionSelect() throws Exception { beforeClassIfNeeded(); + final long startTimeMillis = SystemClock.uptimeMillis(); try { // bring up the activity getActivity(); // find a view and make sure it is not selected - AccessibilityNodeInfo button = getConnection().findAccessibilityNodeInfoByViewId( - R.id.button5); + AccessibilityNodeInfo button = + getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertFalse(button.isSelected()); // select the view assertTrue(button.performAction(ACTION_SELECT)); // find the view again and make sure it is selected - button = getConnection().findAccessibilityNodeInfoByViewId(R.id.button5); + button = getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertTrue(button.isSelected()); } finally { afterClassIfNeeded(); + if (DEBUG) { + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + Log.i(LOG_TAG, "testPerformAccessibilityActionSelect: " + elapsedTimeMillis + "ms"); + } } } @LargeTest public void testPerformAccessibilityActionClearSelection() throws Exception { beforeClassIfNeeded(); + final long startTimeMillis = SystemClock.uptimeMillis(); try { // bring up the activity getActivity(); // find a view and make sure it is not selected - AccessibilityNodeInfo button = getConnection().findAccessibilityNodeInfoByViewId( - R.id.button5); + AccessibilityNodeInfo button = + getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertFalse(button.isSelected()); // select the view assertTrue(button.performAction(ACTION_SELECT)); // find the view again and make sure it is selected - button = getConnection().findAccessibilityNodeInfoByViewId(R.id.button5); + button = getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertTrue(button.isSelected()); // unselect the view assertTrue(button.performAction(ACTION_CLEAR_SELECTION)); // find the view again and make sure it is not selected - button = getConnection().findAccessibilityNodeInfoByViewId(R.id.button5); + button = getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertFalse(button.isSelected()); } finally { afterClassIfNeeded(); + if (DEBUG) { + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + Log.i(LOG_TAG, "testPerformAccessibilityActionClearSelection: " + + elapsedTimeMillis + "ms"); + } } } @LargeTest public void testAccessibilityEventGetSource() throws Exception { beforeClassIfNeeded(); + final long startTimeMillis = SystemClock.uptimeMillis(); try { // bring up the activity getActivity(); // find a view and make sure it is not focused - AccessibilityNodeInfo button = getConnection().findAccessibilityNodeInfoByViewId( - R.id.button5); + AccessibilityNodeInfo button = + getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); assertFalse(button.isSelected()); // focus the view @@ -314,14 +358,14 @@ public class InterrogationActivityTest } // check that last event source - AccessibilityNodeInfo source = sLastAccessibilityEvent.getSource(); + AccessibilityNodeInfo source = sLastFocusAccessibilityEvent.getSource(); assertNotNull(source); // bounds Rect buttonBounds = new Rect(); - button.getBounds(buttonBounds); + button.getBoundsInParent(buttonBounds); Rect sourceBounds = new Rect(); - source.getBounds(sourceBounds); + source.getBoundsInParent(sourceBounds); assertEquals(buttonBounds.left, sourceBounds.left); assertEquals(buttonBounds.right, sourceBounds.right); @@ -346,6 +390,42 @@ public class InterrogationActivityTest assertSame(button.isChecked(), source.isChecked()); } finally { afterClassIfNeeded(); + if (DEBUG) { + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + Log.i(LOG_TAG, "testAccessibilityEventGetSource: " + elapsedTimeMillis + "ms"); + } + } + } + + @LargeTest + public void testObjectContract() throws Exception { + beforeClassIfNeeded(); + final long startTimeMillis = SystemClock.uptimeMillis(); + try { + // bring up the activity + getActivity(); + + // find a view and make sure it is not focused + AccessibilityNodeInfo button = + getConnection().findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); + AccessibilityNodeInfo parent = button.getParent(); + final int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + AccessibilityNodeInfo child = parent.getChild(i); + assertNotNull(child); + if (child.equals(button)) { + assertEquals("Equal objects must have same hasCode.", button.hashCode(), + child.hashCode()); + return; + } + } + fail("Parent's children do not have the info whose parent is the parent."); + } finally { + afterClassIfNeeded(); + if (DEBUG) { + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + Log.i(LOG_TAG, "testObjectContract: " + elapsedTimeMillis + "ms"); + } } } @@ -442,7 +522,9 @@ public class InterrogationActivityTest public void onInterrupt() {} public void onAccessibilityEvent(AccessibilityEvent event) { - sLastAccessibilityEvent = AccessibilityEvent.obtain(event); + if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) { + sLastFocusAccessibilityEvent = AccessibilityEvent.obtain(event); + } synchronized (sConnection) { sConnection.notifyAll(); } diff --git a/core/tests/coretests/src/android/content/res/ConfigurationTest.java b/core/tests/coretests/src/android/content/res/ConfigurationTest.java index f1f745ea9be9..54a5e4e48359 100644 --- a/core/tests/coretests/src/android/content/res/ConfigurationTest.java +++ b/core/tests/coretests/src/android/content/res/ConfigurationTest.java @@ -30,169 +30,169 @@ public class ConfigurationTest extends AndroidTestCase { args = {Locale.class} ) public void testGetLayoutDirectionFromLocale() { - assertEquals(Configuration.LAYOUT_DIRECTION_UNDEFINED, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_UNDEFINED_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(null)); - assertEquals(Configuration.LAYOUT_DIRECTION_LTR, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.ENGLISH)); - assertEquals(Configuration.LAYOUT_DIRECTION_LTR, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.CANADA)); - assertEquals(Configuration.LAYOUT_DIRECTION_LTR, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.CANADA_FRENCH)); - assertEquals(Configuration.LAYOUT_DIRECTION_LTR, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.FRANCE)); - assertEquals(Configuration.LAYOUT_DIRECTION_LTR, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.FRENCH)); - assertEquals(Configuration.LAYOUT_DIRECTION_LTR, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.GERMAN)); - assertEquals(Configuration.LAYOUT_DIRECTION_LTR, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.GERMANY)); - assertEquals(Configuration.LAYOUT_DIRECTION_LTR, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.ITALIAN)); - assertEquals(Configuration.LAYOUT_DIRECTION_LTR, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.ITALY)); - assertEquals(Configuration.LAYOUT_DIRECTION_LTR, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.UK)); - assertEquals(Configuration.LAYOUT_DIRECTION_LTR, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.US)); - assertEquals(Configuration.LAYOUT_DIRECTION_UNDEFINED, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_UNDEFINED_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.ROOT)); - assertEquals(Configuration.LAYOUT_DIRECTION_LTR, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.CHINA)); - assertEquals(Configuration.LAYOUT_DIRECTION_LTR, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.CHINESE)); - assertEquals(Configuration.LAYOUT_DIRECTION_LTR, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.JAPAN)); - assertEquals(Configuration.LAYOUT_DIRECTION_LTR, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.JAPANESE)); - assertEquals(Configuration.LAYOUT_DIRECTION_LTR, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.KOREA)); - assertEquals(Configuration.LAYOUT_DIRECTION_LTR, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.KOREAN)); - assertEquals(Configuration.LAYOUT_DIRECTION_LTR, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.PRC)); - assertEquals(Configuration.LAYOUT_DIRECTION_LTR, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.SIMPLIFIED_CHINESE)); - assertEquals(Configuration.LAYOUT_DIRECTION_LTR, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.TAIWAN)); - assertEquals(Configuration.LAYOUT_DIRECTION_LTR, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(Locale.TRADITIONAL_CHINESE)); Locale locale = new Locale("ar"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("ar", "AE"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("ar", "BH"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("ar", "DZ"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("ar", "EG"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("ar", "IQ"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("ar", "JO"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("ar", "KW"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("ar", "LB"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("ar", "LY"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("ar", "MA"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("ar", "OM"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("ar", "QA"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("ar", "SA"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("ar", "SD"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("ar", "SY"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("ar", "TN"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("ar", "YE"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("fa"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("fa", "AF"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("fa", "IR"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("iw"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("iw", "IL"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("he"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("he", "IL"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); // The following test will not pass until we are able to take care about the scrip subtag // thru having the "likelySubTags" file into ICU4C // locale = new Locale("pa_Arab"); -// assertEquals(Configuration.LAYOUT_DIRECTION_RTL, +// assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, // Configuration.getLayoutDirectionFromLocale(locale)); // locale = new Locale("pa_Arab", "PK"); -// assertEquals(Configuration.LAYOUT_DIRECTION_RTL, +// assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, // Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("ps"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); locale = new Locale("ps", "AF"); - assertEquals(Configuration.LAYOUT_DIRECTION_RTL, + assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, Configuration.getLayoutDirectionFromLocale(locale)); // The following test will not work as the localized display name would be "Urdu" with ICU 4.4 // We will need ICU 4.6 to get the correct localized display name // locale = new Locale("ur"); -// assertEquals(Configuration.LAYOUT_DIRECTION_RTL, +// assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, // Configuration.getLayoutDirectionFromLocale(locale)); // locale = new Locale("ur", "IN"); -// assertEquals(Configuration.LAYOUT_DIRECTION_RTL, +// assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, // Configuration.getLayoutDirectionFromLocale(locale)); // locale = new Locale("ur", "PK"); -// assertEquals(Configuration.LAYOUT_DIRECTION_RTL, +// assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, // Configuration.getLayoutDirectionFromLocale(locale)); // The following test will not pass until we are able to take care about the scrip subtag // thru having the "likelySubTags" file into ICU4C // locale = new Locale("uz_Arab"); -// assertEquals(Configuration.LAYOUT_DIRECTION_RTL, +// assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, // Configuration.getLayoutDirectionFromLocale(locale)); // locale = new Locale("uz_Arab", "AF"); -// assertEquals(Configuration.LAYOUT_DIRECTION_RTL, +// assertEquals(Configuration.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE, // Configuration.getLayoutDirectionFromLocale(locale)); } } diff --git a/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h index 2b314621abab..9294df674358 100644 --- a/include/gui/SurfaceTexture.h +++ b/include/gui/SurfaceTexture.h @@ -188,6 +188,11 @@ private: status_t setBufferCountServerLocked(int bufferCount); + // computeCurrentTransformMatrix computes the transform matrix for the + // current texture. It uses mCurrentTransform and the current GraphicBuffer + // to compute this matrix and stores it in mCurrentTransformMatrix. + void computeCurrentTransformMatrix(); + enum { INVALID_BUFFER_SLOT = -1 }; struct BufferSlot { @@ -288,9 +293,9 @@ private: // by calling setBufferCount or setBufferCountServer int mBufferCount; - // mRequestedBufferCount is the number of buffer slots requested by the - // client. The default is zero, which means the client doesn't care how - // many buffers there is. + // mClientBufferCount is the number of buffer slots requested by the client. + // The default is zero, which means the client doesn't care how many buffers + // there is. int mClientBufferCount; // mServerBufferCount buffer count requested by the server-side @@ -322,6 +327,11 @@ private: // gets set to mLastQueuedTransform each time updateTexImage is called. uint32_t mCurrentTransform; + // mCurrentTransformMatrix is the transform matrix for the current texture. + // It gets computed by computeTransformMatrix each time updateTexImage is + // called. + float mCurrentTransformMatrix[16]; + // mCurrentTimestamp is the timestamp for the current texture. It // gets set to mLastQueuedTimestamp each time updateTexImage is called. int64_t mCurrentTimestamp; @@ -362,6 +372,7 @@ private: // variables of SurfaceTexture objects. It must be locked whenever the // member variables are accessed. mutable Mutex mMutex; + }; // ---------------------------------------------------------------------------- diff --git a/include/utils/BlobCache.h b/include/utils/BlobCache.h new file mode 100644 index 000000000000..8f76d72c18a3 --- /dev/null +++ b/include/utils/BlobCache.h @@ -0,0 +1,181 @@ +/* + ** 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. + */ + +#ifndef ANDROID_BLOB_CACHE_H +#define ANDROID_BLOB_CACHE_H + +#include <stddef.h> + +#include <utils/RefBase.h> +#include <utils/SortedVector.h> +#include <utils/threads.h> + +namespace android { + +// A BlobCache is an in-memory cache for binary key/value pairs. All the public +// methods are thread-safe. +// +// The cache contents can be serialized to a file and reloaded in a subsequent +// execution of the program. This serialization is non-portable and should only +// be loaded by the device that generated it. +class BlobCache : public RefBase { +public: + + // Create an empty blob cache. The blob cache will cache key/value pairs + // with key and value sizes less than or equal to maxKeySize and + // maxValueSize, respectively. The total combined size of ALL cache entries + // (key sizes plus value sizes) will not exceed maxTotalSize. + BlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize); + + // set inserts a new binary value into the cache and associates it with the + // given binary key. If the key or value are too large for the cache then + // the cache remains unchanged. This includes the case where a different + // value was previously associated with the given key - the old value will + // remain in the cache. If the given key and value are small enough to be + // put in the cache (based on the maxKeySize, maxValueSize, and maxTotalSize + // values specified to the BlobCache constructor), then the key/value pair + // will be in the cache after set returns. Note, however, that a subsequent + // call to set may evict old key/value pairs from the cache. + // + // Preconditions: + // key != NULL + // 0 < keySize + // value != NULL + // 0 < valueSize + void set(const void* key, size_t keySize, const void* value, + size_t valueSize); + + // The get function retrieves from the cache the binary value associated + // with a given binary key. If the key is present in the cache then the + // length of the binary value associated with that key is returned. If the + // value argument is non-NULL and the size of the cached value is less than + // valueSize bytes then the cached value is copied into the buffer pointed + // to by the value argument. If the key is not present in the cache then 0 + // is returned and the buffer pointed to by the value argument is not + // modified. + // + // Note that when calling get multiple times with the same key, the later + // calls may fail, returning 0, even if earlier calls succeeded. The return + // value must be checked for each call. + // + // Preconditions: + // key != NULL + // 0 < keySize + // 0 <= valueSize + size_t get(const void* key, size_t keySize, void* value, size_t valueSize); + +private: + // Copying is disallowed. + BlobCache(const BlobCache&); + void operator=(const BlobCache&); + + // clean evicts a randomly chosen set of entries from the cache such that + // the total size of all remaining entries is less than mMaxTotalSize/2. + void clean(); + + // isCleanable returns true if the cache is full enough for the clean method + // to have some effect, and false otherwise. + bool isCleanable() const; + + // A Blob is an immutable sized unstructured data blob. + class Blob : public RefBase { + public: + Blob(const void* data, size_t size, bool copyData); + ~Blob(); + + bool operator<(const Blob& rhs) const; + + const void* getData() const; + size_t getSize() const; + + private: + // Copying is not allowed. + Blob(const Blob&); + void operator=(const Blob&); + + // mData points to the buffer containing the blob data. + const void* mData; + + // mSize is the size of the blob data in bytes. + size_t mSize; + + // mOwnsData indicates whether or not this Blob object should free the + // memory pointed to by mData when the Blob gets destructed. + bool mOwnsData; + }; + + // A CacheEntry is a single key/value pair in the cache. + class CacheEntry { + public: + CacheEntry(); + CacheEntry(const sp<Blob>& key, const sp<Blob>& value); + CacheEntry(const CacheEntry& ce); + + bool operator<(const CacheEntry& rhs) const; + const CacheEntry& operator=(const CacheEntry&); + + sp<Blob> getKey() const; + sp<Blob> getValue() const; + + void setValue(const sp<Blob>& value); + + private: + + // mKey is the key that identifies the cache entry. + sp<Blob> mKey; + + // mValue is the cached data associated with the key. + sp<Blob> mValue; + }; + + // mMaxKeySize is the maximum key size that will be cached. Calls to + // BlobCache::set with a keySize parameter larger than mMaxKeySize will + // simply not add the key/value pair to the cache. + const size_t mMaxKeySize; + + // mMaxValueSize is the maximum value size that will be cached. Calls to + // BlobCache::set with a valueSize parameter larger than mMaxValueSize will + // simply not add the key/value pair to the cache. + const size_t mMaxValueSize; + + // mMaxTotalSize is the maximum size that all cache entries can occupy. This + // includes space for both keys and values. When a call to BlobCache::set + // would otherwise cause this limit to be exceeded, either the key/value + // pair passed to BlobCache::set will not be cached or other cache entries + // will be evicted from the cache to make room for the new entry. + const size_t mMaxTotalSize; + + // mTotalSize is the total combined size of all keys and values currently in + // the cache. + size_t mTotalSize; + + // mRandState is the pseudo-random number generator state. It is passed to + // nrand48 to generate random numbers when needed. It must be protected by + // mMutex. + unsigned short mRandState[3]; + + // mCacheEntries stores all the cache entries that are resident in memory. + // Cache entries are added to it by the 'set' method. + SortedVector<CacheEntry> mCacheEntries; + + // mMutex is used to synchronize access to all member variables. It must be + // locked any time the member variables are written or read. + Mutex mMutex; +}; + +} + +#endif // ANDROID_BLOB_CACHE_H diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp index ee97dcfa9c63..2cda4c82dbcf 100644 --- a/libs/gui/SurfaceTexture.cpp +++ b/libs/gui/SurfaceTexture.cpp @@ -96,6 +96,7 @@ SurfaceTexture::SurfaceTexture(GLuint tex) : sp<ISurfaceComposer> composer(ComposerService::getComposerService()); mGraphicBufferAlloc = composer->createGraphicBufferAlloc(); mNextCrop.makeInvalid(); + memcpy(mCurrentTransformMatrix, mtxIdentity, sizeof(mCurrentTransformMatrix)); } SurfaceTexture::~SurfaceTexture() { @@ -547,6 +548,7 @@ status_t SurfaceTexture::updateTexImage() { mCurrentCrop = mSlots[buf].mCrop; mCurrentTransform = mSlots[buf].mTransform; mCurrentTimestamp = mSlots[buf].mTimestamp; + computeCurrentTransformMatrix(); mDequeueCondition.signal(); } else { // We always bind the texture even if we don't update its contents. @@ -596,8 +598,12 @@ GLenum SurfaceTexture::getCurrentTextureTarget() const { } void SurfaceTexture::getTransformMatrix(float mtx[16]) { - LOGV("SurfaceTexture::getTransformMatrix"); Mutex::Autolock lock(mMutex); + memcpy(mtx, mCurrentTransformMatrix, sizeof(mCurrentTransformMatrix)); +} + +void SurfaceTexture::computeCurrentTransformMatrix() { + LOGV("SurfaceTexture::computeCurrentTransformMatrix"); float xform[16]; for (int i = 0; i < 16; i++) { @@ -684,7 +690,7 @@ void SurfaceTexture::getTransformMatrix(float mtx[16]) { // coordinate of 0, so SurfaceTexture must behave the same way. We don't // want to expose this to applications, however, so we must add an // additional vertical flip to the transform after all the other transforms. - mtxMul(mtx, mtxFlipV, mtxBeforeFlipV); + mtxMul(mCurrentTransformMatrix, mtxFlipV, mtxBeforeFlipV); } nsecs_t SurfaceTexture::getTimestamp() { diff --git a/libs/gui/tests/SurfaceTextureClient_test.cpp b/libs/gui/tests/SurfaceTextureClient_test.cpp index 2f704c89b822..da04b4a3e6eb 100644 --- a/libs/gui/tests/SurfaceTextureClient_test.cpp +++ b/libs/gui/tests/SurfaceTextureClient_test.cpp @@ -514,4 +514,112 @@ TEST_F(SurfaceTextureClientTest, DISABLED_SurfaceTextureSyncModeWaitRetire) { thread->requestExitAndWait(); } +TEST_F(SurfaceTextureClientTest, GetTransformMatrixReturnsVerticalFlip) { + sp<ANativeWindow> anw(mSTC); + sp<SurfaceTexture> st(mST); + android_native_buffer_t* buf[3]; + float mtx[16] = {}; + ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 4)); + ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[0])); + ASSERT_EQ(OK, anw->queueBuffer(anw.get(), buf[0])); + ASSERT_EQ(OK, st->updateTexImage()); + st->getTransformMatrix(mtx); + + EXPECT_EQ(1.f, mtx[0]); + EXPECT_EQ(0.f, mtx[1]); + EXPECT_EQ(0.f, mtx[2]); + EXPECT_EQ(0.f, mtx[3]); + + EXPECT_EQ(0.f, mtx[4]); + EXPECT_EQ(-1.f, mtx[5]); + EXPECT_EQ(0.f, mtx[6]); + EXPECT_EQ(0.f, mtx[7]); + + EXPECT_EQ(0.f, mtx[8]); + EXPECT_EQ(0.f, mtx[9]); + EXPECT_EQ(1.f, mtx[10]); + EXPECT_EQ(0.f, mtx[11]); + + EXPECT_EQ(0.f, mtx[12]); + EXPECT_EQ(1.f, mtx[13]); + EXPECT_EQ(0.f, mtx[14]); + EXPECT_EQ(1.f, mtx[15]); +} + +TEST_F(SurfaceTextureClientTest, GetTransformMatrixSucceedsAfterFreeingBuffers) { + sp<ANativeWindow> anw(mSTC); + sp<SurfaceTexture> st(mST); + android_native_buffer_t* buf[3]; + float mtx[16] = {}; + ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 4)); + ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[0])); + ASSERT_EQ(OK, anw->queueBuffer(anw.get(), buf[0])); + ASSERT_EQ(OK, st->updateTexImage()); + ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 6)); // frees buffers + st->getTransformMatrix(mtx); + + EXPECT_EQ(1.f, mtx[0]); + EXPECT_EQ(0.f, mtx[1]); + EXPECT_EQ(0.f, mtx[2]); + EXPECT_EQ(0.f, mtx[3]); + + EXPECT_EQ(0.f, mtx[4]); + EXPECT_EQ(-1.f, mtx[5]); + EXPECT_EQ(0.f, mtx[6]); + EXPECT_EQ(0.f, mtx[7]); + + EXPECT_EQ(0.f, mtx[8]); + EXPECT_EQ(0.f, mtx[9]); + EXPECT_EQ(1.f, mtx[10]); + EXPECT_EQ(0.f, mtx[11]); + + EXPECT_EQ(0.f, mtx[12]); + EXPECT_EQ(1.f, mtx[13]); + EXPECT_EQ(0.f, mtx[14]); + EXPECT_EQ(1.f, mtx[15]); +} + +TEST_F(SurfaceTextureClientTest, GetTransformMatrixSucceedsAfterFreeingBuffersWithCrop) { + sp<ANativeWindow> anw(mSTC); + sp<SurfaceTexture> st(mST); + android_native_buffer_t* buf[3]; + float mtx[16] = {}; + android_native_rect_t crop; + crop.left = 0; + crop.top = 0; + crop.right = 5; + crop.bottom = 5; + + ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 4)); + ASSERT_EQ(OK, native_window_set_buffers_geometry(anw.get(), 8, 8, 0)); + ASSERT_EQ(OK, anw->dequeueBuffer(anw.get(), &buf[0])); + ASSERT_EQ(OK, native_window_set_crop(anw.get(), &crop)); + ASSERT_EQ(OK, anw->queueBuffer(anw.get(), buf[0])); + ASSERT_EQ(OK, st->updateTexImage()); + ASSERT_EQ(OK, native_window_set_buffer_count(anw.get(), 6)); // frees buffers + st->getTransformMatrix(mtx); + + // This accounts for the 1 texel shrink for each edge that's included in the + // transform matrix to avoid texturing outside the crop region. + EXPECT_EQ(.5f, mtx[0]); + EXPECT_EQ(0.f, mtx[1]); + EXPECT_EQ(0.f, mtx[2]); + EXPECT_EQ(0.f, mtx[3]); + + EXPECT_EQ(0.f, mtx[4]); + EXPECT_EQ(-.5f, mtx[5]); + EXPECT_EQ(0.f, mtx[6]); + EXPECT_EQ(0.f, mtx[7]); + + EXPECT_EQ(0.f, mtx[8]); + EXPECT_EQ(0.f, mtx[9]); + EXPECT_EQ(1.f, mtx[10]); + EXPECT_EQ(0.f, mtx[11]); + + EXPECT_EQ(0.f, mtx[12]); + EXPECT_EQ(.5f, mtx[13]); + EXPECT_EQ(0.f, mtx[14]); + EXPECT_EQ(1.f, mtx[15]); } + +} // namespace android diff --git a/libs/gui/tests/SurfaceTexture_test.cpp b/libs/gui/tests/SurfaceTexture_test.cpp index 8747ba5c7c53..16280d2e01c1 100644 --- a/libs/gui/tests/SurfaceTexture_test.cpp +++ b/libs/gui/tests/SurfaceTexture_test.cpp @@ -14,11 +14,14 @@ * limitations under the License. */ +//#define LOG_NDEBUG 0 + #include <gtest/gtest.h> #include <gui/SurfaceTexture.h> #include <gui/SurfaceTextureClient.h> #include <ui/GraphicBuffer.h> #include <utils/String8.h> +#include <utils/threads.h> #include <surfaceflinger/ISurfaceComposer.h> #include <surfaceflinger/Surface.h> @@ -618,4 +621,269 @@ TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferWithCrop) { } } +/* + * This test is for testing GL -> GL texture streaming via SurfaceTexture. It + * contains functionality to create a producer thread that will perform GL + * rendering to an ANativeWindow that feeds frames to a SurfaceTexture. + * Additionally it supports interlocking the producer and consumer threads so + * that a specific sequence of calls can be deterministically created by the + * test. + * + * The intended usage is as follows: + * + * TEST_F(...) { + * class PT : public ProducerThread { + * virtual void render() { + * ... + * swapBuffers(); + * } + * }; + * + * runProducerThread(new PT()); + * + * // The order of these calls will vary from test to test and may include + * // multiple frames and additional operations (e.g. GL rendering from the + * // texture). + * fc->waitForFrame(); + * mST->updateTexImage(); + * fc->finishFrame(); + * } + * + */ +class SurfaceTextureGLToGLTest : public SurfaceTextureGLTest { +protected: + + // ProducerThread is an abstract base class to simplify the creation of + // OpenGL ES frame producer threads. + class ProducerThread : public Thread { + public: + virtual ~ProducerThread() { + } + + void setEglObjects(EGLDisplay producerEglDisplay, + EGLSurface producerEglSurface, + EGLContext producerEglContext) { + mProducerEglDisplay = producerEglDisplay; + mProducerEglSurface = producerEglSurface; + mProducerEglContext = producerEglContext; + } + + virtual bool threadLoop() { + eglMakeCurrent(mProducerEglDisplay, mProducerEglSurface, + mProducerEglSurface, mProducerEglContext); + render(); + eglMakeCurrent(mProducerEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + return false; + } + + protected: + virtual void render() = 0; + + void swapBuffers() { + eglSwapBuffers(mProducerEglDisplay, mProducerEglSurface); + } + + EGLDisplay mProducerEglDisplay; + EGLSurface mProducerEglSurface; + EGLContext mProducerEglContext; + }; + + // FrameCondition is a utility class for interlocking between the producer + // and consumer threads. The FrameCondition object should be created and + // destroyed in the consumer thread only. The consumer thread should set + // the FrameCondition as the FrameAvailableListener of the SurfaceTexture, + // and should call both waitForFrame and finishFrame once for each expected + // frame. + // + // This interlocking relies on the fact that onFrameAvailable gets called + // synchronously from SurfaceTexture::queueBuffer. + class FrameCondition : public SurfaceTexture::FrameAvailableListener { + public: + // waitForFrame waits for the next frame to arrive. This should be + // called from the consumer thread once for every frame expected by the + // test. + void waitForFrame() { + LOGV("+waitForFrame"); + Mutex::Autolock lock(mMutex); + status_t result = mFrameAvailableCondition.wait(mMutex); + LOGV("-waitForFrame"); + } + + // Allow the producer to return from its swapBuffers call and continue + // on to produce the next frame. This should be called by the consumer + // thread once for every frame expected by the test. + void finishFrame() { + LOGV("+finishFrame"); + Mutex::Autolock lock(mMutex); + mFrameFinishCondition.signal(); + LOGV("-finishFrame"); + } + + // This should be called by SurfaceTexture on the producer thread. + virtual void onFrameAvailable() { + LOGV("+onFrameAvailable"); + Mutex::Autolock lock(mMutex); + mFrameAvailableCondition.signal(); + mFrameFinishCondition.wait(mMutex); + LOGV("-onFrameAvailable"); + } + + protected: + Mutex mMutex; + Condition mFrameAvailableCondition; + Condition mFrameFinishCondition; + }; + + SurfaceTextureGLToGLTest(): + mProducerEglSurface(EGL_NO_SURFACE), + mProducerEglContext(EGL_NO_CONTEXT) { + } + + virtual void SetUp() { + SurfaceTextureGLTest::SetUp(); + + EGLConfig myConfig = {0}; + EGLint numConfigs = 0; + EXPECT_TRUE(eglChooseConfig(mEglDisplay, getConfigAttribs(), &myConfig, + 1, &numConfigs)); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + mProducerEglSurface = eglCreateWindowSurface(mEglDisplay, myConfig, + mANW.get(), NULL); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_SURFACE, mProducerEglSurface); + + mProducerEglContext = eglCreateContext(mEglDisplay, myConfig, + EGL_NO_CONTEXT, getContextAttribs()); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_CONTEXT, mProducerEglContext); + + mFC = new FrameCondition(); + mST->setFrameAvailableListener(mFC); + } + + virtual void TearDown() { + if (mProducerThread != NULL) { + mProducerThread->requestExitAndWait(); + } + if (mProducerEglContext != EGL_NO_CONTEXT) { + eglDestroyContext(mEglDisplay, mProducerEglContext); + } + if (mProducerEglSurface != EGL_NO_SURFACE) { + eglDestroySurface(mEglDisplay, mProducerEglSurface); + } + mProducerThread.clear(); + mFC.clear(); + } + + void runProducerThread(const sp<ProducerThread> producerThread) { + ASSERT_TRUE(mProducerThread == NULL); + mProducerThread = producerThread; + producerThread->setEglObjects(mEglDisplay, mProducerEglSurface, + mProducerEglContext); + producerThread->run(); + } + + EGLSurface mProducerEglSurface; + EGLContext mProducerEglContext; + sp<ProducerThread> mProducerThread; + sp<FrameCondition> mFC; +}; + +// XXX: This test is disabled because it causes hangs on some devices. +TEST_F(SurfaceTextureGLToGLTest, DISABLED_UpdateTexImageBeforeFrameFinishedWorks) { + class PT : public ProducerThread { + virtual void render() { + glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + swapBuffers(); + } + }; + + runProducerThread(new PT()); + + mFC->waitForFrame(); + mST->updateTexImage(); + mFC->finishFrame(); + + // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported! } + +TEST_F(SurfaceTextureGLToGLTest, UpdateTexImageAfterFrameFinishedWorks) { + class PT : public ProducerThread { + virtual void render() { + glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + swapBuffers(); + } + }; + + runProducerThread(new PT()); + + mFC->waitForFrame(); + mFC->finishFrame(); + mST->updateTexImage(); + + // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported! +} + +// XXX: This test is disabled because it causes hangs on some devices. +TEST_F(SurfaceTextureGLToGLTest, DISABLED_RepeatedUpdateTexImageBeforeFrameFinishedWorks) { + enum { NUM_ITERATIONS = 1024 }; + + class PT : public ProducerThread { + virtual void render() { + for (int i = 0; i < NUM_ITERATIONS; i++) { + glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + LOGV("+swapBuffers"); + swapBuffers(); + LOGV("-swapBuffers"); + } + } + }; + + runProducerThread(new PT()); + + for (int i = 0; i < NUM_ITERATIONS; i++) { + mFC->waitForFrame(); + LOGV("+updateTexImage"); + mST->updateTexImage(); + LOGV("-updateTexImage"); + mFC->finishFrame(); + + // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported! + } +} + +// XXX: This test is disabled because it causes hangs on some devices. +TEST_F(SurfaceTextureGLToGLTest, DISABLED_RepeatedUpdateTexImageAfterFrameFinishedWorks) { + enum { NUM_ITERATIONS = 1024 }; + + class PT : public ProducerThread { + virtual void render() { + for (int i = 0; i < NUM_ITERATIONS; i++) { + glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + LOGV("+swapBuffers"); + swapBuffers(); + LOGV("-swapBuffers"); + } + } + }; + + runProducerThread(new PT()); + + for (int i = 0; i < NUM_ITERATIONS; i++) { + mFC->waitForFrame(); + mFC->finishFrame(); + LOGV("+updateTexImage"); + mST->updateTexImage(); + LOGV("-updateTexImage"); + + // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported! + } +} + +} // namespace android diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk index e8d40ba08352..093189c5c05a 100644 --- a/libs/utils/Android.mk +++ b/libs/utils/Android.mk @@ -21,6 +21,7 @@ commonSources:= \ Asset.cpp \ AssetDir.cpp \ AssetManager.cpp \ + BlobCache.cpp \ BufferedTextOutput.cpp \ CallStack.cpp \ Debug.cpp \ diff --git a/libs/utils/BlobCache.cpp b/libs/utils/BlobCache.cpp new file mode 100644 index 000000000000..1298fa733cb0 --- /dev/null +++ b/libs/utils/BlobCache.cpp @@ -0,0 +1,232 @@ +/* + ** 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. + */ + +#define LOG_TAG "BlobCache" +//#define LOG_NDEBUG 0 + +#include <stdlib.h> +#include <string.h> + +#include <utils/BlobCache.h> +#include <utils/Log.h> + +namespace android { + +BlobCache::BlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize): + mMaxKeySize(maxKeySize), + mMaxValueSize(maxValueSize), + mMaxTotalSize(maxTotalSize), + mTotalSize(0) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + mRandState[0] = (now >> 0) & 0xFFFF; + mRandState[1] = (now >> 16) & 0xFFFF; + mRandState[2] = (now >> 32) & 0xFFFF; + LOGV("initializing random seed using %lld", now); +} + +void BlobCache::set(const void* key, size_t keySize, const void* value, + size_t valueSize) { + if (mMaxKeySize < keySize) { + LOGV("set: not caching because the key is too large: %d (limit: %d)", + keySize, mMaxKeySize); + return; + } + if (mMaxValueSize < valueSize) { + LOGV("set: not caching because the value is too large: %d (limit: %d)", + valueSize, mMaxValueSize); + return; + } + if (mMaxTotalSize < keySize + valueSize) { + LOGV("set: not caching because the combined key/value size is too " + "large: %d (limit: %d)", keySize + valueSize, mMaxTotalSize); + return; + } + if (keySize == 0) { + LOGW("set: not caching because keySize is 0"); + return; + } + if (valueSize <= 0) { + LOGW("set: not caching because valueSize is 0"); + return; + } + + Mutex::Autolock lock(mMutex); + sp<Blob> dummyKey(new Blob(key, keySize, false)); + CacheEntry dummyEntry(dummyKey, NULL); + + while (true) { + + ssize_t index = mCacheEntries.indexOf(dummyEntry); + if (index < 0) { + // Create a new cache entry. + sp<Blob> keyBlob(new Blob(key, keySize, true)); + sp<Blob> valueBlob(new Blob(value, valueSize, true)); + size_t newTotalSize = mTotalSize + keySize + valueSize; + if (mMaxTotalSize < newTotalSize) { + if (isCleanable()) { + // Clean the cache and try again. + clean(); + continue; + } else { + LOGV("set: not caching new key/value pair because the " + "total cache size limit would be exceeded: %d " + "(limit: %d)", + keySize + valueSize, mMaxTotalSize); + break; + } + } + mCacheEntries.add(CacheEntry(keyBlob, valueBlob)); + mTotalSize = newTotalSize; + LOGV("set: created new cache entry with %d byte key and %d byte value", + keySize, valueSize); + } else { + // Update the existing cache entry. + sp<Blob> valueBlob(new Blob(value, valueSize, true)); + sp<Blob> oldValueBlob(mCacheEntries[index].getValue()); + size_t newTotalSize = mTotalSize + valueSize - oldValueBlob->getSize(); + if (mMaxTotalSize < newTotalSize) { + if (isCleanable()) { + // Clean the cache and try again. + clean(); + continue; + } else { + LOGV("set: not caching new value because the total cache " + "size limit would be exceeded: %d (limit: %d)", + keySize + valueSize, mMaxTotalSize); + break; + } + } + mCacheEntries.editItemAt(index).setValue(valueBlob); + mTotalSize = newTotalSize; + LOGV("set: updated existing cache entry with %d byte key and %d byte " + "value", keySize, valueSize); + } + break; + } +} + +size_t BlobCache::get(const void* key, size_t keySize, void* value, + size_t valueSize) { + if (mMaxKeySize < keySize) { + LOGV("get: not searching because the key is too large: %d (limit %d)", + keySize, mMaxKeySize); + return 0; + } + Mutex::Autolock lock(mMutex); + sp<Blob> dummyKey(new Blob(key, keySize, false)); + CacheEntry dummyEntry(dummyKey, NULL); + ssize_t index = mCacheEntries.indexOf(dummyEntry); + if (index < 0) { + LOGV("get: no cache entry found for key of size %d", keySize); + return 0; + } + + // The key was found. Return the value if the caller's buffer is large + // enough. + sp<Blob> valueBlob(mCacheEntries[index].getValue()); + size_t valueBlobSize = valueBlob->getSize(); + if (valueBlobSize <= valueSize) { + LOGV("get: copying %d bytes to caller's buffer", valueBlobSize); + memcpy(value, valueBlob->getData(), valueBlobSize); + } else { + LOGV("get: caller's buffer is too small for value: %d (needs %d)", + valueSize, valueBlobSize); + } + return valueBlobSize; +} + +void BlobCache::clean() { + // Remove a random cache entry until the total cache size gets below half + // the maximum total cache size. + while (mTotalSize > mMaxTotalSize / 2) { + size_t i = size_t(nrand48(mRandState) % (mCacheEntries.size())); + const CacheEntry& entry(mCacheEntries[i]); + mTotalSize -= entry.getKey()->getSize() + entry.getValue()->getSize(); + mCacheEntries.removeAt(i); + } +} + +bool BlobCache::isCleanable() const { + return mTotalSize > mMaxTotalSize / 2; +} + +BlobCache::Blob::Blob(const void* data, size_t size, bool copyData): + mData(copyData ? malloc(size) : data), + mSize(size), + mOwnsData(copyData) { + if (copyData) { + memcpy(const_cast<void*>(mData), data, size); + } +} + +BlobCache::Blob::~Blob() { + if (mOwnsData) { + free(const_cast<void*>(mData)); + } +} + +bool BlobCache::Blob::operator<(const Blob& rhs) const { + if (mSize == rhs.mSize) { + return memcmp(mData, rhs.mData, mSize) < 0; + } else { + return mSize < rhs.mSize; + } +} + +const void* BlobCache::Blob::getData() const { + return mData; +} + +size_t BlobCache::Blob::getSize() const { + return mSize; +} + +BlobCache::CacheEntry::CacheEntry() { +} + +BlobCache::CacheEntry::CacheEntry(const sp<Blob>& key, const sp<Blob>& value): + mKey(key), + mValue(value) { +} + +BlobCache::CacheEntry::CacheEntry(const CacheEntry& ce): + mKey(ce.mKey), + mValue(ce.mValue) { +} + +bool BlobCache::CacheEntry::operator<(const CacheEntry& rhs) const { + return *mKey < *rhs.mKey; +} + +const BlobCache::CacheEntry& BlobCache::CacheEntry::operator=(const CacheEntry& rhs) { + mKey = rhs.mKey; + mValue = rhs.mValue; + return *this; +} + +sp<BlobCache::Blob> BlobCache::CacheEntry::getKey() const { + return mKey; +} + +sp<BlobCache::Blob> BlobCache::CacheEntry::getValue() const { + return mValue; +} + +void BlobCache::CacheEntry::setValue(const sp<Blob>& value) { + mValue = value; +} + +} // namespace android diff --git a/libs/utils/tests/Android.mk b/libs/utils/tests/Android.mk index 72d48769a589..87ad98eaad77 100644 --- a/libs/utils/tests/Android.mk +++ b/libs/utils/tests/Android.mk @@ -6,6 +6,7 @@ ifneq ($(TARGET_SIMULATOR),true) # Build the unit tests. test_src_files := \ + BlobCache_test.cpp \ ObbFile_test.cpp \ Looper_test.cpp \ String8_test.cpp \ diff --git a/libs/utils/tests/BlobCache_test.cpp b/libs/utils/tests/BlobCache_test.cpp new file mode 100644 index 000000000000..653ea5e91cde --- /dev/null +++ b/libs/utils/tests/BlobCache_test.cpp @@ -0,0 +1,257 @@ +/* + ** 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. + */ + +#include <gtest/gtest.h> + +#include <utils/BlobCache.h> + +namespace android { + +class BlobCacheTest : public ::testing::Test { +protected: + enum { + MAX_KEY_SIZE = 6, + MAX_VALUE_SIZE = 8, + MAX_TOTAL_SIZE = 13, + }; + + virtual void SetUp() { + mBC = new BlobCache(MAX_KEY_SIZE, MAX_VALUE_SIZE, MAX_TOTAL_SIZE); + } + + virtual void TearDown() { + mBC.clear(); + } + + sp<BlobCache> mBC; +}; + +TEST_F(BlobCacheTest, CacheSingleValueSucceeds) { + char buf[4] = { 0xee, 0xee, 0xee, 0xee }; + mBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mBC->get("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); +} + +TEST_F(BlobCacheTest, CacheTwoValuesSucceeds) { + char buf[2] = { 0xee, 0xee }; + mBC->set("ab", 2, "cd", 2); + mBC->set("ef", 2, "gh", 2); + ASSERT_EQ(size_t(2), mBC->get("ab", 2, buf, 2)); + ASSERT_EQ('c', buf[0]); + ASSERT_EQ('d', buf[1]); + ASSERT_EQ(size_t(2), mBC->get("ef", 2, buf, 2)); + ASSERT_EQ('g', buf[0]); + ASSERT_EQ('h', buf[1]); +} + +TEST_F(BlobCacheTest, GetOnlyWritesInsideBounds) { + char buf[6] = { 0xee, 0xee, 0xee, 0xee, 0xee, 0xee }; + mBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mBC->get("abcd", 4, buf+1, 4)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ('e', buf[1]); + ASSERT_EQ('f', buf[2]); + ASSERT_EQ('g', buf[3]); + ASSERT_EQ('h', buf[4]); + ASSERT_EQ(0xee, buf[5]); +} + +TEST_F(BlobCacheTest, GetOnlyWritesIfBufferIsLargeEnough) { + char buf[3] = { 0xee, 0xee, 0xee }; + mBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mBC->get("abcd", 4, buf, 3)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ(0xee, buf[1]); + ASSERT_EQ(0xee, buf[2]); +} + +TEST_F(BlobCacheTest, GetDoesntAccessNullBuffer) { + mBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mBC->get("abcd", 4, NULL, 0)); +} + +TEST_F(BlobCacheTest, MultipleSetsCacheLatestValue) { + char buf[4] = { 0xee, 0xee, 0xee, 0xee }; + mBC->set("abcd", 4, "efgh", 4); + mBC->set("abcd", 4, "ijkl", 4); + ASSERT_EQ(size_t(4), mBC->get("abcd", 4, buf, 4)); + ASSERT_EQ('i', buf[0]); + ASSERT_EQ('j', buf[1]); + ASSERT_EQ('k', buf[2]); + ASSERT_EQ('l', buf[3]); +} + +TEST_F(BlobCacheTest, SecondSetKeepsFirstValueIfTooLarge) { + char buf[MAX_VALUE_SIZE+1] = { 0xee, 0xee, 0xee, 0xee }; + mBC->set("abcd", 4, "efgh", 4); + mBC->set("abcd", 4, buf, MAX_VALUE_SIZE+1); + ASSERT_EQ(size_t(4), mBC->get("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); +} + +TEST_F(BlobCacheTest, DoesntCacheIfKeyIsTooBig) { + char key[MAX_KEY_SIZE+1]; + char buf[4] = { 0xee, 0xee, 0xee, 0xee }; + for (int i = 0; i < MAX_KEY_SIZE+1; i++) { + key[i] = 'a'; + } + mBC->set(key, MAX_KEY_SIZE+1, "bbbb", 4); + ASSERT_EQ(size_t(0), mBC->get(key, MAX_KEY_SIZE+1, buf, 4)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ(0xee, buf[1]); + ASSERT_EQ(0xee, buf[2]); + ASSERT_EQ(0xee, buf[3]); +} + +TEST_F(BlobCacheTest, DoesntCacheIfValueIsTooBig) { + char buf[MAX_VALUE_SIZE+1]; + for (int i = 0; i < MAX_VALUE_SIZE+1; i++) { + buf[i] = 'b'; + } + mBC->set("abcd", 4, buf, MAX_VALUE_SIZE+1); + for (int i = 0; i < MAX_VALUE_SIZE+1; i++) { + buf[i] = 0xee; + } + ASSERT_EQ(size_t(0), mBC->get("abcd", 4, buf, MAX_VALUE_SIZE+1)); + for (int i = 0; i < MAX_VALUE_SIZE+1; i++) { + SCOPED_TRACE(i); + ASSERT_EQ(0xee, buf[i]); + } +} + +TEST_F(BlobCacheTest, DoesntCacheIfKeyValuePairIsTooBig) { + // Check a testing assumptions + ASSERT_TRUE(MAX_TOTAL_SIZE < MAX_KEY_SIZE + MAX_VALUE_SIZE); + ASSERT_TRUE(MAX_KEY_SIZE < MAX_TOTAL_SIZE); + + enum { bufSize = MAX_TOTAL_SIZE - MAX_KEY_SIZE + 1 }; + + char key[MAX_KEY_SIZE]; + char buf[bufSize]; + for (int i = 0; i < MAX_KEY_SIZE; i++) { + key[i] = 'a'; + } + for (int i = 0; i < bufSize; i++) { + buf[i] = 'b'; + } + + mBC->set(key, MAX_KEY_SIZE, buf, MAX_VALUE_SIZE); + ASSERT_EQ(size_t(0), mBC->get(key, MAX_KEY_SIZE, NULL, 0)); +} + +TEST_F(BlobCacheTest, CacheMaxKeySizeSucceeds) { + char key[MAX_KEY_SIZE]; + char buf[4] = { 0xee, 0xee, 0xee, 0xee }; + for (int i = 0; i < MAX_KEY_SIZE; i++) { + key[i] = 'a'; + } + mBC->set(key, MAX_KEY_SIZE, "wxyz", 4); + ASSERT_EQ(size_t(4), mBC->get(key, MAX_KEY_SIZE, buf, 4)); + ASSERT_EQ('w', buf[0]); + ASSERT_EQ('x', buf[1]); + ASSERT_EQ('y', buf[2]); + ASSERT_EQ('z', buf[3]); +} + +TEST_F(BlobCacheTest, CacheMaxValueSizeSucceeds) { + char buf[MAX_VALUE_SIZE]; + for (int i = 0; i < MAX_VALUE_SIZE; i++) { + buf[i] = 'b'; + } + mBC->set("abcd", 4, buf, MAX_VALUE_SIZE); + for (int i = 0; i < MAX_VALUE_SIZE; i++) { + buf[i] = 0xee; + } + ASSERT_EQ(size_t(MAX_VALUE_SIZE), mBC->get("abcd", 4, buf, + MAX_VALUE_SIZE)); + for (int i = 0; i < MAX_VALUE_SIZE; i++) { + SCOPED_TRACE(i); + ASSERT_EQ('b', buf[i]); + } +} + +TEST_F(BlobCacheTest, CacheMaxKeyValuePairSizeSucceeds) { + // Check a testing assumption + ASSERT_TRUE(MAX_KEY_SIZE < MAX_TOTAL_SIZE); + + enum { bufSize = MAX_TOTAL_SIZE - MAX_KEY_SIZE }; + + char key[MAX_KEY_SIZE]; + char buf[bufSize]; + for (int i = 0; i < MAX_KEY_SIZE; i++) { + key[i] = 'a'; + } + for (int i = 0; i < bufSize; i++) { + buf[i] = 'b'; + } + + mBC->set(key, MAX_KEY_SIZE, buf, bufSize); + ASSERT_EQ(size_t(bufSize), mBC->get(key, MAX_KEY_SIZE, NULL, 0)); +} + +TEST_F(BlobCacheTest, CacheMinKeyAndValueSizeSucceeds) { + char buf[1] = { 0xee }; + mBC->set("x", 1, "y", 1); + ASSERT_EQ(size_t(1), mBC->get("x", 1, buf, 1)); + ASSERT_EQ('y', buf[0]); +} + +TEST_F(BlobCacheTest, CacheSizeDoesntExceedTotalLimit) { + for (int i = 0; i < 256; i++) { + uint8_t k = i; + mBC->set(&k, 1, "x", 1); + } + int numCached = 0; + for (int i = 0; i < 256; i++) { + uint8_t k = i; + if (mBC->get(&k, 1, NULL, 0) == 1) { + numCached++; + } + } + ASSERT_GE(MAX_TOTAL_SIZE / 2, numCached); +} + +TEST_F(BlobCacheTest, ExceedingTotalLimitHalvesCacheSize) { + // Fill up the entire cache with 1 char key/value pairs. + const int maxEntries = MAX_TOTAL_SIZE / 2; + for (int i = 0; i < maxEntries; i++) { + uint8_t k = i; + mBC->set(&k, 1, "x", 1); + } + // Insert one more entry, causing a cache overflow. + { + uint8_t k = maxEntries; + mBC->set(&k, 1, "x", 1); + } + // Count the number of entries in the cache. + int numCached = 0; + for (int i = 0; i < maxEntries+1; i++) { + uint8_t k = i; + if (mBC->get(&k, 1, NULL, 0) == 1) { + numCached++; + } + } + ASSERT_EQ(maxEntries/2 + 1, numCached); +} + +} // namespace android diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index 86671d6d19bb..ec59da6ed966 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -46,6 +46,7 @@ import android.text.TextUtils.SimpleStringSplitter; import android.util.Slog; import android.util.SparseArray; import android.view.IWindow; +import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.IAccessibilityInteractionConnection; @@ -149,9 +150,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub synchronized (mLock) { notifyEventListenerLocked(service, eventType); - AccessibilityEvent oldEvent = service.mPendingEvents.get(eventType); - service.mPendingEvents.remove(eventType); - tryRecycleLocked(oldEvent); } } }; @@ -319,17 +317,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public boolean sendAccessibilityEvent(AccessibilityEvent event) { synchronized (mLock) { - mSecurityPolicy.updateRetrievalAllowingWindowAndEventSourceLocked(event); - notifyAccessibilityServicesDelayedLocked(event, false); - notifyAccessibilityServicesDelayedLocked(event, true); - } - // event not scheduled for dispatch => recycle - if (mHandledFeedbackTypes == 0) { - event.recycle(); - } else { - mHandledFeedbackTypes = 0; + if (mSecurityPolicy.canDispatchAccessibilityEvent(event)) { + mSecurityPolicy.updateRetrievalAllowingWindowAndEventSourceLocked(event); + notifyAccessibilityServicesDelayedLocked(event, false); + notifyAccessibilityServicesDelayedLocked(event, true); + } } - + event.recycle(); + mHandledFeedbackTypes = 0; return (OWN_PROCESS_ID != Binder.getCallingPid()); } @@ -517,46 +512,27 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private void notifyAccessibilityServiceDelayedLocked(Service service, AccessibilityEvent event) { synchronized (mLock) { - int eventType = event.getEventType(); + final int eventType = event.getEventType(); + // Make a copy since during dispatch it is possible the event to + // be modified to remove its source if the receiving service does + // not have permission to access the window content. + AccessibilityEvent newEvent = AccessibilityEvent.obtain(event); AccessibilityEvent oldEvent = service.mPendingEvents.get(eventType); - service.mPendingEvents.put(eventType, event); + service.mPendingEvents.put(eventType, newEvent); - int what = eventType | (service.mId << 16); + final int what = eventType | (service.mId << 16); if (oldEvent != null) { mHandler.removeMessages(what); - tryRecycleLocked(oldEvent); + oldEvent.recycle(); } Message message = mHandler.obtainMessage(what, service); - message.arg1 = event.getEventType(); + message.arg1 = eventType; mHandler.sendMessageDelayed(message, service.mNotificationTimeout); } } /** - * Recycles an event if it can be safely recycled. The condition is that no - * not notified service is interested in the event. - * - * @param event The event. - */ - private void tryRecycleLocked(AccessibilityEvent event) { - if (event == null) { - return; - } - int eventType = event.getEventType(); - List<Service> services = mServices; - - // linear in the number of service which is not large - for (int i = 0, count = services.size(); i < count; i++) { - Service service = services.get(i); - if (service.mPendingEvents.get(eventType) == event) { - return; - } - } - event.recycle(); - } - - /** * Notifies a service for a scheduled event given the event type. * * @param service The service. @@ -565,7 +541,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private void notifyEventListenerLocked(Service service, int eventType) { IEventListener listener = service.mServiceInterface; AccessibilityEvent event = service.mPendingEvents.get(eventType); - + service.mPendingEvents.remove(eventType); try { if (mSecurityPolicy.canRetrieveWindowContent(service)) { event.setConnection(service); @@ -574,6 +550,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } event.setSealed(true); listener.onAccessibilityEvent(event); + event.recycle(); if (DEBUG) { Slog.i(LOG_TAG, "Event " + event + " sent to " + listener); } @@ -926,7 +903,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int viewId) { + public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId) { IAccessibilityInteractionConnection connection = null; synchronized (mLock) { final boolean permissionGranted = mSecurityPolicy.canRetrieveWindowContent(this); @@ -961,10 +938,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return null; } - public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewText(String text) { + public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewTextInActiveWindow( + String text) { + return findAccessibilityNodeInfosByViewText(text, + mSecurityPolicy.mRetrievalAlowingWindowId, View.NO_ID); + } + + public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewText(String text, + int accessibilityWindowId, int accessibilityViewId) { IAccessibilityInteractionConnection connection = null; synchronized (mLock) { - final boolean permissionGranted = mSecurityPolicy.canRetrieveWindowContent(this); + final boolean permissionGranted = + mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, accessibilityWindowId); if (permissionGranted) { connection = getConnectionToRetrievalAllowingWindowLocked(); } @@ -978,7 +963,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final long identityToken = Binder.clearCallingIdentity(); try { final int interactionId = mInteractionIdCounter.getAndIncrement(); - connection.findAccessibilityNodeInfosByViewText(text, interactionId, mCallback); + connection.findAccessibilityNodeInfosByViewText(text, accessibilityViewId, + interactionId, mCallback); List<AccessibilityNodeInfo> infos = mCallback.getFindAccessibilityNodeInfosResultAndClear(interactionId); if (infos != null) { @@ -1112,16 +1098,23 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub | AccessibilityEvent.TYPE_VIEW_CLICKED | AccessibilityEvent.TYPE_VIEW_FOCUSED | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED - | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED; + | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | AccessibilityEvent.TYPE_VIEW_SELECTED + | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED; private int mRetrievalAlowingWindowId; + private boolean canDispatchAccessibilityEvent(AccessibilityEvent event) { + // Send window changed event only for the retrieval allowing window. + return (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED + || event.getWindowId() == mRetrievalAlowingWindowId); + } + public void updateRetrievalAllowingWindowAndEventSourceLocked(AccessibilityEvent event) { - final int windowId = event.getSourceAccessibilityWindowId(); + final int windowId = event.getWindowId(); final int eventType = event.getEventType(); if ((eventType & RETRIEVAL_ALLOWING_EVENT_TYPES) != 0) { mRetrievalAlowingWindowId = windowId; - } else { + } else { event.setSource(null); } } diff --git a/tests/GridLayoutTest/res/layout/grid3.xml b/tests/GridLayoutTest/res/layout/grid3.xml index 31dc75abfb9c..5cdacf74faa5 100644 --- a/tests/GridLayoutTest/res/layout/grid3.xml +++ b/tests/GridLayoutTest/res/layout/grid3.xml @@ -19,14 +19,17 @@ android:layout_width="match_parent" android:layout_height="match_parent" + android:useDefaultMargins="true" android:marginsIncludedInAlignment="false" + android:columnCount="4" > <TextView android:text="Email account" android:textSize="48dip" + android:layout_columnSpan="4" android:layout_gravity="center_horizontal" /> @@ -34,12 +37,14 @@ <TextView android:text="You can configure email in just a few steps:" android:textSize="20dip" + android:layout_columnSpan="4" android:layout_gravity="left" /> <TextView android:text="Email address:" + android:layout_gravity="right" /> @@ -49,6 +54,7 @@ <TextView android:text="Password:" + android:layout_column="0" android:layout_gravity="right" /> @@ -58,14 +64,15 @@ /> <Space - android:layout_rowWeight="1" - android:layout_columnWeight="1" android:layout_row="4" android:layout_column="2" + android:layout_rowWeight="1" + android:layout_columnWeight="1" /> <Button android:text="Manual setup" + android:layout_row="5" android:layout_column="3" android:layout_gravity="fill_horizontal" @@ -73,6 +80,7 @@ <Button android:text="Next" + android:layout_column="3" android:layout_gravity="fill_horizontal" /> diff --git a/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java b/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java index 5e29cf1ac8da..32365d7f7050 100644 --- a/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java +++ b/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java @@ -20,11 +20,15 @@ import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.view.View; - -import android.widget.*; +import android.widget.Button; +import android.widget.EditText; +import android.widget.GridLayout; +import android.widget.Space; +import android.widget.TextView; import static android.text.InputType.TYPE_CLASS_TEXT; -import static android.view.inputmethod.EditorInfo.*; +import static android.view.inputmethod.EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS; +import static android.view.inputmethod.EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; import static android.widget.GridLayout.*; public class Activity2 extends Activity { @@ -42,12 +46,12 @@ public class Activity2 extends Activity { Group row6 = new Group(6, CENTER); Group row7 = new Group(7, CENTER); - Group col1a = new Group(1, 5, CENTER); - Group col1b = new Group(1, 5, LEFT); + Group col1a = new Group(1, 4, CENTER); + Group col1b = new Group(1, 4, LEFT); Group col1c = new Group(1, RIGHT); - Group col2 = new Group(2, LEFT); - Group col3 = new Group(3, FILL); - Group col4 = new Group(4, FILL); + Group col2 = new Group(2, LEFT); + Group col3 = new Group(3, FILL); + Group col4 = new Group(4, FILL); { TextView v = new TextView(context); @@ -55,20 +59,17 @@ public class Activity2 extends Activity { v.setText("Email setup"); vg.addView(v, new LayoutParams(row1, col1a)); } - { TextView v = new TextView(context); v.setTextSize(20); v.setText("You can configure email in just a few steps:"); vg.addView(v, new LayoutParams(row2, col1b)); } - { TextView v = new TextView(context); v.setText("Email address:"); vg.addView(v, new LayoutParams(row3, col1c)); } - { EditText v = new EditText(context); v.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_EMAIL_ADDRESS); @@ -78,13 +79,11 @@ public class Activity2 extends Activity { vg.addView(v, lp); } } - { TextView v = new TextView(context); v.setText("Password:"); vg.addView(v, new LayoutParams(row4, col1c)); } - { TextView v = new EditText(context); v.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD); @@ -94,7 +93,6 @@ public class Activity2 extends Activity { vg.addView(v, lp); } } - { Space v = new Space(context); { @@ -104,13 +102,11 @@ public class Activity2 extends Activity { vg.addView(v, lp); } } - { Button v = new Button(context); v.setText("Manual setup"); vg.addView(v, new LayoutParams(row6, col4)); } - { Button v = new Button(context); v.setText("Next"); diff --git a/wifi/java/android/net/wifi/SupplicantState.java b/wifi/java/android/net/wifi/SupplicantState.java index 91e685fde905..509b02ca82d8 100644 --- a/wifi/java/android/net/wifi/SupplicantState.java +++ b/wifi/java/android/net/wifi/SupplicantState.java @@ -216,6 +216,28 @@ public enum SupplicantState implements Parcelable { } } + static boolean isDriverActive(SupplicantState state) { + switch(state) { + case DISCONNECTED: + case DORMANT: + case INACTIVE: + case AUTHENTICATING: + case ASSOCIATING: + case ASSOCIATED: + case SCANNING: + case FOUR_WAY_HANDSHAKE: + case GROUP_HANDSHAKE: + case COMPLETED: + return true; + case INTERFACE_DISABLED: + case UNINITIALIZED: + case INVALID: + return false; + default: + throw new IllegalArgumentException("Unknown supplicant state"); + } + } + /** Implement the Parcelable interface {@hide} */ public int describeContents() { return 0; diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java index 4a45825b1b0b..4ec4cfcd34d9 100644 --- a/wifi/java/android/net/wifi/WifiMonitor.java +++ b/wifi/java/android/net/wifi/WifiMonitor.java @@ -107,7 +107,7 @@ public class WifiMonitor { * <pre> * CTRL-EVENT-DRIVER-STATE state * </pre> - * <code>state</code> is either STARTED or STOPPED + * <code>state</code> can be HANGED */ private static final String driverStateEvent = "DRIVER-STATE"; /** @@ -304,11 +304,7 @@ public class WifiMonitor { if (state == null) { return; } - if (state.equals("STOPPED")) { - mWifiStateMachine.notifyDriverStopped(); - } else if (state.equals("STARTED")) { - mWifiStateMachine.notifyDriverStarted(); - } else if (state.equals("HANGED")) { + if (state.equals("HANGED")) { mWifiStateMachine.notifyDriverHung(); } } diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java index 6df37bbddc3a..3df3736f0497 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -212,22 +212,18 @@ public class WifiStateMachine extends StateMachine { static final int SUP_CONNECTION_EVENT = BASE + 31; /* Connection to supplicant lost */ static final int SUP_DISCONNECTION_EVENT = BASE + 32; - /* Driver start completed */ - static final int DRIVER_START_EVENT = BASE + 33; - /* Driver stop completed */ - static final int DRIVER_STOP_EVENT = BASE + 34; - /* Network connection completed */ - static final int NETWORK_CONNECTION_EVENT = BASE + 36; + /* Network connection completed */ + static final int NETWORK_CONNECTION_EVENT = BASE + 33; /* Network disconnection completed */ - static final int NETWORK_DISCONNECTION_EVENT = BASE + 37; + static final int NETWORK_DISCONNECTION_EVENT = BASE + 34; /* Scan results are available */ - static final int SCAN_RESULTS_EVENT = BASE + 38; + static final int SCAN_RESULTS_EVENT = BASE + 35; /* Supplicate state changed */ - static final int SUPPLICANT_STATE_CHANGE_EVENT = BASE + 39; + static final int SUPPLICANT_STATE_CHANGE_EVENT = BASE + 36; /* Password failure and EAP authentication failure */ - static final int AUTHENTICATION_FAILURE_EVENT = BASE + 40; + static final int AUTHENTICATION_FAILURE_EVENT = BASE + 37; /* WPS overlap detected */ - static final int WPS_OVERLAP_EVENT = BASE + 41; + static final int WPS_OVERLAP_EVENT = BASE + 38; /* Supplicant commands */ @@ -1421,6 +1417,35 @@ public class WifiStateMachine extends StateMachine { return mNetworkInfo.getDetailedState(); } + + private SupplicantState handleSupplicantStateChange(Message message) { + StateChangeResult stateChangeResult = (StateChangeResult) message.obj; + SupplicantState state = stateChangeResult.state; + // Supplicant state change + // [31-13] Reserved for future use + // [8 - 0] Supplicant state (as defined in SupplicantState.java) + // 50023 supplicant_state_changed (custom|1|5) + EventLog.writeEvent(EVENTLOG_SUPPLICANT_STATE_CHANGED, state.ordinal()); + mWifiInfo.setSupplicantState(state); + // Network id is only valid when we start connecting + if (SupplicantState.isConnecting(state)) { + mWifiInfo.setNetworkId(stateChangeResult.networkId); + } else { + mWifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID); + } + + if (state == SupplicantState.ASSOCIATING) { + /* BSSID is valid only in ASSOCIATING state */ + mWifiInfo.setBSSID(stateChangeResult.BSSID); + } + setNetworkDetailedState(WifiInfo.getDetailedStateOf(state)); + + mSupplicantStateTracker.sendMessage(Message.obtain(message)); + mWpsStateMachine.sendMessage(Message.obtain(message)); + + return state; + } + /** * Resets the Wi-Fi Connections by clearing any state, resetting any sockets * using the interface, stopping DHCP & disabling interface @@ -1674,14 +1699,6 @@ public class WifiStateMachine extends StateMachine { sendMessage(SCAN_RESULTS_EVENT); } - void notifyDriverStarted() { - sendMessage(DRIVER_START_EVENT); - } - - void notifyDriverStopped() { - sendMessage(DRIVER_STOP_EVENT); - } - void notifyDriverHung() { setWifiEnabled(false); setWifiEnabled(true); @@ -1737,8 +1754,6 @@ public class WifiStateMachine extends StateMachine { case CMD_REASSOCIATE: case SUP_CONNECTION_EVENT: case SUP_DISCONNECTION_EVENT: - case DRIVER_START_EVENT: - case DRIVER_STOP_EVENT: case NETWORK_CONNECTION_EVENT: case NETWORK_DISCONNECTION_EVENT: case SCAN_RESULTS_EVENT: @@ -2284,13 +2299,19 @@ public class WifiStateMachine extends StateMachine { public boolean processMessage(Message message) { if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); switch(message.what) { - case DRIVER_START_EVENT: - transitionTo(mDriverStartedState); + case SUPPLICANT_STATE_CHANGE_EVENT: + SupplicantState state = handleSupplicantStateChange(message); + /* If suplicant is exiting out of INTERFACE_DISABLED state into + * a state that indicates driver has started, it is ready to + * receive driver commands + */ + if (SupplicantState.isDriverActive(state)) { + transitionTo(mDriverStartedState); + } break; /* Queue driver commands & connection events */ case CMD_START_DRIVER: case CMD_STOP_DRIVER: - case SUPPLICANT_STATE_CHANGE_EVENT: case NETWORK_CONNECTION_EVENT: case NETWORK_DISCONNECTION_EVENT: case AUTHENTICATION_FAILURE_EVENT: @@ -2429,8 +2450,11 @@ public class WifiStateMachine extends StateMachine { public boolean processMessage(Message message) { if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); switch(message.what) { - case DRIVER_STOP_EVENT: - transitionTo(mDriverStoppedState); + case SUPPLICANT_STATE_CHANGE_EVENT: + SupplicantState state = handleSupplicantStateChange(message); + if (state == SupplicantState.INTERFACE_DISABLED) { + transitionTo(mDriverStoppedState); + } break; /* Queue driver commands */ case CMD_START_DRIVER: @@ -2465,11 +2489,23 @@ public class WifiStateMachine extends StateMachine { public boolean processMessage(Message message) { if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); switch (message.what) { - case CMD_START_DRIVER: - mWakeLock.acquire(); - WifiNative.startDriverCommand(); - transitionTo(mDriverStartingState); - mWakeLock.release(); + case CMD_START_DRIVER: + mWakeLock.acquire(); + WifiNative.startDriverCommand(); + mWakeLock.release(); + break; + case SUPPLICANT_STATE_CHANGE_EVENT: + SupplicantState state = handleSupplicantStateChange(message); + /* A driver start causes supplicant to first report an INTERFACE_DISABLED + * state before transitioning out of it for connection. Stay in + * DriverStoppedState until we get an INTERFACE_DISABLED state and transition + * to DriverStarting upon getting that + * TODO: Fix this when the supplicant can be made to just transition out of + * INTERFACE_DISABLED state when driver gets started + */ + if (state == SupplicantState.INTERFACE_DISABLED) { + transitionTo(mDriverStartingState); + } break; default: return NOT_HANDLED; @@ -2535,29 +2571,8 @@ public class WifiStateMachine extends StateMachine { sendErrorBroadcast(WifiManager.WPS_OVERLAP_ERROR); break; case SUPPLICANT_STATE_CHANGE_EVENT: - stateChangeResult = (StateChangeResult) message.obj; - SupplicantState state = stateChangeResult.state; - // Supplicant state change - // [31-13] Reserved for future use - // [8 - 0] Supplicant state (as defined in SupplicantState.java) - // 50023 supplicant_state_changed (custom|1|5) - EventLog.writeEvent(EVENTLOG_SUPPLICANT_STATE_CHANGED, state.ordinal()); - mWifiInfo.setSupplicantState(state); - // Network id is only valid when we start connecting - if (SupplicantState.isConnecting(state)) { - mWifiInfo.setNetworkId(stateChangeResult.networkId); - } else { - mWifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID); - } - - if (state == SupplicantState.ASSOCIATING) { - /* BSSID is valid only in ASSOCIATING state */ - mWifiInfo.setBSSID(stateChangeResult.BSSID); - } - - mSupplicantStateTracker.sendMessage(Message.obtain(message)); - mWpsStateMachine.sendMessage(Message.obtain(message)); - break; + handleSupplicantStateChange(message); + break; /* Do a redundant disconnect without transition */ case CMD_DISCONNECT: WifiNative.disconnectCommand(); @@ -2964,12 +2979,7 @@ public class WifiStateMachine extends StateMachine { /* Ignore network disconnect */ case NETWORK_DISCONNECTION_EVENT: break; - case SUPPLICANT_STATE_CHANGE_EVENT: - StateChangeResult stateChangeResult = (StateChangeResult) message.obj; - setNetworkDetailedState(WifiInfo.getDetailedStateOf(stateChangeResult.state)); - /* DriverStartedState does the rest of the handling */ - return NOT_HANDLED; - case CMD_START_SCAN: + case CMD_START_SCAN: /* Disable background scan temporarily during a regular scan */ if (mEnableBackgroundScan) { WifiNative.enableBackgroundScanCommand(false); |