diff options
36 files changed, 3038 insertions, 489 deletions
diff --git a/api/current.txt b/api/current.txt index fc244d8..721e33e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -16782,6 +16782,9 @@ package android.renderscript { method public void addF32(android.renderscript.Float3); method public void addF32(android.renderscript.Float4); method public void addF64(double); + method public void addF64(android.renderscript.Double2); + method public void addF64(android.renderscript.Double3); + method public void addF64(android.renderscript.Double4); method public void addI16(short); method public void addI16(android.renderscript.Short2); method public void addI16(android.renderscript.Short3); @@ -16791,6 +16794,9 @@ package android.renderscript { method public void addI32(android.renderscript.Int3); method public void addI32(android.renderscript.Int4); method public void addI64(long); + method public void addI64(android.renderscript.Long2); + method public void addI64(android.renderscript.Long3); + method public void addI64(android.renderscript.Long4); method public void addI8(byte); method public void addI8(android.renderscript.Byte2); method public void addI8(android.renderscript.Byte3); @@ -16808,6 +16814,9 @@ package android.renderscript { method public void addU32(android.renderscript.Long3); method public void addU32(android.renderscript.Long4); method public void addU64(long); + method public void addU64(android.renderscript.Long2); + method public void addU64(android.renderscript.Long3); + method public void addU64(android.renderscript.Long4); method public void addU8(short); method public void addU8(android.renderscript.Short2); method public void addU8(android.renderscript.Short3); @@ -21695,7 +21704,10 @@ package android.view { method public void dispatchDisplayHint(int); method public boolean dispatchDragEvent(android.view.DragEvent); method protected void dispatchDraw(android.graphics.Canvas); + method protected boolean dispatchGenericFocusedEvent(android.view.MotionEvent); method public boolean dispatchGenericMotionEvent(android.view.MotionEvent); + method protected boolean dispatchGenericPointerEvent(android.view.MotionEvent); + method protected boolean dispatchHoverEvent(android.view.MotionEvent); method public boolean dispatchKeyEvent(android.view.KeyEvent); method public boolean dispatchKeyEventPreIme(android.view.KeyEvent); method public boolean dispatchKeyShortcutEvent(android.view.KeyEvent); @@ -21883,6 +21895,7 @@ package android.view { method public void onFinishTemporaryDetach(); method protected void onFocusChanged(boolean, int, android.graphics.Rect); method public boolean onGenericMotionEvent(android.view.MotionEvent); + method public void onHoverChanged(boolean); method public boolean onHoverEvent(android.view.MotionEvent); method public void onInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent); method public void onInitializeAccessibilityNodeInfo(android.view.accessibility.AccessibilityNodeInfo); @@ -22159,6 +22172,10 @@ package android.view { method public abstract boolean onGenericMotion(android.view.View, android.view.MotionEvent); } + public static abstract interface View.OnHoverListener { + method public abstract boolean onHover(android.view.View, android.view.MotionEvent); + } + public static abstract interface View.OnKeyListener { method public abstract boolean onKey(android.view.View, int, android.view.KeyEvent); } @@ -22329,6 +22346,7 @@ package android.view { method protected void measureChildren(int, int); method public final void offsetDescendantRectToMyCoords(android.view.View, android.graphics.Rect); method public final void offsetRectIntoDescendantCoords(android.view.View, android.graphics.Rect); + method public boolean onInterceptHoverEvent(android.view.MotionEvent); method public boolean onInterceptTouchEvent(android.view.MotionEvent); method protected abstract void onLayout(boolean, int, int, int, int); method protected boolean onRequestFocusInDescendants(int, android.graphics.Rect); @@ -25272,10 +25290,16 @@ package android.widget { method public void dismiss(); method public android.view.Menu getMenu(); method public android.view.MenuInflater getMenuInflater(); + method public void inflate(int); + method public void setOnDismissListener(android.widget.PopupMenu.OnDismissListener); method public void setOnMenuItemClickListener(android.widget.PopupMenu.OnMenuItemClickListener); method public void show(); } + public static abstract interface PopupMenu.OnDismissListener { + method public abstract void onDismiss(android.widget.PopupMenu); + } + public static abstract interface PopupMenu.OnMenuItemClickListener { method public abstract boolean onMenuItemClick(android.view.MenuItem); } diff --git a/core/java/android/pim/EventRecurrence.java b/core/java/android/pim/EventRecurrence.java index 56c4f7a..cde7dac 100644 --- a/core/java/android/pim/EventRecurrence.java +++ b/core/java/android/pim/EventRecurrence.java @@ -18,36 +18,18 @@ package android.pim; import android.text.TextUtils; import android.text.format.Time; +import android.util.Log; +import android.util.TimeFormatException; import java.util.Calendar; +import java.util.HashMap; -public class EventRecurrence -{ - /** - * Thrown when a recurrence string provided can not be parsed according - * to RFC2445. - */ - public static class InvalidFormatException extends RuntimeException - { - InvalidFormatException(String s) { - super(s); - } - } - - public EventRecurrence() - { - wkst = MO; - } - - /** - * Parse an iCalendar/RFC2445 recur type according to Section 4.3.10. - */ - public native void parse(String recur); +/** + * Event recurrence utility functions. + */ +public class EventRecurrence { + private static String TAG = "EventRecur"; - public void setStartDate(Time date) { - startDate = date; - } - public static final int SECONDLY = 1; public static final int MINUTELY = 2; public static final int HOURLY = 3; @@ -64,13 +46,15 @@ public class EventRecurrence public static final int FR = 0x00200000; public static final int SA = 0x00400000; - public Time startDate; - public int freq; + public Time startDate; // set by setStartDate(), not parse() + + public int freq; // SECONDLY, MINUTELY, etc. public String until; public int count; public int interval; - public int wkst; + public int wkst; // SU, MO, TU, etc. + /* lists with zero entries may be null references */ public int[] bysecond; public int bysecondCount; public int[] byminute; @@ -79,7 +63,7 @@ public class EventRecurrence public int byhourCount; public int[] byday; public int[] bydayNum; - public int bydayCount; + public int bydayCount; public int[] bymonthday; public int bymonthdayCount; public int[] byyearday; @@ -91,6 +75,134 @@ public class EventRecurrence public int[] bysetpos; public int bysetposCount; + /** maps a part string to a parser object */ + private static HashMap<String,PartParser> sParsePartMap; + static { + sParsePartMap = new HashMap<String,PartParser>(); + sParsePartMap.put("FREQ", new ParseFreq()); + sParsePartMap.put("UNTIL", new ParseUntil()); + sParsePartMap.put("COUNT", new ParseCount()); + sParsePartMap.put("INTERVAL", new ParseInterval()); + sParsePartMap.put("BYSECOND", new ParseBySecond()); + sParsePartMap.put("BYMINUTE", new ParseByMinute()); + sParsePartMap.put("BYHOUR", new ParseByHour()); + sParsePartMap.put("BYDAY", new ParseByDay()); + sParsePartMap.put("BYMONTHDAY", new ParseByMonthDay()); + sParsePartMap.put("BYYEARDAY", new ParseByYearDay()); + sParsePartMap.put("BYWEEKNO", new ParseByWeekNo()); + sParsePartMap.put("BYMONTH", new ParseByMonth()); + sParsePartMap.put("BYSETPOS", new ParseBySetPos()); + sParsePartMap.put("WKST", new ParseWkst()); + } + + /* values for bit vector that keeps track of what we have already seen */ + private static final int PARSED_FREQ = 1 << 0; + private static final int PARSED_UNTIL = 1 << 1; + private static final int PARSED_COUNT = 1 << 2; + private static final int PARSED_INTERVAL = 1 << 3; + private static final int PARSED_BYSECOND = 1 << 4; + private static final int PARSED_BYMINUTE = 1 << 5; + private static final int PARSED_BYHOUR = 1 << 6; + private static final int PARSED_BYDAY = 1 << 7; + private static final int PARSED_BYMONTHDAY = 1 << 8; + private static final int PARSED_BYYEARDAY = 1 << 9; + private static final int PARSED_BYWEEKNO = 1 << 10; + private static final int PARSED_BYMONTH = 1 << 11; + private static final int PARSED_BYSETPOS = 1 << 12; + private static final int PARSED_WKST = 1 << 13; + + /** maps a FREQ value to an integer constant */ + private static final HashMap<String,Integer> sParseFreqMap = new HashMap<String,Integer>(); + static { + sParseFreqMap.put("SECONDLY", SECONDLY); + sParseFreqMap.put("MINUTELY", MINUTELY); + sParseFreqMap.put("HOURLY", HOURLY); + sParseFreqMap.put("DAILY", DAILY); + sParseFreqMap.put("WEEKLY", WEEKLY); + sParseFreqMap.put("MONTHLY", MONTHLY); + sParseFreqMap.put("YEARLY", YEARLY); + } + + /** maps a two-character weekday string to an integer constant */ + private static final HashMap<String,Integer> sParseWeekdayMap = new HashMap<String,Integer>(); + static { + sParseWeekdayMap.put("SU", SU); + sParseWeekdayMap.put("MO", MO); + sParseWeekdayMap.put("TU", TU); + sParseWeekdayMap.put("WE", WE); + sParseWeekdayMap.put("TH", TH); + sParseWeekdayMap.put("FR", FR); + sParseWeekdayMap.put("SA", SA); + } + + /** If set, allow lower-case recurrence rule strings. Minor performance impact. */ + private static final boolean ALLOW_LOWER_CASE = false; + + /** If set, validate the value of UNTIL parts. Minor performance impact. */ + private static final boolean VALIDATE_UNTIL = false; + + /** If set, require that only one of {UNTIL,COUNT} is present. Breaks compat w/ old parser. */ + private static final boolean ONLY_ONE_UNTIL_COUNT = false; + + + /** + * Thrown when a recurrence string provided can not be parsed according + * to RFC2445. + */ + public static class InvalidFormatException extends RuntimeException { + InvalidFormatException(String s) { + super(s); + } + } + + /** + * Parse an iCalendar/RFC2445 recur type according to Section 4.3.10. The string is + * parsed twice, by the old and new parsers, and the results are compared. + * <p> + * TODO: this will go away, and what is now parse2() will simply become parse(). + */ + public void parse(String recur) { + InvalidFormatException newExcep = null; + try { + parse2(recur); + } catch (InvalidFormatException ife) { + newExcep = ife; + } + + boolean oldThrew = false; + try { + EventRecurrence check = new EventRecurrence(); + check.parseNative(recur); + if (newExcep == null) { + // Neither threw, check to see if results match. + if (!equals(check)) { + throw new InvalidFormatException("Recurrence rule parse does not match [" + + recur + "]"); + } + } + } catch (InvalidFormatException ife) { + oldThrew = true; + if (newExcep == null) { + // Old threw, but new didn't. Log a warning, but don't throw. + Log.d(TAG, "NOTE: old parser rejected [" + recur + "]: " + ife.getMessage()); + } + } + + if (newExcep != null) { + if (!oldThrew) { + // New threw, but old didn't. Log a warning and throw the exception. + Log.d(TAG, "NOTE: new parser rejected [" + recur + "]: " + newExcep.getMessage()); + } + throw newExcep; + } + } + + native void parseNative(String recur); + + public void setStartDate(Time date) { + startDate = date; + } + /** * Converts one of the Calendar.SUNDAY constants to the SU, MO, etc. * constants. btw, I think we should switch to those here too, to @@ -118,7 +230,7 @@ public class EventRecurrence throw new RuntimeException("bad day of week: " + day); } } - + public static int timeDay2Day(int day) { switch (day) @@ -191,16 +303,16 @@ public class EventRecurrence throw new RuntimeException("bad day of week: " + day); } } - + /** * Converts one of the internal day constants (SU, MO, etc.) to the * two-letter string representing that constant. - * - * @throws IllegalArgumentException Thrown if the day argument is not one of - * the defined day constants. - * + * * @param day one the internal constants SU, MO, etc. * @return the two-letter string for the day ("SU", "MO", etc.) + * + * @throws IllegalArgumentException Thrown if the day argument is not one of + * the defined day constants. */ private static String day2String(int day) { switch (day) { @@ -283,7 +395,7 @@ public class EventRecurrence s.append(";UNTIL="); s.append(until); } - + if (this.count != 0) { s.append(";COUNT="); s.append(this.count); @@ -323,36 +435,484 @@ public class EventRecurrence return s.toString(); } - + public boolean repeatsOnEveryWeekDay() { if (this.freq != WEEKLY) { - return false; + return false; } - + int count = this.bydayCount; if (count != 5) { return false; } - + for (int i = 0 ; i < count ; i++) { int day = byday[i]; if (day == SU || day == SA) { return false; } } - + return true; } - + public boolean repeatsMonthlyOnDayCount() { if (this.freq != MONTHLY) { return false; } - + if (bydayCount != 1 || bymonthdayCount != 0) { return false; } - + + return true; + } + + /** + * Determines whether two integer arrays contain identical elements. + * <p> + * The native implementation over-allocated the arrays (and may have stuff left over from + * a previous run), so we can't just check the arrays -- the separately-maintained count + * field also matters. We assume that a null array will have a count of zero, and that the + * array can hold as many elements as the associated count indicates. + * <p> + * TODO: replace this with Arrays.equals() when the old parser goes away. + */ + private static boolean arraysEqual(int[] array1, int count1, int[] array2, int count2) { + if (count1 != count2) { + return false; + } + + for (int i = 0; i < count1; i++) { + if (array1[i] != array2[i]) + return false; + } + return true; } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof EventRecurrence)) { + return false; + } + + EventRecurrence er = (EventRecurrence) obj; + return (startDate == null ? + er.startDate == null : Time.compare(startDate, er.startDate) == 0) && + freq == er.freq && + (until == null ? er.until == null : until.equals(er.until)) && + count == er.count && + interval == er.interval && + wkst == er.wkst && + arraysEqual(bysecond, bysecondCount, er.bysecond, er.bysecondCount) && + arraysEqual(byminute, byminuteCount, er.byminute, er.byminuteCount) && + arraysEqual(byhour, byhourCount, er.byhour, er.byhourCount) && + arraysEqual(byday, bydayCount, er.byday, er.bydayCount) && + arraysEqual(bydayNum, bydayCount, er.bydayNum, er.bydayCount) && + arraysEqual(bymonthday, bymonthdayCount, er.bymonthday, er.bymonthdayCount) && + arraysEqual(byyearday, byyeardayCount, er.byyearday, er.byyeardayCount) && + arraysEqual(byweekno, byweeknoCount, er.byweekno, er.byweeknoCount) && + arraysEqual(bymonth, bymonthCount, er.bymonth, er.bymonthCount) && + arraysEqual(bysetpos, bysetposCount, er.bysetpos, er.bysetposCount); + } + + @Override public int hashCode() { + // We overrode equals, so we must override hashCode(). Nobody seems to need this though. + throw new UnsupportedOperationException(); + } + + /** + * Resets parser-modified fields to their initial state. Does not alter startDate. + * <p> + * The original parser always set all of the "count" fields, "wkst", and "until", + * essentially allowing the same object to be used multiple times by calling parse(). + * It's unclear whether this behavior was intentional. For now, be paranoid and + * preserve the existing behavior by resetting the fields. + * <p> + * We don't need to touch the integer arrays; they will either be ignored or + * overwritten. The "startDate" field is not set by the parser, so we ignore it here. + */ + private void resetFields() { + until = null; + freq = count = interval = bysecondCount = byminuteCount = byhourCount = + bydayCount = bymonthdayCount = byyeardayCount = byweeknoCount = bymonthCount = + bysetposCount = 0; + } + + /** + * Parses an rfc2445 recurrence rule string into its component pieces. Attempting to parse + * malformed input will result in an EventRecurrence.InvalidFormatException. + * + * @param recur The recurrence rule to parse (in un-folded form). + */ + void parse2(String recur) { + /* + * From RFC 2445 section 4.3.10: + * + * recur = "FREQ"=freq *( + * ; either UNTIL or COUNT may appear in a 'recur', + * ; but UNTIL and COUNT MUST NOT occur in the same 'recur' + * + * ( ";" "UNTIL" "=" enddate ) / + * ( ";" "COUNT" "=" 1*DIGIT ) / + * + * ; the rest of these keywords are optional, + * ; but MUST NOT occur more than once + * + * ( ";" "INTERVAL" "=" 1*DIGIT ) / + * ( ";" "BYSECOND" "=" byseclist ) / + * ( ";" "BYMINUTE" "=" byminlist ) / + * ( ";" "BYHOUR" "=" byhrlist ) / + * ( ";" "BYDAY" "=" bywdaylist ) / + * ( ";" "BYMONTHDAY" "=" bymodaylist ) / + * ( ";" "BYYEARDAY" "=" byyrdaylist ) / + * ( ";" "BYWEEKNO" "=" bywknolist ) / + * ( ";" "BYMONTH" "=" bymolist ) / + * ( ";" "BYSETPOS" "=" bysplist ) / + * ( ";" "WKST" "=" weekday ) / + * ( ";" x-name "=" text ) + * ) + * + * Examples: + * FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU + * FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8 + * + * Strategy: + * (1) Split the string at ';' boundaries to get an array of rule "parts". + * (2) For each part, find substrings for left/right sides of '=' (name/value). + * (3) Call a <name>-specific parsing function to parse the <value> into an + * output field. + * + * By keeping track of which names we've seen in a bit vector, we can verify the + * constraints indicated above (FREQ appears first, none of them appear more than once -- + * though x-[name] would require special treatment), and we have either UNTIL or COUNT + * but not both. + * + * In general, RFC 2445 property names (e.g. "FREQ") and enumerations ("TU") must + * be handled in a case-insensitive fashion, but case may be significant for other + * properties. We don't have any case-sensitive values in RRULE, except possibly + * for the custom "X-" properties, but we ignore those anyway. Thus, we can trivially + * convert the entire string to upper case and then use simple comparisons. + * + * Differences from previous version: + * - allows lower-case property and enumeration values [optional] + * - enforces that FREQ appears first + * - enforces that only one of UNTIL and COUNT may be specified + * - allows (but ignores) X-* parts + * - improved validation on various values (e.g. UNTIL timestamps) + * - error messages are more specific + */ + + /* TODO: replace with "if (freq != 0) throw" if nothing requires this */ + resetFields(); + + int parseFlags = 0; + String[] parts; + if (ALLOW_LOWER_CASE) { + parts = recur.toUpperCase().split(";"); + } else { + parts = recur.split(";"); + } + for (String part : parts) { + int equalIndex = part.indexOf('='); + if (equalIndex <= 0) { + /* no '=' or no LHS */ + throw new InvalidFormatException("Missing LHS in " + part); + } + + String lhs = part.substring(0, equalIndex); + String rhs = part.substring(equalIndex + 1); + if (rhs.length() == 0) { + throw new InvalidFormatException("Missing RHS in " + part); + } + + /* + * In lieu of a "switch" statement that allows string arguments, we use a + * map from strings to parsing functions. + */ + PartParser parser = sParsePartMap.get(lhs); + if (parser == null) { + if (lhs.startsWith("X-")) { + //Log.d(TAG, "Ignoring custom part " + lhs); + continue; + } + throw new InvalidFormatException("Couldn't find parser for " + lhs); + } else { + int flag = parser.parsePart(rhs, this); + if ((parseFlags & flag) != 0) { + throw new InvalidFormatException("Part " + lhs + " was specified twice"); + } + if (parseFlags == 0 && flag != PARSED_FREQ) { + throw new InvalidFormatException("FREQ must be specified first"); + } + parseFlags |= flag; + } + } + + // If not specified, week starts on Monday. + if ((parseFlags & PARSED_WKST) == 0) { + wkst = MO; + } + + // FREQ is mandatory. + if ((parseFlags & PARSED_FREQ) == 0) { + throw new InvalidFormatException("Must specify a FREQ value"); + } + + // Can't have both UNTIL and COUNT. + if ((parseFlags & (PARSED_UNTIL | PARSED_COUNT)) == (PARSED_UNTIL | PARSED_COUNT)) { + if (ONLY_ONE_UNTIL_COUNT) { + throw new InvalidFormatException("Must not specify both UNTIL and COUNT: " + recur); + } else { + Log.w(TAG, "Warning: rrule has both UNTIL and COUNT: " + recur); + } + } + } + + /** + * Base class for the RRULE part parsers. + */ + abstract static class PartParser { + /** + * Parses a single part. + * + * @param value The right-hand-side of the part. + * @param er The EventRecurrence into which the result is stored. + * @return A bit value indicating which part was parsed. + */ + public abstract int parsePart(String value, EventRecurrence er); + + /** + * Parses an integer, with range-checking. + * + * @param str The string to parse. + * @param minVal Minimum allowed value. + * @param maxVal Maximum allowed value. + * @param allowZero Is 0 allowed? + * @return The parsed value. + */ + public static int parseIntRange(String str, int minVal, int maxVal, boolean allowZero) { + try { + if (str.charAt(0) == '+') { + // Integer.parseInt does not allow a leading '+', so skip it manually. + str = str.substring(1); + } + int val = Integer.parseInt(str); + if (val < minVal || val > maxVal || (val == 0 && !allowZero)) { + throw new InvalidFormatException("Integer value out of range: " + str); + } + return val; + } catch (NumberFormatException nfe) { + throw new InvalidFormatException("Invalid integer value: " + str); + } + } + + /** + * Parses a comma-separated list of integers, with range-checking. + * + * @param listStr The string to parse. + * @param minVal Minimum allowed value. + * @param maxVal Maximum allowed value. + * @param allowZero Is 0 allowed? + * @return A new array with values, sized to hold the exact number of elements. + */ + public static int[] parseNumberList(String listStr, int minVal, int maxVal, + boolean allowZero) { + int[] values; + + if (listStr.indexOf(",") < 0) { + // Common case: only one entry, skip split() overhead. + values = new int[1]; + values[0] = parseIntRange(listStr, minVal, maxVal, allowZero); + } else { + String[] valueStrs = listStr.split(","); + int len = valueStrs.length; + values = new int[len]; + for (int i = 0; i < len; i++) { + values[i] = parseIntRange(valueStrs[i], minVal, maxVal, allowZero); + } + } + return values; + } + } + + /** parses FREQ={SECONDLY,MINUTELY,...} */ + private static class ParseFreq extends PartParser { + @Override public int parsePart(String value, EventRecurrence er) { + Integer freq = sParseFreqMap.get(value); + if (freq == null) { + throw new InvalidFormatException("Invalid FREQ value: " + value); + } + er.freq = freq; + return PARSED_FREQ; + } + } + /** parses UNTIL=enddate, e.g. "19970829T021400" */ + private static class ParseUntil extends PartParser { + @Override public int parsePart(String value, EventRecurrence er) { + if (VALIDATE_UNTIL) { + try { + // Parse the time to validate it. The result isn't retained. + Time until = new Time(); + until.parse(value); + } catch (TimeFormatException tfe) { + throw new InvalidFormatException("Invalid UNTIL value: " + value); + } + } + er.until = value; + return PARSED_UNTIL; + } + } + /** parses COUNT=[non-negative-integer] */ + private static class ParseCount extends PartParser { + @Override public int parsePart(String value, EventRecurrence er) { + er.count = parseIntRange(value, 0, Integer.MAX_VALUE, true); + return PARSED_COUNT; + } + } + /** parses INTERVAL=[non-negative-integer] */ + private static class ParseInterval extends PartParser { + @Override public int parsePart(String value, EventRecurrence er) { + er.interval = parseIntRange(value, 1, Integer.MAX_VALUE, false); + return PARSED_INTERVAL; + } + } + /** parses BYSECOND=byseclist */ + private static class ParseBySecond extends PartParser { + @Override public int parsePart(String value, EventRecurrence er) { + int[] bysecond = parseNumberList(value, 0, 59, true); + er.bysecond = bysecond; + er.bysecondCount = bysecond.length; + return PARSED_BYSECOND; + } + } + /** parses BYMINUTE=byminlist */ + private static class ParseByMinute extends PartParser { + @Override public int parsePart(String value, EventRecurrence er) { + int[] byminute = parseNumberList(value, 0, 59, true); + er.byminute = byminute; + er.byminuteCount = byminute.length; + return PARSED_BYMINUTE; + } + } + /** parses BYHOUR=byhrlist */ + private static class ParseByHour extends PartParser { + @Override public int parsePart(String value, EventRecurrence er) { + int[] byhour = parseNumberList(value, 0, 23, true); + er.byhour = byhour; + er.byhourCount = byhour.length; + return PARSED_BYHOUR; + } + } + /** parses BYDAY=bywdaylist, e.g. "1SU,-1SU" */ + private static class ParseByDay extends PartParser { + @Override public int parsePart(String value, EventRecurrence er) { + int[] byday; + int[] bydayNum; + int bydayCount; + + if (value.indexOf(",") < 0) { + /* only one entry, skip split() overhead */ + bydayCount = 1; + byday = new int[1]; + bydayNum = new int[1]; + parseWday(value, byday, bydayNum, 0); + } else { + String[] wdays = value.split(","); + int len = wdays.length; + bydayCount = len; + byday = new int[len]; + bydayNum = new int[len]; + for (int i = 0; i < len; i++) { + parseWday(wdays[i], byday, bydayNum, i); + } + } + er.byday = byday; + er.bydayNum = bydayNum; + er.bydayCount = bydayCount; + return PARSED_BYDAY; + } + + /** parses [int]weekday, putting the pieces into parallel array entries */ + private static void parseWday(String str, int[] byday, int[] bydayNum, int index) { + int wdayStrStart = str.length() - 2; + String wdayStr; + + if (wdayStrStart > 0) { + /* number is included; parse it out and advance to weekday */ + String numPart = str.substring(0, wdayStrStart); + int num = parseIntRange(numPart, -53, 53, false); + bydayNum[index] = num; + wdayStr = str.substring(wdayStrStart); + } else { + /* just the weekday string */ + wdayStr = str; + } + Integer wday = sParseWeekdayMap.get(wdayStr); + if (wday == null) { + throw new InvalidFormatException("Invalid BYDAY value: " + str); + } + byday[index] = wday; + } + } + /** parses BYMONTHDAY=bymodaylist */ + private static class ParseByMonthDay extends PartParser { + @Override public int parsePart(String value, EventRecurrence er) { + int[] bymonthday = parseNumberList(value, -31, 31, false); + er.bymonthday = bymonthday; + er.bymonthdayCount = bymonthday.length; + return PARSED_BYMONTHDAY; + } + } + /** parses BYYEARDAY=byyrdaylist */ + private static class ParseByYearDay extends PartParser { + @Override public int parsePart(String value, EventRecurrence er) { + int[] byyearday = parseNumberList(value, -366, 366, false); + er.byyearday = byyearday; + er.byyeardayCount = byyearday.length; + return PARSED_BYYEARDAY; + } + } + /** parses BYWEEKNO=bywknolist */ + private static class ParseByWeekNo extends PartParser { + @Override public int parsePart(String value, EventRecurrence er) { + int[] byweekno = parseNumberList(value, -53, 53, false); + er.byweekno = byweekno; + er.byweeknoCount = byweekno.length; + return PARSED_BYWEEKNO; + } + } + /** parses BYMONTH=bymolist */ + private static class ParseByMonth extends PartParser { + @Override public int parsePart(String value, EventRecurrence er) { + int[] bymonth = parseNumberList(value, 1, 12, false); + er.bymonth = bymonth; + er.bymonthCount = bymonth.length; + return PARSED_BYMONTH; + } + } + /** parses BYSETPOS=bysplist */ + private static class ParseBySetPos extends PartParser { + @Override public int parsePart(String value, EventRecurrence er) { + int[] bysetpos = parseNumberList(value, Integer.MIN_VALUE, Integer.MAX_VALUE, true); + er.bysetpos = bysetpos; + er.bysetposCount = bysetpos.length; + return PARSED_BYSETPOS; + } + } + /** parses WKST={SU,MO,...} */ + private static class ParseWkst extends PartParser { + @Override public int parsePart(String value, EventRecurrence er) { + Integer wkst = sParseWeekdayMap.get(value); + if (wkst == null) { + throw new InvalidFormatException("Invalid WKST value: " + value); + } + er.wkst = wkst; + return PARSED_WKST; + } + } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 603edf0..19e9a67 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -300,6 +300,21 @@ public final class Settings { "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS"; /** + * Activity Action: Show a dialog to select input method. + * <p> + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SHOW_INPUT_METHOD_PICKER = + "android.settings.SHOW_INPUT_METHOD_PICKER"; + + /** * Activity Action: Show settings to manage the user input dictionary. * <p> * In some cases, a matching Activity may not exist, so ensure you diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java index e14b975..49f3bbe 100644 --- a/core/java/android/view/InputEventConsistencyVerifier.java +++ b/core/java/android/view/InputEventConsistencyVerifier.java @@ -32,6 +32,11 @@ import android.util.Log; public final class InputEventConsistencyVerifier { private static final boolean IS_ENG_BUILD = "eng".equals(Build.TYPE); + private static final String EVENT_TYPE_KEY = "KeyEvent"; + private static final String EVENT_TYPE_TRACKBALL = "TrackballEvent"; + private static final String EVENT_TYPE_TOUCH = "TouchEvent"; + private static final String EVENT_TYPE_GENERIC_MOTION = "GenericMotionEvent"; + // The number of recent events to log when a problem is detected. // Can be set to 0 to disable logging recent events but the runtime overhead of // this feature is negligible on current hardware. @@ -54,6 +59,7 @@ public final class InputEventConsistencyVerifier { // It does not make sense to examine the contents of the last event since it may have // been recycled. private InputEvent mLastEvent; + private String mLastEventType; private int mLastNestingLevel; // Copy of the most recent events. @@ -185,7 +191,7 @@ public final class InputEventConsistencyVerifier { * and both dispatching methods call into the consistency verifier. */ public void onKeyEvent(KeyEvent event, int nestingLevel) { - if (!startEvent(event, nestingLevel, "KeyEvent")) { + if (!startEvent(event, nestingLevel, EVENT_TYPE_KEY)) { return; } @@ -247,7 +253,7 @@ public final class InputEventConsistencyVerifier { * and both dispatching methods call into the consistency verifier. */ public void onTrackballEvent(MotionEvent event, int nestingLevel) { - if (!startEvent(event, nestingLevel, "TrackballEvent")) { + if (!startEvent(event, nestingLevel, EVENT_TYPE_TRACKBALL)) { return; } @@ -310,23 +316,19 @@ public final class InputEventConsistencyVerifier { * and both dispatching methods call into the consistency verifier. */ public void onTouchEvent(MotionEvent event, int nestingLevel) { - if (!startEvent(event, nestingLevel, "TouchEvent")) { + if (!startEvent(event, nestingLevel, EVENT_TYPE_TOUCH)) { return; } final int action = event.getAction(); final boolean newStream = action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_CANCEL; - if (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled) { - if (newStream) { - mTouchEventStreamIsTainted = false; - mTouchEventStreamUnhandled = false; - mTouchEventStreamPointers = 0; - } else { - finishEvent(mTouchEventStreamIsTainted); - return; - } + if (newStream && (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled)) { + mTouchEventStreamIsTainted = false; + mTouchEventStreamUnhandled = false; + mTouchEventStreamPointers = 0; } + final boolean wasTainted = mTouchEventStreamIsTainted; try { ensureMetaStateIsNormalized(event.getMetaState()); @@ -439,7 +441,7 @@ public final class InputEventConsistencyVerifier { problem("Source was not SOURCE_CLASS_POINTER."); } } finally { - finishEvent(false); + finishEvent(wasTainted); } } @@ -453,7 +455,7 @@ public final class InputEventConsistencyVerifier { * and both dispatching methods call into the consistency verifier. */ public void onGenericMotionEvent(MotionEvent event, int nestingLevel) { - if (!startEvent(event, nestingLevel, "GenericMotionEvent")) { + if (!startEvent(event, nestingLevel, EVENT_TYPE_GENERIC_MOTION)) { return; } @@ -568,21 +570,19 @@ public final class InputEventConsistencyVerifier { } private boolean startEvent(InputEvent event, int nestingLevel, String eventType) { - // Ignore the event if it is already tainted. - if (event.isTainted()) { - return false; - } - // Ignore the event if we already checked it at a higher nesting level. - if (event == mLastEvent && nestingLevel < mLastNestingLevel) { + if (event == mLastEvent && nestingLevel < mLastNestingLevel + && eventType == mLastEventType) { return false; } if (nestingLevel > 0) { mLastEvent = event; + mLastEventType = eventType; mLastNestingLevel = nestingLevel; } else { mLastEvent = null; + mLastEventType = null; mLastNestingLevel = 0; } @@ -593,27 +593,30 @@ public final class InputEventConsistencyVerifier { private void finishEvent(boolean tainted) { if (mViolationMessage != null && mViolationMessage.length() != 0) { - mViolationMessage.append("\n in ").append(mCaller); - mViolationMessage.append("\n "); - appendEvent(mViolationMessage, 0, mCurrentEvent, false); - - if (RECENT_EVENTS_TO_LOG != 0 && mRecentEvents != null) { - mViolationMessage.append("\n -- recent events --"); - for (int i = 0; i < RECENT_EVENTS_TO_LOG; i++) { - final int index = (mMostRecentEventIndex + RECENT_EVENTS_TO_LOG - i) - % RECENT_EVENTS_TO_LOG; - final InputEvent event = mRecentEvents[index]; - if (event == null) { - break; + if (!tainted) { + // Write a log message only if the event was not already tainted. + mViolationMessage.append("\n in ").append(mCaller); + mViolationMessage.append("\n "); + appendEvent(mViolationMessage, 0, mCurrentEvent, false); + + if (RECENT_EVENTS_TO_LOG != 0 && mRecentEvents != null) { + mViolationMessage.append("\n -- recent events --"); + for (int i = 0; i < RECENT_EVENTS_TO_LOG; i++) { + final int index = (mMostRecentEventIndex + RECENT_EVENTS_TO_LOG - i) + % RECENT_EVENTS_TO_LOG; + final InputEvent event = mRecentEvents[index]; + if (event == null) { + break; + } + mViolationMessage.append("\n "); + appendEvent(mViolationMessage, i + 1, event, mRecentEventsUnhandled[index]); } - mViolationMessage.append("\n "); - appendEvent(mViolationMessage, i + 1, event, mRecentEventsUnhandled[index]); } - } - Log.d(mLogTag, mViolationMessage.toString()); + Log.d(mLogTag, mViolationMessage.toString()); + tainted = true; + } mViolationMessage.setLength(0); - tainted = true; } if (tainted) { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 4403591..f70ca90 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -2262,6 +2262,8 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit private OnTouchListener mOnTouchListener; + private OnHoverListener mOnHoverListener; + private OnGenericMotionListener mOnGenericMotionListener; private OnDragListener mOnDragListener; @@ -2484,6 +2486,12 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit Rect mLocalDirtyRect; /** + * Set to true when the view is sending hover accessibility events because it + * is the innermost hovered view. + */ + private boolean mSendingHoverAccessibilityEvents; + + /** * Consistency verifier for debugging purposes. * @hide */ @@ -2503,6 +2511,9 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED | LAYOUT_DIRECTION_INHERIT; mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS); + mUserPaddingStart = -1; + mUserPaddingEnd = -1; + mUserPaddingRelative = false; } /** @@ -2864,13 +2875,16 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit mUserPaddingRelative = (startPadding >= 0 || endPadding >= 0); + // Cache user padding as we cannot fully resolve padding here (we dont have yet the resolved + // layout direction). Those cached values will be used later during padding resolution. + mUserPaddingStart = startPadding; + mUserPaddingEnd = endPadding; + if (padding >= 0) { leftPadding = padding; topPadding = padding; rightPadding = padding; bottomPadding = padding; - startPadding = padding; - endPadding = padding; } // If the user specified the padding (either with android:padding or @@ -2882,11 +2896,6 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit rightPadding >= 0 ? rightPadding : mPaddingRight, bottomPadding >= 0 ? bottomPadding : mPaddingBottom); - // Cache user padding as we cannot fully resolve padding here (we dont have yet the resolved - // layout direction). Those cached values will be used later during padding resolution. - mUserPaddingStart = startPadding; - mUserPaddingEnd = endPadding; - if (viewFlagMasks != 0) { setFlags(viewFlagValues, viewFlagMasks); } @@ -5117,9 +5126,6 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit return true; } - if (mInputEventConsistencyVerifier != null) { - mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); - } return false; } @@ -5147,6 +5153,12 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit || action == MotionEvent.ACTION_HOVER_MOVE || action == MotionEvent.ACTION_HOVER_EXIT) { if (dispatchHoverEvent(event)) { + // For compatibility with existing applications that handled HOVER_MOVE + // events in onGenericMotionEvent, dispatch the event there. The + // onHoverEvent method did not exist at the time. + if (action == MotionEvent.ACTION_HOVER_MOVE) { + dispatchGenericMotionEventInternal(event); + } return true; } } else if (dispatchGenericPointerEvent(event)) { @@ -5156,6 +5168,17 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit return true; } + if (dispatchGenericMotionEventInternal(event)) { + return true; + } + + if (mInputEventConsistencyVerifier != null) { + mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); + } + return false; + } + + private boolean dispatchGenericMotionEventInternal(MotionEvent event) { //noinspection SimplifiableIfStatement if (mOnGenericMotionListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnGenericMotionListener.onGenericMotion(this, event)) { @@ -5181,13 +5204,42 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. - * @hide */ protected boolean dispatchHoverEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_HOVER_ENTER: + if (!hasHoveredChild() && !mSendingHoverAccessibilityEvents) { + mSendingHoverAccessibilityEvents = true; + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER); + } + break; + case MotionEvent.ACTION_HOVER_EXIT: + if (mSendingHoverAccessibilityEvents) { + mSendingHoverAccessibilityEvents = false; + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); + } + break; + } + + if (mOnHoverListener != null && (mViewFlags & ENABLED_MASK) == ENABLED + && mOnHoverListener.onHover(this, event)) { + return true; + } + return onHoverEvent(event); } /** + * Returns true if the view has a child to which it has recently sent + * {@link MotionEvent#ACTION_HOVER_ENTER}. If this view is hovered and + * it does not have a hovered child, then it must be the innermost hovered view. + * @hide + */ + protected boolean hasHoveredChild() { + return false; + } + + /** * Dispatch a generic motion event to the view under the first pointer. * <p> * Do not call this method directly. @@ -5196,7 +5248,6 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. - * @hide */ protected boolean dispatchGenericPointerEvent(MotionEvent event) { return false; @@ -5211,7 +5262,6 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. - * @hide */ protected boolean dispatchGenericFocusedEvent(MotionEvent event) { return false; @@ -5788,71 +5838,129 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit /** * Implement this method to handle hover events. * <p> - * Hover events are pointer events with action {@link MotionEvent#ACTION_HOVER_ENTER}, - * {@link MotionEvent#ACTION_HOVER_MOVE}, or {@link MotionEvent#ACTION_HOVER_EXIT}. - * </p><p> - * The view receives hover enter as the pointer enters the bounds of the view and hover - * exit as the pointer exits the bound of the view or just before the pointer goes down - * (which implies that {@link #onTouchEvent(MotionEvent)} will be called soon). - * </p><p> - * If the view would like to handle the hover event itself and prevent its children - * from receiving hover, it should return true from this method. If this method returns - * true and a child has already received a hover enter event, the child will - * automatically receive a hover exit event. + * This method is called whenever a pointer is hovering into, over, or out of the + * bounds of a view and the view is not currently being touched. + * Hover events are represented as pointer events with action + * {@link MotionEvent#ACTION_HOVER_ENTER}, {@link MotionEvent#ACTION_HOVER_MOVE}, + * or {@link MotionEvent#ACTION_HOVER_EXIT}. + * </p> + * <ul> + * <li>The view receives a hover event with action {@link MotionEvent#ACTION_HOVER_ENTER} + * when the pointer enters the bounds of the view.</li> + * <li>The view receives a hover event with action {@link MotionEvent#ACTION_HOVER_MOVE} + * when the pointer has already entered the bounds of the view and has moved.</li> + * <li>The view receives a hover event with action {@link MotionEvent#ACTION_HOVER_EXIT} + * when the pointer has exited the bounds of the view or when the pointer is + * about to go down due to a button click, tap, or similar user action that + * causes the view to be touched.</li> + * </ul> + * <p> + * The view should implement this method to return true to indicate that it is + * handling the hover event, such as by changing its drawable state. * </p><p> - * The default implementation sets the hovered state of the view if the view is - * clickable. + * The default implementation calls {@link #setHovered} to update the hovered state + * of the view when a hover enter or hover exit event is received, if the view + * is enabled and is clickable. * </p> * * @param event The motion event that describes the hover. - * @return True if this view handled the hover event and does not want its children - * to receive the hover event. + * @return True if the view handled the hover event. + * + * @see #isHovered + * @see #setHovered + * @see #onHoverChanged */ public boolean onHoverEvent(MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_HOVER_ENTER: - setHovered(true); - break; + if (isHoverable()) { + switch (event.getAction()) { + case MotionEvent.ACTION_HOVER_ENTER: + setHovered(true); + break; + case MotionEvent.ACTION_HOVER_EXIT: + setHovered(false); + break; + } + return true; + } + return false; + } - case MotionEvent.ACTION_HOVER_EXIT: - setHovered(false); - break; + /** + * Returns true if the view should handle {@link #onHoverEvent} + * by calling {@link #setHovered} to change its hovered state. + * + * @return True if the view is hoverable. + */ + private boolean isHoverable() { + final int viewFlags = mViewFlags; + if ((viewFlags & ENABLED_MASK) == DISABLED) { + return false; } - return false; + return (viewFlags & CLICKABLE) == CLICKABLE + || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE; } /** * Returns true if the view is currently hovered. * * @return True if the view is currently hovered. + * + * @see #setHovered + * @see #onHoverChanged */ + @ViewDebug.ExportedProperty public boolean isHovered() { return (mPrivateFlags & HOVERED) != 0; } /** * Sets whether the view is currently hovered. + * <p> + * Calling this method also changes the drawable state of the view. This + * enables the view to react to hover by using different drawable resources + * to change its appearance. + * </p><p> + * The {@link #onHoverChanged} method is called when the hovered state changes. + * </p> * * @param hovered True if the view is hovered. + * + * @see #isHovered + * @see #onHoverChanged */ public void setHovered(boolean hovered) { if (hovered) { if ((mPrivateFlags & HOVERED) == 0) { mPrivateFlags |= HOVERED; refreshDrawableState(); - sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER); + onHoverChanged(true); } } else { if ((mPrivateFlags & HOVERED) != 0) { mPrivateFlags &= ~HOVERED; refreshDrawableState(); - sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); + onHoverChanged(false); } } } /** + * Implement this method to handle hover state changes. + * <p> + * This method is called whenever the hover state changes as a result of a + * call to {@link #setHovered}. + * </p> + * + * @param hovered The current hover state, as returned by {@link #isHovered}. + * + * @see #isHovered + * @see #setHovered + */ + public void onHoverChanged(boolean hovered) { + } + + /** * Implement this method to handle touch screen motion events. * * @param event The motion event. @@ -8860,12 +8968,12 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit // Start user padding override Left user padding. Otherwise, if Left user // padding is not defined, use the default left padding. If Left user padding // is defined, just use it. - if (mUserPaddingStart >= 0) { + if (mUserPaddingStart > 0) { mUserPaddingLeft = mUserPaddingStart; } else if (mUserPaddingLeft < 0) { mUserPaddingLeft = mPaddingLeft; } - if (mUserPaddingEnd >= 0) { + if (mUserPaddingEnd > 0) { mUserPaddingRight = mUserPaddingEnd; } else if (mUserPaddingRight < 0) { mUserPaddingRight = mPaddingRight; @@ -11026,6 +11134,10 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit */ public void setPaddingRelative(int start, int top, int end, int bottom) { mUserPaddingRelative = true; + + mUserPaddingStart = start; + mUserPaddingEnd = end; + switch(getResolvedLayoutDirection()) { case LAYOUT_DIRECTION_RTL: setPadding(end, top, start, bottom); @@ -13097,6 +13209,24 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit } /** + * Interface definition for a callback to be invoked when a hover event is + * dispatched to this view. The callback will be invoked before the hover + * event is given to the view. + */ + public interface OnHoverListener { + /** + * Called when a hover event is dispatched to a view. This allows listeners to + * get a chance to respond before the target view. + * + * @param v The view the hover event has been dispatched to. + * @param event The MotionEvent object containing full information about + * the event. + * @return True if the listener has consumed the event, false otherwise. + */ + boolean onHover(View v, MotionEvent event); + } + + /** * Interface definition for a callback to be invoked when a generic motion event is * dispatched to this view. The callback will be invoked before the generic motion * event is given to the view. diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index a6bce75..e928f80 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -143,8 +143,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @ViewDebug.ExportedProperty(category = "events") private float mLastTouchDownY; - // Child which last received ACTION_HOVER_ENTER and ACTION_HOVER_MOVE. - private View mHoveredChild; + // First hover target in the linked list of hover targets. + // The hover targets are children which have received ACTION_HOVER_ENTER. + // They might not have actually handled the hover event, but we will + // continue sending hover events to them as long as the pointer remains over + // their bounds and the view group does not intercept hover. + private HoverTarget mFirstHoverTarget; + + // True if the view group itself received a hover event. + // It might not have actually handled the hover event. + private boolean mHoveredSelf; /** * Internal flags. @@ -1222,56 +1230,31 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return false; } - /** @hide */ + /** + * {@inheritDoc} + */ @Override protected boolean dispatchHoverEvent(MotionEvent event) { - // Send the hover enter or hover move event to the view group first. - // If it handles the event then a hovered child should receive hover exit. - boolean handled = false; - final boolean interceptHover; final int action = event.getAction(); - if (action == MotionEvent.ACTION_HOVER_EXIT) { - interceptHover = true; - } else { - handled = super.dispatchHoverEvent(event); - interceptHover = handled; - } - // Send successive hover events to the hovered child as long as the pointer - // remains within the child's bounds. + // First check whether the view group wants to intercept the hover event. + final boolean interceptHover = onInterceptHoverEvent(event); + event.setAction(action); // restore action in case it was changed + MotionEvent eventNoHistory = event; - if (mHoveredChild != null) { + boolean handled = false; + + // Send events to the hovered children and build a new list of hover targets until + // one is found that handles the event. + HoverTarget firstOldHoverTarget = mFirstHoverTarget; + mFirstHoverTarget = null; + if (!interceptHover && action != MotionEvent.ACTION_HOVER_EXIT) { final float x = event.getX(); final float y = event.getY(); - - if (interceptHover - || !isTransformedTouchPointInView(x, y, mHoveredChild, null)) { - // Pointer exited the child. - // Send it a hover exit with only the most recent coordinates. We could - // try to find the exact point in history when the pointer left the view - // but it is not worth the effort. - eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory); - eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT); - handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, mHoveredChild); - eventNoHistory.setAction(action); - mHoveredChild = null; - } else { - // Pointer is still within the child. - //noinspection ConstantConditions - handled |= dispatchTransformedGenericPointerEvent(event, mHoveredChild); - } - } - - // Find a new hovered child if needed. - if (!interceptHover && mHoveredChild == null - && (action == MotionEvent.ACTION_HOVER_ENTER - || action == MotionEvent.ACTION_HOVER_MOVE)) { final int childrenCount = mChildrenCount; if (childrenCount != 0) { final View[] children = mChildren; - final float x = event.getX(); - final float y = event.getY(); - + HoverTarget lastHoverTarget = null; for (int i = childrenCount - 1; i >= 0; i--) { final View child = children[i]; if (!canViewReceivePointerEvents(child) @@ -1279,24 +1262,140 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager continue; } - // Found the hovered child. - mHoveredChild = child; + // Obtain a hover target for this child. Dequeue it from the + // old hover target list if the child was previously hovered. + HoverTarget hoverTarget = firstOldHoverTarget; + final boolean wasHovered; + for (HoverTarget predecessor = null; ;) { + if (hoverTarget == null) { + hoverTarget = HoverTarget.obtain(child); + wasHovered = false; + break; + } + + if (hoverTarget.child == child) { + if (predecessor != null) { + predecessor.next = hoverTarget.next; + } else { + firstOldHoverTarget = hoverTarget.next; + } + hoverTarget.next = null; + wasHovered = true; + break; + } + + predecessor = hoverTarget; + hoverTarget = hoverTarget.next; + } + + // Enqueue the hover target onto the new hover target list. + if (lastHoverTarget != null) { + lastHoverTarget.next = hoverTarget; + } else { + lastHoverTarget = hoverTarget; + mFirstHoverTarget = hoverTarget; + } + + // Dispatch the event to the child. + if (action == MotionEvent.ACTION_HOVER_ENTER) { + if (!wasHovered) { + // Send the enter as is. + handled |= dispatchTransformedGenericPointerEvent( + event, child); // enter + } + } else if (action == MotionEvent.ACTION_HOVER_MOVE) { + if (!wasHovered) { + // Synthesize an enter from a move. + eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory); + eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER); + handled |= dispatchTransformedGenericPointerEvent( + eventNoHistory, child); // enter + eventNoHistory.setAction(action); + + handled |= dispatchTransformedGenericPointerEvent( + eventNoHistory, child); // move + } else { + // Send the move as is. + handled |= dispatchTransformedGenericPointerEvent(event, child); + } + } + if (handled) { + break; + } + } + } + } + + // Send exit events to all previously hovered children that are no longer hovered. + while (firstOldHoverTarget != null) { + final View child = firstOldHoverTarget.child; + + // Exit the old hovered child. + if (action == MotionEvent.ACTION_HOVER_EXIT) { + // Send the exit as is. + handled |= dispatchTransformedGenericPointerEvent( + event, child); // exit + } else { + // Synthesize an exit from a move or enter. + // Ignore the result because hover focus has moved to a different view. + if (action == MotionEvent.ACTION_HOVER_MOVE) { + dispatchTransformedGenericPointerEvent( + event, child); // move + } + eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory); + eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT); + dispatchTransformedGenericPointerEvent( + eventNoHistory, child); // exit + eventNoHistory.setAction(action); + } + + final HoverTarget nextOldHoverTarget = firstOldHoverTarget.next; + firstOldHoverTarget.recycle(); + firstOldHoverTarget = nextOldHoverTarget; + } + + // Send events to the view group itself if no children have handled it. + boolean newHoveredSelf = !handled; + if (newHoveredSelf == mHoveredSelf) { + if (newHoveredSelf) { + // Send event to the view group as before. + handled |= super.dispatchHoverEvent(event); + } + } else { + if (mHoveredSelf) { + // Exit the view group. + if (action == MotionEvent.ACTION_HOVER_EXIT) { + // Send the exit as is. + handled |= super.dispatchHoverEvent(event); // exit + } else { + // Synthesize an exit from a move or enter. + // Ignore the result because hover focus is moving to a different view. if (action == MotionEvent.ACTION_HOVER_MOVE) { - // Pointer was moving within the view group and entered the child. - // Send it a hover enter and hover move with only the most recent - // coordinates. We could try to find the exact point in history when - // the pointer entered the view but it is not worth the effort. - eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory); - eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER); - handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, child); - eventNoHistory.setAction(action); - - handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, child); - } else { /* must be ACTION_HOVER_ENTER */ - // Pointer entered the child. - handled |= dispatchTransformedGenericPointerEvent(event, child); + super.dispatchHoverEvent(event); // move } - break; + eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory); + eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT); + super.dispatchHoverEvent(eventNoHistory); // exit + eventNoHistory.setAction(action); + } + mHoveredSelf = false; + } + + if (newHoveredSelf) { + // Enter the view group. + if (action == MotionEvent.ACTION_HOVER_ENTER) { + // Send the enter as is. + handled |= super.dispatchHoverEvent(event); // enter + mHoveredSelf = true; + } else if (action == MotionEvent.ACTION_HOVER_MOVE) { + // Synthesize an enter from a move. + eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory); + eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER); + handled |= super.dispatchHoverEvent(eventNoHistory); // enter + eventNoHistory.setAction(action); + + handled |= super.dispatchHoverEvent(eventNoHistory); // move + mHoveredSelf = true; } } } @@ -1306,25 +1405,55 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager eventNoHistory.recycle(); } - // Send hover exit to the view group. If there was a child, we will already have - // sent the hover exit to it. - if (action == MotionEvent.ACTION_HOVER_EXIT) { - handled |= super.dispatchHoverEvent(event); - } - // Done. return handled; } + /** @hide */ @Override - public boolean onHoverEvent(MotionEvent event) { - // Handle the event only if leaf. This guarantees that - // the leafs (or any custom class that returns true from - // this method) will get a change to process the hover. - //noinspection SimplifiableIfStatement - if (getChildCount() == 0) { - return super.onHoverEvent(event); - } + protected boolean hasHoveredChild() { + return mFirstHoverTarget != null; + } + + /** + * Implement this method to intercept hover events before they are handled + * by child views. + * <p> + * This method is called before dispatching a hover event to a child of + * the view group or to the view group's own {@link #onHoverEvent} to allow + * the view group a chance to intercept the hover event. + * This method can also be used to watch all pointer motions that occur within + * the bounds of the view group even when the pointer is hovering over + * a child of the view group rather than over the view group itself. + * </p><p> + * The view group can prevent its children from receiving hover events by + * implementing this method and returning <code>true</code> to indicate + * that it would like to intercept hover events. The view group must + * continuously return <code>true</code> from {@link #onInterceptHoverEvent} + * for as long as it wishes to continue intercepting hover events from + * its children. + * </p><p> + * Interception preserves the invariant that at most one view can be + * hovered at a time by transferring hover focus from the currently hovered + * child to the view group or vice-versa as needed. + * </p><p> + * If this method returns <code>true</code> and a child is already hovered, then the + * child view will first receive a hover exit event and then the view group + * itself will receive a hover enter event in {@link #onHoverEvent}. + * Likewise, if this method had previously returned <code>true</code> to intercept hover + * events and instead returns <code>false</code> while the pointer is hovering + * within the bounds of one of a child, then the view group will first receive a + * hover exit event in {@link #onHoverEvent} and then the hovered child will + * receive a hover enter event. + * </p><p> + * The default implementation always returns false. + * </p> + * + * @param event The motion event that describes the hover. + * @return True if the view group would like to intercept the hover event + * and prevent its children from receiving it. + */ + public boolean onInterceptHoverEvent(MotionEvent event) { return false; } @@ -1335,7 +1464,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return MotionEvent.obtainNoHistory(event); } - /** @hide */ + /** + * {@inheritDoc} + */ @Override protected boolean dispatchGenericPointerEvent(MotionEvent event) { // Send the event to the child under the pointer. @@ -1362,7 +1493,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return super.dispatchGenericPointerEvent(event); } - /** @hide */ + /** + * {@inheritDoc} + */ @Override protected boolean dispatchGenericFocusedEvent(MotionEvent event) { // Send the event to the focused child or to this view group if it has focus. @@ -3337,10 +3470,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager mTransition.removeChild(this, view); } - if (view == mHoveredChild) { - mHoveredChild = null; - } - boolean clearChildFocus = false; if (view == mFocused) { view.clearFocusForRemoval(); @@ -3404,7 +3533,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final OnHierarchyChangeListener onHierarchyChangeListener = mOnHierarchyChangeListener; final boolean notifyListener = onHierarchyChangeListener != null; final View focused = mFocused; - final View hoveredChild = mHoveredChild; final boolean detach = mAttachInfo != null; View clearChildFocus = null; @@ -3418,10 +3546,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager mTransition.removeChild(this, view); } - if (view == hoveredChild) { - mHoveredChild = null; - } - if (view == focused) { view.clearFocusForRemoval(); clearChildFocus = view; @@ -3479,7 +3603,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final OnHierarchyChangeListener listener = mOnHierarchyChangeListener; final boolean notify = listener != null; final View focused = mFocused; - final View hoveredChild = mHoveredChild; final boolean detach = mAttachInfo != null; View clearChildFocus = null; @@ -3492,10 +3615,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager mTransition.removeChild(this, view); } - if (view == hoveredChild) { - mHoveredChild = null; - } - if (view == focused) { view.clearFocusForRemoval(); clearChildFocus = view; @@ -5242,4 +5361,50 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } } + + /* Describes a hovered view. */ + private static final class HoverTarget { + private static final int MAX_RECYCLED = 32; + private static final Object sRecycleLock = new Object(); + private static HoverTarget sRecycleBin; + private static int sRecycledCount; + + // The hovered child view. + public View child; + + // The next target in the target list. + public HoverTarget next; + + private HoverTarget() { + } + + public static HoverTarget obtain(View child) { + final HoverTarget target; + synchronized (sRecycleLock) { + if (sRecycleBin == null) { + target = new HoverTarget(); + } else { + target = sRecycleBin; + sRecycleBin = target.next; + sRecycledCount--; + target.next = null; + } + } + target.child = child; + return target; + } + + public void recycle() { + synchronized (sRecycleLock) { + if (sRecycledCount < MAX_RECYCLED) { + next = sRecycleBin; + sRecycleBin = this; + sRecycledCount += 1; + } else { + next = null; + } + child = null; + } + } + } } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 18ef38a..555667b 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -252,7 +252,8 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Finds {@link AccessibilityNodeInfo}s by text. The match is case - * insensitive containment. + * insensitive containment. The search is relative to this info i.e. + * this info is the root of the traversed tree. * * @param text The searched text. * @return A list of node info. @@ -260,7 +261,7 @@ public class AccessibilityNodeInfo implements Parcelable { public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) { enforceSealed(); if (!canPerformRequestOverConnection(mAccessibilityViewId)) { - return null; + return Collections.emptyList(); } try { return mConnection.findAccessibilityNodeInfosByViewText(text, mAccessibilityWindowId, diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index c56e6db..9f632d1 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -5707,8 +5707,8 @@ public class WebView extends AbsoluteLayout return false; } WebViewCore.CursorData data = cursorDataNoPosition(); - data.mX = viewToContentX((int) event.getX()); - data.mY = viewToContentY((int) event.getY()); + data.mX = viewToContentX((int) event.getX() + mScrollX); + data.mY = viewToContentY((int) event.getY() + mScrollY); mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data); return true; } diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index 0e52869..e88d257 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -535,7 +535,7 @@ public class GridLayout extends ViewGroup { return result; } - private int getDefaultMargin(View c, boolean leading, boolean horizontal) { + private int getDefaultMargin(View c, boolean horizontal, boolean leading) { // In the absence of any other information, calculate a default gap such // that, in a grid of identical components, the heights and the vertical // gaps are in the proportion of the golden ratio. @@ -544,12 +544,12 @@ public class GridLayout extends ViewGroup { return (int) (c.getMeasuredHeight() / GOLDEN_RATIO / 2); } - private int getDefaultMargin(View c, boolean isAtEdge, boolean leading, boolean horizontal) { + private int getDefaultMargin(View c, boolean isAtEdge, boolean horizontal, boolean leading) { // todo remove DEFAULT_CONTAINER_MARGIN. Use padding? Seek advice on Themes/Styles, etc. - return isAtEdge ? DEFAULT_CONTAINER_MARGIN : getDefaultMargin(c, leading, horizontal); + return isAtEdge ? DEFAULT_CONTAINER_MARGIN : getDefaultMargin(c, horizontal, leading); } - private int getDefaultMarginValue(View c, LayoutParams p, boolean leading, boolean horizontal) { + private int getDefaultMarginValue(View c, LayoutParams p, boolean horizontal, boolean leading) { if (!mUseDefaultMargins) { return 0; } @@ -558,15 +558,19 @@ public class GridLayout extends ViewGroup { Interval span = group.span; boolean isAtEdge = leading ? (span.min == 0) : (span.max == axis.getCount()); - return getDefaultMargin(c, isAtEdge, leading, horizontal); + return getDefaultMargin(c, isAtEdge, horizontal, leading); } - private int getMargin(View view, boolean leading, boolean horizontal) { + private int getMargin(View view, boolean horizontal, boolean leading) { LayoutParams lp = getLayoutParams(view); int margin = horizontal ? (leading ? lp.leftMargin : lp.rightMargin) : (leading ? lp.topMargin : lp.bottomMargin); - return margin == UNDEFINED ? getDefaultMarginValue(view, lp, leading, horizontal) : margin; + return margin == UNDEFINED ? getDefaultMarginValue(view, lp, horizontal, leading) : margin; + } + + private int getTotalMargin(View child, boolean horizontal) { + return getMargin(child, horizontal, true) + getMargin(child, horizontal, false); } private static int valueIfDefined(int value, int defaultValue) { @@ -749,8 +753,8 @@ public class GridLayout extends ViewGroup { View c = getChildAt(i); drawRectangle(canvas, c.getLeft() - getMargin(c, true, true), - c.getTop() - getMargin(c, true, false), - c.getRight() + getMargin(c, false, true), + c.getTop() - getMargin(c, false, true), + c.getRight() + getMargin(c, true, false), c.getBottom() + getMargin(c, false, false), paint); } } @@ -794,17 +798,12 @@ public class GridLayout extends ViewGroup { return c.getVisibility() == View.GONE; } - private void measureChildWithMargins(View child, - int parentWidthMeasureSpec, int parentHeightMeasureSpec) { - + private void measureChildWithMargins(View child, int widthMeasureSpec, int heightMeasureSpec) { LayoutParams lp = getLayoutParams(child); - int hMargins = getMargin(child, true, true) + getMargin(child, false, true); - int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, - mPaddingLeft + mPaddingRight + hMargins, lp.width); - int vMargins = getMargin(child, true, false) + getMargin(child, false, false); - int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, - mPaddingTop + mPaddingBottom + vMargins, lp.height); - + int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, + mPaddingLeft + mPaddingRight + getTotalMargin(child, true), lp.width); + int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, + mPaddingTop + mPaddingBottom + getTotalMargin(child, false), lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } @@ -842,9 +841,7 @@ public class GridLayout extends ViewGroup { private int getMeasurementIncludingMargin(View c, boolean horizontal, int measurementType) { int result = getMeasurement(c, horizontal, measurementType); if (mAlignmentMode == ALIGN_MARGINS) { - int leadingMargin = getMargin(c, true, horizontal); - int trailingMargin = getMargin(c, false, horizontal); - return result + leadingMargin + trailingMargin; + return result + getTotalMargin(c, horizontal); } return result; } @@ -919,8 +916,8 @@ public class GridLayout extends ViewGroup { if (mAlignmentMode == ALIGN_MARGINS) { int leftMargin = getMargin(c, true, true); - int topMargin = getMargin(c, true, false); - int rightMargin = getMargin(c, false, true); + int topMargin = getMargin(c, false, true); + int rightMargin = getMargin(c, true, false); int bottomMargin = getMargin(c, false, false); // Same calculation as getMeasurementIncludingMargin() @@ -1387,7 +1384,7 @@ public class GridLayout extends ViewGroup { Group g = horizontal ? lp.columnGroup : lp.rowGroup; Interval span = g.span; int index = leading ? span.min : span.max; - margins[index] = max(margins[index], getMargin(c, leading, horizontal)); + margins[index] = max(margins[index], getMargin(c, horizontal, leading)); } } @@ -1817,7 +1814,8 @@ public class GridLayout extends ViewGroup { } private int getDefaultWeight(int size) { - return (size == MATCH_PARENT) ? DEFAULT_WEIGHT_1 : DEFAULT_WEIGHT_0; + //return (size == MATCH_PARENT) ? DEFAULT_WEIGHT_1 : DEFAULT_WEIGHT_0; + return DEFAULT_WEIGHT_0; } private void init(Context context, AttributeSet attrs, int defaultGravity) { diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java index 82770ad..17512d8 100644 --- a/core/java/android/widget/PopupMenu.java +++ b/core/java/android/widget/PopupMenu.java @@ -18,6 +18,7 @@ package android.widget; import com.android.internal.view.menu.MenuBuilder; import com.android.internal.view.menu.MenuPopupHelper; +import com.android.internal.view.menu.MenuPresenter; import com.android.internal.view.menu.SubMenuBuilder; import android.content.Context; @@ -32,12 +33,25 @@ import android.view.View; * If the IME is visible the popup will not overlap it until it is touched. Touching outside * of the popup will dismiss it. */ -public class PopupMenu implements MenuBuilder.Callback { +public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { private Context mContext; private MenuBuilder mMenu; private View mAnchor; private MenuPopupHelper mPopup; private OnMenuItemClickListener mMenuItemClickListener; + private OnDismissListener mDismissListener; + + /** + * Callback interface used to notify the application that the menu has closed. + */ + public interface OnDismissListener { + /** + * Called when the associated menu has been dismissed. + * + * @param menu The PopupMenu that was dismissed. + */ + public void onDismiss(PopupMenu menu); + } /** * Construct a new PopupMenu. @@ -53,6 +67,7 @@ public class PopupMenu implements MenuBuilder.Callback { mMenu.setCallback(this); mAnchor = anchor; mPopup = new MenuPopupHelper(context, mMenu, anchor); + mPopup.setCallback(this); } /** @@ -77,6 +92,15 @@ public class PopupMenu implements MenuBuilder.Callback { } /** + * Inflate a menu resource into this PopupMenu. This is equivalent to calling + * popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu()). + * @param menuRes Menu resource to inflate + */ + public void inflate(int menuRes) { + getMenuInflater().inflate(menuRes, mMenu); + } + + /** * Show the menu popup anchored to the view specified during construction. * @see #dismiss() */ @@ -92,11 +116,25 @@ public class PopupMenu implements MenuBuilder.Callback { mPopup.dismiss(); } + /** + * Set a listener that will be notified when the user selects an item from the menu. + * + * @param listener Listener to notify + */ public void setOnMenuItemClickListener(OnMenuItemClickListener listener) { mMenuItemClickListener = listener; } /** + * Set a listener that will be notified when this menu is dismissed. + * + * @param listener Listener to notify + */ + public void setOnDismissListener(OnDismissListener listener) { + mDismissListener = listener; + } + + /** * @hide */ public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { @@ -110,12 +148,15 @@ public class PopupMenu implements MenuBuilder.Callback { * @hide */ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + if (mDismissListener != null) { + mDismissListener.onDismiss(this); + } } /** * @hide */ - public boolean onSubMenuSelected(SubMenuBuilder subMenu) { + public boolean onOpenSubMenu(MenuBuilder subMenu) { if (!subMenu.hasVisibleItems()) { return true; } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 02c2b8f..77df7c8 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -7658,12 +7658,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } @Override - public void findViewsWithText(ArrayList<View> outViews, CharSequence text) { + public void findViewsWithText(ArrayList<View> outViews, CharSequence searched) { + if (TextUtils.isEmpty(searched)) { + return; + } CharSequence thisText = getText(); if (TextUtils.isEmpty(thisText)) { return; } - if (thisText.toString().toLowerCase().contains(text)) { + String searchedLowerCase = searched.toString().toLowerCase(); + String thisTextLowerCase = thisText.toString().toLowerCase(); + if (thisTextLowerCase.contains(searched)) { outViews.add(this); } } diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java index 98c2747..322a854 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java @@ -336,6 +336,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter { if (groupId != 0) { seenGroups.put(groupId, true); } + item.setIsActionButton(true); } else if (item.requestsActionButton()) { // Items in a group with other items that already have an action slot // can break the max actions rule, but not the width limit. diff --git a/core/java/com/android/internal/view/menu/ListMenuPresenter.java b/core/java/com/android/internal/view/menu/ListMenuPresenter.java index cc09927..27e4191 100644 --- a/core/java/com/android/internal/view/menu/ListMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/ListMenuPresenter.java @@ -184,12 +184,12 @@ public class ListMenuPresenter implements MenuPresenter, AdapterView.OnItemClick private class MenuAdapter extends BaseAdapter { public int getCount() { - ArrayList<MenuItemImpl> items = mMenu.getVisibleItems(); + ArrayList<MenuItemImpl> items = mMenu.getNonActionItems(); return items.size() - mItemIndexOffset; } public MenuItemImpl getItem(int position) { - ArrayList<MenuItemImpl> items = mMenu.getVisibleItems(); + ArrayList<MenuItemImpl> items = mMenu.getNonActionItems(); return items.get(position + mItemIndexOffset); } diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java index 1a6cc54..253511c 100644 --- a/core/java/com/android/internal/view/menu/MenuItemImpl.java +++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java @@ -507,7 +507,7 @@ public final class MenuItemImpl implements MenuItem { } public boolean isActionButton() { - return (mFlags & IS_ACTION) == IS_ACTION || requiresActionButton(); + return (mFlags & IS_ACTION) == IS_ACTION; } public boolean requestsActionButton() { diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java index 8db7e3c..cffbb4e 100644 --- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java +++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java @@ -126,6 +126,7 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On public void onDismiss() { mPopup = null; + mMenu.close(); if (mTreeObserver != null) { if (!mTreeObserver.isAlive()) mTreeObserver = mAnchorView.getViewTreeObserver(); mTreeObserver.removeGlobalOnLayoutListener(this); diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index cbb110a..0c0205c 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -464,8 +464,7 @@ public class LockPatternView extends View { break; case MeasureSpec.EXACTLY: default: - // use the specified size, if non-zero - result = specSize != 0 ? specSize : desired; + result = specSize; } return result; } diff --git a/core/jni/android_pim_EventRecurrence.cpp b/core/jni/android_pim_EventRecurrence.cpp index 44e898d..3e11569 100644 --- a/core/jni/android_pim_EventRecurrence.cpp +++ b/core/jni/android_pim_EventRecurrence.cpp @@ -147,7 +147,7 @@ EventRecurrence_parse(JNIEnv* env, jobject This, jstring jstr) */
static JNINativeMethod METHODS[] = {
/* name, signature, funcPtr */
- { "parse", "(Ljava/lang/String;)V", (void*)EventRecurrence_parse }
+ { "parseNative", "(Ljava/lang/String;)V", (void*)EventRecurrence_parse }
};
static const char*const CLASS_NAME = "android/pim/EventRecurrence";
diff --git a/core/res/res/drawable-hdpi/ic_notification_ime_default.png b/core/res/res/drawable-hdpi/ic_notification_ime_default.png Binary files differnew file mode 100644 index 0000000..1a9d88c --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_notification_ime_default.png diff --git a/core/res/res/drawable-mdpi/ic_notification_ime_default.png b/core/res/res/drawable-mdpi/ic_notification_ime_default.png Binary files differnew file mode 100644 index 0000000..1a9d88c --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_notification_ime_default.png diff --git a/core/res/res/layout/keyguard_screen_unlock_portrait.xml b/core/res/res/layout/keyguard_screen_unlock_portrait.xml index dd68d82..03c6022 100644 --- a/core/res/res/layout/keyguard_screen_unlock_portrait.xml +++ b/core/res/res/layout/keyguard_screen_unlock_portrait.xml @@ -101,6 +101,10 @@ android:visibility="gone" /> + <!-- We need MATCH_PARENT here only to force the size of the parent to be passed to + the pattern view for it to compute its size. This is an unusual case, caused by + LockPatternView's requirement to maintain a square aspect ratio based on the width + of the screen. --> <com.android.internal.widget.LockPatternView android:id="@+id/lockPattern" android:layout_width="match_parent" @@ -109,6 +113,8 @@ android:layout_marginRight="8dip" android:layout_marginBottom="4dip" android:layout_marginLeft="8dip" + android:layout_gravity="center|bottom" + android:layout_rowWeight="1" /> <TextView @@ -123,8 +129,7 @@ <!-- Footer: an emergency call button and an initially hidden "Forgot pattern" button --> <LinearLayout android:orientation="horizontal" - android:layout_width="match_parent" - android:layout_gravity="center"> + android:layout_gravity="fill_horizontal"> <Button android:id="@+id/emergencyCallButton" android:layout_width="wrap_content" diff --git a/core/res/res/values-sw600dp/bools.xml b/core/res/res/values-sw600dp/bools.xml index 734031f..d73ff99 100644 --- a/core/res/res/values-sw600dp/bools.xml +++ b/core/res/res/values-sw600dp/bools.xml @@ -16,4 +16,5 @@ <resources> <bool name="preferences_prefer_dual_pane">true</bool> + <bool name="show_ongoing_ime_switcher">false</bool> </resources> diff --git a/core/res/res/values/bools.xml b/core/res/res/values/bools.xml index 9d6309d..9647bb7 100644 --- a/core/res/res/values/bools.xml +++ b/core/res/res/values/bools.xml @@ -19,4 +19,5 @@ <bool name="action_bar_embed_tabs">false</bool> <bool name="split_action_bar_is_narrow">true</bool> <bool name="preferences_prefer_dual_pane">false</bool> + <bool name="show_ongoing_ime_switcher">true</bool> </resources> diff --git a/core/tests/coretests/src/android/pim/EventRecurrenceTest.java b/core/tests/coretests/src/android/pim/EventRecurrenceTest.java new file mode 100644 index 0000000..05000f1 --- /dev/null +++ b/core/tests/coretests/src/android/pim/EventRecurrenceTest.java @@ -0,0 +1,753 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.pim; + +import android.pim.EventRecurrence.InvalidFormatException; +import android.test.suitebuilder.annotation.SmallTest; +import android.test.suitebuilder.annotation.Suppress; + +import junit.framework.TestCase; + +import java.util.Arrays; + +/** + * Test android.pim.EventRecurrence. + * + * adb shell am instrument -w -e class android.pim.EventRecurrenceTest \ + * com.android.frameworks.coretests/android.test.InstrumentationTestRunner + */ +public class EventRecurrenceTest extends TestCase { + + @SmallTest + public void test0() throws Exception { + verifyRecurType("FREQ=SECONDLY", + /* int freq */ EventRecurrence.SECONDLY, + /* String until */ null, + /* int count */ 0, + /* int interval */ 0, + /* int[] bysecond */ null, + /* int[] byminute */ null, + /* int[] byhour */ null, + /* int[] byday */ null, + /* int[] bydayNum */ null, + /* int[] bymonthday */ null, + /* int[] byyearday */ null, + /* int[] byweekno */ null, + /* int[] bymonth */ null, + /* int[] bysetpos */ null, + /* int wkst */ EventRecurrence.MO + ); + } + + @SmallTest + public void test1() throws Exception { + verifyRecurType("FREQ=MINUTELY", + /* int freq */ EventRecurrence.MINUTELY, + /* String until */ null, + /* int count */ 0, + /* int interval */ 0, + /* int[] bysecond */ null, + /* int[] byminute */ null, + /* int[] byhour */ null, + /* int[] byday */ null, + /* int[] bydayNum */ null, + /* int[] bymonthday */ null, + /* int[] byyearday */ null, + /* int[] byweekno */ null, + /* int[] bymonth */ null, + /* int[] bysetpos */ null, + /* int wkst */ EventRecurrence.MO + ); + } + + @SmallTest + public void test2() throws Exception { + verifyRecurType("FREQ=HOURLY", + /* int freq */ EventRecurrence.HOURLY, + /* String until */ null, + /* int count */ 0, + /* int interval */ 0, + /* int[] bysecond */ null, + /* int[] byminute */ null, + /* int[] byhour */ null, + /* int[] byday */ null, + /* int[] bydayNum */ null, + /* int[] bymonthday */ null, + /* int[] byyearday */ null, + /* int[] byweekno */ null, + /* int[] bymonth */ null, + /* int[] bysetpos */ null, + /* int wkst */ EventRecurrence.MO + ); + } + + @SmallTest + public void test3() throws Exception { + verifyRecurType("FREQ=DAILY", + /* int freq */ EventRecurrence.DAILY, + /* String until */ null, + /* int count */ 0, + /* int interval */ 0, + /* int[] bysecond */ null, + /* int[] byminute */ null, + /* int[] byhour */ null, + /* int[] byday */ null, + /* int[] bydayNum */ null, + /* int[] bymonthday */ null, + /* int[] byyearday */ null, + /* int[] byweekno */ null, + /* int[] bymonth */ null, + /* int[] bysetpos */ null, + /* int wkst */ EventRecurrence.MO + ); + } + + @SmallTest + public void test4() throws Exception { + verifyRecurType("FREQ=WEEKLY", + /* int freq */ EventRecurrence.WEEKLY, + /* String until */ null, + /* int count */ 0, + /* int interval */ 0, + /* int[] bysecond */ null, + /* int[] byminute */ null, + /* int[] byhour */ null, + /* int[] byday */ null, + /* int[] bydayNum */ null, + /* int[] bymonthday */ null, + /* int[] byyearday */ null, + /* int[] byweekno */ null, + /* int[] bymonth */ null, + /* int[] bysetpos */ null, + /* int wkst */ EventRecurrence.MO + ); + } + + @SmallTest + public void test5() throws Exception { + verifyRecurType("FREQ=MONTHLY", + /* int freq */ EventRecurrence.MONTHLY, + /* String until */ null, + /* int count */ 0, + /* int interval */ 0, + /* int[] bysecond */ null, + /* int[] byminute */ null, + /* int[] byhour */ null, + /* int[] byday */ null, + /* int[] bydayNum */ null, + /* int[] bymonthday */ null, + /* int[] byyearday */ null, + /* int[] byweekno */ null, + /* int[] bymonth */ null, + /* int[] bysetpos */ null, + /* int wkst */ EventRecurrence.MO + ); + } + + @SmallTest + public void test6() throws Exception { + verifyRecurType("FREQ=YEARLY", + /* int freq */ EventRecurrence.YEARLY, + /* String until */ null, + /* int count */ 0, + /* int interval */ 0, + /* int[] bysecond */ null, + /* int[] byminute */ null, + /* int[] byhour */ null, + /* int[] byday */ null, + /* int[] bydayNum */ null, + /* int[] bymonthday */ null, + /* int[] byyearday */ null, + /* int[] byweekno */ null, + /* int[] bymonth */ null, + /* int[] bysetpos */ null, + /* int wkst */ EventRecurrence.MO + ); + } + + @SmallTest + public void test7() throws Exception { + // with an until + verifyRecurType("FREQ=DAILY;UNTIL=112233T223344Z", + /* int freq */ EventRecurrence.DAILY, + /* String until */ "112233T223344Z", + /* int count */ 0, + /* int interval */ 0, + /* int[] bysecond */ null, + /* int[] byminute */ null, + /* int[] byhour */ null, + /* int[] byday */ null, + /* int[] bydayNum */ null, + /* int[] bymonthday */ null, + /* int[] byyearday */ null, + /* int[] byweekno */ null, + /* int[] bymonth */ null, + /* int[] bysetpos */ null, + /* int wkst */ EventRecurrence.MO + ); + } + + @SmallTest + public void test8() throws Exception { + // with a count + verifyRecurType("FREQ=DAILY;COUNT=334", + /* int freq */ EventRecurrence.DAILY, + /* String until */ null, + /* int count */ 334, + /* int interval */ 0, + /* int[] bysecond */ null, + /* int[] byminute */ null, + /* int[] byhour */ null, + /* int[] byday */ null, + /* int[] bydayNum */ null, + /* int[] bymonthday */ null, + /* int[] byyearday */ null, + /* int[] byweekno */ null, + /* int[] bymonth */ null, + /* int[] bysetpos */ null, + /* int wkst */ EventRecurrence.MO + ); + } + + @SmallTest + public void test9() throws Exception { + // with a count + verifyRecurType("FREQ=DAILY;INTERVAL=5000", + /* int freq */ EventRecurrence.DAILY, + /* String until */ null, + /* int count */ 0, + /* int interval */ 5000, + /* int[] bysecond */ null, + /* int[] byminute */ null, + /* int[] byhour */ null, + /* int[] byday */ null, + /* int[] bydayNum */ null, + /* int[] bymonthday */ null, + /* int[] byyearday */ null, + /* int[] byweekno */ null, + /* int[] bymonth */ null, + /* int[] bysetpos */ null, + /* int wkst */ EventRecurrence.MO + ); + } + + @SmallTest + public void test10() throws Exception { + // verifyRecurType all of the BY* ones with one element + verifyRecurType("FREQ=DAILY" + + ";BYSECOND=0" + + ";BYMINUTE=1" + + ";BYHOUR=2" + + ";BYMONTHDAY=30" + + ";BYYEARDAY=300" + + ";BYWEEKNO=53" + + ";BYMONTH=12" + + ";BYSETPOS=-15" + + ";WKST=SU", + /* int freq */ EventRecurrence.DAILY, + /* String until */ null, + /* int count */ 0, + /* int interval */ 0, + /* int[] bysecond */ new int[]{0}, + /* int[] byminute */ new int[]{1}, + /* int[] byhour */ new int[]{2}, + /* int[] byday */ null, + /* int[] bydayNum */ null, + /* int[] bymonthday */ new int[]{30}, + /* int[] byyearday */ new int[]{300}, + /* int[] byweekno */ new int[]{53}, + /* int[] bymonth */ new int[]{12}, + /* int[] bysetpos */ new int[]{-15}, + /* int wkst */ EventRecurrence.SU + ); + } + + @SmallTest + public void test11() throws Exception { + // verifyRecurType all of the BY* ones with one element + verifyRecurType("FREQ=DAILY" + + ";BYSECOND=0,30,59" + + ";BYMINUTE=0,41,59" + + ";BYHOUR=0,4,23" + + ";BYMONTHDAY=-31,-1,1,31" + + ";BYYEARDAY=-366,-1,1,366" + + ";BYWEEKNO=-53,-1,1,53" + + ";BYMONTH=1,12" + + ";BYSETPOS=1,2,3,4,500,10000" + + ";WKST=SU", + /* int freq */ EventRecurrence.DAILY, + /* String until */ null, + /* int count */ 0, + /* int interval */ 0, + /* int[] bysecond */ new int[]{0, 30, 59}, + /* int[] byminute */ new int[]{0, 41, 59}, + /* int[] byhour */ new int[]{0, 4, 23}, + /* int[] byday */ null, + /* int[] bydayNum */ null, + /* int[] bymonthday */ new int[]{-31, -1, 1, 31}, + /* int[] byyearday */ new int[]{-366, -1, 1, 366}, + /* int[] byweekno */ new int[]{-53, -1, 1, 53}, + /* int[] bymonth */ new int[]{1, 12}, + /* int[] bysetpos */ new int[]{1, 2, 3, 4, 500, 10000}, + /* int wkst */ EventRecurrence.SU + ); + } + + private static class Check { + Check(String k, int... v) { + key = k; + values = v; + } + + String key; + int[] values; + } + + // this is a negative verifyRecurType case to verifyRecurType the range of the numbers accepted + @SmallTest + public void test12() throws Exception { + Check[] checks = new Check[]{ + new Check("BYSECOND", -100, -1, 60, 100), + new Check("BYMINUTE", -100, -1, 60, 100), + new Check("BYHOUR", -100, -1, 24, 100), + new Check("BYMONTHDAY", -100, -32, 0, 32, 100), + new Check("BYYEARDAY", -400, -367, 0, 367, 400), + new Check("BYWEEKNO", -100, -54, 0, 54, 100), + new Check("BYMONTH", -100, -5, 0, 13, 100) + }; + + for (Check ck : checks) { + for (int n : ck.values) { + String recur = "FREQ=DAILY;" + ck.key + "=" + n; + try { + EventRecurrence er = new EventRecurrence(); + er.parse(recur); + fail("Negative verifyRecurType failed. " + + " parse failed to throw an exception for '" + + recur + "'"); + } catch (EventRecurrence.InvalidFormatException e) { + // expected + } + } + } + } + + // verifyRecurType BYDAY + @SmallTest + public void test13() throws Exception { + verifyRecurType("FREQ=DAILY;BYDAY=1SU,-2MO,+33TU,WE,TH,FR,SA", + /* int freq */ EventRecurrence.DAILY, + /* String until */ null, + /* int count */ 0, + /* int interval */ 0, + /* int[] bysecond */ null, + /* int[] byminute */ null, + /* int[] byhour */ null, + /* int[] byday */ new int[] { + EventRecurrence.SU, + EventRecurrence.MO, + EventRecurrence.TU, + EventRecurrence.WE, + EventRecurrence.TH, + EventRecurrence.FR, + EventRecurrence.SA + }, + /* int[] bydayNum */ new int[]{1, -2, 33, 0, 0, 0, 0}, + /* int[] bymonthday */ null, + /* int[] byyearday */ null, + /* int[] byweekno */ null, + /* int[] bymonth */ null, + /* int[] bysetpos */ null, + /* int wkst */ EventRecurrence.MO + ); + } + + @Suppress + // Repro bug #2331761 - this should fail because of the last comma into BYDAY + public void test14() throws Exception { + verifyRecurType("FREQ=WEEKLY;WKST=MO;UNTIL=20100129T130000Z;INTERVAL=1;BYDAY=MO,TU,WE,", + /* int freq */ EventRecurrence.WEEKLY, + /* String until */ "20100129T130000Z", + /* int count */ 0, + /* int interval */ 1, + /* int[] bysecond */ null, + /* int[] byminute */ null, + /* int[] byhour */ null, + /* int[] byday */ new int[] { + EventRecurrence.MO, + EventRecurrence.TU, + EventRecurrence.WE, + }, + /* int[] bydayNum */ new int[]{0, 0, 0}, + /* int[] bymonthday */ null, + /* int[] byyearday */ null, + /* int[] byweekno */ null, + /* int[] bymonth */ null, + /* int[] bysetpos */ null, + /* int wkst */ EventRecurrence.MO + ); + } + + // This test should pass + public void test15() throws Exception { + verifyRecurType("FREQ=WEEKLY;WKST=MO;UNTIL=20100129T130000Z;INTERVAL=1;" + + "BYDAY=MO,TU,WE,TH,FR,SA,SU", + /* int freq */ EventRecurrence.WEEKLY, + /* String until */ "20100129T130000Z", + /* int count */ 0, + /* int interval */ 1, + /* int[] bysecond */ null, + /* int[] byminute */ null, + /* int[] byhour */ null, + /* int[] byday */ new int[] { + EventRecurrence.MO, + EventRecurrence.TU, + EventRecurrence.WE, + EventRecurrence.TH, + EventRecurrence.FR, + EventRecurrence.SA, + EventRecurrence.SU + }, + /* int[] bydayNum */ new int[]{0, 0, 0, 0, 0, 0, 0}, + /* int[] bymonthday */ null, + /* int[] byyearday */ null, + /* int[] byweekno */ null, + /* int[] bymonth */ null, + /* int[] bysetpos */ null, + /* int wkst */ EventRecurrence.MO + ); + } + + // Sample coming from RFC2445 + public void test16() throws Exception { + verifyRecurType("FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1", + /* int freq */ EventRecurrence.MONTHLY, + /* String until */ null, + /* int count */ 0, + /* int interval */ 0, + /* int[] bysecond */ null, + /* int[] byminute */ null, + /* int[] byhour */ null, + /* int[] byday */ new int[] { + EventRecurrence.MO, + EventRecurrence.TU, + EventRecurrence.WE, + EventRecurrence.TH, + EventRecurrence.FR + }, + /* int[] bydayNum */ new int[] {0, 0, 0, 0, 0}, + /* int[] bymonthday */ null, + /* int[] byyearday */ null, + /* int[] byweekno */ null, + /* int[] bymonth */ null, + /* int[] bysetpos */ new int[] { -1 }, + /* int wkst */ EventRecurrence.MO + ); + } + + // Sample coming from RFC2445 + public void test17() throws Exception { + verifyRecurType("FREQ=DAILY;COUNT=10;INTERVAL=2", + /* int freq */ EventRecurrence.DAILY, + /* String until */ null, + /* int count */ 10, + /* int interval */ 2, + /* int[] bysecond */ null, + /* int[] byminute */ null, + /* int[] byhour */ null, + /* int[] byday */ null, + /* int[] bydayNum */ null, + /* int[] bymonthday */ null, + /* int[] byyearday */ null, + /* int[] byweekno */ null, + /* int[] bymonth */ null, + /* int[] bysetpos */ null, + /* int wkst */ EventRecurrence.MO + ); + } + + // Sample coming from RFC2445 + public void test18() throws Exception { + verifyRecurType("FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10", + /* int freq */ EventRecurrence.YEARLY, + /* String until */ null, + /* int count */ 0, + /* int interval */ 0, + /* int[] bysecond */ null, + /* int[] byminute */ null, + /* int[] byhour */ null, + /* int[] byday */ new int[] { + EventRecurrence.SU + }, + /* int[] bydayNum */ new int[] { -1 }, + /* int[] bymonthday */ null, + /* int[] byyearday */ null, + /* int[] byweekno */ null, + /* int[] bymonth */ new int[] { 10 }, + /* int[] bysetpos */ null, + /* int wkst */ EventRecurrence.MO + ); + } + + // Sample coming from bug #1640517 + public void test19() throws Exception { + verifyRecurType("FREQ=YEARLY;BYMONTH=3;BYDAY=TH", + /* int freq */ EventRecurrence.YEARLY, + /* String until */ null, + /* int count */ 0, + /* int interval */ 0, + /* int[] bysecond */ null, + /* int[] byminute */ null, + /* int[] byhour */ null, + /* int[] byday */ new int[] { + EventRecurrence.TH + }, + /* int[] bydayNum */ new int[] { 0 }, + /* int[] bymonthday */ null, + /* int[] byyearday */ null, + /* int[] byweekno */ null, + /* int[] bymonth */ new int[] { 3 }, + /* int[] bysetpos */ null, + /* int wkst */ EventRecurrence.MO + ); + } + + // for your copying pleasure + public void fakeTestXX() throws Exception { + verifyRecurType("FREQ=DAILY;", + /* int freq */ EventRecurrence.DAILY, + /* String until */ null, + /* int count */ 0, + /* int interval */ 0, + /* int[] bysecond */ null, + /* int[] byminute */ null, + /* int[] byhour */ null, + /* int[] byday */ null, + /* int[] bydayNum */ null, + /* int[] bymonthday */ null, + /* int[] byyearday */ null, + /* int[] byweekno */ null, + /* int[] bymonth */ null, + /* int[] bysetpos */ null, + /* int wkst */ EventRecurrence.MO + ); + } + + private static void cmp(int vlen, int[] v, int[] correct, String name) { + if ((correct == null && v != null) + || (correct != null && v == null)) { + throw new RuntimeException("One is null, one isn't for " + name + + ": correct=" + Arrays.toString(correct) + + " actual=" + Arrays.toString(v)); + } + if ((correct == null && vlen != 0) + || (vlen != (correct == null ? 0 : correct.length))) { + throw new RuntimeException("Reported length mismatch for " + name + + ": correct=" + ((correct == null) ? "null" : correct.length) + + " actual=" + vlen); + } + if (correct == null) { + return; + } + if (v.length < correct.length) { + throw new RuntimeException("Array length mismatch for " + name + + ": correct=" + Arrays.toString(correct) + + " actual=" + Arrays.toString(v)); + } + for (int i = 0; i < correct.length; i++) { + if (v[i] != correct[i]) { + throw new RuntimeException("Array value mismatch for " + name + + ": correct=" + Arrays.toString(correct) + + " actual=" + Arrays.toString(v)); + } + } + } + + private static boolean eq(String a, String b) { + if ((a == null && b != null) || (a != null && b == null)) { + return false; + } else { + return a == b || a.equals(b); + } + } + + private static void verifyRecurType(String recur, + int freq, String until, int count, int interval, + int[] bysecond, int[] byminute, int[] byhour, + int[] byday, int[] bydayNum, int[] bymonthday, + int[] byyearday, int[] byweekno, int[] bymonth, + int[] bysetpos, int wkst) { + EventRecurrence eventRecurrence = new EventRecurrence(); + eventRecurrence.parse(recur); + if (eventRecurrence.freq != freq + || !eq(eventRecurrence.until, until) + || eventRecurrence.count != count + || eventRecurrence.interval != interval + || eventRecurrence.wkst != wkst) { + System.out.println("Error... got:"); + print(eventRecurrence); + System.out.println("expected:"); + System.out.println("{"); + System.out.println(" freq=" + freq); + System.out.println(" until=" + until); + System.out.println(" count=" + count); + System.out.println(" interval=" + interval); + System.out.println(" wkst=" + wkst); + System.out.println(" bysecond=" + Arrays.toString(bysecond)); + System.out.println(" byminute=" + Arrays.toString(byminute)); + System.out.println(" byhour=" + Arrays.toString(byhour)); + System.out.println(" byday=" + Arrays.toString(byday)); + System.out.println(" bydayNum=" + Arrays.toString(bydayNum)); + System.out.println(" bymonthday=" + Arrays.toString(bymonthday)); + System.out.println(" byyearday=" + Arrays.toString(byyearday)); + System.out.println(" byweekno=" + Arrays.toString(byweekno)); + System.out.println(" bymonth=" + Arrays.toString(bymonth)); + System.out.println(" bysetpos=" + Arrays.toString(bysetpos)); + System.out.println("}"); + throw new RuntimeException("Mismatch in fields"); + } + cmp(eventRecurrence.bysecondCount, eventRecurrence.bysecond, bysecond, "bysecond"); + cmp(eventRecurrence.byminuteCount, eventRecurrence.byminute, byminute, "byminute"); + cmp(eventRecurrence.byhourCount, eventRecurrence.byhour, byhour, "byhour"); + cmp(eventRecurrence.bydayCount, eventRecurrence.byday, byday, "byday"); + cmp(eventRecurrence.bydayCount, eventRecurrence.bydayNum, bydayNum, "bydayNum"); + cmp(eventRecurrence.bymonthdayCount, eventRecurrence.bymonthday, bymonthday, "bymonthday"); + cmp(eventRecurrence.byyeardayCount, eventRecurrence.byyearday, byyearday, "byyearday"); + cmp(eventRecurrence.byweeknoCount, eventRecurrence.byweekno, byweekno, "byweekno"); + cmp(eventRecurrence.bymonthCount, eventRecurrence.bymonth, bymonth, "bymonth"); + cmp(eventRecurrence.bysetposCount, eventRecurrence.bysetpos, bysetpos, "bysetpos"); + } + + private static void print(EventRecurrence er) { + System.out.println("{"); + System.out.println(" freq=" + er.freq); + System.out.println(" until=" + er.until); + System.out.println(" count=" + er.count); + System.out.println(" interval=" + er.interval); + System.out.println(" wkst=" + er.wkst); + System.out.println(" bysecond=" + Arrays.toString(er.bysecond)); + System.out.println(" bysecondCount=" + er.bysecondCount); + System.out.println(" byminute=" + Arrays.toString(er.byminute)); + System.out.println(" byminuteCount=" + er.byminuteCount); + System.out.println(" byhour=" + Arrays.toString(er.byhour)); + System.out.println(" byhourCount=" + er.byhourCount); + System.out.println(" byday=" + Arrays.toString(er.byday)); + System.out.println(" bydayNum=" + Arrays.toString(er.bydayNum)); + System.out.println(" bydayCount=" + er.bydayCount); + System.out.println(" bymonthday=" + Arrays.toString(er.bymonthday)); + System.out.println(" bymonthdayCount=" + er.bymonthdayCount); + System.out.println(" byyearday=" + Arrays.toString(er.byyearday)); + System.out.println(" byyeardayCount=" + er.byyeardayCount); + System.out.println(" byweekno=" + Arrays.toString(er.byweekno)); + System.out.println(" byweeknoCount=" + er.byweeknoCount); + System.out.println(" bymonth=" + Arrays.toString(er.bymonth)); + System.out.println(" bymonthCount=" + er.bymonthCount); + System.out.println(" bysetpos=" + Arrays.toString(er.bysetpos)); + System.out.println(" bysetposCount=" + er.bysetposCount); + System.out.println("}"); + } + + + /** A list of valid rules. The parser must accept these. */ + private static final String[] GOOD_RRULES = { + /* extracted wholesale from from RFC 2445 section 4.8.5.4 */ + "FREQ=DAILY;COUNT=10", + "FREQ=DAILY;UNTIL=19971224T000000Z", + "FREQ=DAILY;INTERVAL=2", + "FREQ=DAILY;INTERVAL=10;COUNT=5", + "FREQ=YEARLY;UNTIL=20000131T090000Z;BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA", + "FREQ=DAILY;UNTIL=20000131T090000Z;BYMONTH=1", + "FREQ=WEEKLY;COUNT=10", + "FREQ=WEEKLY;UNTIL=19971224T000000Z", + "FREQ=WEEKLY;INTERVAL=2;WKST=SU", + "FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH", + "FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH", + "FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR", + "FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH", + "FREQ=MONTHLY;COUNT=10;BYDAY=1FR", + "FREQ=MONTHLY;UNTIL=19971224T000000Z;BYDAY=1FR", + "FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU", + "FREQ=MONTHLY;COUNT=6;BYDAY=-2MO", + "FREQ=MONTHLY;BYMONTHDAY=-3", + "FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15", + "FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1", + "FREQ=MONTHLY;INTERVAL=18;COUNT=10;BYMONTHDAY=10,11,12,13,14,15", + "FREQ=MONTHLY;INTERVAL=2;BYDAY=TU", + "FREQ=YEARLY;COUNT=10;BYMONTH=6,7", + "FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3", + "FREQ=YEARLY;INTERVAL=3;COUNT=10;BYYEARDAY=1,100,200", + "FREQ=YEARLY;BYDAY=20MO", + "FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO", + "FREQ=YEARLY;BYMONTH=3;BYDAY=TH", + "FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8", + "FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13", + "FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13", + "FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8", + "FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3", + "FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2", + "FREQ=HOURLY;INTERVAL=3;UNTIL=19970902T170000Z", + "FREQ=MINUTELY;INTERVAL=15;COUNT=6", + "FREQ=MINUTELY;INTERVAL=90;COUNT=4", + "FREQ=DAILY;BYHOUR=9,10,11,12,13,14,15,16;BYMINUTE=0,20,40", + "FREQ=MINUTELY;INTERVAL=20;BYHOUR=9,10,11,12,13,14,15,16", + "FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO", + "FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU", + /* a few more */ + "FREQ=SECONDLY;BYSECOND=0,15,59", + "FREQ=MINUTELY;BYMINUTE=0,15,59", + "FREQ=HOURLY;BYHOUR=+0,+15,+23", + "FREQ=DAILY;X-WHATEVER=blah", // fails on old parser + //"freq=daily;wkst=su", // fails on old parser + }; + + /** The parser must reject these. */ + private static final String[] BAD_RRULES = { + "INTERVAL=4;FREQ=YEARLY", // FREQ must come first + "FREQ=MONTHLY;FREQ=MONTHLY", // can't specify twice + "FREQ=MONTHLY;COUNT=1;COUNT=1", // can't specify twice + "FREQ=SECONDLY;BYSECOND=60", // range + "FREQ=MINUTELY;BYMINUTE=-1", // range + "FREQ=HOURLY;BYHOUR=24", // range + "FREQ=YEARLY;BYMONTHDAY=0", // zero not valid + //"FREQ=YEARLY;COUNT=1;UNTIL=12345", // can't have both COUNT and UNTIL + //"FREQ=DAILY;UNTIL=19970829T021400e", // invalid date + }; + + /** + * Simple test of good/bad rules. + */ + @SmallTest + public void testBasicParse() { + for (String rule : GOOD_RRULES) { + EventRecurrence recur = new EventRecurrence(); + recur.parse(rule); + } + + for (String rule : BAD_RRULES) { + EventRecurrence recur = new EventRecurrence(); + boolean didThrow = false; + + try { + recur.parse(rule); + } catch (InvalidFormatException ife) { + didThrow = true; + } + + assertTrue("Expected throw on " + rule, didThrow); + } + } +} diff --git a/docs/html/guide/topics/usb/adk.jd b/docs/html/guide/topics/usb/adk.jd index e4e1215..0e35637 100644 --- a/docs/html/guide/topics/usb/adk.jd +++ b/docs/html/guide/topics/usb/adk.jd @@ -55,7 +55,6 @@ page.title=Android Open Accessory Development Kit </ol> - <h2>See also</h2> <ol> @@ -66,6 +65,12 @@ page.title=Android Open Accessory Development Kit <h2>Where to buy</h2> <ol> + <li><a href="http://shop.moderndevice.com/products/freeduino-usb-host-board"> + Modern Device</a></li> + + <li><a href="http://www.seeedstudio.com/depot/seeeduino-adk-main-board-p-846.html"> + Seeed Studio</a></li> + <li><a href= "http://www.rt-net.jp/shop/index.php?main_page=product_info&cPath=3_4&products_id=1"> RT Corp</a></li> @@ -77,8 +82,6 @@ page.title=Android Open Accessory Development Kit <li><a href="https://store.diydrones.com/ProductDetails.asp?ProductCode=BR-PhoneDrone"> DIY Drones</a></li> - <li><a href="http://shop.moderndevice.com/products/freeduino-usb-host-board"> - Modern Device</a></li> </ol> </div> </div> @@ -106,15 +109,22 @@ page.title=Android Open Accessory Development Kit development boards:</p> <ul> + <li><a href="http://shop.moderndevice.com/products/freeduino-usb-host-board">Modern + Device</a> provides an Arduino-compatible board that supports the ADK firmware.</li> + + <li><a href="http://www.seeedstudio.com/depot/seeeduino-adk-main-board-p-846.html"> + Seeed Studio</a> provides an Arduino-compatible board that supports the ADK firmware.</li> + <li><a href="http://www.rt-net.jp/shop/index.php?main_page=product_info&cPath=3_4&products_id=1"> RT Corp</a> provides an Arduino-compatible board based on the Android ADK board design.</li> + <li><a href="http://www.microchip.com/android">Microchip</a> provides a A PIC based USB microcontroller board.</li> + <li><a href="https://store.diydrones.com/ProductDetails.asp?ProductCode=BR-PhoneDrone">DIY Drones</a> provides an Arduino-compatible board geared towards RC (radio controlled) and UAV (unmanned aerial vehicle) enthusiasts.</li> - <li><a href="http://shop.moderndevice.com/products/freeduino-usb-host-board">Modern - Device</a> provides an Arduino-compatible board that supports the ADK firmware.</li> + </ul> <p>We expect more hardware distributers to create a variety of kits, so please stay tuned for @@ -125,7 +135,7 @@ page.title=Android Open Accessory Development Kit accessory that is based on the <a href="http://www.arduino.cc/">Arduino open source electronics prototyping platform</a>, the accessory's hardware design files, code that implements the accessory's firmware, and the Android application that interacts with the accessory. The hardware - design files and firmware code are contained in the <a href= + design files and firmware code are contained in the <a href=ctive "https://dl-ssl.google.com/android/adk/adk_release_0512.zip">ADK package download</a>.</p> <p>The main hardware and software components of the ADK include:</p> diff --git a/docs/html/sdk/sdk_toc.cs b/docs/html/sdk/sdk_toc.cs index 5750c81..d02c13d 100644 --- a/docs/html/sdk/sdk_toc.cs +++ b/docs/html/sdk/sdk_toc.cs @@ -175,7 +175,8 @@ class="new">new!</span></li> <span style="display:none" class="zh-TW"></span> </h2> <ul> - <li><a href="<?cs var:toroot ?>sdk/ndk/index.html">Android NDK, r5c</a> + <li><a href="<?cs var:toroot ?>sdk/ndk/index.html">Android NDK, r5c <span + class="new">new!</span></a> </li> <li><a href="<?cs var:toroot ?>sdk/ndk/overview.html">What is the NDK?</a></li> </ul> diff --git a/graphics/java/android/renderscript/AllocationAdapter.java b/graphics/java/android/renderscript/AllocationAdapter.java index 61f2e1f..77dd86a 100644 --- a/graphics/java/android/renderscript/AllocationAdapter.java +++ b/graphics/java/android/renderscript/AllocationAdapter.java @@ -27,8 +27,14 @@ import android.util.TypedValue; * **/ public class AllocationAdapter extends Allocation { + private boolean mConstrainedLOD; + private boolean mConstrainedFace; + private boolean mConstrainedY; + private boolean mConstrainedZ; + private int mSelectedDimX; private int mSelectedDimY; + private int mSelectedDimZ; private int mSelectedCount; private Allocation mAlloc; @@ -37,13 +43,14 @@ public class AllocationAdapter extends Allocation { AllocationAdapter(int id, RenderScript rs, Allocation alloc) { super(id, rs, null, alloc.mUsage); - Type t = alloc.getType(); - mSelectedDimX = t.getX(); - mSelectedDimY = t.getY(); - mSelectedCount = t.getCount(); + mAlloc = alloc; } + int getID() { + return mAlloc.getID(); + } + public void copyFrom(BaseObj[] d) { mRS.validate(); if (d.length != mSelectedCount) { @@ -54,7 +61,7 @@ public class AllocationAdapter extends Allocation { for (int ct=0; ct < d.length; ct++) { i[ct] = d[ct].getID(); } - subData1D(0, mType.getCount(), i); + subData1D(0, mAlloc.mType.getCount(), i); } void validateBitmap(Bitmap b) { @@ -93,7 +100,7 @@ public class AllocationAdapter extends Allocation { public void subData(int xoff, FieldPacker fp) { - int eSize = mType.mElement.getSizeBytes(); + int eSize = mAlloc.mType.mElement.getSizeBytes(); final byte[] data = fp.getData(); int count = data.length / eSize; @@ -107,7 +114,7 @@ public class AllocationAdapter extends Allocation { public void subElementData(int xoff, int component_number, FieldPacker fp) { - if (component_number >= mType.mElement.mElements.length) { + if (component_number >= mAlloc.mType.mElement.mElements.length) { throw new RSIllegalArgumentException("Component_number " + component_number + " out of range."); } if(xoff < 0) { @@ -115,7 +122,7 @@ public class AllocationAdapter extends Allocation { } final byte[] data = fp.getData(); - int eSize = mType.mElement.mElements[component_number].getSizeBytes(); + int eSize = mAlloc.mType.mElement.mElements[component_number].getSizeBytes(); if (data.length != eSize) { throw new RSIllegalArgumentException("Field packer sizelength " + data.length + @@ -133,12 +140,13 @@ public class AllocationAdapter extends Allocation { if(count < 1) { throw new RSIllegalArgumentException("Count must be >= 1."); } - if((off + count) > mSelectedDimX * mSelectedDimY) { - throw new RSIllegalArgumentException("Overflow, Available count " + mType.getCount() + + if((off + count) > mSelectedCount) { + throw new RSIllegalArgumentException("Overflow, Available count " + mAlloc.mType.getCount() + ", got " + count + " at offset " + off + "."); } if((len) < dataSize) { - throw new RSIllegalArgumentException("Array too small for allocation type."); + throw new RSIllegalArgumentException("Array too small for allocation type. len = " + + len + ", dataSize = " + dataSize); } } @@ -223,8 +231,51 @@ public class AllocationAdapter extends Allocation { mRS.nAllocationRead(getID(), d); } + private void initLOD(int lod) { + if (lod < 0) { + throw new RSIllegalArgumentException("Attempting to set negative lod (" + lod + ")."); + } + + int tx = mAlloc.mType.getX(); + int ty = mAlloc.mType.getY(); + int tz = mAlloc.mType.getZ(); + + for (int ct=0; ct < lod; ct++) { + if ((tx==1) && (ty == 1) && (tz == 1)) { + throw new RSIllegalArgumentException("Attempting to set lod (" + lod + ") out of range."); + } + + if (tx > 1) tx >>= 1; + if (ty > 1) ty >>= 1; + if (tz > 1) tz >>= 1; + } + + mSelectedDimX = tx; + mSelectedDimY = ty; + mSelectedCount = tx; + if (ty > 1) { + mSelectedCount *= ty; + } + if (tz > 1) { + mSelectedCount *= tz; + } + } + + /** + * Set the active LOD. The LOD must be within the range for the + * type being adapted. + * + * @param lod The LOD to make active. + */ public void setLOD(int lod) { - mSelectedLOD = lod; + if (!mAlloc.getType().hasMipmaps()) { + throw new RSInvalidStateException("Cannot set LOD when the allocation type does not include mipmaps."); + } + if (!mConstrainedLOD) { + throw new RSInvalidStateException("Cannot set LOD when the adapter includes mipmaps."); + } + + initLOD(lod); } public void setFace(Type.CubemapFace cf) { @@ -245,6 +296,11 @@ public class AllocationAdapter extends Allocation { static public AllocationAdapter create2D(RenderScript rs, Allocation a) { rs.validate(); AllocationAdapter aa = new AllocationAdapter(0, rs, a); + aa.mConstrainedLOD = true; + aa.mConstrainedFace = true; + aa.mConstrainedY = false; + aa.mConstrainedZ = true; + aa.initLOD(0); return aa; } diff --git a/graphics/java/android/renderscript/FieldPacker.java b/graphics/java/android/renderscript/FieldPacker.java index fac7144..2739a4b8 100644 --- a/graphics/java/android/renderscript/FieldPacker.java +++ b/graphics/java/android/renderscript/FieldPacker.java @@ -165,6 +165,22 @@ public class FieldPacker { addF32(v.w); } + public void addF64(Double2 v) { + addF64(v.x); + addF64(v.y); + } + public void addF64(Double3 v) { + addF64(v.x); + addF64(v.y); + addF64(v.z); + } + public void addF64(Double4 v) { + addF64(v.x); + addF64(v.y); + addF64(v.z); + addF64(v.w); + } + public void addI8(Byte2 v) { addI8(v.x); addI8(v.y); @@ -261,6 +277,38 @@ public class FieldPacker { addU32(v.w); } + public void addI64(Long2 v) { + addI64(v.x); + addI64(v.y); + } + public void addI64(Long3 v) { + addI64(v.x); + addI64(v.y); + addI64(v.z); + } + public void addI64(Long4 v) { + addI64(v.x); + addI64(v.y); + addI64(v.z); + addI64(v.w); + } + + public void addU64(Long2 v) { + addU64(v.x); + addU64(v.y); + } + public void addU64(Long3 v) { + addU64(v.x); + addU64(v.y); + addU64(v.z); + } + public void addU64(Long4 v) { + addU64(v.x); + addU64(v.y); + addU64(v.z); + addU64(v.w); + } + public void addMatrix(Matrix4f v) { for (int i=0; i < v.mMat.length; i++) { addF32(v.mMat[i]); diff --git a/libs/gui/tests/SurfaceTexture_test.cpp b/libs/gui/tests/SurfaceTexture_test.cpp index f6cefa6..f219639 100644 --- a/libs/gui/tests/SurfaceTexture_test.cpp +++ b/libs/gui/tests/SurfaceTexture_test.cpp @@ -173,11 +173,11 @@ protected: } virtual EGLint getSurfaceWidth() { - return 64; + return 512; } virtual EGLint getSurfaceHeight() { - return 64; + return 512; } void loadShader(GLenum shaderType, const char* pSource, GLuint* outShader) { @@ -526,18 +526,19 @@ TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferNpot) { glClearColor(0.2, 0.2, 0.2, 0.2); glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, texWidth, texHeight); drawTexture(); EXPECT_TRUE(checkPixel( 0, 0, 255, 127, 255, 255)); EXPECT_TRUE(checkPixel(63, 0, 0, 133, 0, 255)); - EXPECT_TRUE(checkPixel(63, 63, 0, 133, 0, 255)); - EXPECT_TRUE(checkPixel( 0, 63, 255, 127, 255, 255)); + EXPECT_TRUE(checkPixel(63, 65, 0, 133, 0, 255)); + EXPECT_TRUE(checkPixel( 0, 65, 255, 127, 255, 255)); - EXPECT_TRUE(checkPixel(22, 44, 247, 70, 255, 255)); - EXPECT_TRUE(checkPixel(45, 52, 209, 32, 235, 255)); - EXPECT_TRUE(checkPixel(52, 51, 100, 255, 73, 255)); + EXPECT_TRUE(checkPixel(22, 44, 255, 127, 255, 255)); + EXPECT_TRUE(checkPixel(45, 52, 255, 127, 255, 255)); + EXPECT_TRUE(checkPixel(52, 51, 98, 255, 73, 255)); EXPECT_TRUE(checkPixel( 7, 31, 155, 0, 118, 255)); - EXPECT_TRUE(checkPixel(31, 9, 148, 71, 110, 255)); + EXPECT_TRUE(checkPixel(31, 9, 107, 24, 87, 255)); EXPECT_TRUE(checkPixel(29, 35, 255, 127, 255, 255)); EXPECT_TRUE(checkPixel(36, 22, 155, 29, 0, 255)); } @@ -570,6 +571,7 @@ TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferPow2) { glClearColor(0.2, 0.2, 0.2, 0.2); glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, texWidth, texHeight); drawTexture(); EXPECT_TRUE(checkPixel( 0, 0, 0, 133, 0, 255)); @@ -628,6 +630,7 @@ TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferWithCrop) { glClearColor(0.2, 0.2, 0.2, 0.2); glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, 64, 64); drawTexture(); EXPECT_TRUE(checkPixel( 0, 0, 82, 255, 35, 255)); @@ -675,28 +678,29 @@ TEST_F(SurfaceTextureGLTest, DISABLED_TexturingFromCpuFilledRGBABufferNpot) { glClearColor(0.2, 0.2, 0.2, 0.2); glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, texWidth, texHeight); drawTexture(); EXPECT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35)); EXPECT_TRUE(checkPixel(63, 0, 231, 231, 231, 231)); - EXPECT_TRUE(checkPixel(63, 63, 231, 231, 231, 231)); - EXPECT_TRUE(checkPixel( 0, 63, 35, 35, 35, 35)); + EXPECT_TRUE(checkPixel(63, 65, 231, 231, 231, 231)); + EXPECT_TRUE(checkPixel( 0, 65, 35, 35, 35, 35)); EXPECT_TRUE(checkPixel(15, 10, 35, 231, 231, 231)); - EXPECT_TRUE(checkPixel(24, 63, 35, 231, 231, 35)); - EXPECT_TRUE(checkPixel(19, 40, 87, 179, 35, 35)); + EXPECT_TRUE(checkPixel(24, 63, 38, 228, 231, 35)); + EXPECT_TRUE(checkPixel(19, 40, 35, 231, 35, 35)); EXPECT_TRUE(checkPixel(38, 30, 231, 35, 35, 35)); EXPECT_TRUE(checkPixel(42, 54, 35, 35, 35, 231)); - EXPECT_TRUE(checkPixel(37, 33, 35, 231, 231, 231)); + EXPECT_TRUE(checkPixel(37, 33, 228, 38, 38, 38)); EXPECT_TRUE(checkPixel(31, 8, 231, 35, 35, 231)); - EXPECT_TRUE(checkPixel(36, 47, 231, 35, 231, 231)); - EXPECT_TRUE(checkPixel(24, 63, 35, 231, 231, 35)); - EXPECT_TRUE(checkPixel(48, 3, 231, 231, 35, 35)); + EXPECT_TRUE(checkPixel(36, 47, 228, 35, 231, 231)); + EXPECT_TRUE(checkPixel(24, 63, 38, 228, 231, 35)); + EXPECT_TRUE(checkPixel(48, 3, 228, 228, 38, 35)); EXPECT_TRUE(checkPixel(54, 50, 35, 231, 231, 231)); - EXPECT_TRUE(checkPixel(24, 25, 191, 191, 231, 231)); - EXPECT_TRUE(checkPixel(10, 9, 93, 93, 231, 231)); + EXPECT_TRUE(checkPixel(24, 25, 41, 41, 231, 231)); + EXPECT_TRUE(checkPixel(10, 9, 38, 38, 231, 231)); EXPECT_TRUE(checkPixel(29, 4, 35, 35, 35, 231)); - EXPECT_TRUE(checkPixel(56, 31, 35, 231, 231, 35)); + EXPECT_TRUE(checkPixel(56, 31, 38, 228, 231, 35)); EXPECT_TRUE(checkPixel(58, 55, 35, 35, 231, 231)); } @@ -730,6 +734,7 @@ TEST_F(SurfaceTextureGLTest, DISABLED_TexturingFromCpuFilledRGBABufferPow2) { glClearColor(0.2, 0.2, 0.2, 0.2); glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, texWidth, texHeight); drawTexture(); EXPECT_TRUE(checkPixel( 0, 0, 231, 231, 231, 231)); @@ -803,6 +808,7 @@ TEST_F(SurfaceTextureGLTest, DISABLED_TexturingFromGLFilledRGBABufferPow2) { glClearColor(0.2, 0.2, 0.2, 0.2); glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, texWidth, texHeight); drawTexture(); EXPECT_TRUE(checkPixel( 0, 0, 153, 153, 153, 153)); diff --git a/libs/rs/driver/rsdRuntimeMath.cpp b/libs/rs/driver/rsdRuntimeMath.cpp index acb990d..d29da7e 100644 --- a/libs/rs/driver/rsdRuntimeMath.cpp +++ b/libs/rs/driver/rsdRuntimeMath.cpp @@ -268,6 +268,87 @@ static float SC_frac(float v) { } +static int32_t SC_AtomicCas(volatile int32_t *ptr, int32_t expectedValue, int32_t newValue) { + int32_t prev; + + do { + int32_t ret = android_atomic_release_cas(expectedValue, newValue, ptr); + if (!ret) { + // The android cas return 0 if it wrote the value. This means the + // previous value was the expected value and we can return. + return expectedValue; + } + // We didn't write the value and need to load the "previous" value. + prev = *ptr; + + // A race condition exists where the expected value could appear after our cas failed + // above. In this case loop until we have a legit previous value or the + // write passes. + } while (prev == expectedValue); + return prev; +} + + +static int32_t SC_AtomicInc(volatile int32_t *ptr) { + return android_atomic_inc(ptr); +} + +static int32_t SC_AtomicDec(volatile int32_t *ptr) { + return android_atomic_dec(ptr); +} + +static int32_t SC_AtomicAdd(volatile int32_t *ptr, int32_t value) { + return android_atomic_add(value, ptr); +} + +static int32_t SC_AtomicSub(volatile int32_t *ptr, int32_t value) { + int32_t prev, status; + do { + prev = *ptr; + status = android_atomic_release_cas(prev, prev - value, ptr); + } while (__builtin_expect(status != 0, 0)); + return prev; +} + +static int32_t SC_AtomicAnd(volatile int32_t *ptr, int32_t value) { + return android_atomic_and(value, ptr); +} + +static int32_t SC_AtomicOr(volatile int32_t *ptr, int32_t value) { + return android_atomic_or(value, ptr); +} + +static int32_t SC_AtomicXor(volatile int32_t *ptr, int32_t value) { + int32_t prev, status; + do { + prev = *ptr; + status = android_atomic_release_cas(prev, prev ^ value, ptr); + } while (__builtin_expect(status != 0, 0)); + return prev; +} + +static int32_t SC_AtomicMin(volatile int32_t *ptr, int32_t value) { + int32_t prev, status; + do { + prev = *ptr; + int32_t n = rsMin(value, prev); + status = android_atomic_release_cas(prev, n, ptr); + } while (__builtin_expect(status != 0, 0)); + return prev; +} + +static int32_t SC_AtomicMax(volatile int32_t *ptr, int32_t value) { + int32_t prev, status; + do { + prev = *ptr; + int32_t n = rsMax(value, prev); + status = android_atomic_release_cas(prev, n, ptr); + } while (__builtin_expect(status != 0, 0)); + return prev; +} + + + ////////////////////////////////////////////////////////////////////////////// // Class implementation ////////////////////////////////////////////////////////////////////////////// @@ -425,6 +506,28 @@ static RsdSymbolTable gSyms[] = { { "_Z6rsRandff", (void *)&SC_randf2, true }, { "_Z6rsFracf", (void *)&SC_frac, true }, + // Atomics + { "_Z11rsAtomicIncPVi", (void *)&SC_AtomicInc, true }, + { "_Z11rsAtomicIncPVj", (void *)&SC_AtomicInc, true }, + { "_Z11rsAtomicDecPVi", (void *)&SC_AtomicDec, true }, + { "_Z11rsAtomicDecPVj", (void *)&SC_AtomicDec, true }, + { "_Z11rsAtomicAddPVii", (void *)&SC_AtomicAdd, true }, + { "_Z11rsAtomicAddPVjj", (void *)&SC_AtomicAdd, true }, + { "_Z11rsAtomicSubPVii", (void *)&SC_AtomicSub, true }, + { "_Z11rsAtomicSubPVjj", (void *)&SC_AtomicSub, true }, + { "_Z11rsAtomicAndPVii", (void *)&SC_AtomicAnd, true }, + { "_Z11rsAtomicAndPVjj", (void *)&SC_AtomicAnd, true }, + { "_Z10rsAtomicOrPVii", (void *)&SC_AtomicOr, true }, + { "_Z10rsAtomicOrPVjj", (void *)&SC_AtomicOr, true }, + { "_Z11rsAtomicXorPVii", (void *)&SC_AtomicXor, true }, + { "_Z11rsAtomicXorPVjj", (void *)&SC_AtomicXor, true }, + { "_Z11rsAtomicMinPVii", (void *)&SC_AtomicMin, true }, + { "_Z11rsAtomicMinPVjj", (void *)&SC_AtomicMin, true }, + { "_Z11rsAtomicMaxPVii", (void *)&SC_AtomicMax, true }, + { "_Z11rsAtomicMaxPVjj", (void *)&SC_AtomicMax, true }, + { "_Z11rsAtomicCasPViii", (void *)&SC_AtomicCas, true }, + { "_Z11rsAtomicCasPVjjj", (void *)&SC_AtomicCas, true }, + { NULL, NULL, false } }; diff --git a/libs/rs/scriptc/rs_math.rsh b/libs/rs/scriptc/rs_math.rsh index 584317e..f38f72c 100644 --- a/libs/rs/scriptc/rs_math.rsh +++ b/libs/rs/scriptc/rs_math.rsh @@ -258,4 +258,226 @@ extern void __attribute__((overloadable)) rs_allocation output, const void * usrData, const rs_script_call_t *); + + +/** + * Atomic add one to the value at addr. + * Equal to rsAtomicAdd(addr, 1) + * + * @param addr Address of value to increment + * + * @return old value + */ +extern int32_t __attribute__((overloadable)) + rsAtomicInc(volatile int32_t* addr); +/** + * Atomic add one to the value at addr. + * Equal to rsAtomicAdd(addr, 1) + * + * @param addr Address of value to increment + * + * @return old value + */ +extern uint32_t __attribute__((overloadable)) + rsAtomicInc(volatile uint32_t* addr); + +/** + * Atomic subtract one from the value at addr. Equal to rsAtomicSub(addr, 1) + * + * @param addr Address of value to decrement + * + * @return old value + */ +extern int32_t __attribute__((overloadable)) + rsAtomicDec(volatile int32_t* addr); +/** + * Atomic subtract one from the value at addr. Equal to rsAtomicSub(addr, 1) + * + * @param addr Address of value to decrement + * + * @return old value + */ +extern uint32_t __attribute__((overloadable)) + rsAtomicDec(volatile uint32_t* addr); + +/** + * Atomic add a value to the value at addr. addr[0] += value + * + * @param addr Address of value to modify + * @param value Amount to add to the value at addr + * + * @return old value + */ +extern int32_t __attribute__((overloadable)) + rsAtomicAdd(volatile int32_t* addr, int32_t value); +/** + * Atomic add a value to the value at addr. addr[0] += value + * + * @param addr Address of value to modify + * @param value Amount to add to the value at addr + * + * @return old value + */ +extern uint32_t __attribute__((overloadable)) + rsAtomicAdd(volatile uint32_t* addr, uint32_t value); + +/** + * Atomic Subtract a value from the value at addr. addr[0] -= value + * + * @param addr Address of value to modify + * @param value Amount to subtract from the value at addr + * + * @return old value + */ +extern int32_t __attribute__((overloadable)) + rsAtomicSub(volatile int32_t* addr, int32_t value); +/** + * Atomic Subtract a value from the value at addr. addr[0] -= value + * + * @param addr Address of value to modify + * @param value Amount to subtract from the value at addr + * + * @return old value + */ +extern uint32_t __attribute__((overloadable)) + rsAtomicSub(volatile uint32_t* addr, uint32_t value); + +/** + * Atomic Bitwise and a value from the value at addr. addr[0] &= value + * + * @param addr Address of value to modify + * @param value Amount to and with the value at addr + * + * @return old value + */ +extern int32_t __attribute__((overloadable)) + rsAtomicAnd(volatile int32_t* addr, int32_t value); +/** + * Atomic Bitwise and a value from the value at addr. addr[0] &= value + * + * @param addr Address of value to modify + * @param value Amount to and with the value at addr + * + * @return old value + */ +extern uint32_t __attribute__((overloadable)) + rsAtomicAnd(volatile uint32_t* addr, uint32_t value); + +/** + * Atomic Bitwise or a value from the value at addr. addr[0] |= value + * + * @param addr Address of value to modify + * @param value Amount to or with the value at addr + * + * @return old value + */ +extern int32_t __attribute__((overloadable)) + rsAtomicOr(volatile int32_t* addr, int32_t value); +/** + * Atomic Bitwise or a value from the value at addr. addr[0] |= value + * + * @param addr Address of value to modify + * @param value Amount to or with the value at addr + * + * @return old value + */ +extern uint32_t __attribute__((overloadable)) + rsAtomicOr(volatile uint32_t* addr, uint32_t value); + +/** + * Atomic Bitwise xor a value from the value at addr. addr[0] ^= value + * + * @param addr Address of value to modify + * @param value Amount to xor with the value at addr + * + * @return old value + */ +extern uint32_t __attribute__((overloadable)) + rsAtomicXor(volatile uint32_t* addr, uint32_t value); +/** + * Atomic Bitwise xor a value from the value at addr. addr[0] ^= value + * + * @param addr Address of value to modify + * @param value Amount to xor with the value at addr + * + * @return old value + */ +extern int32_t __attribute__((overloadable)) + rsAtomicXor(volatile int32_t* addr, int32_t value); + +/** + * Atomic Set the value at addr to the min of addr and value + * addr[0] = rsMin(addr[0], value) + * + * @param addr Address of value to modify + * @param value comparison value + * + * @return old value + */ +extern uint32_t __attribute__((overloadable)) + rsAtomicMin(volatile uint32_t* addr, uint32_t value); +/** + * Atomic Set the value at addr to the min of addr and value + * addr[0] = rsMin(addr[0], value) + * + * @param addr Address of value to modify + * @param value comparison value + * + * @return old value + */ +extern int32_t __attribute__((overloadable)) + rsAtomicMin(volatile int32_t* addr, int32_t value); + +/** + * Atomic Set the value at addr to the max of addr and value + * addr[0] = rsMax(addr[0], value) + * + * @param addr Address of value to modify + * @param value comparison value + * + * @return old value + */ +extern uint32_t __attribute__((overloadable)) + rsAtomicMax(volatile uint32_t* addr, uint32_t value); +/** + * Atomic Set the value at addr to the max of addr and value + * addr[0] = rsMin(addr[0], value) + * + * @param addr Address of value to modify + * @param value comparison value + * + * @return old value + */ +extern int32_t __attribute__((overloadable)) + rsAtomicMax(volatile int32_t* addr, int32_t value); + +/** + * Compare-and-set operation with a full memory barrier. + * + * If the value at addr matches compareValue then newValue is written. + * + * @param addr The address to compare and replace if the compare passes. + * @param compareValue The value to test addr[0] against. + * @param newValue The value to write if the test passes. + * + * @return old value + */ +extern int32_t __attribute__((overloadable)) + rsAtomicCas(volatile int32_t* addr, int32_t compareValue, int32_t newValue); + +/** + * Compare-and-set operation with a full memory barrier. + * + * If the value at addr matches compareValue then newValue is written. + * + * @param addr The address to compare and replace if the compare passes. + * @param compareValue The value to test addr[0] against. + * @param newValue The value to write if the test passes. + * + * @return old value + */ +extern uint32_t __attribute__((overloadable)) + rsAtomicCas(volatile uint32_t* addr, int32_t compareValue, int32_t newValue); + + #endif diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp index 58f6699..ea9911c 100644 --- a/media/libstagefright/MPEG4Writer.cpp +++ b/media/libstagefright/MPEG4Writer.cpp @@ -47,10 +47,6 @@ static const uint8_t kNalUnitTypeSeqParamSet = 0x07; static const uint8_t kNalUnitTypePicParamSet = 0x08; static const int64_t kInitialDelayTimeUs = 700000LL; -// Using longer adjustment period to suppress fluctuations in -// the audio encoding paths -static const int64_t kVideoMediaTimeAdjustPeriodTimeUs = 600000000LL; // 10 minutes - class MPEG4Writer::Track { public: Track(MPEG4Writer *owner, const sp<MediaSource> &source, size_t trackId); @@ -88,8 +84,6 @@ private: int64_t mTrackDurationUs; int64_t mMaxChunkDurationUs; - // For realtime applications, we need to adjust the media clock - // for video track based on the audio media clock bool mIsRealTimeRecording; int64_t mMaxTimeStampUs; int64_t mEstimatedTrackSizeBytes; @@ -175,28 +169,9 @@ private: int64_t mPreviousTrackTimeUs; int64_t mTrackEveryTimeDurationUs; - // Has the media time adjustment for video started? - bool mIsMediaTimeAdjustmentOn; - // The time stamp when previous media time adjustment period starts - int64_t mPrevMediaTimeAdjustTimestampUs; - // Number of vidoe frames whose time stamp may be adjusted - int64_t mMediaTimeAdjustNumFrames; - // The sample number when previous meida time adjustmnet period starts - int64_t mPrevMediaTimeAdjustSample; - // The total accumulated drift time within a period of - // kVideoMediaTimeAdjustPeriodTimeUs. - int64_t mTotalDriftTimeToAdjustUs; - // The total accumalated drift time since the start of the recording - // excluding the current time adjustment period - int64_t mPrevTotalAccumDriftTimeUs; - // Update the audio track's drift information. void updateDriftTime(const sp<MetaData>& meta); - // Adjust the time stamp of the video track according to - // the drift time information from the audio track. - void adjustMediaTime(int64_t *timestampUs); - static void *ThreadWrapper(void *me); status_t threadEntry(); @@ -1512,12 +1487,7 @@ status_t MPEG4Writer::Track::start(MetaData *params) { mNumSttsTableEntries = 0; mNumCttsTableEntries = 0; mMdatSizeBytes = 0; - mIsMediaTimeAdjustmentOn = false; - mPrevMediaTimeAdjustTimestampUs = 0; - mMediaTimeAdjustNumFrames = 0; - mPrevMediaTimeAdjustSample = 0; - mTotalDriftTimeToAdjustUs = 0; - mPrevTotalAccumDriftTimeUs = 0; + mMaxChunkDurationUs = 0; mHasNegativeCttsDeltaDuration = false; @@ -1816,128 +1786,6 @@ status_t MPEG4Writer::Track::makeAVCCodecSpecificData( } /* -* The video track's media time adjustment for real-time applications -* is described as follows: -* -* First, the media time adjustment is done for every period of -* kVideoMediaTimeAdjustPeriodTimeUs. kVideoMediaTimeAdjustPeriodTimeUs -* is currently a fixed value chosen heuristically. The value of -* kVideoMediaTimeAdjustPeriodTimeUs should not be very large or very small -* for two considerations: on one hand, a relatively large value -* helps reduce large fluctuation of drift time in the audio encoding -* path; while on the other hand, a relatively small value helps keep -* restoring synchronization in audio/video more frequently. Note for the -* very first period of kVideoMediaTimeAdjustPeriodTimeUs, there is -* no media time adjustment for the video track. -* -* Second, the total accumulated audio track time drift found -* in a period of kVideoMediaTimeAdjustPeriodTimeUs is distributed -* over a stream of incoming video frames. The number of video frames -* affected is determined based on the number of recorded video frames -* within the past kVideoMediaTimeAdjustPeriodTimeUs period. -* We choose to distribute the drift time over only a portion -* (rather than all) of the total number of recorded video frames -* in order to make sure that the video track media time adjustment is -* completed for the current period before the next video track media -* time adjustment period starts. Currently, the portion chosen is a -* half (0.5). -* -* Last, various additional checks are performed to ensure that -* the actual audio encoding path does not have too much drift. -* In particular, 1) we want to limit the average incremental time -* adjustment for each video frame to be less than a threshold -* for a single period of kVideoMediaTimeAdjustPeriodTimeUs. -* Currently, the threshold is set to 5 ms. If the average incremental -* media time adjustment for a video frame is larger than the -* threshold, the audio encoding path has too much time drift. -* 2) We also want to limit the total time drift in the audio -* encoding path to be less than a threshold for a period of -* kVideoMediaTimeAdjustPeriodTimeUs. Currently, the threshold -* is 0.5% of kVideoMediaTimeAdjustPeriodTimeUs. If the time drift of -* the audio encoding path is larger than the threshold, the audio -* encoding path has too much time drift. We treat the large time -* drift of the audio encoding path as errors, since there is no -* way to keep audio/video in synchronization for real-time -* applications if the time drift is too large unless we drop some -* video frames, which has its own problems that we don't want -* to get into for the time being. -*/ -void MPEG4Writer::Track::adjustMediaTime(int64_t *timestampUs) { - if (*timestampUs - mPrevMediaTimeAdjustTimestampUs >= - kVideoMediaTimeAdjustPeriodTimeUs) { - - LOGV("New media time adjustment period at %lld us", *timestampUs); - mIsMediaTimeAdjustmentOn = true; - mMediaTimeAdjustNumFrames = - (mNumSamples - mPrevMediaTimeAdjustSample) >> 1; - - mPrevMediaTimeAdjustTimestampUs = *timestampUs; - mPrevMediaTimeAdjustSample = mNumSamples; - int64_t totalAccumDriftTimeUs = mOwner->getDriftTimeUs(); - mTotalDriftTimeToAdjustUs = - totalAccumDriftTimeUs - mPrevTotalAccumDriftTimeUs; - - mPrevTotalAccumDriftTimeUs = totalAccumDriftTimeUs; - - // Check on incremental adjusted time per frame - int64_t adjustTimePerFrameUs = - mTotalDriftTimeToAdjustUs / mMediaTimeAdjustNumFrames; - - if (adjustTimePerFrameUs < 0) { - adjustTimePerFrameUs = -adjustTimePerFrameUs; - } - if (adjustTimePerFrameUs >= 5000) { - LOGE("Adjusted time per video frame is %lld us", - adjustTimePerFrameUs); - CHECK(!"Video frame time adjustment is too large!"); - } - - // Check on total accumulated time drift within a period of - // kVideoMediaTimeAdjustPeriodTimeUs. - int64_t driftPercentage = (mTotalDriftTimeToAdjustUs * 1000) - / kVideoMediaTimeAdjustPeriodTimeUs; - - if (driftPercentage < 0) { - driftPercentage = -driftPercentage; - } - if (driftPercentage > 5) { - LOGE("Audio track has time drift %lld us over %lld us", - mTotalDriftTimeToAdjustUs, - kVideoMediaTimeAdjustPeriodTimeUs); - - CHECK(!"The audio track media time drifts too much!"); - } - - } - - if (mIsMediaTimeAdjustmentOn) { - if (mNumSamples - mPrevMediaTimeAdjustSample <= - mMediaTimeAdjustNumFrames) { - - // Do media time incremental adjustment - int64_t incrementalAdjustTimeUs = - (mTotalDriftTimeToAdjustUs * - (mNumSamples - mPrevMediaTimeAdjustSample)) - / mMediaTimeAdjustNumFrames; - - *timestampUs += - (incrementalAdjustTimeUs + mPrevTotalAccumDriftTimeUs); - - LOGV("Incremental video frame media time adjustment: %lld us", - (incrementalAdjustTimeUs + mPrevTotalAccumDriftTimeUs)); - } else { - // Within the remaining adjustment period, - // no incremental adjustment is needed. - *timestampUs += - (mTotalDriftTimeToAdjustUs + mPrevTotalAccumDriftTimeUs); - - LOGV("Fixed video frame media time adjustment: %lld us", - (mTotalDriftTimeToAdjustUs + mPrevTotalAccumDriftTimeUs)); - } - } -} - -/* * Updates the drift time from the audio track so that * the video track can get the updated drift time information * from the file writer. The fluctuation of the drift time of the audio @@ -2080,32 +1928,6 @@ status_t MPEG4Writer::Track::threadEntry() { int32_t isSync = false; meta_data->findInt32(kKeyIsSyncFrame, &isSync); - - /* - * The original timestamp found in the data buffer will be modified as below: - * - * There is a playback offset into this track if the track's start time - * is not the same as the movie start time, which will be recorded in edst - * box of the output file. The playback offset is to make sure that the - * starting time of the audio/video tracks are synchronized. Although the - * track's media timestamp may be subject to various modifications - * as outlined below, the track's playback offset time remains unchanged - * once the first data buffer of the track is received. - * - * The media time stamp will be calculated by subtracting the playback offset - * (and potential pause durations) from the original timestamp in the buffer. - * - * If this track is a video track for a real-time recording application with - * both audio and video tracks, its media timestamp will subject to further - * modification based on the media clock of the audio track. This modification - * is needed for the purpose of maintaining good audio/video synchronization. - * - * If the recording session is paused and resumed multiple times, the track - * media timestamp will be modified as if the recording session had never been - * paused at all during playback of the recorded output file. In other words, - * the output file will have no memory of pause/resume durations. - * - */ CHECK(meta_data->findInt64(kKeyTime, ×tampUs)); //////////////////////////////////////////////////////////////////////////////// @@ -2146,31 +1968,13 @@ status_t MPEG4Writer::Track::threadEntry() { timestampUs, cttsDeltaTimeUs); } - // Media time adjustment for real-time applications if (mIsRealTimeRecording) { if (mIsAudio) { updateDriftTime(meta_data); - } else { - adjustMediaTime(×tampUs); } } CHECK(timestampUs >= 0); - if (mNumSamples > 1) { - if (timestampUs <= lastTimestampUs) { - LOGW("Frame arrives too late!"); - // Don't drop the late frame, since dropping a frame may cause - // problems later during playback - - // The idea here is to avoid having two or more samples with the - // same timestamp in the output file. - if (mTimeScale >= 1000000LL) { - timestampUs = lastTimestampUs + 1; - } else { - timestampUs = lastTimestampUs + (1000000LL + (mTimeScale >> 1)) / mTimeScale; - } - } - } LOGV("%s media time stamp: %lld and previous paused duration %lld", mIsAudio? "Audio": "Video", timestampUs, previousPausedDurationUs); diff --git a/media/libstagefright/omx/OMXMaster.cpp b/media/libstagefright/omx/OMXMaster.cpp index 545e6d4..504d470 100644 --- a/media/libstagefright/omx/OMXMaster.cpp +++ b/media/libstagefright/omx/OMXMaster.cpp @@ -86,7 +86,11 @@ void OMXMaster::addPlugin(OMXPluginBase *plugin) { mPluginByComponentName.add(name8, plugin); } - CHECK_EQ(err, OMX_ErrorNoMore); + + if (err != OMX_ErrorNoMore) { + LOGE("OMX plugin failed w/ error 0x%08x after registering %d " + "components", err, mPluginByComponentName.size()); + } } void OMXMaster::clearPlugins() { diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index fbde9d1..8037d7a 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -35,6 +35,8 @@ import org.xmlpull.v1.XmlSerializer; import android.app.ActivityManagerNative; import android.app.AlertDialog; +import android.app.Notification; +import android.app.NotificationManager; import android.app.PendingIntent; import android.content.ComponentName; import android.content.ContentResolver; @@ -52,6 +54,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.database.ContentObserver; +import android.graphics.BitmapFactory; import android.inputmethodservice.InputMethodService; import android.os.Binder; import android.os.Environment; @@ -154,6 +157,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans = new LruCache<SuggestionSpan, InputMethodInfo>(SECURE_SUGGESTION_SPANS_MAX_SIZE); + // Ongoing notification + private final NotificationManager mNotificationManager; + private final Notification mImeSwitcherNotification; + private final PendingIntent mImeSwitchPendingIntent; + private final boolean mShowOngoingImeSwitcherForPhones; + private boolean mNotificationShown; + class SessionState { final ClientState client; final IInputMethod method; @@ -508,6 +518,25 @@ public class InputMethodManagerService extends IInputMethodManager.Stub handleMessage(msg); } }); + + mNotificationManager = (NotificationManager) + mContext.getSystemService(Context.NOTIFICATION_SERVICE); + mImeSwitcherNotification = new Notification(); + mImeSwitcherNotification.icon = com.android.internal.R.drawable.ic_notification_ime_default; + mImeSwitcherNotification.when = 0; + mImeSwitcherNotification.flags = Notification.FLAG_ONGOING_EVENT; + mImeSwitcherNotification.tickerText = null; + mImeSwitcherNotification.defaults = 0; // please be quiet + mImeSwitcherNotification.sound = null; + mImeSwitcherNotification.vibrate = null; + Intent intent = new Intent(Settings.ACTION_SHOW_INPUT_METHOD_PICKER); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mImeSwitchPendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0); + mShowOngoingImeSwitcherForPhones = mRes.getBoolean( + com.android.internal.R.bool.show_ongoing_ime_switcher); + synchronized (mMethodMap) { mFileManager = new InputMethodFileManager(mMethodMap); } @@ -522,6 +551,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mStatusBar = statusBar; statusBar.setIconVisibility("ime", false); + mNotificationShown = false; // mSettings should be created before buildInputMethodListLocked mSettings = new InputMethodSettings( @@ -1022,6 +1052,32 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + private boolean needsToShowImeSwitchOngoingNotification() { + if (!mShowOngoingImeSwitcherForPhones) return false; + synchronized (mMethodMap) { + List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked(); + final int N = imis.size(); + int count = 0; + for(int i = 0; i < N; ++i) { + final InputMethodInfo imi = imis.get(i); + final List<InputMethodSubtype> subtypes = getEnabledInputMethodSubtypeListLocked( + imi, true); + final int subtypeCount = subtypes.size(); + if (subtypeCount == 0) { + ++count; + } else { + for (int j = 0; j < subtypeCount; ++j) { + if (!subtypes.get(j).isAuxiliary()) { + ++count; + } + } + } + if (count > 1) return true; + } + } + return false; + } + @Override public void setImeWindowStatus(IBinder token, int vis, int backDisposition) { int uid = Binder.getCallingUid(); @@ -1036,6 +1092,25 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mImeWindowVis = vis; mBackDisposition = backDisposition; mStatusBar.setImeWindowStatus(token, vis, backDisposition); + final boolean iconVisibility = (vis & InputMethodService.IME_ACTIVE) != 0; + if (iconVisibility && needsToShowImeSwitchOngoingNotification()) { + final PackageManager pm = mContext.getPackageManager(); + final CharSequence label = mMethodMap.get(mCurMethodId).loadLabel(pm); + final CharSequence title = mRes.getText( + com.android.internal.R.string.select_input_method); + mImeSwitcherNotification.setLatestEventInfo( + mContext, title, label, mImeSwitchPendingIntent); + mNotificationManager.notify( + com.android.internal.R.string.select_input_method, + mImeSwitcherNotification); + mNotificationShown = true; + } else { + if (mNotificationShown) { + mNotificationManager.cancel( + com.android.internal.R.string.select_input_method); + mNotificationShown = false; + } + } } } finally { Binder.restoreCallingIdentity(ident); diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java index 541bf22..e77998e 100644 --- a/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java +++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2008-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. @@ -65,6 +65,7 @@ public class RSTestCore { unitTests = new ArrayList<UnitTest>(); unitTests.add(new UT_primitives(this, mRes, mCtx)); + unitTests.add(new UT_vector(this, mRes, mCtx)); unitTests.add(new UT_rsdebug(this, mRes, mCtx)); unitTests.add(new UT_rstime(this, mRes, mCtx)); unitTests.add(new UT_rstypes(this, mRes, mCtx)); diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_vector.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_vector.java new file mode 100644 index 0000000..748701d --- /dev/null +++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_vector.java @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.rs.test; + +import android.content.Context; +import android.content.res.Resources; +import android.renderscript.*; + +public class UT_vector extends UnitTest { + private Resources mRes; + + protected UT_vector(RSTestCore rstc, Resources res, Context ctx) { + super(rstc, "Vector", ctx); + mRes = res; + } + + private boolean initializeGlobals(ScriptC_vector s) { + Float2 F2 = s.get_f2(); + if (F2.x != 1.0f || F2.y != 2.0f) { + return false; + } + F2.x = 2.99f; + F2.y = 3.99f; + s.set_f2(F2); + + Float3 F3 = s.get_f3(); + if (F3.x != 1.0f || F3.y != 2.0f || F3.z != 3.0f) { + return false; + } + F3.x = 2.99f; + F3.y = 3.99f; + F3.z = 4.99f; + s.set_f3(F3); + + Float4 F4 = s.get_f4(); + if (F4.x != 1.0f || F4.y != 2.0f || F4.z != 3.0f || F4.w != 4.0f) { + return false; + } + F4.x = 2.99f; + F4.y = 3.99f; + F4.z = 4.99f; + F4.w = 5.99f; + s.set_f4(F4); + + Double2 D2 = s.get_d2(); + if (D2.x != 1.0 || D2.y != 2.0) { + return false; + } + D2.x = 2.99; + D2.y = 3.99; + s.set_d2(D2); + + Double3 D3 = s.get_d3(); + if (D3.x != 1.0 || D3.y != 2.0 || D3.z != 3.0) { + return false; + } + D3.x = 2.99; + D3.y = 3.99; + D3.z = 4.99; + s.set_d3(D3); + + Double4 D4 = s.get_d4(); + if (D4.x != 1.0 || D4.y != 2.0 || D4.z != 3.0 || D4.w != 4.0) { + return false; + } + D4.x = 2.99; + D4.y = 3.99; + D4.z = 4.99; + D4.w = 5.99; + s.set_d4(D4); + + Byte2 B2 = s.get_i8_2(); + if (B2.x != 1 || B2.y != 2) { + return false; + } + B2.x = 2; + B2.y = 3; + s.set_i8_2(B2); + + Byte3 B3 = s.get_i8_3(); + if (B3.x != 1 || B3.y != 2 || B3.z != 3) { + return false; + } + B3.x = 2; + B3.y = 3; + B3.z = 4; + s.set_i8_3(B3); + + Byte4 B4 = s.get_i8_4(); + if (B4.x != 1 || B4.y != 2 || B4.z != 3 || B4.w != 4) { + return false; + } + B4.x = 2; + B4.y = 3; + B4.z = 4; + B4.w = 5; + s.set_i8_4(B4); + + Short2 S2 = s.get_u8_2(); + if (S2.x != 1 || S2.y != 2) { + return false; + } + S2.x = 2; + S2.y = 3; + s.set_u8_2(S2); + + Short3 S3 = s.get_u8_3(); + if (S3.x != 1 || S3.y != 2 || S3.z != 3) { + return false; + } + S3.x = 2; + S3.y = 3; + S3.z = 4; + s.set_u8_3(S3); + + Short4 S4 = s.get_u8_4(); + if (S4.x != 1 || S4.y != 2 || S4.z != 3 || S4.w != 4) { + return false; + } + S4.x = 2; + S4.y = 3; + S4.z = 4; + S4.w = 5; + s.set_u8_4(S4); + + S2 = s.get_i16_2(); + if (S2.x != 1 || S2.y != 2) { + return false; + } + S2.x = 2; + S2.y = 3; + s.set_i16_2(S2); + + S3 = s.get_i16_3(); + if (S3.x != 1 || S3.y != 2 || S3.z != 3) { + return false; + } + S3.x = 2; + S3.y = 3; + S3.z = 4; + s.set_i16_3(S3); + + S4 = s.get_i16_4(); + if (S4.x != 1 || S4.y != 2 || S4.z != 3 || S4.w != 4) { + return false; + } + S4.x = 2; + S4.y = 3; + S4.z = 4; + S4.w = 5; + s.set_i16_4(S4); + + Int2 I2 = s.get_u16_2(); + if (I2.x != 1 || I2.y != 2) { + return false; + } + I2.x = 2; + I2.y = 3; + s.set_u16_2(I2); + + Int3 I3 = s.get_u16_3(); + if (I3.x != 1 || I3.y != 2 || I3.z != 3) { + return false; + } + I3.x = 2; + I3.y = 3; + I3.z = 4; + s.set_u16_3(I3); + + Int4 I4 = s.get_u16_4(); + if (I4.x != 1 || I4.y != 2 || I4.z != 3 || I4.w != 4) { + return false; + } + I4.x = 2; + I4.y = 3; + I4.z = 4; + I4.w = 5; + s.set_u16_4(I4); + + I2 = s.get_i32_2(); + if (I2.x != 1 || I2.y != 2) { + return false; + } + I2.x = 2; + I2.y = 3; + s.set_i32_2(I2); + + I3 = s.get_i32_3(); + if (I3.x != 1 || I3.y != 2 || I3.z != 3) { + return false; + } + I3.x = 2; + I3.y = 3; + I3.z = 4; + s.set_i32_3(I3); + + I4 = s.get_i32_4(); + if (I4.x != 1 || I4.y != 2 || I4.z != 3 || I4.w != 4) { + return false; + } + I4.x = 2; + I4.y = 3; + I4.z = 4; + I4.w = 5; + s.set_i32_4(I4); + + Long2 L2 = s.get_u32_2(); + if (L2.x != 1 || L2.y != 2) { + return false; + } + L2.x = 2; + L2.y = 3; + s.set_u32_2(L2); + + Long3 L3 = s.get_u32_3(); + if (L3.x != 1 || L3.y != 2 || L3.z != 3) { + return false; + } + L3.x = 2; + L3.y = 3; + L3.z = 4; + s.set_u32_3(L3); + + Long4 L4 = s.get_u32_4(); + if (L4.x != 1 || L4.y != 2 || L4.z != 3 || L4.w != 4) { + return false; + } + L4.x = 2; + L4.y = 3; + L4.z = 4; + L4.w = 5; + s.set_u32_4(L4); + + L2 = s.get_i64_2(); + if (L2.x != 1 || L2.y != 2) { + return false; + } + L2.x = 2; + L2.y = 3; + s.set_i64_2(L2); + + L3 = s.get_i64_3(); + if (L3.x != 1 || L3.y != 2 || L3.z != 3) { + return false; + } + L3.x = 2; + L3.y = 3; + L3.z = 4; + s.set_i64_3(L3); + + L4 = s.get_i64_4(); + if (L4.x != 1 || L4.y != 2 || L4.z != 3 || L4.w != 4) { + return false; + } + L4.x = 2; + L4.y = 3; + L4.z = 4; + L4.w = 5; + s.set_i64_4(L4); + + L2 = s.get_u64_2(); + if (L2.x != 1 || L2.y != 2) { + return false; + } + L2.x = 2; + L2.y = 3; + s.set_u64_2(L2); + + L3 = s.get_u64_3(); + if (L3.x != 1 || L3.y != 2 || L3.z != 3) { + return false; + } + L3.x = 2; + L3.y = 3; + L3.z = 4; + s.set_u64_3(L3); + + L4 = s.get_u64_4(); + if (L4.x != 1 || L4.y != 2 || L4.z != 3 || L4.w != 4) { + return false; + } + L4.x = 2; + L4.y = 3; + L4.z = 4; + L4.w = 5; + s.set_u64_4(L4); + + return true; + } + + public void run() { + RenderScript pRS = RenderScript.create(mCtx); + ScriptC_vector s = new ScriptC_vector(pRS, mRes, R.raw.vector); + pRS.setMessageHandler(mRsMessage); + if (!initializeGlobals(s)) { + result = -1; + } else { + s.invoke_vector_test(); + pRS.finish(); + waitForMessage(); + } + pRS.destroy(); + } +} diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/vector.rs b/tests/RenderScriptTests/tests/src/com/android/rs/test/vector.rs new file mode 100644 index 0000000..0430a2f --- /dev/null +++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/vector.rs @@ -0,0 +1,198 @@ +#include "shared.rsh" + +// Testing vector types +float2 f2 = { 1.0f, 2.0f }; +float3 f3 = { 1.0f, 2.0f, 3.0f }; +float4 f4 = { 1.0f, 2.0f, 3.0f, 4.0f }; + +double2 d2 = { 1.0, 2.0 }; +double3 d3 = { 1.0, 2.0, 3.0 }; +double4 d4 = { 1.0, 2.0, 3.0, 4.0 }; + +char2 i8_2 = { 1, 2 }; +char3 i8_3 = { 1, 2, 3 }; +char4 i8_4 = { 1, 2, 3, 4 }; + +uchar2 u8_2 = { 1, 2 }; +uchar3 u8_3 = { 1, 2, 3 }; +uchar4 u8_4 = { 1, 2, 3, 4 }; + +short2 i16_2 = { 1, 2 }; +short3 i16_3 = { 1, 2, 3 }; +short4 i16_4 = { 1, 2, 3, 4 }; + +ushort2 u16_2 = { 1, 2 }; +ushort3 u16_3 = { 1, 2, 3 }; +ushort4 u16_4 = { 1, 2, 3, 4 }; + +int2 i32_2 = { 1, 2 }; +int3 i32_3 = { 1, 2, 3 }; +int4 i32_4 = { 1, 2, 3, 4 }; + +uint2 u32_2 = { 1, 2 }; +uint3 u32_3 = { 1, 2, 3 }; +uint4 u32_4 = { 1, 2, 3, 4 }; + +long2 i64_2 = { 1, 2 }; +long3 i64_3 = { 1, 2, 3 }; +long4 i64_4 = { 1, 2, 3, 4 }; + +ulong2 u64_2 = { 1, 2 }; +ulong3 u64_3 = { 1, 2, 3 }; +ulong4 u64_4 = { 1, 2, 3, 4 }; + +static bool test_vector_types() { + bool failed = false; + + rsDebug("Testing F32", 0); + _RS_ASSERT(f2.x == 2.99f); + _RS_ASSERT(f2.y == 3.99f); + + _RS_ASSERT(f3.x == 2.99f); + _RS_ASSERT(f3.y == 3.99f); + _RS_ASSERT(f3.z == 4.99f); + + _RS_ASSERT(f4.x == 2.99f); + _RS_ASSERT(f4.y == 3.99f); + _RS_ASSERT(f4.z == 4.99f); + _RS_ASSERT(f4.w == 5.99f); + + rsDebug("Testing F64", 0); + _RS_ASSERT(d2.x == 2.99); + _RS_ASSERT(d2.y == 3.99); + + _RS_ASSERT(d3.x == 2.99); + _RS_ASSERT(d3.y == 3.99); + _RS_ASSERT(d3.z == 4.99); + + _RS_ASSERT(d4.x == 2.99); + _RS_ASSERT(d4.y == 3.99); + _RS_ASSERT(d4.z == 4.99); + _RS_ASSERT(d4.w == 5.99); + + rsDebug("Testing I8", 0); + _RS_ASSERT(i8_2.x == 2); + _RS_ASSERT(i8_2.y == 3); + + _RS_ASSERT(i8_3.x == 2); + _RS_ASSERT(i8_3.y == 3); + _RS_ASSERT(i8_3.z == 4); + + _RS_ASSERT(i8_4.x == 2); + _RS_ASSERT(i8_4.y == 3); + _RS_ASSERT(i8_4.z == 4); + _RS_ASSERT(i8_4.w == 5); + + rsDebug("Testing U8", 0); + _RS_ASSERT(u8_2.x == 2); + _RS_ASSERT(u8_2.y == 3); + + _RS_ASSERT(u8_3.x == 2); + _RS_ASSERT(u8_3.y == 3); + _RS_ASSERT(u8_3.z == 4); + + _RS_ASSERT(u8_4.x == 2); + _RS_ASSERT(u8_4.y == 3); + _RS_ASSERT(u8_4.z == 4); + _RS_ASSERT(u8_4.w == 5); + + rsDebug("Testing I16", 0); + _RS_ASSERT(i16_2.x == 2); + _RS_ASSERT(i16_2.y == 3); + + _RS_ASSERT(i16_3.x == 2); + _RS_ASSERT(i16_3.y == 3); + _RS_ASSERT(i16_3.z == 4); + + _RS_ASSERT(i16_4.x == 2); + _RS_ASSERT(i16_4.y == 3); + _RS_ASSERT(i16_4.z == 4); + _RS_ASSERT(i16_4.w == 5); + + rsDebug("Testing U16", 0); + _RS_ASSERT(u16_2.x == 2); + _RS_ASSERT(u16_2.y == 3); + + _RS_ASSERT(u16_3.x == 2); + _RS_ASSERT(u16_3.y == 3); + _RS_ASSERT(u16_3.z == 4); + + _RS_ASSERT(u16_4.x == 2); + _RS_ASSERT(u16_4.y == 3); + _RS_ASSERT(u16_4.z == 4); + _RS_ASSERT(u16_4.w == 5); + + rsDebug("Testing I32", 0); + _RS_ASSERT(i32_2.x == 2); + _RS_ASSERT(i32_2.y == 3); + + _RS_ASSERT(i32_3.x == 2); + _RS_ASSERT(i32_3.y == 3); + _RS_ASSERT(i32_3.z == 4); + + _RS_ASSERT(i32_4.x == 2); + _RS_ASSERT(i32_4.y == 3); + _RS_ASSERT(i32_4.z == 4); + _RS_ASSERT(i32_4.w == 5); + + rsDebug("Testing U32", 0); + _RS_ASSERT(u32_2.x == 2); + _RS_ASSERT(u32_2.y == 3); + + _RS_ASSERT(u32_3.x == 2); + _RS_ASSERT(u32_3.y == 3); + _RS_ASSERT(u32_3.z == 4); + + _RS_ASSERT(u32_4.x == 2); + _RS_ASSERT(u32_4.y == 3); + _RS_ASSERT(u32_4.z == 4); + _RS_ASSERT(u32_4.w == 5); + + rsDebug("Testing I64", 0); + _RS_ASSERT(i64_2.x == 2); + _RS_ASSERT(i64_2.y == 3); + + _RS_ASSERT(i64_3.x == 2); + _RS_ASSERT(i64_3.y == 3); + _RS_ASSERT(i64_3.z == 4); + + _RS_ASSERT(i64_4.x == 2); + _RS_ASSERT(i64_4.y == 3); + _RS_ASSERT(i64_4.z == 4); + _RS_ASSERT(i64_4.w == 5); + + rsDebug("Testing U64", 0); + _RS_ASSERT(u64_2.x == 2); + _RS_ASSERT(u64_2.y == 3); + + _RS_ASSERT(u64_3.x == 2); + _RS_ASSERT(u64_3.y == 3); + _RS_ASSERT(u64_3.z == 4); + + _RS_ASSERT(u64_4.x == 2); + _RS_ASSERT(u64_4.y == 3); + _RS_ASSERT(u64_4.z == 4); + _RS_ASSERT(u64_4.w == 5); + + if (failed) { + rsDebug("test_vector FAILED", 0); + } + else { + rsDebug("test_vector PASSED", 0); + } + + return failed; +} + +void vector_test() { + bool failed = false; + failed |= test_vector_types(); + + if (failed) { + rsSendToClientBlocking(RS_MSG_TEST_FAILED); + } + else { + rsSendToClientBlocking(RS_MSG_TEST_PASSED); + } +} + |
