diff options
Diffstat (limited to 'core/java/android')
190 files changed, 8015 insertions, 4140 deletions
diff --git a/core/java/android/alsa/AlsaCardsParser.java b/core/java/android/alsa/AlsaCardsParser.java index 8b44881..26a61ae 100644 --- a/core/java/android/alsa/AlsaCardsParser.java +++ b/core/java/android/alsa/AlsaCardsParser.java @@ -22,46 +22,69 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; -import java.util.Vector; +import java.util.ArrayList; /** * @hide Retrieves information from an ALSA "cards" file. */ public class AlsaCardsParser { private static final String TAG = "AlsaCardsParser"; + protected static final boolean DEBUG = true; - private static LineTokenizer tokenizer_ = new LineTokenizer(" :[]"); + private static final String kCardsFilePath = "/proc/asound/cards"; + + private static LineTokenizer mTokenizer = new LineTokenizer(" :[]"); + + private ArrayList<AlsaCardRecord> mCardRecords = new ArrayList<AlsaCardRecord>(); public class AlsaCardRecord { + private static final String TAG = "AlsaCardRecord"; + private static final String kUsbCardKeyStr = "at usb-"; + public int mCardNum = -1; public String mField1 = ""; public String mCardName = ""; public String mCardDescription = ""; + public boolean mIsUsb = false; public AlsaCardRecord() {} public boolean parse(String line, int lineIndex) { int tokenIndex = 0; int delimIndex = 0; + if (lineIndex == 0) { // line # (skip) - tokenIndex = tokenizer_.nextToken(line, tokenIndex); - delimIndex = tokenizer_.nextDelimiter(line, tokenIndex); + tokenIndex = mTokenizer.nextToken(line, tokenIndex); + delimIndex = mTokenizer.nextDelimiter(line, tokenIndex); + + try { + // mCardNum + mCardNum = Integer.parseInt(line.substring(tokenIndex, delimIndex)); + } catch (NumberFormatException e) { + Slog.e(TAG, "Failed to parse line " + lineIndex + " of " + kCardsFilePath + + ": " + line.substring(tokenIndex, delimIndex)); + return false; + } // mField1 - tokenIndex = tokenizer_.nextToken(line, delimIndex); - delimIndex = tokenizer_.nextDelimiter(line, tokenIndex); + tokenIndex = mTokenizer.nextToken(line, delimIndex); + delimIndex = mTokenizer.nextDelimiter(line, tokenIndex); mField1 = line.substring(tokenIndex, delimIndex); // mCardName - tokenIndex = tokenizer_.nextToken(line, delimIndex); - // delimIndex = tokenizer_.nextDelimiter(line, tokenIndex); + tokenIndex = mTokenizer.nextToken(line, delimIndex); mCardName = line.substring(tokenIndex); + // done } else if (lineIndex == 1) { - tokenIndex = tokenizer_.nextToken(line, 0); + tokenIndex = mTokenizer.nextToken(line, 0); if (tokenIndex != -1) { - mCardDescription = line.substring(tokenIndex); + int keyIndex = line.indexOf(kUsbCardKeyStr); + mIsUsb = keyIndex != -1; + if (mIsUsb) { + mCardDescription = line.substring(tokenIndex, keyIndex - 1); + } } } @@ -73,44 +96,127 @@ public class AlsaCardsParser { } } - private Vector<AlsaCardRecord> cardRecords_ = new Vector<AlsaCardRecord>(); + public AlsaCardsParser() {} public void scan() { - cardRecords_.clear(); - final String cardsFilePath = "/proc/asound/cards"; - File cardsFile = new File(cardsFilePath); - try { - FileReader reader = new FileReader(cardsFile); - BufferedReader bufferedReader = new BufferedReader(reader); - String line = ""; - while ((line = bufferedReader.readLine()) != null) { - AlsaCardRecord cardRecord = new AlsaCardRecord(); - cardRecord.parse(line, 0); - cardRecord.parse(line = bufferedReader.readLine(), 1); - cardRecords_.add(cardRecord); - } - reader.close(); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - public AlsaCardRecord getCardRecordAt(int index) { - return cardRecords_.get(index); - } - - public int getNumCardRecords() { - return cardRecords_.size(); - } - - public void Log() { - int numCardRecs = getNumCardRecords(); - for (int index = 0; index < numCardRecs; ++index) { - Slog.w(TAG, "usb:" + getCardRecordAt(index).textFormat()); - } + if (DEBUG) { + Slog.i(TAG, "AlsaCardsParser.scan()"); + } + mCardRecords = new ArrayList<AlsaCardRecord>(); + + File cardsFile = new File(kCardsFilePath); + try { + FileReader reader = new FileReader(cardsFile); + BufferedReader bufferedReader = new BufferedReader(reader); + String line = ""; + while ((line = bufferedReader.readLine()) != null) { + AlsaCardRecord cardRecord = new AlsaCardRecord(); + if (DEBUG) { + Slog.i(TAG, " " + line); + } + cardRecord.parse(line, 0); + + line = bufferedReader.readLine(); + if (DEBUG) { + Slog.i(TAG, " " + line); + } + cardRecord.parse(line, 1); + + mCardRecords.add(cardRecord); + } + reader.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } } - public AlsaCardsParser() {} + public ArrayList<AlsaCardRecord> getScanRecords() { + return mCardRecords; + } + + public AlsaCardRecord getCardRecordAt(int index) { + return mCardRecords.get(index); + } + + public AlsaCardRecord getCardRecordFor(int cardNum) { + for (AlsaCardRecord rec : mCardRecords) { + if (rec.mCardNum == cardNum) { + return rec; + } + } + + return null; + } + + public int getNumCardRecords() { + return mCardRecords.size(); + } + + public boolean isCardUsb(int cardNum) { + for (AlsaCardRecord rec : mCardRecords) { + if (rec.mCardNum == cardNum) { + return rec.mIsUsb; + } + } + + return false; + } + + // return -1 if none found + public int getDefaultUsbCard() { + // Choose the most-recently added EXTERNAL card + // or return the first added EXTERNAL card? + for (AlsaCardRecord rec : mCardRecords) { + if (rec.mIsUsb) { + return rec.mCardNum; + } + } + + return -1; + } + + public int getDefaultCard() { + // return an external card if possible + int card = getDefaultUsbCard(); + + if (card < 0 && getNumCardRecords() > 0) { + // otherwise return the (internal) card with the highest number + card = getCardRecordAt(getNumCardRecords() - 1).mCardNum; + } + return card; + } + + static public boolean hasCardNumber(ArrayList<AlsaCardRecord> recs, int cardNum) { + for (AlsaCardRecord cardRec : recs) { + if (cardRec.mCardNum == cardNum) { + return true; + } + } + return false; + } + + public ArrayList<AlsaCardRecord> getNewCardRecords(ArrayList<AlsaCardRecord> prevScanRecs) { + ArrayList<AlsaCardRecord> newRecs = new ArrayList<AlsaCardRecord>(); + for (AlsaCardRecord rec : mCardRecords) { + // now scan to see if this card number is in the previous scan list + if (!hasCardNumber(prevScanRecs, rec.mCardNum)) { + newRecs.add(rec); + } + } + return newRecs; + } + + // + // Logging + // + public void Log(String heading) { + if (DEBUG) { + Slog.i(TAG, heading); + for (AlsaCardRecord cardRec : mCardRecords) { + Slog.i(TAG, cardRec.textFormat()); + } + } + } } diff --git a/core/java/android/alsa/AlsaDevicesParser.java b/core/java/android/alsa/AlsaDevicesParser.java index 82cc1ae..b140d3d 100644 --- a/core/java/android/alsa/AlsaDevicesParser.java +++ b/core/java/android/alsa/AlsaDevicesParser.java @@ -21,7 +21,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; -import java.util.Vector; +import java.util.ArrayList; /** * @hide @@ -29,6 +29,9 @@ import java.util.Vector; */ public class AlsaDevicesParser { private static final String TAG = "AlsaDevicesParser"; + protected static final boolean DEBUG = false; + + private static final String kDevicesFilePath = "/proc/asound/devices"; private static final int kIndex_CardDeviceField = 5; private static final int kStartIndex_CardNum = 6; @@ -58,8 +61,7 @@ public class AlsaDevicesParser { int mDeviceType = kDeviceType_Unknown; int mDeviceDir = kDeviceDir_Unknown; - public AlsaDeviceRecord() { - } + public AlsaDeviceRecord() {} public boolean parse(String line) { // "0123456789012345678901234567890" @@ -89,51 +91,57 @@ public class AlsaDevicesParser { } String token = line.substring(tokenOffset, delimOffset); - switch (tokenIndex) { - case kToken_LineNum: - // ignore - break; - - case kToken_CardNum: - mCardNum = Integer.parseInt(token); - if (line.charAt(delimOffset) != '-') { - tokenIndex++; // no device # in the token stream - } - break; - - case kToken_DeviceNum: - mDeviceNum = Integer.parseInt(token); - break; - - case kToken_Type0: - if (token.equals("digital")) { - // NOP - } else if (token.equals("control")) { - mDeviceType = kDeviceType_Control; - } else if (token.equals("raw")) { - // NOP - } - break; - - case kToken_Type1: - if (token.equals("audio")) { - mDeviceType = kDeviceType_Audio; - } else if (token.equals("midi")) { - mDeviceType = kDeviceType_MIDI; - mHasMIDIDevices = true; - } - break; - - case kToken_Type2: - if (token.equals("capture")) { - mDeviceDir = kDeviceDir_Capture; - mHasCaptureDevices = true; - } else if (token.equals("playback")) { - mDeviceDir = kDeviceDir_Playback; - mHasPlaybackDevices = true; - } - break; - } // switch (tokenIndex) + try { + switch (tokenIndex) { + case kToken_LineNum: + // ignore + break; + + case kToken_CardNum: + mCardNum = Integer.parseInt(token); + if (line.charAt(delimOffset) != '-') { + tokenIndex++; // no device # in the token stream + } + break; + + case kToken_DeviceNum: + mDeviceNum = Integer.parseInt(token); + break; + + case kToken_Type0: + if (token.equals("digital")) { + // NOP + } else if (token.equals("control")) { + mDeviceType = kDeviceType_Control; + } else if (token.equals("raw")) { + // NOP + } + break; + + case kToken_Type1: + if (token.equals("audio")) { + mDeviceType = kDeviceType_Audio; + } else if (token.equals("midi")) { + mDeviceType = kDeviceType_MIDI; + mHasMIDIDevices = true; + } + break; + + case kToken_Type2: + if (token.equals("capture")) { + mDeviceDir = kDeviceDir_Capture; + mHasCaptureDevices = true; + } else if (token.equals("playback")) { + mDeviceDir = kDeviceDir_Playback; + mHasPlaybackDevices = true; + } + break; + } // switch (tokenIndex) + } catch (NumberFormatException e) { + Slog.e(TAG, "Failed to parse token " + tokenIndex + " of " + kDevicesFilePath + + " token: " + token); + return false; + } tokenIndex++; } // while (true) @@ -176,38 +184,27 @@ public class AlsaDevicesParser { } } - private Vector<AlsaDeviceRecord> - deviceRecords_ = new Vector<AlsaDeviceRecord>(); + private ArrayList<AlsaDeviceRecord> mDeviceRecords = new ArrayList<AlsaDeviceRecord>(); - private boolean isLineDeviceRecord(String line) { - return line.charAt(kIndex_CardDeviceField) == '['; - } + public AlsaDevicesParser() {} - public AlsaDevicesParser() { + // + // Access + // + public int getDefaultDeviceNum(int card) { + // TODO - This (obviously) isn't sufficient. Revisit. + return 0; } - public int getNumDeviceRecords() { - return deviceRecords_.size(); - } - - public AlsaDeviceRecord getDeviceRecordAt(int index) { - return deviceRecords_.get(index); - } - - public void Log() { - int numDevRecs = getNumDeviceRecords(); - for (int index = 0; index < numDevRecs; ++index) { - Slog.w(TAG, "usb:" + getDeviceRecordAt(index).textFormat()); - } - } - - public boolean hasPlaybackDevices() { + // + // Predicates + // + public boolean hasPlaybackDevices() { return mHasPlaybackDevices; } public boolean hasPlaybackDevices(int card) { - for (int index = 0; index < deviceRecords_.size(); index++) { - AlsaDeviceRecord deviceRecord = deviceRecords_.get(index); + for (AlsaDeviceRecord deviceRecord : mDeviceRecords) { if (deviceRecord.mCardNum == card && deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_Audio && deviceRecord.mDeviceDir == AlsaDeviceRecord.kDeviceDir_Playback) { @@ -222,8 +219,7 @@ public class AlsaDevicesParser { } public boolean hasCaptureDevices(int card) { - for (int index = 0; index < deviceRecords_.size(); index++) { - AlsaDeviceRecord deviceRecord = deviceRecords_.get(index); + for (AlsaDeviceRecord deviceRecord : mDeviceRecords) { if (deviceRecord.mCardNum == card && deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_Audio && deviceRecord.mDeviceDir == AlsaDeviceRecord.kDeviceDir_Capture) { @@ -238,8 +234,7 @@ public class AlsaDevicesParser { } public boolean hasMIDIDevices(int card) { - for (int index = 0; index < deviceRecords_.size(); index++) { - AlsaDeviceRecord deviceRecord = deviceRecords_.get(index); + for (AlsaDeviceRecord deviceRecord : mDeviceRecords) { if (deviceRecord.mCardNum == card && deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_MIDI) { return true; @@ -248,11 +243,17 @@ public class AlsaDevicesParser { return false; } + // + // Process + // + private boolean isLineDeviceRecord(String line) { + return line.charAt(kIndex_CardDeviceField) == '['; + } + public void scan() { - deviceRecords_.clear(); + mDeviceRecords.clear(); - final String devicesFilePath = "/proc/asound/devices"; - File devicesFile = new File(devicesFilePath); + File devicesFile = new File(kDevicesFilePath); try { FileReader reader = new FileReader(devicesFile); BufferedReader bufferedReader = new BufferedReader(reader); @@ -261,7 +262,7 @@ public class AlsaDevicesParser { if (isLineDeviceRecord(line)) { AlsaDeviceRecord deviceRecord = new AlsaDeviceRecord(); deviceRecord.parse(line); - deviceRecords_.add(deviceRecord); + mDeviceRecords.add(deviceRecord); } } reader.close(); @@ -271,5 +272,17 @@ public class AlsaDevicesParser { e.printStackTrace(); } } + + // + // Loging + // + public void Log(String heading) { + if (DEBUG) { + Slog.i(TAG, heading); + for (AlsaDeviceRecord deviceRecord : mDeviceRecords) { + Slog.i(TAG, deviceRecord.textFormat()); + } + } + } } // class AlsaDevicesParser diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java index 688d7e4..6ef3da8 100644 --- a/core/java/android/animation/AnimatorInflater.java +++ b/core/java/android/animation/AnimatorInflater.java @@ -42,6 +42,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; +import java.util.List; /** * This class is used to instantiate animator XML files into Animator objects. @@ -66,8 +67,7 @@ public class AnimatorInflater { private static final int VALUE_TYPE_FLOAT = 0; private static final int VALUE_TYPE_INT = 1; private static final int VALUE_TYPE_PATH = 2; - private static final int VALUE_TYPE_COLOR = 4; - private static final int VALUE_TYPE_CUSTOM = 5; + private static final int VALUE_TYPE_COLOR = 3; private static final boolean DBG_ANIMATOR_INFLATER = false; @@ -296,39 +296,50 @@ public class AnimatorInflater { } } - /** - * @param anim The animator, must not be null - * @param arrayAnimator Incoming typed array for Animator's attributes. - * @param arrayObjectAnimator Incoming typed array for Object Animator's - * attributes. - * @param pixelSize The relative pixel size, used to calculate the - * maximum error for path animations. - */ - private static void parseAnimatorFromTypeArray(ValueAnimator anim, - TypedArray arrayAnimator, TypedArray arrayObjectAnimator, float pixelSize) { - long duration = arrayAnimator.getInt(R.styleable.Animator_duration, 300); - - long startDelay = arrayAnimator.getInt(R.styleable.Animator_startOffset, 0); - - int valueType = arrayAnimator.getInt(R.styleable.Animator_valueType, - VALUE_TYPE_FLOAT); - - TypeEvaluator evaluator = null; + private static PropertyValuesHolder getPVH(TypedArray styledAttributes, int valueType, + int valueFromId, int valueToId, String propertyName) { boolean getFloats = (valueType == VALUE_TYPE_FLOAT); - TypedValue tvFrom = arrayAnimator.peekValue(R.styleable.Animator_valueFrom); + TypedValue tvFrom = styledAttributes.peekValue(valueFromId); boolean hasFrom = (tvFrom != null); int fromType = hasFrom ? tvFrom.type : 0; - TypedValue tvTo = arrayAnimator.peekValue(R.styleable.Animator_valueTo); + TypedValue tvTo = styledAttributes.peekValue(valueToId); boolean hasTo = (tvTo != null); int toType = hasTo ? tvTo.type : 0; - // TODO: Further clean up this part of code into 4 types : path, color, - // integer and float. + PropertyValuesHolder returnValue = null; + if (valueType == VALUE_TYPE_PATH) { - evaluator = setupAnimatorForPath(anim, arrayAnimator); + String fromString = styledAttributes.getString(valueFromId); + String toString = styledAttributes.getString(valueToId); + PathParser.PathDataNode[] nodesFrom = PathParser.createNodesFromPathData(fromString); + PathParser.PathDataNode[] nodesTo = PathParser.createNodesFromPathData(toString); + + if (nodesFrom != null || nodesTo != null) { + if (nodesFrom != null) { + TypeEvaluator evaluator = + new PathDataEvaluator(PathParser.deepCopyNodes(nodesFrom)); + if (nodesTo != null) { + if (!PathParser.canMorph(nodesFrom, nodesTo)) { + throw new InflateException(" Can't morph from " + fromString + " to " + + toString); + } + returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator, + nodesFrom, nodesTo); + } else { + returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator, + (Object) nodesFrom); + } + } else if (nodesTo != null) { + TypeEvaluator evaluator = + new PathDataEvaluator(PathParser.deepCopyNodes(nodesTo)); + returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator, + (Object) nodesTo); + } + } } else { + TypeEvaluator evaluator = null; // Integer and float value types are handled here. if ((hasFrom && (fromType >= TypedValue.TYPE_FIRST_COLOR_INT) && (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) || @@ -338,7 +349,101 @@ public class AnimatorInflater { getFloats = false; evaluator = ArgbEvaluator.getInstance(); } - setupValues(anim, arrayAnimator, getFloats, hasFrom, fromType, hasTo, toType); + if (getFloats) { + float valueFrom; + float valueTo; + if (hasFrom) { + if (fromType == TypedValue.TYPE_DIMENSION) { + valueFrom = styledAttributes.getDimension(valueFromId, 0f); + } else { + valueFrom = styledAttributes.getFloat(valueFromId, 0f); + } + if (hasTo) { + if (toType == TypedValue.TYPE_DIMENSION) { + valueTo = styledAttributes.getDimension(valueToId, 0f); + } else { + valueTo = styledAttributes.getFloat(valueToId, 0f); + } + returnValue = PropertyValuesHolder.ofFloat(propertyName, + valueFrom, valueTo); + } else { + returnValue = PropertyValuesHolder.ofFloat(propertyName, valueFrom); + } + } else { + if (toType == TypedValue.TYPE_DIMENSION) { + valueTo = styledAttributes.getDimension(valueToId, 0f); + } else { + valueTo = styledAttributes.getFloat(valueToId, 0f); + } + returnValue = PropertyValuesHolder.ofFloat(propertyName, valueTo); + } + } else { + int valueFrom; + int valueTo; + if (hasFrom) { + if (fromType == TypedValue.TYPE_DIMENSION) { + valueFrom = (int) styledAttributes.getDimension(valueFromId, 0f); + } else if ((fromType >= TypedValue.TYPE_FIRST_COLOR_INT) && + (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) { + valueFrom = styledAttributes.getColor(valueFromId, 0); + } else { + valueFrom = styledAttributes.getInt(valueFromId, 0); + } + if (hasTo) { + if (toType == TypedValue.TYPE_DIMENSION) { + valueTo = (int) styledAttributes.getDimension(valueToId, 0f); + } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) && + (toType <= TypedValue.TYPE_LAST_COLOR_INT)) { + valueTo = styledAttributes.getColor(valueToId, 0); + } else { + valueTo = styledAttributes.getInt(valueToId, 0); + } + returnValue = PropertyValuesHolder.ofInt(propertyName, valueFrom, valueTo); + } else { + returnValue = PropertyValuesHolder.ofInt(propertyName, valueFrom); + } + } else { + if (hasTo) { + if (toType == TypedValue.TYPE_DIMENSION) { + valueTo = (int) styledAttributes.getDimension(valueToId, 0f); + } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) && + (toType <= TypedValue.TYPE_LAST_COLOR_INT)) { + valueTo = styledAttributes.getColor(valueToId, 0); + } else { + valueTo = styledAttributes.getInt(valueToId, 0); + } + returnValue = PropertyValuesHolder.ofInt(propertyName, valueTo); + } + } + } + if (returnValue != null && evaluator != null) { + returnValue.setEvaluator(evaluator); + } + } + + return returnValue; + } + + /** + * @param anim The animator, must not be null + * @param arrayAnimator Incoming typed array for Animator's attributes. + * @param arrayObjectAnimator Incoming typed array for Object Animator's + * attributes. + * @param pixelSize The relative pixel size, used to calculate the + * maximum error for path animations. + */ + private static void parseAnimatorFromTypeArray(ValueAnimator anim, + TypedArray arrayAnimator, TypedArray arrayObjectAnimator, float pixelSize) { + long duration = arrayAnimator.getInt(R.styleable.Animator_duration, 300); + + long startDelay = arrayAnimator.getInt(R.styleable.Animator_startOffset, 0); + + int valueType = arrayAnimator.getInt(R.styleable.Animator_valueType, VALUE_TYPE_FLOAT); + + PropertyValuesHolder pvh = getPVH(arrayAnimator, valueType, + R.styleable.Animator_valueFrom, R.styleable.Animator_valueTo, ""); + if (pvh != null) { + anim.setValues(pvh); } anim.setDuration(duration); @@ -353,12 +458,10 @@ public class AnimatorInflater { arrayAnimator.getInt(R.styleable.Animator_repeatMode, ValueAnimator.RESTART)); } - if (evaluator != null) { - anim.setEvaluator(evaluator); - } if (arrayObjectAnimator != null) { - setupObjectAnimator(anim, arrayObjectAnimator, getFloats, pixelSize); + setupObjectAnimator(anim, arrayObjectAnimator, valueType == VALUE_TYPE_FLOAT, + pixelSize); } } @@ -570,6 +673,7 @@ public class AnimatorInflater { } String name = parser.getName(); + boolean gotValues = false; if (name.equals("objectAnimator")) { anim = loadObjectAnimator(res, theme, attrs, pixelSize); @@ -588,11 +692,18 @@ public class AnimatorInflater { createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering, pixelSize); a.recycle(); + } else if (name.equals("propertyValuesHolder")) { + PropertyValuesHolder[] values = loadValues(res, theme, parser, + Xml.asAttributeSet(parser)); + if (values != null && anim != null && (anim instanceof ValueAnimator)) { + ((ValueAnimator) anim).setValues(values); + } + gotValues = true; } else { throw new RuntimeException("Unknown animator name: " + parser.getName()); } - if (parent != null) { + if (parent != null && !gotValues) { if (childAnims == null) { childAnims = new ArrayList<Animator>(); } @@ -612,7 +723,233 @@ public class AnimatorInflater { } } return anim; + } + + private static PropertyValuesHolder[] loadValues(Resources res, Theme theme, + XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException { + ArrayList<PropertyValuesHolder> values = null; + + int type; + while ((type = parser.getEventType()) != XmlPullParser.END_TAG && + type != XmlPullParser.END_DOCUMENT) { + + if (type != XmlPullParser.START_TAG) { + parser.next(); + continue; + } + + String name = parser.getName(); + + if (name.equals("propertyValuesHolder")) { + TypedArray a; + if (theme != null) { + a = theme.obtainStyledAttributes(attrs, R.styleable.PropertyValuesHolder, 0, 0); + } else { + a = res.obtainAttributes(attrs, R.styleable.PropertyValuesHolder); + } + String propertyName = a.getString(R.styleable.PropertyValuesHolder_propertyName); + int valueType = a.getInt(R.styleable.PropertyValuesHolder_valueType, + VALUE_TYPE_FLOAT); + PropertyValuesHolder pvh = loadPvh(res, theme, parser, propertyName, valueType); + if (pvh == null) { + pvh = getPVH(a, valueType, + R.styleable.PropertyValuesHolder_valueFrom, + R.styleable.PropertyValuesHolder_valueTo, propertyName); + } + if (pvh != null) { + if (values == null) { + values = new ArrayList<PropertyValuesHolder>(); + } + values.add(pvh); + } + a.recycle(); + } + + parser.next(); + } + + PropertyValuesHolder[] valuesArray = null; + if (values != null) { + int count = values.size(); + valuesArray = new PropertyValuesHolder[count]; + for (int i = 0; i < count; ++i) { + valuesArray[i] = values.get(i); + } + } + return valuesArray; + } + + private static void dumpKeyframes(Object[] keyframes, String header) { + if (keyframes == null || keyframes.length == 0) { + return; + } + Log.d(TAG, header); + int count = keyframes.length; + for (int i = 0; i < count; ++i) { + Keyframe keyframe = (Keyframe) keyframes[i]; + Log.d(TAG, "Keyframe " + i + ": fraction " + + (keyframe.getFraction() < 0 ? "null" : keyframe.getFraction()) + ", " + + ", value : " + ((keyframe.hasValue()) ? keyframe.getValue() : "null")); + } + } + + private static PropertyValuesHolder loadPvh(Resources res, Theme theme, XmlPullParser parser, + String propertyName, int valueType) + throws XmlPullParserException, IOException { + + PropertyValuesHolder value = null; + ArrayList<Keyframe> keyframes = null; + + int type; + while ((type = parser.next()) != XmlPullParser.END_TAG && + type != XmlPullParser.END_DOCUMENT) { + String name = parser.getName(); + if (name.equals("keyframe")) { + Keyframe keyframe = loadKeyframe(res, theme, Xml.asAttributeSet(parser), valueType); + if (keyframe != null) { + if (keyframes == null) { + keyframes = new ArrayList<Keyframe>(); + } + keyframes.add(keyframe); + } + parser.next(); + } + } + + int count; + if (keyframes != null && (count = keyframes.size()) > 0) { + // make sure we have keyframes at 0 and 1 + // If we have keyframes with set fractions, add keyframes at start/end + // appropriately. If start/end have no set fractions: + // if there's only one keyframe, set its fraction to 1 and add one at 0 + // if >1 keyframe, set the last fraction to 1, the first fraction to 0 + Keyframe firstKeyframe = keyframes.get(0); + Keyframe lastKeyframe = keyframes.get(count - 1); + float endFraction = lastKeyframe.getFraction(); + if (endFraction < 1) { + if (endFraction < 0) { + lastKeyframe.setFraction(1); + } else { + keyframes.add(keyframes.size(), createNewKeyframe(lastKeyframe, 1)); + ++count; + } + } + float startFraction = firstKeyframe.getFraction(); + if (startFraction != 0) { + if (startFraction < 0) { + firstKeyframe.setFraction(0); + } else { + keyframes.add(0, createNewKeyframe(firstKeyframe, 0)); + ++count; + } + } + Keyframe[] keyframeArray = new Keyframe[count]; + keyframes.toArray(keyframeArray); + for (int i = 0; i < count; ++i) { + Keyframe keyframe = keyframeArray[i]; + if (keyframe.getFraction() < 0) { + if (i == 0) { + keyframe.setFraction(0); + } else if (i == count - 1) { + keyframe.setFraction(1); + } else { + // figure out the start/end parameters of the current gap + // in fractions and distribute the gap among those keyframes + int startIndex = i; + int endIndex = i; + for (int j = startIndex + 1; j < count - 1; ++j) { + if (keyframeArray[j].getFraction() >= 0) { + break; + } + endIndex = j; + } + float gap = keyframeArray[endIndex + 1].getFraction() - + keyframeArray[startIndex - 1].getFraction(); + distributeKeyframes(keyframeArray, gap, startIndex, endIndex); + } + } + } + value = PropertyValuesHolder.ofKeyframe(propertyName, keyframeArray); + if (valueType == VALUE_TYPE_COLOR) { + value.setEvaluator(ArgbEvaluator.getInstance()); + } + } + + return value; + } + + private static Keyframe createNewKeyframe(Keyframe sampleKeyframe, float fraction) { + return sampleKeyframe.getType() == float.class ? + Keyframe.ofFloat(fraction) : + (sampleKeyframe.getType() == int.class) ? + Keyframe.ofInt(fraction) : + Keyframe.ofObject(fraction); + } + + /** + * Utility function to set fractions on keyframes to cover a gap in which the + * fractions are not currently set. Keyframe fractions will be distributed evenly + * in this gap. For example, a gap of 1 keyframe in the range 0-1 will be at .5, a gap + * of .6 spread between two keyframes will be at .2 and .4 beyond the fraction at the + * keyframe before startIndex. + * Assumptions: + * - First and last keyframe fractions (bounding this spread) are already set. So, + * for example, if no fractions are set, we will already set first and last keyframe + * fraction values to 0 and 1. + * - startIndex must be >0 (which follows from first assumption). + * - endIndex must be >= startIndex. + * + * @param keyframes the array of keyframes + * @param gap The total gap we need to distribute + * @param startIndex The index of the first keyframe whose fraction must be set + * @param endIndex The index of the last keyframe whose fraction must be set + */ + private static void distributeKeyframes(Keyframe[] keyframes, float gap, + int startIndex, int endIndex) { + int count = endIndex - startIndex + 2; + float increment = gap / count; + for (int i = startIndex; i <= endIndex; ++i) { + keyframes[i].setFraction(keyframes[i-1].getFraction() + increment); + } + } + + private static Keyframe loadKeyframe(Resources res, Theme theme, AttributeSet attrs, + int valueType) + throws XmlPullParserException, IOException { + + TypedArray a; + if (theme != null) { + a = theme.obtainStyledAttributes(attrs, R.styleable.Keyframe, 0, 0); + } else { + a = res.obtainAttributes(attrs, R.styleable.Keyframe); + } + + Keyframe keyframe = null; + + float fraction = a.getFloat(R.styleable.Keyframe_fraction, -1); + + boolean hasValue = a.peekValue(R.styleable.Keyframe_value) != null; + + if (hasValue) { + switch (valueType) { + case VALUE_TYPE_FLOAT: + float value = a.getFloat(R.styleable.Keyframe_value, 0); + keyframe = Keyframe.ofFloat(fraction, value); + break; + case VALUE_TYPE_COLOR: + case VALUE_TYPE_INT: + int intValue = a.getInt(R.styleable.Keyframe_value, 0); + keyframe = Keyframe.ofInt(fraction, intValue); + break; + } + } else { + keyframe = (valueType == VALUE_TYPE_FLOAT) ? Keyframe.ofFloat(fraction) : + Keyframe.ofInt(fraction); + } + + a.recycle(); + return keyframe; } private static ObjectAnimator loadObjectAnimator(Resources res, Theme theme, AttributeSet attrs, diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java index 92762c3..53d5237 100644 --- a/core/java/android/animation/AnimatorSet.java +++ b/core/java/android/animation/AnimatorSet.java @@ -972,6 +972,18 @@ public final class AnimatorSet extends Animator { } } + @Override + public String toString() { + String returnVal = "AnimatorSet@" + Integer.toHexString(hashCode()) + "{"; + boolean prevNeedsSort = mNeedsSort; + sortNodes(); + mNeedsSort = prevNeedsSort; + for (Node node : mSortedNodes) { + returnVal += "\n " + node.animation.toString(); + } + return returnVal + "\n}"; + } + /** * Dependency holds information about the node that some other node is * dependent upon and the nature of that dependency. diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java index 59daaab..71855da 100644 --- a/core/java/android/animation/ObjectAnimator.java +++ b/core/java/android/animation/ObjectAnimator.java @@ -33,6 +33,27 @@ import java.util.ArrayList; * are then determined internally and the animation will call these functions as necessary to * animate the property. * + * <p>Animators can be created from either code or resource files, as shown here:</p> + * + * {@sample development/samples/ApiDemos/res/anim/object_animator.xml ObjectAnimatorResources} + * + * <p>When using resource files, it is possible to use {@link PropertyValuesHolder} and + * {@link Keyframe} to create more complex animations. Using PropertyValuesHolders + * allows animators to animate several properties in parallel, as shown in this sample:</p> + * + * {@sample development/samples/ApiDemos/res/anim/object_animator_pvh.xml + * PropertyValuesHolderResources} + * + * <p>Using Keyframes allows animations to follow more complex paths from the start + * to the end values. Note that you can specify explicit fractional values (from 0 to 1) for + * each keyframe to determine when, in the overall duration, the animation should arrive at that + * value. Alternatively, you can leave the fractions off and the keyframes will be equally + * distributed within the total duration. Also, a keyframe with no value will derive its value + * from the target object when the animator starts, just like animators with only one + * value specified.</p> + * + * {@sample development/samples/ApiDemos/res/anim/object_animator_pvh_kf.xml KeyframeResources} + * * <div class="special reference"> * <h3>Developer Guides</h3> * <p>For more information about animating with {@code ObjectAnimator}, read the diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index 9709555..292bb1d 100644 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -17,6 +17,7 @@ package android.animation; import android.content.res.ConfigurationBoundResourceCache; +import android.os.Debug; import android.os.Looper; import android.os.Trace; import android.util.AndroidRuntimeException; @@ -40,6 +41,21 @@ import java.util.HashMap; * out of an animation. This behavior can be changed by calling * {@link ValueAnimator#setInterpolator(TimeInterpolator)}.</p> * + * <p>Animators can be created from either code or resource files. Here is an example + * of a ValueAnimator resource file:</p> + * + * {@sample development/samples/ApiDemos/res/anim/animator.xml ValueAnimatorResources} + * + * <p>It is also possible to use a combination of {@link PropertyValuesHolder} and + * {@link Keyframe} resource tags to create a multi-step animation. + * Note that you can specify explicit fractional values (from 0 to 1) for + * each keyframe to determine when, in the overall duration, the animation should arrive at that + * value. Alternatively, you can leave the fractions off and the keyframes will be equally + * distributed within the total duration:</p> + * + * {@sample development/samples/ApiDemos/res/anim/value_animator_pvh_kf.xml + * ValueAnimatorKeyframeResources} + * * <div class="special reference"> * <h3>Developer Guides</h3> * <p>For more information about animating with {@code ValueAnimator}, read the diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 9568897..a09bca1 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -362,7 +362,7 @@ import java.util.HashMap; * * <p>Note the "Killable" column in the above table -- for those methods that * are marked as being killable, after that method returns the process hosting the - * activity may killed by the system <em>at any time</em> without another line + * activity may be killed by the system <em>at any time</em> without another line * of its code being executed. Because of this, you should use the * {@link #onPause} method to write any persistent data (such as user edits) * to storage. In addition, the method @@ -1242,22 +1242,18 @@ public class Activity extends ContextThemeWrapper } /** - * @hide * Check whether this activity is running as part of a voice interaction with the user. * If true, it should perform its interaction with the user through the * {@link VoiceInteractor} returned by {@link #getVoiceInteractor}. */ - @SystemApi public boolean isVoiceInteraction() { return mVoiceInteractor != null; } /** - * @hide * Retrieve the active {@link VoiceInteractor} that the user is going through to * interact with this activity. */ - @SystemApi public VoiceInteractor getVoiceInteractor() { return mVoiceInteractor; } @@ -4619,7 +4615,7 @@ public class Activity extends ContextThemeWrapper if (Looper.myLooper() != mMainThread.getLooper()) { throw new IllegalStateException("Must be called from main thread"); } - mMainThread.requestRelaunchActivity(mToken, null, null, 0, false, null, false); + mMainThread.requestRelaunchActivity(mToken, null, null, 0, false, null, null, false); } /** @@ -6080,6 +6076,17 @@ public class Activity extends ContextThemeWrapper " did not call through to super.onResume()"); } + // invisible activities must be finished before onResume() completes + if (!mVisibleFromClient && !mFinished) { + Log.w(TAG, "An activity without a UI must call finish() before onResume() completes"); + if (getApplicationInfo().targetSdkVersion + > android.os.Build.VERSION_CODES.LOLLIPOP_MR1) { + throw new IllegalStateException( + "Activity " + mComponent.toShortString() + + " did not call finish() prior to onResume() completing"); + } + } + // Now really resume, and install the current status bar and menu. mCalled = false; diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 7a636db..c6ffef6 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -256,6 +256,9 @@ public class ActivityManager { /** @hide User operation call: given user id is the current user, can't be stopped. */ public static final int USER_OP_IS_CURRENT = -2; + /** @hide Process does not exist. */ + public static final int PROCESS_STATE_NONEXISTENT = -1; + /** @hide Process is a persistent system process. */ public static final int PROCESS_STATE_PERSISTENT = 0; diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index a7099d4..47f57ea 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -690,14 +690,6 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } - case MOVE_TASK_TO_BACK_TRANSACTION: { - data.enforceInterface(IActivityManager.descriptor); - int task = data.readInt(); - moveTaskToBack(task); - reply.writeNoException(); - return true; - } - case MOVE_ACTIVITY_TASK_TO_BACK_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder token = data.readStrongBinder(); @@ -729,7 +721,6 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM case RESIZE_STACK_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); int stackId = data.readInt(); - float weight = data.readFloat(); Rect r = Rect.CREATOR.createFromParcel(data); resizeStack(stackId, r); reply.writeNoException(); @@ -775,6 +766,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case GET_FOCUSED_STACK_ID_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int focusedStackId = getFocusedStackId(); + reply.writeNoException(); + reply.writeInt(focusedStackId); + return true; + } + case REGISTER_TASK_STACK_LISTENER_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder token = data.readStrongBinder(); @@ -2184,13 +2183,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } - case CREATE_ACTIVITY_CONTAINER_TRANSACTION: { + case CREATE_VIRTUAL_ACTIVITY_CONTAINER_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder parentActivityToken = data.readStrongBinder(); IActivityContainerCallback callback = IActivityContainerCallback.Stub.asInterface(data.readStrongBinder()); IActivityContainer activityContainer = - createActivityContainer(parentActivityToken, callback); + createVirtualActivityContainer(parentActivityToken, callback); reply.writeNoException(); if (activityContainer != null) { reply.writeInt(1); @@ -2210,6 +2209,20 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case CREATE_STACK_ON_DISPLAY: { + data.enforceInterface(IActivityManager.descriptor); + int displayId = data.readInt(); + IActivityContainer activityContainer = createStackOnDisplay(displayId); + reply.writeNoException(); + if (activityContainer != null) { + reply.writeInt(1); + reply.writeStrongBinder(activityContainer.asBinder()); + } else { + reply.writeInt(0); + } + return true; + } + case GET_ACTIVITY_CONTAINER_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder activityToken = data.readStrongBinder(); @@ -2287,6 +2300,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case SET_TASK_RESIZEABLE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int taskId = data.readInt(); + boolean resizeable = (data.readInt() == 1) ? true : false; + setTaskResizeable(taskId, resizeable); + reply.writeNoException(); + return true; + } + case GET_TASK_DESCRIPTION_ICON_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); String filename = data.readString(); @@ -3000,7 +3022,7 @@ class ActivityManagerProxy implements IActivityManager ArrayList<IAppTask> list = null; int N = reply.readInt(); if (N >= 0) { - list = new ArrayList<IAppTask>(); + list = new ArrayList<>(); while (N > 0) { IAppTask task = IAppTask.Stub.asInterface(reply.readStrongBinder()); list.add(task); @@ -3038,7 +3060,8 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); return size; } - public List getTasks(int maxNum, int flags) throws RemoteException { + public List<ActivityManager.RunningTaskInfo> getTasks(int maxNum, int flags) + throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); @@ -3046,10 +3069,10 @@ class ActivityManagerProxy implements IActivityManager data.writeInt(flags); mRemote.transact(GET_TASKS_TRANSACTION, data, reply, 0); reply.readException(); - ArrayList list = null; + ArrayList<ActivityManager.RunningTaskInfo> list = null; int N = reply.readInt(); if (N >= 0) { - list = new ArrayList(); + list = new ArrayList<>(); while (N > 0) { ActivityManager.RunningTaskInfo info = ActivityManager.RunningTaskInfo.CREATOR @@ -3093,7 +3116,8 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); return taskThumbnail; } - public List getServices(int maxNum, int flags) throws RemoteException { + public List<ActivityManager.RunningServiceInfo> getServices(int maxNum, int flags) + throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); @@ -3101,10 +3125,10 @@ class ActivityManagerProxy implements IActivityManager data.writeInt(flags); mRemote.transact(GET_SERVICES_TRANSACTION, data, reply, 0); reply.readException(); - ArrayList list = null; + ArrayList<ActivityManager.RunningServiceInfo> list = null; int N = reply.readInt(); if (N >= 0) { - list = new ArrayList(); + list = new ArrayList<>(); while (N > 0) { ActivityManager.RunningServiceInfo info = ActivityManager.RunningServiceInfo.CREATOR @@ -3174,17 +3198,6 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); reply.recycle(); } - public void moveTaskToBack(int task) throws RemoteException - { - Parcel data = Parcel.obtain(); - Parcel reply = Parcel.obtain(); - data.writeInterfaceToken(IActivityManager.descriptor); - data.writeInt(task); - mRemote.transact(MOVE_TASK_TO_BACK_TRANSACTION, data, reply, 0); - reply.readException(); - data.recycle(); - reply.recycle(); - } public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) throws RemoteException { Parcel data = Parcel.obtain(); @@ -3294,6 +3307,18 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } @Override + public int getFocusedStackId() throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(GET_FOCUSED_STACK_ID_TRANSACTION, data, reply, 0); + reply.readException(); + int focusedStackId = reply.readInt(); + data.recycle(); + reply.recycle(); + return focusedStackId; + } + @Override public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException { Parcel data = Parcel.obtain(); @@ -5217,14 +5242,14 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } - public IActivityContainer createActivityContainer(IBinder parentActivityToken, + public IActivityContainer createVirtualActivityContainer(IBinder parentActivityToken, IActivityContainerCallback callback) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(parentActivityToken); data.writeStrongBinder(callback == null ? null : callback.asBinder()); - mRemote.transact(CREATE_ACTIVITY_CONTAINER_TRANSACTION, data, reply, 0); + mRemote.transact(CREATE_VIRTUAL_ACTIVITY_CONTAINER_TRANSACTION, data, reply, 0); reply.readException(); final int result = reply.readInt(); final IActivityContainer res; @@ -5250,6 +5275,25 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } + public IActivityContainer createStackOnDisplay(int displayId) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(displayId); + mRemote.transact(CREATE_STACK_ON_DISPLAY, data, reply, 0); + reply.readException(); + final int result = reply.readInt(); + final IActivityContainer res; + if (result == 1) { + res = IActivityContainer.Stub.asInterface(reply.readStrongBinder()); + } else { + res = null; + } + data.recycle(); + reply.recycle(); + return res; + } + public IActivityContainer getEnclosingActivityContainer(IBinder activityToken) throws RemoteException { Parcel data = Parcel.obtain(); @@ -5367,6 +5411,19 @@ class ActivityManagerProxy implements IActivityManager } @Override + public void setTaskResizeable(int taskId, boolean resizeable) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(taskId); + data.writeInt(resizeable ? 1 : 0); + mRemote.transact(SET_TASK_RESIZEABLE_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + @Override public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index fb2bcd0..de3c95c 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -46,7 +46,6 @@ import android.graphics.Canvas; import android.hardware.display.DisplayManagerGlobal; import android.net.ConnectivityManager; import android.net.IConnectivityManager; -import android.net.LinkProperties; import android.net.Network; import android.net.Proxy; import android.net.ProxyInfo; @@ -87,8 +86,6 @@ import android.util.Slog; import android.util.SuperNotCalledException; import android.view.Display; import android.view.HardwareRenderer; -import android.view.IWindowManager; -import android.view.IWindowSessionCallback; import android.view.View; import android.view.ViewDebug; import android.view.ViewManager; @@ -165,8 +162,8 @@ public final class ActivityThread { private static final long MIN_TIME_BETWEEN_GCS = 5*1000; private static final Pattern PATTERN_SEMICOLON = Pattern.compile(";"); private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003; - private static final int LOG_ON_PAUSE_CALLED = 30021; - private static final int LOG_ON_RESUME_CALLED = 30022; + private static final int LOG_AM_ON_PAUSE_CALLED = 30021; + private static final int LOG_AM_ON_RESUME_CALLED = 30022; /** Type for IActivityManager.serviceDoneExecuting: anonymous operation */ public static final int SERVICE_DONE_EXECUTING_ANON = 0; @@ -296,6 +293,7 @@ public final class ActivityThread { boolean hideForNow; Configuration newConfig; Configuration createdConfig; + Configuration overrideConfig; ActivityClientRecord nextIdle; ProfilerInfo profilerInfo; @@ -618,12 +616,13 @@ public final class ActivityThread { // we use token to identify this activity without having to send the // activity itself back to the activity manager. (matters more with ipc) + @Override public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, - ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, - String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state, - PersistableBundle persistentState, List<ResultInfo> pendingResults, - List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward, - ProfilerInfo profilerInfo) { + ActivityInfo info, Configuration curConfig, Configuration overrideConfig, + CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor, + int procState, Bundle state, PersistableBundle persistentState, + List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, + boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) { updateProcessState(procState, false); @@ -647,16 +646,19 @@ public final class ActivityThread { r.profilerInfo = profilerInfo; + r.overrideConfig = overrideConfig; updatePendingConfiguration(curConfig); sendMessage(H.LAUNCH_ACTIVITY, r); } + @Override public final void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, - int configChanges, boolean notResumed, Configuration config) { + int configChanges, boolean notResumed, Configuration config, + Configuration overrideConfig) { requestRelaunchActivity(token, pendingResults, pendingNewIntents, - configChanges, notResumed, config, true); + configChanges, notResumed, config, overrideConfig, true); } public final void scheduleNewIntent(List<ReferrerIntent> intents, IBinder token) { @@ -890,6 +892,7 @@ public final class ActivityThread { sendMessage(H.LOW_MEMORY, null); } + @Override public void scheduleActivityConfigurationChanged(IBinder token) { sendMessage(H.ACTIVITY_CONFIGURATION_CHANGED, token); } @@ -1671,7 +1674,7 @@ public final class ActivityThread { String[] libDirs, int displayId, Configuration overrideConfiguration, LoadedApk pkgInfo) { return mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs, - displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo(), null); + displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo()); } final Handler getHandler() { @@ -2356,7 +2359,8 @@ public final class ActivityThread { private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) { - ContextImpl appContext = ContextImpl.createActivityContext(this, r.packageInfo, r.token); + ContextImpl appContext = + ContextImpl.createActivityContext(this, r.packageInfo, r.overrideConfig); appContext.setOuterContext(activity); Context baseContext = appContext; @@ -3000,7 +3004,7 @@ public final class ActivityThread { } r.activity.performResume(); - EventLog.writeEvent(LOG_ON_RESUME_CALLED, + EventLog.writeEvent(LOG_AM_ON_RESUME_CALLED, UserHandle.myUserId(), r.activity.getComponentName().getClassName()); r.paused = false; @@ -3270,7 +3274,7 @@ public final class ActivityThread { // Now we are idle. r.activity.mCalled = false; mInstrumentation.callActivityOnPause(r.activity); - EventLog.writeEvent(LOG_ON_PAUSE_CALLED, UserHandle.myUserId(), + EventLog.writeEvent(LOG_AM_ON_PAUSE_CALLED, UserHandle.myUserId(), r.activity.getComponentName().getClassName()); if (!r.activity.mCalled) { throw new SuperNotCalledException( @@ -3564,7 +3568,7 @@ public final class ActivityThread { // request all activities to relaunch for the changes to take place for (Map.Entry<IBinder, ActivityClientRecord> entry : mActivities.entrySet()) { - requestRelaunchActivity(entry.getKey(), null, null, 0, false, null, false); + requestRelaunchActivity(entry.getKey(), null, null, 0, false, null, null, false); } } } @@ -3667,7 +3671,7 @@ public final class ActivityThread { try { r.activity.mCalled = false; mInstrumentation.callActivityOnPause(r.activity); - EventLog.writeEvent(LOG_ON_PAUSE_CALLED, UserHandle.myUserId(), + EventLog.writeEvent(LOG_AM_ON_PAUSE_CALLED, UserHandle.myUserId(), r.activity.getComponentName().getClassName()); if (!r.activity.mCalled) { throw new SuperNotCalledException( @@ -3808,7 +3812,7 @@ public final class ActivityThread { public final void requestRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, int configChanges, boolean notResumed, Configuration config, - boolean fromServer) { + Configuration overrideConfig, boolean fromServer) { ActivityClientRecord target = null; synchronized (mResourcesManager) { @@ -3857,6 +3861,9 @@ public final class ActivityThread { if (config != null) { target.createdConfig = config; } + if (overrideConfig != null) { + target.overrideConfig = overrideConfig; + } target.pendingConfigChanges |= configChanges; } } @@ -3969,6 +3976,7 @@ public final class ActivityThread { } } r.startsNotResumed = tmp.startsNotResumed; + r.overrideConfig = tmp.overrideConfig; handleLaunchActivity(r, currentIntent); } diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java index 94ea2c5..6d455b1 100644 --- a/core/java/android/app/ActivityView.java +++ b/core/java/android/app/ActivityView.java @@ -83,7 +83,7 @@ public class ActivityView extends ViewGroup { try { mActivityContainer = new ActivityContainerWrapper( - ActivityManagerNative.getDefault().createActivityContainer( + ActivityManagerNative.getDefault().createVirtualActivityContainer( mActivity.getActivityToken(), new ActivityContainerCallback(this))); } catch (RemoteException e) { throw new RuntimeException("ActivityView: Unable to create ActivityContainer. " diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index eb3ddb2..349b66d 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -140,6 +140,10 @@ public abstract class ApplicationThreadNative extends Binder int ident = data.readInt(); ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data); Configuration curConfig = Configuration.CREATOR.createFromParcel(data); + Configuration overrideConfig = null; + if (data.readInt() != 0) { + overrideConfig = Configuration.CREATOR.createFromParcel(data); + } CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data); String referrer = data.readString(); IVoiceInteractor voiceInteractor = IVoiceInteractor.Stub.asInterface( @@ -153,8 +157,8 @@ public abstract class ApplicationThreadNative extends Binder boolean isForward = data.readInt() != 0; ProfilerInfo profilerInfo = data.readInt() != 0 ? ProfilerInfo.CREATOR.createFromParcel(data) : null; - scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, referrer, - voiceInteractor, procState, state, persistentState, ri, pi, + scheduleLaunchActivity(intent, b, ident, info, curConfig, overrideConfig, compatInfo, + referrer, voiceInteractor, procState, state, persistentState, ri, pi, notResumed, isForward, profilerInfo); return true; } @@ -167,14 +171,15 @@ public abstract class ApplicationThreadNative extends Binder List<ReferrerIntent> pi = data.createTypedArrayList(ReferrerIntent.CREATOR); int configChanges = data.readInt(); boolean notResumed = data.readInt() != 0; - Configuration config = null; + Configuration config = Configuration.CREATOR.createFromParcel(data); + Configuration overrideConfig = null; if (data.readInt() != 0) { - config = Configuration.CREATOR.createFromParcel(data); + overrideConfig = Configuration.CREATOR.createFromParcel(data); } - scheduleRelaunchActivity(b, ri, pi, configChanges, notResumed, config); + scheduleRelaunchActivity(b, ri, pi, configChanges, notResumed, config, overrideConfig); return true; } - + case SCHEDULE_NEW_INTENT_TRANSACTION: { data.enforceInterface(IApplicationThread.descriptor); @@ -775,11 +780,11 @@ class ApplicationThreadProxy implements IApplicationThread { } public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, - ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, - String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state, - PersistableBundle persistentState, List<ResultInfo> pendingResults, - List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward, - ProfilerInfo profilerInfo) throws RemoteException { + ActivityInfo info, Configuration curConfig, Configuration overrideConfig, + CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor, + int procState, Bundle state, PersistableBundle persistentState, + List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, + boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); intent.writeToParcel(data, 0); @@ -787,6 +792,12 @@ class ApplicationThreadProxy implements IApplicationThread { data.writeInt(ident); info.writeToParcel(data, 0); curConfig.writeToParcel(data, 0); + if (overrideConfig != null) { + data.writeInt(1); + overrideConfig.writeToParcel(data, 0); + } else { + data.writeInt(0); + } compatInfo.writeToParcel(data, 0); data.writeString(referrer); data.writeStrongBinder(voiceInteractor != null ? voiceInteractor.asBinder() : null); @@ -810,8 +821,8 @@ class ApplicationThreadProxy implements IApplicationThread { public final void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, - int configChanges, boolean notResumed, Configuration config) - throws RemoteException { + int configChanges, boolean notResumed, Configuration config, + Configuration overrideConfig) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeStrongBinder(token); @@ -819,9 +830,10 @@ class ApplicationThreadProxy implements IApplicationThread { data.writeTypedList(pendingNewIntents); data.writeInt(configChanges); data.writeInt(notResumed ? 1 : 0); - if (config != null) { + config.writeToParcel(data, 0); + if (overrideConfig != null) { data.writeInt(1); - config.writeToParcel(data, 0); + overrideConfig.writeToParcel(data, 0); } else { data.writeInt(0); } @@ -1112,8 +1124,7 @@ class ApplicationThreadProxy implements IApplicationThread { data.recycle(); } - public final void scheduleActivityConfigurationChanged( - IBinder token) throws RemoteException { + public final void scheduleActivityConfigurationChanged(IBinder token) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeStrongBinder(token); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 2ef046d..39caf0b 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -80,6 +80,8 @@ import android.media.projection.MediaProjectionManager; import android.media.session.MediaSessionManager; import android.media.tv.ITvInputManager; import android.media.tv.TvInputManager; +import android.midi.IMidiManager; +import android.midi.MidiManager; import android.net.ConnectivityManager; import android.net.IConnectivityManager; import android.net.EthernetManager; @@ -92,6 +94,8 @@ import android.net.nsd.INsdManager; import android.net.nsd.NsdManager; import android.net.wifi.IWifiManager; import android.net.wifi.WifiManager; +import android.net.wifi.passpoint.IWifiPasspointManager; +import android.net.wifi.passpoint.WifiPasspointManager; import android.net.wifi.p2p.IWifiP2pManager; import android.net.wifi.p2p.WifiP2pManager; import android.net.wifi.IWifiScanner; @@ -603,6 +607,13 @@ class ContextImpl extends Context { return new WifiManager(ctx.getOuterContext(), service); }}); + registerService(WIFI_PASSPOINT_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + IBinder b = ServiceManager.getService(WIFI_PASSPOINT_SERVICE); + IWifiPasspointManager service = IWifiPasspointManager.Stub.asInterface(b); + return new WifiPasspointManager(ctx.getOuterContext(), service); + }}); + registerService(WIFI_P2P_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(WIFI_P2P_SERVICE); @@ -768,6 +779,12 @@ class ContextImpl extends Context { IBinder b = ServiceManager.getService(APPWIDGET_SERVICE); return new AppWidgetManager(ctx, IAppWidgetService.Stub.asInterface(b)); }}); + + registerService(MIDI_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + IBinder b = ServiceManager.getService(MIDI_SERVICE); + return new MidiManager(ctx, IMidiManager.Stub.asInterface(b)); + }}); } static ContextImpl getImpl(Context context) { @@ -2247,11 +2264,10 @@ class ContextImpl extends Context { } static ContextImpl createActivityContext(ActivityThread mainThread, - LoadedApk packageInfo, IBinder activityToken) { + LoadedApk packageInfo, Configuration overrideConfiguration) { if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); - if (activityToken == null) throw new IllegalArgumentException("activityInfo"); - return new ContextImpl(null, mainThread, - packageInfo, activityToken, null, false, null, null); + return new ContextImpl(null, mainThread, packageInfo, null, null, false, null, + overrideConfiguration); } private ContextImpl(ContextImpl container, ActivityThread mainThread, @@ -2286,15 +2302,14 @@ class ContextImpl extends Context { Resources resources = packageInfo.getResources(mainThread); if (resources != null) { - if (activityToken != null - || displayId != Display.DEFAULT_DISPLAY + if (displayId != Display.DEFAULT_DISPLAY || overrideConfiguration != null || (compatInfo != null && compatInfo.applicationScale != resources.getCompatibilityInfo().applicationScale)) { resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(), packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(), packageInfo.getApplicationInfo().sharedLibraryFiles, displayId, - overrideConfiguration, compatInfo, activityToken); + overrideConfiguration, compatInfo); } } mResources = resources; diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java index f79d32b..3fbbdff 100644 --- a/core/java/android/app/DatePickerDialog.java +++ b/core/java/android/app/DatePickerDialog.java @@ -131,6 +131,9 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener, switch (which) { case BUTTON_POSITIVE: if (mDateSetListener != null) { + // Clearing focus forces the dialog to commit any pending + // changes, e.g. typed text in a NumberPicker. + mDatePicker.clearFocus(); mDateSetListener.onDateSet(mDatePicker, mDatePicker.getYear(), mDatePicker.getMonth(), mDatePicker.getDayOfMonth()); } diff --git a/core/java/android/app/IActivityContainer.aidl b/core/java/android/app/IActivityContainer.aidl index 52884f7..ff1175f 100644 --- a/core/java/android/app/IActivityContainer.aidl +++ b/core/java/android/app/IActivityContainer.aidl @@ -32,6 +32,7 @@ interface IActivityContainer { void checkEmbeddedAllowed(in Intent intent); void checkEmbeddedAllowedIntentSender(in IIntentSender intentSender); int getDisplayId(); + int getStackId(); boolean injectEvent(in InputEvent event); void release(); } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 70c14c6..467c99a 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -131,7 +131,6 @@ public interface IActivityManager extends IInterface { public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState() throws RemoteException; public void moveTaskToFront(int task, int flags, Bundle options) throws RemoteException; - public void moveTaskToBack(int task) throws RemoteException; public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) throws RemoteException; public void moveTaskBackwards(int task) throws RemoteException; public void moveTaskToStack(int taskId, int stackId, boolean toTop) throws RemoteException; @@ -140,6 +139,7 @@ public interface IActivityManager extends IInterface { public StackInfo getStackInfo(int stackId) throws RemoteException; public boolean isInHomeStack(int taskId) throws RemoteException; public void setFocusedStack(int stackId) throws RemoteException; + public int getFocusedStackId() throws RemoteException; public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException; public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException; public ContentProviderHolder getContentProvider(IApplicationThread caller, @@ -434,9 +434,11 @@ public interface IActivityManager extends IInterface { public void performIdleMaintenance() throws RemoteException; - public IActivityContainer createActivityContainer(IBinder parentActivityToken, + public IActivityContainer createVirtualActivityContainer(IBinder parentActivityToken, IActivityContainerCallback callback) throws RemoteException; + public IActivityContainer createStackOnDisplay(int displayId) throws RemoteException; + public void deleteActivityContainer(IActivityContainer container) throws RemoteException; public IActivityContainer getEnclosingActivityContainer(IBinder activityToken) @@ -458,6 +460,7 @@ public interface IActivityManager extends IInterface { public void setTaskDescription(IBinder token, ActivityManager.TaskDescription values) throws RemoteException; + public void setTaskResizeable(int taskId, boolean resizeable) throws RemoteException; public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException; public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts) @@ -599,7 +602,7 @@ public interface IActivityManager extends IInterface { int GET_CALLING_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+21; int GET_TASKS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+22; int MOVE_TASK_TO_FRONT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+23; - int MOVE_TASK_TO_BACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+24; + int MOVE_TASK_BACKWARDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+25; int GET_TASK_FOR_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+26; @@ -739,7 +742,7 @@ public interface IActivityManager extends IInterface { int KILL_UID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+164; int SET_USER_IS_MONKEY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+165; int HANG_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+166; - int CREATE_ACTIVITY_CONTAINER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+167; + int CREATE_VIRTUAL_ACTIVITY_CONTAINER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+167; int MOVE_TASK_TO_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+168; int RESIZE_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+169; int GET_ALL_STACK_INFOS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+170; @@ -798,4 +801,7 @@ public interface IActivityManager extends IInterface { // Start of M transactions int NOTIFY_CLEARTEXT_NETWORK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+280; + int CREATE_STACK_ON_DISPLAY = IBinder.FIRST_CALL_TRANSACTION+281; + int GET_FOCUSED_STACK_ID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+282; + int SET_TASK_RESIZEABLE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+283; } diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index 8bf8cd7..f2c912e 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -33,7 +33,6 @@ import android.os.PersistableBundle; import android.os.RemoteException; import android.os.IBinder; import android.os.IInterface; -import android.service.voice.IVoiceInteractionSession; import com.android.internal.app.IVoiceInteractor; import com.android.internal.content.ReferrerIntent; @@ -59,14 +58,14 @@ public interface IApplicationThread extends IInterface { throws RemoteException; void scheduleSendResult(IBinder token, List<ResultInfo> results) throws RemoteException; void scheduleLaunchActivity(Intent intent, IBinder token, int ident, - ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, - String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state, - PersistableBundle persistentState, List<ResultInfo> pendingResults, - List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward, - ProfilerInfo profilerInfo) throws RemoteException; + ActivityInfo info, Configuration curConfig, Configuration overrideConfig, + CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor, + int procState, Bundle state, PersistableBundle persistentState, + List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, + boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) throws RemoteException; void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, - List<ReferrerIntent> pendingNewIntents, int configChanges, - boolean notResumed, Configuration config) throws RemoteException; + List<ReferrerIntent> pendingNewIntents, int configChanges, boolean notResumed, + Configuration config, Configuration overrideConfig) throws RemoteException; void scheduleNewIntent(List<ReferrerIntent> intent, IBinder token) throws RemoteException; void scheduleDestroyActivity(IBinder token, boolean finished, int configChanges) throws RemoteException; diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 860b9cc..87e744c 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5065,6 +5065,403 @@ public class Notification implements Parcelable } /** + * <p>Helper class to add Android Auto extensions to notifications. To create a notification + * with car extensions: + * + * <ol> + * <li>Create an {@link Notification.Builder}, setting any desired + * properties. + * <li>Create a {@link CarExtender}. + * <li>Set car-specific properties using the {@code add} and {@code set} methods of + * {@link CarExtender}. + * <li>Call {@link Notification.Builder#extend(Notification.Extender)} + * to apply the extensions to a notification. + * </ol> + * + * <pre class="prettyprint"> + * Notification notification = new Notification.Builder(context) + * ... + * .extend(new CarExtender() + * .set*(...)) + * .build(); + * </pre> + * + * <p>Car extensions can be accessed on an existing notification by using the + * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods + * to access values. + */ + public static final class CarExtender implements Extender { + private static final String TAG = "CarExtender"; + + private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS"; + private static final String EXTRA_LARGE_ICON = "large_icon"; + private static final String EXTRA_CONVERSATION = "car_conversation"; + private static final String EXTRA_COLOR = "app_color"; + + private Bitmap mLargeIcon; + private UnreadConversation mUnreadConversation; + private int mColor = Notification.COLOR_DEFAULT; + + /** + * Create a {@link CarExtender} with default options. + */ + public CarExtender() { + } + + /** + * Create a {@link CarExtender} from the CarExtender options of an existing Notification. + * + * @param notif The notification from which to copy options. + */ + public CarExtender(Notification notif) { + Bundle carBundle = notif.extras == null ? + null : notif.extras.getBundle(EXTRA_CAR_EXTENDER); + if (carBundle != null) { + mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON); + mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT); + + Bundle b = carBundle.getBundle(EXTRA_CONVERSATION); + mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b); + } + } + + /** + * Apply car extensions to a notification that is being built. This is typically called by + * the {@link Notification.Builder#extend(Notification.Extender)} + * method of {@link Notification.Builder}. + */ + @Override + public Notification.Builder extend(Notification.Builder builder) { + Bundle carExtensions = new Bundle(); + + if (mLargeIcon != null) { + carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon); + } + if (mColor != Notification.COLOR_DEFAULT) { + carExtensions.putInt(EXTRA_COLOR, mColor); + } + + if (mUnreadConversation != null) { + Bundle b = mUnreadConversation.getBundleForUnreadConversation(); + carExtensions.putBundle(EXTRA_CONVERSATION, b); + } + + builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions); + return builder; + } + + /** + * Sets the accent color to use when Android Auto presents the notification. + * + * Android Auto uses the color set with {@link Notification.Builder#setColor(int)} + * to accent the displayed notification. However, not all colors are acceptable in an + * automotive setting. This method can be used to override the color provided in the + * notification in such a situation. + */ + public CarExtender setColor(int color) { + mColor = color; + return this; + } + + /** + * Gets the accent color. + * + * @see setColor + */ + public int getColor() { + return mColor; + } + + /** + * Sets the large icon of the car notification. + * + * If no large icon is set in the extender, Android Auto will display the icon + * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)} + * + * @param largeIcon The large icon to use in the car notification. + * @return This object for method chaining. + */ + public CarExtender setLargeIcon(Bitmap largeIcon) { + mLargeIcon = largeIcon; + return this; + } + + /** + * Gets the large icon used in this car notification, or null if no icon has been set. + * + * @return The large icon for the car notification. + * @see CarExtender#setLargeIcon + */ + public Bitmap getLargeIcon() { + return mLargeIcon; + } + + /** + * Sets the unread conversation in a message notification. + * + * @param unreadConversation The unread part of the conversation this notification conveys. + * @return This object for method chaining. + */ + public CarExtender setUnreadConversation(UnreadConversation unreadConversation) { + mUnreadConversation = unreadConversation; + return this; + } + + /** + * Returns the unread conversation conveyed by this notification. + * @see #setUnreadConversation(UnreadConversation) + */ + public UnreadConversation getUnreadConversation() { + return mUnreadConversation; + } + + /** + * A class which holds the unread messages from a conversation. + */ + public static class UnreadConversation { + private static final String KEY_AUTHOR = "author"; + private static final String KEY_TEXT = "text"; + private static final String KEY_MESSAGES = "messages"; + private static final String KEY_REMOTE_INPUT = "remote_input"; + private static final String KEY_ON_REPLY = "on_reply"; + private static final String KEY_ON_READ = "on_read"; + private static final String KEY_PARTICIPANTS = "participants"; + private static final String KEY_TIMESTAMP = "timestamp"; + + private final String[] mMessages; + private final RemoteInput mRemoteInput; + private final PendingIntent mReplyPendingIntent; + private final PendingIntent mReadPendingIntent; + private final String[] mParticipants; + private final long mLatestTimestamp; + + UnreadConversation(String[] messages, RemoteInput remoteInput, + PendingIntent replyPendingIntent, PendingIntent readPendingIntent, + String[] participants, long latestTimestamp) { + mMessages = messages; + mRemoteInput = remoteInput; + mReadPendingIntent = readPendingIntent; + mReplyPendingIntent = replyPendingIntent; + mParticipants = participants; + mLatestTimestamp = latestTimestamp; + } + + /** + * Gets the list of messages conveyed by this notification. + */ + public String[] getMessages() { + return mMessages; + } + + /** + * Gets the remote input that will be used to convey the response to a message list, or + * null if no such remote input exists. + */ + public RemoteInput getRemoteInput() { + return mRemoteInput; + } + + /** + * Gets the pending intent that will be triggered when the user replies to this + * notification. + */ + public PendingIntent getReplyPendingIntent() { + return mReplyPendingIntent; + } + + /** + * Gets the pending intent that Android Auto will send after it reads aloud all messages + * in this object's message list. + */ + public PendingIntent getReadPendingIntent() { + return mReadPendingIntent; + } + + /** + * Gets the participants in the conversation. + */ + public String[] getParticipants() { + return mParticipants; + } + + /** + * Gets the firs participant in the conversation. + */ + public String getParticipant() { + return mParticipants.length > 0 ? mParticipants[0] : null; + } + + /** + * Gets the timestamp of the conversation. + */ + public long getLatestTimestamp() { + return mLatestTimestamp; + } + + Bundle getBundleForUnreadConversation() { + Bundle b = new Bundle(); + String author = null; + if (mParticipants != null && mParticipants.length > 1) { + author = mParticipants[0]; + } + Parcelable[] messages = new Parcelable[mMessages.length]; + for (int i = 0; i < messages.length; i++) { + Bundle m = new Bundle(); + m.putString(KEY_TEXT, mMessages[i]); + m.putString(KEY_AUTHOR, author); + messages[i] = m; + } + b.putParcelableArray(KEY_MESSAGES, messages); + if (mRemoteInput != null) { + b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput); + } + b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent); + b.putParcelable(KEY_ON_READ, mReadPendingIntent); + b.putStringArray(KEY_PARTICIPANTS, mParticipants); + b.putLong(KEY_TIMESTAMP, mLatestTimestamp); + return b; + } + + static UnreadConversation getUnreadConversationFromBundle(Bundle b) { + if (b == null) { + return null; + } + Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES); + String[] messages = null; + if (parcelableMessages != null) { + String[] tmp = new String[parcelableMessages.length]; + boolean success = true; + for (int i = 0; i < tmp.length; i++) { + if (!(parcelableMessages[i] instanceof Bundle)) { + success = false; + break; + } + tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT); + if (tmp[i] == null) { + success = false; + break; + } + } + if (success) { + messages = tmp; + } else { + return null; + } + } + + PendingIntent onRead = b.getParcelable(KEY_ON_READ); + PendingIntent onReply = b.getParcelable(KEY_ON_REPLY); + + RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT); + + String[] participants = b.getStringArray(KEY_PARTICIPANTS); + if (participants == null || participants.length != 1) { + return null; + } + + return new UnreadConversation(messages, + remoteInput, + onReply, + onRead, + participants, b.getLong(KEY_TIMESTAMP)); + } + }; + + /** + * Builder class for {@link CarExtender.UnreadConversation} objects. + */ + public static class Builder { + private final List<String> mMessages = new ArrayList<String>(); + private final String mParticipant; + private RemoteInput mRemoteInput; + private PendingIntent mReadPendingIntent; + private PendingIntent mReplyPendingIntent; + private long mLatestTimestamp; + + /** + * Constructs a new builder for {@link CarExtender.UnreadConversation}. + * + * @param name The name of the other participant in the conversation. + */ + public Builder(String name) { + mParticipant = name; + } + + /** + * Appends a new unread message to the list of messages for this conversation. + * + * The messages should be added from oldest to newest. + * + * @param message The text of the new unread message. + * @return This object for method chaining. + */ + public Builder addMessage(String message) { + mMessages.add(message); + return this; + } + + /** + * Sets the pending intent and remote input which will convey the reply to this + * notification. + * + * @param pendingIntent The pending intent which will be triggered on a reply. + * @param remoteInput The remote input parcelable which will carry the reply. + * @return This object for method chaining. + * + * @see CarExtender.UnreadConversation#getRemoteInput + * @see CarExtender.UnreadConversation#getReplyPendingIntent + */ + public Builder setReplyAction( + PendingIntent pendingIntent, RemoteInput remoteInput) { + mRemoteInput = remoteInput; + mReplyPendingIntent = pendingIntent; + + return this; + } + + /** + * Sets the pending intent that will be sent once the messages in this notification + * are read. + * + * @param pendingIntent The pending intent to use. + * @return This object for method chaining. + */ + public Builder setReadPendingIntent(PendingIntent pendingIntent) { + mReadPendingIntent = pendingIntent; + return this; + } + + /** + * Sets the timestamp of the most recent message in an unread conversation. + * + * If a messaging notification has been posted by your application and has not + * yet been cancelled, posting a later notification with the same id and tag + * but without a newer timestamp may result in Android Auto not displaying a + * heads up notification for the later notification. + * + * @param timestamp The timestamp of the most recent message in the conversation. + * @return This object for method chaining. + */ + public Builder setLatestTimestamp(long timestamp) { + mLatestTimestamp = timestamp; + return this; + } + + /** + * Builds a new unread conversation object. + * + * @return The new unread conversation object. + */ + public UnreadConversation build() { + String[] messages = mMessages.toArray(new String[mMessages.size()]); + String[] participants = { mParticipant }; + return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent, + mReadPendingIntent, participants, mLatestTimestamp); + } + } + } + + /** * Get an array of Notification objects from a parcelable array bundle field. * Update the bundle to have a typed array so fetches in the future don't need * to do an array copy. diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 1691d8e..fac40b2 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -25,7 +25,6 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.ResourcesKey; import android.hardware.display.DisplayManagerGlobal; -import android.os.IBinder; import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.Slog; @@ -38,8 +37,7 @@ import java.util.Locale; /** @hide */ public class ResourcesManager { static final String TAG = "ResourcesManager"; - static final boolean DEBUG_CACHE = false; - static final boolean DEBUG_STATS = true; + private static final boolean DEBUG = false; private static ResourcesManager sResourcesManager; final ArrayMap<ResourcesKey, WeakReference<Resources> > mActiveResources @@ -51,7 +49,6 @@ public class ResourcesManager { CompatibilityInfo mResCompatibilityInfo; Configuration mResConfiguration; - final Configuration mTmpConfig = new Configuration(); public static ResourcesManager getInstance() { synchronized (ResourcesManager.class) { @@ -144,30 +141,32 @@ public class ResourcesManager { * Creates the top level Resources for applications with the given compatibility info. * * @param resDir the resource directory. + * @param splitResDirs split resource directories. * @param overlayDirs the resource overlay directories. * @param libDirs the shared library resource dirs this app references. - * @param compatInfo the compability info. Must not be null. - * @param token the application token for determining stack bounds. + * @param displayId display Id. + * @param overrideConfiguration override configurations. + * @param compatInfo the compatibility info. Must not be null. */ public Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs, String[] libDirs, int displayId, - Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) { + Configuration overrideConfiguration, CompatibilityInfo compatInfo) { final float scale = compatInfo.applicationScale; - ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, token); + Configuration overrideConfigCopy = (overrideConfiguration != null) + ? new Configuration(overrideConfiguration) : null; + ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfigCopy, scale); Resources r; synchronized (this) { // Resources is app scale dependent. - if (false) { - Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale); - } + if (DEBUG) Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale); + WeakReference<Resources> wr = mActiveResources.get(key); r = wr != null ? wr.get() : null; //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate()); if (r != null && r.getAssets().isUpToDate()) { - if (false) { - Slog.w(TAG, "Returning cached resources " + r + " " + resDir - + ": appScale=" + r.getCompatibilityInfo().applicationScale); - } + if (DEBUG) Slog.w(TAG, "Returning cached resources " + r + " " + resDir + + ": appScale=" + r.getCompatibilityInfo().applicationScale + + " key=" + key + " overrideConfig=" + overrideConfiguration); return r; } } @@ -213,7 +212,7 @@ public class ResourcesManager { //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); DisplayMetrics dm = getDisplayMetricsLocked(displayId); Configuration config; - boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); + final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); final boolean hasOverrideConfig = key.hasOverrideConfiguration(); if (!isDefaultDisplay || hasOverrideConfig) { config = new Configuration(getConfiguration()); @@ -222,16 +221,14 @@ public class ResourcesManager { } if (hasOverrideConfig) { config.updateFrom(key.mOverrideConfiguration); + if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration); } } else { config = getConfiguration(); } - r = new Resources(assets, dm, config, compatInfo, token); - if (false) { - Slog.i(TAG, "Created app resources " + resDir + " " + r + ": " - + r.getConfiguration() + " appScale=" - + r.getCompatibilityInfo().applicationScale); - } + r = new Resources(assets, dm, config, compatInfo); + if (DEBUG) Slog.i(TAG, "Created app resources " + resDir + " " + r + ": " + + r.getConfiguration() + " appScale=" + r.getCompatibilityInfo().applicationScale); synchronized (this) { WeakReference<Resources> wr = mActiveResources.get(key); @@ -244,7 +241,8 @@ public class ResourcesManager { } // XXX need to remove entries when weak references go away - mActiveResources.put(key, new WeakReference<Resources>(r)); + mActiveResources.put(key, new WeakReference<>(r)); + if (DEBUG) Slog.v(TAG, "mActiveResources.size()=" + mActiveResources.size()); return r; } } @@ -255,7 +253,7 @@ public class ResourcesManager { mResConfiguration = new Configuration(); } if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) { - if (DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq=" + if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq=" + mResConfiguration.seq + ", newSeq=" + config.seq); return false; } @@ -287,7 +285,7 @@ public class ResourcesManager { ResourcesKey key = mActiveResources.keyAt(i); Resources r = mActiveResources.valueAt(i).get(); if (r != null) { - if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " + if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " + r + " config to: " + config); int displayId = key.mDisplayId; boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java index 4427ce1..e617553 100644 --- a/core/java/android/app/SharedPreferencesImpl.java +++ b/core/java/android/app/SharedPreferencesImpl.java @@ -16,6 +16,7 @@ package android.app; +import android.annotation.Nullable; import android.content.SharedPreferences; import android.os.FileUtils; import android.os.Looper; @@ -217,7 +218,8 @@ final class SharedPreferencesImpl implements SharedPreferences { } } - public String getString(String key, String defValue) { + @Nullable + public String getString(String key, @Nullable String defValue) { synchronized (this) { awaitLoadedLocked(); String v = (String)mMap.get(key); @@ -225,7 +227,8 @@ final class SharedPreferencesImpl implements SharedPreferences { } } - public Set<String> getStringSet(String key, Set<String> defValues) { + @Nullable + public Set<String> getStringSet(String key, @Nullable Set<String> defValues) { synchronized (this) { awaitLoadedLocked(); Set<String> v = (Set<String>) mMap.get(key); @@ -303,13 +306,13 @@ final class SharedPreferencesImpl implements SharedPreferences { private final Map<String, Object> mModified = Maps.newHashMap(); private boolean mClear = false; - public Editor putString(String key, String value) { + public Editor putString(String key, @Nullable String value) { synchronized (this) { mModified.put(key, value); return this; } } - public Editor putStringSet(String key, Set<String> values) { + public Editor putStringSet(String key, @Nullable Set<String> values) { synchronized (this) { mModified.put(key, (values == null) ? null : new HashSet<String>(values)); diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java index 723cb9b..7f9693f 100644 --- a/core/java/android/app/VoiceInteractor.java +++ b/core/java/android/app/VoiceInteractor.java @@ -16,7 +16,6 @@ package android.app; -import android.annotation.SystemApi; import android.content.Context; import android.os.Bundle; import android.os.IBinder; @@ -34,7 +33,6 @@ import com.android.internal.os.SomeArgs; import java.util.ArrayList; /** - * @hide * Interface for an {@link Activity} to interact with the user through voice. Use * {@link android.app.Activity#getVoiceInteractor() Activity.getVoiceInteractor} * to retrieve the interface, if the activity is currently involved in a voice interaction. @@ -56,7 +54,6 @@ import java.util.ArrayList; * request, rather than holding on to the activity instance yourself, either explicitly * or implicitly through a non-static inner class. */ -@SystemApi public class VoiceInteractor { static final String TAG = "VoiceInteractor"; static final boolean DEBUG = true; @@ -108,9 +105,10 @@ public class VoiceInteractor { request = pullRequest((IVoiceInteractorRequest)args.arg1, msg.arg1 != 0); if (DEBUG) Log.d(TAG, "onCommandResult: req=" + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request - + " result=" + args.arg2); + + " completed=" + msg.arg1 + " result=" + args.arg2); if (request != null) { - ((CommandRequest)request).onCommandResult((Bundle) args.arg2); + ((CommandRequest)request).onCommandResult(msg.arg1 != 0, + (Bundle) args.arg2); if (msg.arg1 != 0) { request.clear(); } @@ -321,7 +319,7 @@ public class VoiceInteractor { * complete an action (e.g. booking a table might have several possible times that the * user could select from or an app might need the user to agree to a terms of service). * The result of the confirmation will be returned through an asynchronous call to - * either {@link #onCommandResult(android.os.Bundle)} or + * either {@link #onCommandResult(boolean, android.os.Bundle)} or * {@link #onCancel()}. * * <p>The command is a string that describes the generic operation to be performed. @@ -338,7 +336,12 @@ public class VoiceInteractor { mArgs = args; } - public void onCommandResult(Bundle result) { + /** + * Results for CommandRequest can be returned in partial chunks. + * The isCompleted is set to true iff all results have been returned, indicating the + * CommandRequest has completed. + */ + public void onCommandResult(boolean isCompleted, Bundle result) { } IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName, diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 1ed709b..ecf25fc 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -52,11 +52,15 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; +import java.security.KeyFactory; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -328,6 +332,16 @@ public class DevicePolicyManager { = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM"; /** + * A boolean extra indicating whether device encryption is required as part of Device Owner + * provisioning. + * + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. + */ + public static final String EXTRA_PROVISIONING_SKIP_ENCRYPTION = + "android.app.extra.PROVISIONING_SKIP_ENCRYPTION"; + + /** * This MIME type is used for starting the Device Owner provisioning. * * <p>During device owner provisioning a device admin app is set as the owner of the device. @@ -357,7 +371,8 @@ public class DevicePolicyManager { * <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_HOST}, optional</li> * <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_PORT} (convert to String), optional</li> * <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_BYPASS}, optional</li> - * <li>{@link #EXTRA_PROVISIONING_WIFI_PAC_URL}, optional</li></ul> + * <li>{@link #EXTRA_PROVISIONING_WIFI_PAC_URL}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li></ul> * * <p> When device owner provisioning has completed, an intent of the type * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} is broadcasted to the @@ -690,7 +705,7 @@ public class DevicePolicyManager { public void setPasswordQuality(ComponentName admin, int quality) { if (mService != null) { try { - mService.setPasswordQuality(admin, quality, UserHandle.myUserId()); + mService.setPasswordQuality(admin, quality); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -743,7 +758,7 @@ public class DevicePolicyManager { public void setPasswordMinimumLength(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordMinimumLength(admin, length, UserHandle.myUserId()); + mService.setPasswordMinimumLength(admin, length); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -797,7 +812,7 @@ public class DevicePolicyManager { public void setPasswordMinimumUpperCase(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordMinimumUpperCase(admin, length, UserHandle.myUserId()); + mService.setPasswordMinimumUpperCase(admin, length); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -858,7 +873,7 @@ public class DevicePolicyManager { public void setPasswordMinimumLowerCase(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordMinimumLowerCase(admin, length, UserHandle.myUserId()); + mService.setPasswordMinimumLowerCase(admin, length); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -918,7 +933,7 @@ public class DevicePolicyManager { public void setPasswordMinimumLetters(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordMinimumLetters(admin, length, UserHandle.myUserId()); + mService.setPasswordMinimumLetters(admin, length); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -976,7 +991,7 @@ public class DevicePolicyManager { public void setPasswordMinimumNumeric(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordMinimumNumeric(admin, length, UserHandle.myUserId()); + mService.setPasswordMinimumNumeric(admin, length); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1035,7 +1050,7 @@ public class DevicePolicyManager { public void setPasswordMinimumSymbols(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordMinimumSymbols(admin, length, UserHandle.myUserId()); + mService.setPasswordMinimumSymbols(admin, length); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1093,7 +1108,7 @@ public class DevicePolicyManager { public void setPasswordMinimumNonLetter(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordMinimumNonLetter(admin, length, UserHandle.myUserId()); + mService.setPasswordMinimumNonLetter(admin, length); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1153,7 +1168,7 @@ public class DevicePolicyManager { public void setPasswordHistoryLength(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordHistoryLength(admin, length, UserHandle.myUserId()); + mService.setPasswordHistoryLength(admin, length); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1185,7 +1200,7 @@ public class DevicePolicyManager { public void setPasswordExpirationTimeout(ComponentName admin, long timeout) { if (mService != null) { try { - mService.setPasswordExpirationTimeout(admin, timeout, UserHandle.myUserId()); + mService.setPasswordExpirationTimeout(admin, timeout); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1330,7 +1345,7 @@ public class DevicePolicyManager { public void setMaximumFailedPasswordsForWipe(ComponentName admin, int num) { if (mService != null) { try { - mService.setMaximumFailedPasswordsForWipe(admin, num, UserHandle.myUserId()); + mService.setMaximumFailedPasswordsForWipe(admin, num); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1414,7 +1429,7 @@ public class DevicePolicyManager { public boolean resetPassword(String password, int flags) { if (mService != null) { try { - return mService.resetPassword(password, flags, UserHandle.myUserId()); + return mService.resetPassword(password, flags); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1438,7 +1453,7 @@ public class DevicePolicyManager { public void setMaximumTimeToLock(ComponentName admin, long timeMs) { if (mService != null) { try { - mService.setMaximumTimeToLock(admin, timeMs, UserHandle.myUserId()); + mService.setMaximumTimeToLock(admin, timeMs); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1588,7 +1603,7 @@ public class DevicePolicyManager { != android.net.Proxy.PROXY_VALID) throw new IllegalArgumentException(); } - return mService.setGlobalProxy(admin, hostSpec, exclSpec, UserHandle.myUserId()); + return mService.setGlobalProxy(admin, hostSpec, exclSpec); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1754,7 +1769,7 @@ public class DevicePolicyManager { public int setStorageEncryption(ComponentName admin, boolean encrypt) { if (mService != null) { try { - return mService.setStorageEncryption(admin, encrypt, UserHandle.myUserId()); + return mService.setStorageEncryption(admin, encrypt); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1934,13 +1949,15 @@ public class DevicePolicyManager { String alias) { try { final byte[] pemCert = Credentials.convertToPem(cert); - return mService.installKeyPair(who, privKey.getEncoded(), pemCert, alias); - } catch (CertificateException e) { - Log.w(TAG, "Error encoding certificate", e); - } catch (IOException e) { - Log.w(TAG, "Error writing certificate", e); + final byte[] pkcs8Key = KeyFactory.getInstance(privKey.getAlgorithm()) + .getKeySpec(privKey, PKCS8EncodedKeySpec.class).getEncoded(); + return mService.installKeyPair(who, pkcs8Key, pemCert, alias); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + Log.w(TAG, "Failed to obtain private key material", e); + } catch (CertificateException | IOException e) { + Log.w(TAG, "Could not pem-encode certificate", e); } return false; } @@ -1971,7 +1988,7 @@ public class DevicePolicyManager { public void setCameraDisabled(ComponentName admin, boolean disabled) { if (mService != null) { try { - mService.setCameraDisabled(admin, disabled, UserHandle.myUserId()); + mService.setCameraDisabled(admin, disabled); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -2015,7 +2032,7 @@ public class DevicePolicyManager { public void setScreenCaptureDisabled(ComponentName admin, boolean disabled) { if (mService != null) { try { - mService.setScreenCaptureDisabled(admin, UserHandle.myUserId(), disabled); + mService.setScreenCaptureDisabled(admin, disabled); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -2059,7 +2076,7 @@ public class DevicePolicyManager { public void setAutoTimeRequired(ComponentName admin, boolean required) { if (mService != null) { try { - mService.setAutoTimeRequired(admin, UserHandle.myUserId(), required); + mService.setAutoTimeRequired(admin, required); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -2100,7 +2117,7 @@ public class DevicePolicyManager { public void setKeyguardDisabledFeatures(ComponentName admin, int which) { if (mService != null) { try { - mService.setKeyguardDisabledFeatures(admin, which, UserHandle.myUserId()); + mService.setKeyguardDisabledFeatures(admin, which); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -2417,27 +2434,6 @@ public class DevicePolicyManager { } /** - * @deprecated Use setProfileOwner(ComponentName ...) - * @hide - * Sets the given package as the profile owner of the given user profile. The package must - * already be installed and there shouldn't be an existing profile owner registered for this - * user. Also, this method must be called before the user has been used for the first time. - * @param packageName the package name of the application to be registered as profile owner. - * @param ownerName the human readable name of the organisation associated with this DPM. - * @param userHandle the userId to set the profile owner for. - * @return whether the package was successfully registered as the profile owner. - * @throws IllegalArgumentException if packageName is null, the package isn't installed, or - * the user has already been set up. - */ - public boolean setProfileOwner(String packageName, String ownerName, int userHandle) - throws IllegalArgumentException { - if (packageName == null) { - throw new NullPointerException("packageName cannot be null"); - } - return setProfileOwner(new ComponentName(packageName, ""), ownerName, userHandle); - } - - /** * @hide * Sets the given component as the profile owner of the given user profile. The package must * already be installed and there shouldn't be an existing profile owner registered for this @@ -2703,8 +2699,7 @@ public class DevicePolicyManager { PersistableBundle configuration) { if (mService != null) { try { - mService.setTrustAgentConfiguration(admin, target, configuration, - UserHandle.myUserId()); + mService.setTrustAgentConfiguration(admin, target, configuration); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 0ca60c0..67bca4e 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -32,34 +32,34 @@ import java.util.List; * {@hide} */ interface IDevicePolicyManager { - void setPasswordQuality(in ComponentName who, int quality, int userHandle); + void setPasswordQuality(in ComponentName who, int quality); int getPasswordQuality(in ComponentName who, int userHandle); - void setPasswordMinimumLength(in ComponentName who, int length, int userHandle); + void setPasswordMinimumLength(in ComponentName who, int length); int getPasswordMinimumLength(in ComponentName who, int userHandle); - void setPasswordMinimumUpperCase(in ComponentName who, int length, int userHandle); + void setPasswordMinimumUpperCase(in ComponentName who, int length); int getPasswordMinimumUpperCase(in ComponentName who, int userHandle); - void setPasswordMinimumLowerCase(in ComponentName who, int length, int userHandle); + void setPasswordMinimumLowerCase(in ComponentName who, int length); int getPasswordMinimumLowerCase(in ComponentName who, int userHandle); - void setPasswordMinimumLetters(in ComponentName who, int length, int userHandle); + void setPasswordMinimumLetters(in ComponentName who, int length); int getPasswordMinimumLetters(in ComponentName who, int userHandle); - void setPasswordMinimumNumeric(in ComponentName who, int length, int userHandle); + void setPasswordMinimumNumeric(in ComponentName who, int length); int getPasswordMinimumNumeric(in ComponentName who, int userHandle); - void setPasswordMinimumSymbols(in ComponentName who, int length, int userHandle); + void setPasswordMinimumSymbols(in ComponentName who, int length); int getPasswordMinimumSymbols(in ComponentName who, int userHandle); - void setPasswordMinimumNonLetter(in ComponentName who, int length, int userHandle); + void setPasswordMinimumNonLetter(in ComponentName who, int length); int getPasswordMinimumNonLetter(in ComponentName who, int userHandle); - void setPasswordHistoryLength(in ComponentName who, int length, int userHandle); + void setPasswordHistoryLength(in ComponentName who, int length); int getPasswordHistoryLength(in ComponentName who, int userHandle); - void setPasswordExpirationTimeout(in ComponentName who, long expiration, int userHandle); + void setPasswordExpirationTimeout(in ComponentName who, long expiration); long getPasswordExpirationTimeout(in ComponentName who, int userHandle); long getPasswordExpiration(in ComponentName who, int userHandle); @@ -68,33 +68,33 @@ interface IDevicePolicyManager { int getCurrentFailedPasswordAttempts(int userHandle); int getProfileWithMinimumFailedPasswordsForWipe(int userHandle); - void setMaximumFailedPasswordsForWipe(in ComponentName admin, int num, int userHandle); + void setMaximumFailedPasswordsForWipe(in ComponentName admin, int num); int getMaximumFailedPasswordsForWipe(in ComponentName admin, int userHandle); - boolean resetPassword(String password, int flags, int userHandle); + boolean resetPassword(String password, int flags); - void setMaximumTimeToLock(in ComponentName who, long timeMs, int userHandle); + void setMaximumTimeToLock(in ComponentName who, long timeMs); long getMaximumTimeToLock(in ComponentName who, int userHandle); void lockNow(); void wipeData(int flags, int userHandle); - ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList, int userHandle); + ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList); ComponentName getGlobalProxyAdmin(int userHandle); void setRecommendedGlobalProxy(in ComponentName admin, in ProxyInfo proxyInfo); - int setStorageEncryption(in ComponentName who, boolean encrypt, int userHandle); + int setStorageEncryption(in ComponentName who, boolean encrypt); boolean getStorageEncryption(in ComponentName who, int userHandle); int getStorageEncryptionStatus(int userHandle); - void setCameraDisabled(in ComponentName who, boolean disabled, int userHandle); + void setCameraDisabled(in ComponentName who, boolean disabled); boolean getCameraDisabled(in ComponentName who, int userHandle); - void setScreenCaptureDisabled(in ComponentName who, int userHandle, boolean disabled); + void setScreenCaptureDisabled(in ComponentName who, boolean disabled); boolean getScreenCaptureDisabled(in ComponentName who, int userHandle); - void setKeyguardDisabledFeatures(in ComponentName who, int which, int userHandle); + void setKeyguardDisabledFeatures(in ComponentName who, int which); int getKeyguardDisabledFeatures(in ComponentName who, int userHandle); void setActiveAdmin(in ComponentName policyReceiver, boolean refreshing, int userHandle); @@ -186,7 +186,7 @@ interface IDevicePolicyManager { boolean getCrossProfileCallerIdDisabledForUser(int userId); void setTrustAgentConfiguration(in ComponentName admin, in ComponentName agent, - in PersistableBundle args, int userId); + in PersistableBundle args); List<PersistableBundle> getTrustAgentConfiguration(in ComponentName admin, in ComponentName agent, int userId); @@ -194,7 +194,7 @@ interface IDevicePolicyManager { boolean removeCrossProfileWidgetProvider(in ComponentName admin, String packageName); List<String> getCrossProfileWidgetProviders(in ComponentName admin); - void setAutoTimeRequired(in ComponentName who, int userHandle, boolean required); + void setAutoTimeRequired(in ComponentName who, boolean required); boolean getAutoTimeRequired(); boolean isRemovingAdmin(in ComponentName adminReceiver, int userHandle); diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java index 8e86824..1af4953 100644 --- a/core/java/android/appwidget/AppWidgetHost.java +++ b/core/java/android/appwidget/AppWidgetHost.java @@ -16,6 +16,7 @@ package android.appwidget; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; @@ -58,18 +59,28 @@ public class AppWidgetHost { private DisplayMetrics mDisplayMetrics; private String mContextOpPackageName; - Handler mHandler; - int mHostId; - Callbacks mCallbacks = new Callbacks(); - final HashMap<Integer,AppWidgetHostView> mViews = new HashMap<Integer, AppWidgetHostView>(); + private final Handler mHandler; + private final int mHostId; + private final Callbacks mCallbacks; + private final HashMap<Integer,AppWidgetHostView> mViews = new HashMap<>(); private OnClickHandler mOnClickHandler; - class Callbacks extends IAppWidgetHost.Stub { + static class Callbacks extends IAppWidgetHost.Stub { + private final WeakReference<Handler> mWeakHandler; + + public Callbacks(Handler handler) { + mWeakHandler = new WeakReference<>(handler); + } + public void updateAppWidget(int appWidgetId, RemoteViews views) { if (isLocalBinder() && views != null) { views = views.clone(); } - Message msg = mHandler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views); + Handler handler = mWeakHandler.get(); + if (handler == null) { + return; + } + Message msg = handler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views); msg.sendToTarget(); } @@ -77,20 +88,36 @@ public class AppWidgetHost { if (isLocalBinder() && info != null) { info = info.clone(); } - Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED, + Handler handler = mWeakHandler.get(); + if (handler == null) { + return; + } + Message msg = handler.obtainMessage(HANDLE_PROVIDER_CHANGED, appWidgetId, 0, info); msg.sendToTarget(); } public void providersChanged() { - mHandler.obtainMessage(HANDLE_PROVIDERS_CHANGED).sendToTarget(); + Handler handler = mWeakHandler.get(); + if (handler == null) { + return; + } + handler.obtainMessage(HANDLE_PROVIDERS_CHANGED).sendToTarget(); } public void viewDataChanged(int appWidgetId, int viewId) { - Message msg = mHandler.obtainMessage(HANDLE_VIEW_DATA_CHANGED, + Handler handler = mWeakHandler.get(); + if (handler == null) { + return; + } + Message msg = handler.obtainMessage(HANDLE_VIEW_DATA_CHANGED, appWidgetId, viewId); msg.sendToTarget(); } + + private static boolean isLocalBinder() { + return Process.myPid() == Binder.getCallingPid(); + } } class UpdateHandler extends Handler { @@ -132,6 +159,7 @@ public class AppWidgetHost { mHostId = hostId; mOnClickHandler = handler; mHandler = new UpdateHandler(looper); + mCallbacks = new Callbacks(mHandler); mDisplayMetrics = context.getResources().getDisplayMetrics(); bindService(); } @@ -251,10 +279,6 @@ public class AppWidgetHost { } } - private boolean isLocalBinder() { - return Process.myPid() == Binder.getCallingPid(); - } - /** * Stop listening to changes for this AppWidget. */ diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index 1ff476e..022e225 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -588,9 +588,10 @@ public class AppWidgetHostView extends FrameLayout { return tv; } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(AppWidgetHostView.class.getName()); } diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index 0450150..767f59e 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -22,6 +22,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.media.AudioManager; import android.os.IBinder; import android.os.ParcelUuid; import android.os.RemoteException; @@ -415,9 +416,16 @@ public final class BluetoothA2dp implements BluetoothProfile { } /** - * Tells remote device to adjust volume. Only if absolute volume is supported. + * Tells remote device to adjust volume. Only if absolute volume is + * supported. Uses the following values: + * <ul> + * <li>{@link AudioManager#ADJUST_LOWER}</li> + * <li>{@link AudioManager#ADJUST_RAISE}</li> + * <li>{@link AudioManager#ADJUST_MUTE}</li> + * <li>{@link AudioManager#ADJUST_UNMUTE}</li> + * </ul> * - * @param direction 1 to increase volume, or -1 to decrease volume + * @param direction One of the supported adjust values. * @hide */ public void adjustAvrcpAbsoluteVolume(int direction) { diff --git a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java index a15bd97..7b5a045 100644 --- a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java +++ b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java @@ -61,6 +61,7 @@ public final class BluetoothHeadsetClientCall implements Parcelable { */ public static final int CALL_STATE_TERMINATED = 7; + private final BluetoothDevice mDevice; private final int mId; private int mState; private String mNumber; @@ -70,8 +71,9 @@ public final class BluetoothHeadsetClientCall implements Parcelable { /** * Creates BluetoothHeadsetClientCall instance. */ - public BluetoothHeadsetClientCall(int id, int state, String number, boolean multiParty, - boolean outgoing) { + public BluetoothHeadsetClientCall(BluetoothDevice device, int id, int state, String number, + boolean multiParty, boolean outgoing) { + mDevice = device; mId = id; mState = state; mNumber = number != null ? number : ""; @@ -114,6 +116,15 @@ public final class BluetoothHeadsetClientCall implements Parcelable { } /** + * Gets call's device. + * + * @return call device. + */ + public BluetoothDevice getDevice() { + return mDevice; + } + + /** * Gets call's Id. * * @return call id. @@ -161,7 +172,9 @@ public final class BluetoothHeadsetClientCall implements Parcelable { } public String toString() { - StringBuilder builder = new StringBuilder("BluetoothHeadsetClientCall{mId: "); + StringBuilder builder = new StringBuilder("BluetoothHeadsetClientCall{mDevice: "); + builder.append(mDevice); + builder.append(", mId: "); builder.append(mId); builder.append(", mState: "); switch (mState) { @@ -192,8 +205,9 @@ public final class BluetoothHeadsetClientCall implements Parcelable { new Parcelable.Creator<BluetoothHeadsetClientCall>() { @Override public BluetoothHeadsetClientCall createFromParcel(Parcel in) { - return new BluetoothHeadsetClientCall(in.readInt(), in.readInt(), - in.readString(), in.readInt() == 1, in.readInt() == 1); + return new BluetoothHeadsetClientCall((BluetoothDevice)in.readParcelable(null), + in.readInt(), in.readInt(), in.readString(), + in.readInt() == 1, in.readInt() == 1); } @Override @@ -204,6 +218,7 @@ public final class BluetoothHeadsetClientCall implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { + out.writeParcelable(mDevice, 0); out.writeInt(mId); out.writeInt(mState); out.writeString(mNumber); diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java index 136e54d..49ac062 100644 --- a/core/java/android/content/ContentProviderOperation.java +++ b/core/java/android/content/ContentProviderOperation.java @@ -208,6 +208,22 @@ public class ContentProviderOperation implements Parcelable { return mType; } + public boolean isInsert() { + return mType == TYPE_INSERT; + } + + public boolean isDelete() { + return mType == TYPE_DELETE; + } + + public boolean isUpdate() { + return mType == TYPE_UPDATE; + } + + public boolean isAssertQuery() { + return mType == TYPE_ASSERT; + } + public boolean isWriteOperation() { return mType == TYPE_DELETE || mType == TYPE_INSERT || mType == TYPE_UPDATE; } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 26735a6..dec2524 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -24,6 +24,7 @@ import android.annotation.SystemApi; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; +import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; @@ -146,12 +147,13 @@ public abstract class Context { @IntDef(flag = true, value = { BIND_AUTO_CREATE, - BIND_AUTO_CREATE, BIND_DEBUG_UNBIND, BIND_NOT_FOREGROUND, BIND_ABOVE_CLIENT, BIND_ALLOW_OOM_MANAGEMENT, - BIND_WAIVE_PRIORITY + BIND_WAIVE_PRIORITY, + BIND_IMPORTANT, + BIND_ADJUST_WITH_ACTIVITY }) @Retention(RetentionPolicy.SOURCE) public @interface BindServiceFlags {} @@ -391,18 +393,55 @@ public abstract class Context { } /** - * Return a drawable object associated with a particular resource ID and + * Returns a color associated with a particular resource ID and styled for + * the current theme. + * + * @param id The desired resource identifier, as generated by the aapt + * tool. This integer encodes the package, type, and resource + * entry. The value 0 is an invalid identifier. + * @return A single color value in the form 0xAARRGGBB. + * @throws android.content.res.Resources.NotFoundException if the given ID + * does not exist. + */ + @Nullable + public final int getColor(int id) { + return getResources().getColor(id, getTheme()); + } + + /** + * Returns a drawable object associated with a particular resource ID and * styled for the current theme. * * @param id The desired resource identifier, as generated by the aapt * tool. This integer encodes the package, type, and resource * entry. The value 0 is an invalid identifier. - * @return Drawable An object that can be used to draw this resource. + * @return An object that can be used to draw this resource, or + * {@code null} if the resource could not be resolved. + * @throws android.content.res.Resources.NotFoundException if the given ID + * does not exist. */ + @Nullable public final Drawable getDrawable(int id) { return getResources().getDrawable(id, getTheme()); } + /** + * Returns a color state list associated with a particular resource ID and + * styled for the current theme. + * + * @param id The desired resource identifier, as generated by the aapt + * tool. This integer encodes the package, type, and resource + * entry. The value 0 is an invalid identifier. + * @return A color state list, or {@code null} if the resource could not be + * resolved. + * @throws android.content.res.Resources.NotFoundException if the given ID + * does not exist. + */ + @Nullable + public final ColorStateList getColorStateList(int id) { + return getResources().getColorStateList(id, getTheme()); + } + /** * Set the base theme for this context. Note that this should be called * before any views are instantiated in the Context (for example before @@ -2143,6 +2182,7 @@ public abstract class Context { BATTERY_SERVICE, JOB_SCHEDULER_SERVICE, MEDIA_PROJECTION_SERVICE, + MIDI_SERVICE, }) @Retention(RetentionPolicy.SOURCE) public @interface ServiceName {} @@ -2916,6 +2956,15 @@ public abstract class Context { public static final String MEDIA_PROJECTION_SERVICE = "media_projection"; /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.midi.MidiManager} for accessing the MIDI service. + * + * @see #getSystemService + * @hide + */ + public static final String MIDI_SERVICE = "midi"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 2fe727c..37a46be 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2808,14 +2808,12 @@ public class Intent implements Parcelable, Cloneable { @SdkConstant(SdkConstantType.INTENT_CATEGORY) public static final String CATEGORY_BROWSABLE = "android.intent.category.BROWSABLE"; /** - * @hide * Categories for activities that can participate in voice interaction. * An activity that supports this category must be prepared to run with * no UI shown at all (though in some case it may have a UI shown), and * rely on {@link android.app.VoiceInteractor} to interact with the user. */ @SdkConstant(SdkConstantType.INTENT_CATEGORY) - @SystemApi public static final String CATEGORY_VOICE = "android.intent.category.VOICE"; /** * Set if the activity should be considered as an alternative action to diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java index 46c9234..cd32dae 100644 --- a/core/java/android/content/SharedPreferences.java +++ b/core/java/android/content/SharedPreferences.java @@ -16,6 +16,8 @@ package android.content; +import android.annotation.Nullable; + import java.util.Map; import java.util.Set; @@ -78,7 +80,7 @@ public interface SharedPreferences { * @return Returns a reference to the same Editor object, so you can * chain put calls together. */ - Editor putString(String key, String value); + Editor putString(String key, @Nullable String value); /** * Set a set of String values in the preferences editor, to be written @@ -91,7 +93,7 @@ public interface SharedPreferences { * @return Returns a reference to the same Editor object, so you can * chain put calls together. */ - Editor putStringSet(String key, Set<String> values); + Editor putStringSet(String key, @Nullable Set<String> values); /** * Set an int value in the preferences editor, to be written back once @@ -254,7 +256,8 @@ public interface SharedPreferences { * * @throws ClassCastException */ - String getString(String key, String defValue); + @Nullable + String getString(String key, @Nullable String defValue); /** * Retrieve a set of String values from the preferences. @@ -272,7 +275,8 @@ public interface SharedPreferences { * * @throws ClassCastException */ - Set<String> getStringSet(String key, Set<String> defValues); + @Nullable + Set<String> getStringSet(String key, @Nullable Set<String> defValues); /** * Retrieve an int value from the preferences. diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 641f843..4723c0d 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -641,6 +641,13 @@ public class ActivityInfo extends ComponentInfo */ public String parentActivityName; + /** + * Value indicating if the activity is resizeable to any dimension. + * See {@link android.R.attr#resizeableActivity}. + * @hide + */ + public boolean resizeable; + public ActivityInfo() { } @@ -702,6 +709,7 @@ public class ActivityInfo extends ComponentInfo if (uiOptions != 0) { pw.println(prefix + " uiOptions=0x" + Integer.toHexString(uiOptions)); } + pw.println(prefix + "resizeable=" + resizeable); super.dumpBack(pw, prefix); } @@ -730,6 +738,7 @@ public class ActivityInfo extends ComponentInfo dest.writeString(parentActivityName); dest.writeInt(persistableMode); dest.writeInt(maxRecents); + dest.writeInt(resizeable ? 1 : 0); } public static final Parcelable.Creator<ActivityInfo> CREATOR @@ -757,5 +766,6 @@ public class ActivityInfo extends ComponentInfo parentActivityName = source.readString(); persistableMode = source.readInt(); maxRecents = source.readInt(); + resizeable = (source.readInt() == 1); } } diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java index ee23fcd..87b97aa 100644 --- a/core/java/android/content/pm/LauncherActivityInfo.java +++ b/core/java/android/content/pm/LauncherActivityInfo.java @@ -39,6 +39,7 @@ public class LauncherActivityInfo { private ActivityInfo mActivityInfo; private ComponentName mComponentName; + private ResolveInfo mResolveInfo; private UserHandle mUser; private long mFirstInstallTime; @@ -52,6 +53,7 @@ public class LauncherActivityInfo { LauncherActivityInfo(Context context, ResolveInfo info, UserHandle user, long firstInstallTime) { this(context); + mResolveInfo = info; mActivityInfo = info.activityInfo; mComponentName = LauncherApps.getComponentName(info); mUser = user; @@ -92,7 +94,7 @@ public class LauncherActivityInfo { * @return The label for the activity. */ public CharSequence getLabel() { - return mActivityInfo.loadLabel(mPm); + return mResolveInfo.loadLabel(mPm); } /** @@ -104,8 +106,22 @@ public class LauncherActivityInfo { * @return The drawable associated with the activity */ public Drawable getIcon(int density) { - // TODO: Use density - return mActivityInfo.loadIcon(mPm); + int iconRes = mResolveInfo.getIconResource(); + Resources resources = null; + Drawable icon = null; + // Get the preferred density icon from the app's resources + if (density != 0 && iconRes != 0) { + try { + resources = mPm.getResourcesForApplication(mActivityInfo.applicationInfo); + icon = resources.getDrawableForDensity(iconRes, density); + } catch (NameNotFoundException | Resources.NotFoundException exc) { + } + } + // Get the default density icon + if (icon == null) { + icon = mResolveInfo.loadIcon(mPm); + } + return icon; } /** @@ -151,23 +167,7 @@ public class LauncherActivityInfo { * @return A badged icon for the activity. */ public Drawable getBadgedIcon(int density) { - int iconRes = mActivityInfo.getIconResource(); - Resources resources = null; - Drawable originalIcon = null; - try { - resources = mPm.getResourcesForApplication(mActivityInfo.applicationInfo); - try { - if (density != 0) { - originalIcon = resources.getDrawableForDensity(iconRes, density); - } - } catch (Resources.NotFoundException e) { - } - } catch (NameNotFoundException nnfe) { - } - - if (originalIcon == null) { - originalIcon = mActivityInfo.loadIcon(mPm); - } + Drawable originalIcon = getIcon(density); if (originalIcon instanceof BitmapDrawable) { return mPm.getUserBadgedIcon(originalIcon, mUser); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index d7d9e8b..04777ba 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -49,6 +49,7 @@ import android.util.Pair; import android.util.Slog; import android.util.TypedValue; +import com.android.internal.R; import com.android.internal.util.ArrayUtils; import com.android.internal.util.XmlUtils; @@ -2759,9 +2760,17 @@ public class PackageParser { } } + addSharedLibrariesForBackwardCompatibility(owner); + return true; } + private static void addSharedLibrariesForBackwardCompatibility(Package owner) { + if (owner.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1) { + owner.usesLibraries = ArrayUtils.add(owner.usesLibraries, "org.apache.http.legacy"); + } + } + /** * Parse the {@code application} XML tree at the current parse location in a * <em>split APK</em> manifest. @@ -2946,20 +2955,19 @@ public class PackageParser { XmlPullParser parser, AttributeSet attrs, int flags, String[] outError, boolean receiver, boolean hardwareAccelerated) throws XmlPullParserException, IOException { - TypedArray sa = res.obtainAttributes(attrs, - com.android.internal.R.styleable.AndroidManifestActivity); + TypedArray sa = res.obtainAttributes(attrs, R.styleable.AndroidManifestActivity); if (mParseActivityArgs == null) { mParseActivityArgs = new ParseComponentArgs(owner, outError, - com.android.internal.R.styleable.AndroidManifestActivity_name, - com.android.internal.R.styleable.AndroidManifestActivity_label, - com.android.internal.R.styleable.AndroidManifestActivity_icon, - com.android.internal.R.styleable.AndroidManifestActivity_logo, - com.android.internal.R.styleable.AndroidManifestActivity_banner, + R.styleable.AndroidManifestActivity_name, + R.styleable.AndroidManifestActivity_label, + R.styleable.AndroidManifestActivity_icon, + R.styleable.AndroidManifestActivity_logo, + R.styleable.AndroidManifestActivity_banner, mSeparateProcesses, - com.android.internal.R.styleable.AndroidManifestActivity_process, - com.android.internal.R.styleable.AndroidManifestActivity_description, - com.android.internal.R.styleable.AndroidManifestActivity_enabled); + R.styleable.AndroidManifestActivity_process, + R.styleable.AndroidManifestActivity_description, + R.styleable.AndroidManifestActivity_enabled); } mParseActivityArgs.tag = receiver ? "<receiver>" : "<activity>"; @@ -2972,22 +2980,18 @@ public class PackageParser { return null; } - boolean setExported = sa.hasValue( - com.android.internal.R.styleable.AndroidManifestActivity_exported); + boolean setExported = sa.hasValue(R.styleable.AndroidManifestActivity_exported); if (setExported) { - a.info.exported = sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_exported, false); + a.info.exported = sa.getBoolean(R.styleable.AndroidManifestActivity_exported, false); } - a.info.theme = sa.getResourceId( - com.android.internal.R.styleable.AndroidManifestActivity_theme, 0); + a.info.theme = sa.getResourceId(R.styleable.AndroidManifestActivity_theme, 0); - a.info.uiOptions = sa.getInt( - com.android.internal.R.styleable.AndroidManifestActivity_uiOptions, + a.info.uiOptions = sa.getInt(R.styleable.AndroidManifestActivity_uiOptions, a.info.applicationInfo.uiOptions); String parentName = sa.getNonConfigurationString( - com.android.internal.R.styleable.AndroidManifestActivity_parentActivityName, + R.styleable.AndroidManifestActivity_parentActivityName, Configuration.NATIVE_CONFIG_VERSION); if (parentName != null) { String parentClassName = buildClassName(a.info.packageName, parentName, outError); @@ -3001,8 +3005,7 @@ public class PackageParser { } String str; - str = sa.getNonConfigurationString( - com.android.internal.R.styleable.AndroidManifestActivity_permission, 0); + str = sa.getNonConfigurationString(R.styleable.AndroidManifestActivity_permission, 0); if (str == null) { a.info.permission = owner.applicationInfo.permission; } else { @@ -3010,140 +3013,111 @@ public class PackageParser { } str = sa.getNonConfigurationString( - com.android.internal.R.styleable.AndroidManifestActivity_taskAffinity, + R.styleable.AndroidManifestActivity_taskAffinity, Configuration.NATIVE_CONFIG_VERSION); a.info.taskAffinity = buildTaskAffinityName(owner.applicationInfo.packageName, owner.applicationInfo.taskAffinity, str, outError); a.info.flags = 0; if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_multiprocess, - false)) { + R.styleable.AndroidManifestActivity_multiprocess, false)) { a.info.flags |= ActivityInfo.FLAG_MULTIPROCESS; } - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_finishOnTaskLaunch, - false)) { + if (sa.getBoolean(R.styleable.AndroidManifestActivity_finishOnTaskLaunch, false)) { a.info.flags |= ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH; } - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_clearTaskOnLaunch, - false)) { + if (sa.getBoolean(R.styleable.AndroidManifestActivity_clearTaskOnLaunch, false)) { a.info.flags |= ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH; } - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_noHistory, - false)) { + if (sa.getBoolean(R.styleable.AndroidManifestActivity_noHistory, false)) { a.info.flags |= ActivityInfo.FLAG_NO_HISTORY; } - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_alwaysRetainTaskState, - false)) { + if (sa.getBoolean(R.styleable.AndroidManifestActivity_alwaysRetainTaskState, false)) { a.info.flags |= ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE; } - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_stateNotNeeded, - false)) { + if (sa.getBoolean(R.styleable.AndroidManifestActivity_stateNotNeeded, false)) { a.info.flags |= ActivityInfo.FLAG_STATE_NOT_NEEDED; } - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_excludeFromRecents, - false)) { + if (sa.getBoolean(R.styleable.AndroidManifestActivity_excludeFromRecents, false)) { a.info.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; } - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_allowTaskReparenting, + if (sa.getBoolean(R.styleable.AndroidManifestActivity_allowTaskReparenting, (owner.applicationInfo.flags&ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING) != 0)) { a.info.flags |= ActivityInfo.FLAG_ALLOW_TASK_REPARENTING; } - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_finishOnCloseSystemDialogs, - false)) { + if (sa.getBoolean(R.styleable.AndroidManifestActivity_finishOnCloseSystemDialogs, false)) { a.info.flags |= ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS; } - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_showOnLockScreen, - false)) { + if (sa.getBoolean(R.styleable.AndroidManifestActivity_showOnLockScreen, false)) { a.info.flags |= ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN; } - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_immersive, - false)) { + if (sa.getBoolean(R.styleable.AndroidManifestActivity_immersive, false)) { a.info.flags |= ActivityInfo.FLAG_IMMERSIVE; } + if (sa.getBoolean(R.styleable.AndroidManifestActivity_primaryUserOnly, false)) { + a.info.flags |= ActivityInfo.FLAG_PRIMARY_USER_ONLY; + } + if (!receiver) { - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_hardwareAccelerated, + if (sa.getBoolean(R.styleable.AndroidManifestActivity_hardwareAccelerated, hardwareAccelerated)) { a.info.flags |= ActivityInfo.FLAG_HARDWARE_ACCELERATED; } a.info.launchMode = sa.getInt( - com.android.internal.R.styleable.AndroidManifestActivity_launchMode, - ActivityInfo.LAUNCH_MULTIPLE); + R.styleable.AndroidManifestActivity_launchMode, ActivityInfo.LAUNCH_MULTIPLE); a.info.documentLaunchMode = sa.getInt( - com.android.internal.R.styleable.AndroidManifestActivity_documentLaunchMode, + R.styleable.AndroidManifestActivity_documentLaunchMode, ActivityInfo.DOCUMENT_LAUNCH_NONE); a.info.maxRecents = sa.getInt( - com.android.internal.R.styleable.AndroidManifestActivity_maxRecents, + R.styleable.AndroidManifestActivity_maxRecents, ActivityManager.getDefaultAppRecentsLimitStatic()); a.info.screenOrientation = sa.getInt( - com.android.internal.R.styleable.AndroidManifestActivity_screenOrientation, + R.styleable.AndroidManifestActivity_screenOrientation, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); - a.info.configChanges = sa.getInt( - com.android.internal.R.styleable.AndroidManifestActivity_configChanges, - 0); + a.info.configChanges = sa.getInt(R.styleable.AndroidManifestActivity_configChanges, 0); a.info.softInputMode = sa.getInt( - com.android.internal.R.styleable.AndroidManifestActivity_windowSoftInputMode, - 0); + R.styleable.AndroidManifestActivity_windowSoftInputMode, 0); a.info.persistableMode = sa.getInteger( - com.android.internal.R.styleable.AndroidManifestActivity_persistableMode, + R.styleable.AndroidManifestActivity_persistableMode, ActivityInfo.PERSIST_ROOT_ONLY); - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_allowEmbedded, - false)) { + if (sa.getBoolean(R.styleable.AndroidManifestActivity_allowEmbedded, false)) { a.info.flags |= ActivityInfo.FLAG_ALLOW_EMBEDDED; } - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_autoRemoveFromRecents, - false)) { + if (sa.getBoolean(R.styleable.AndroidManifestActivity_autoRemoveFromRecents, false)) { a.info.flags |= ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS; } - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_relinquishTaskIdentity, - false)) { + if (sa.getBoolean(R.styleable.AndroidManifestActivity_relinquishTaskIdentity, false)) { a.info.flags |= ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY; } - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_resumeWhilePausing, - false)) { + if (sa.getBoolean(R.styleable.AndroidManifestActivity_resumeWhilePausing, false)) { a.info.flags |= ActivityInfo.FLAG_RESUME_WHILE_PAUSING; } + + a.info.resizeable = sa.getBoolean( + R.styleable.AndroidManifestActivity_resizeableActivity, + owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.MNC); } else { a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE; a.info.configChanges = 0; - } - if (receiver) { - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_singleUser, - false)) { + if (sa.getBoolean(R.styleable.AndroidManifestActivity_singleUser, false)) { a.info.flags |= ActivityInfo.FLAG_SINGLE_USER; if (a.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) { Slog.w(TAG, "Activity exported request ignored due to singleUser: " @@ -3153,11 +3127,6 @@ public class PackageParser { setExported = true; } } - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_primaryUserOnly, - false)) { - a.info.flags |= ActivityInfo.FLAG_PRIMARY_USER_ONLY; - } } sa.recycle(); diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java index 68a39d3..b42d8bc 100644 --- a/core/java/android/content/res/ColorStateList.java +++ b/core/java/android/content/res/ColorStateList.java @@ -16,8 +16,12 @@ package android.content.res; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.res.Resources.Theme; import android.graphics.Color; +import com.android.internal.R; import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; @@ -25,6 +29,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.util.AttributeSet; +import android.util.Log; import android.util.MathUtils; import android.util.SparseArray; import android.util.StateSet; @@ -64,15 +69,23 @@ import java.util.Arrays; * List Resource</a>.</p> */ public class ColorStateList implements Parcelable { - private int[][] mStateSpecs; // must be parallel to mColors - private int[] mColors; // must be parallel to mStateSpecs - private int mDefaultColor = 0xffff0000; - + private static final String TAG = "ColorStateList"; + private static final int DEFAULT_COLOR = Color.RED; private static final int[][] EMPTY = new int[][] { new int[0] }; private static final SparseArray<WeakReference<ColorStateList>> sCache = new SparseArray<WeakReference<ColorStateList>>(); - private ColorStateList() { } + private int[][] mThemeAttrs; + private int mChangingConfigurations; + + private int[][] mStateSpecs; + private int[] mColors; + private int mDefaultColor; + private boolean mIsOpaque; + + private ColorStateList() { + // Not publicly instantiable. + } /** * Creates a ColorStateList that returns the specified mapping from @@ -82,53 +95,102 @@ public class ColorStateList implements Parcelable { mStateSpecs = states; mColors = colors; - if (states.length > 0) { - mDefaultColor = colors[0]; - - for (int i = 0; i < states.length; i++) { - if (states[i].length == 0) { - mDefaultColor = colors[i]; - } - } - } + onColorsChanged(); } /** - * Creates or retrieves a ColorStateList that always returns a single color. + * @return A ColorStateList containing a single color. */ + @NonNull public static ColorStateList valueOf(int color) { - // TODO: should we collect these eventually? synchronized (sCache) { - final WeakReference<ColorStateList> ref = sCache.get(color); + final int index = sCache.indexOfKey(color); + if (index >= 0) { + final ColorStateList cached = sCache.valueAt(index).get(); + if (cached != null) { + return cached; + } - ColorStateList csl = ref != null ? ref.get() : null; - if (csl != null) { - return csl; + // Prune missing entry. + sCache.removeAt(index); } - csl = new ColorStateList(EMPTY, new int[] { color }); + // Prune the cache before adding new items. + final int N = sCache.size(); + for (int i = N - 1; i >= 0; i--) { + if (sCache.valueAt(i).get() == null) { + sCache.removeAt(i); + } + } + + final ColorStateList csl = new ColorStateList(EMPTY, new int[] { color }); sCache.put(color, new WeakReference<ColorStateList>(csl)); return csl; } } /** - * Create a ColorStateList from an XML document, given a set of {@link Resources}. + * Creates a ColorStateList with the same properties as another + * ColorStateList. + * <p> + * The properties of the new ColorStateList can be modified without + * affecting the source ColorStateList. + * + * @param orig the source color state list */ + private ColorStateList(ColorStateList orig) { + if (orig != null) { + mStateSpecs = orig.mStateSpecs; + mDefaultColor = orig.mDefaultColor; + mIsOpaque = orig.mIsOpaque; + + // Deep copy, this may change due to theming. + mColors = orig.mColors.clone(); + } + } + + /** + * Creates a ColorStateList from an XML document. + * + * @param r Resources against which the ColorStateList should be inflated. + * @param parser Parser for the XML document defining the ColorStateList. + * @return A new color state list. + * + * @deprecated Use #createFromXml(Resources, XmlPullParser parser, Theme) + */ + @NonNull + @Deprecated public static ColorStateList createFromXml(Resources r, XmlPullParser parser) throws XmlPullParserException, IOException { + return createFromXml(r, parser, null); + } + + /** + * Creates a ColorStateList from an XML document using given a set of + * {@link Resources} and a {@link Theme}. + * + * @param r Resources against which the ColorStateList should be inflated. + * @param parser Parser for the XML document defining the ColorStateList. + * @param theme Optional theme to apply to the color state list, may be + * {@code null}. + * @return A new color state list. + */ + @NonNull + public static ColorStateList createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser, + @Nullable Theme theme) throws XmlPullParserException, IOException { final AttributeSet attrs = Xml.asAttributeSet(parser); int type; - while ((type=parser.next()) != XmlPullParser.START_TAG + while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { + // Seek parser to start tag. } if (type != XmlPullParser.START_TAG) { throw new XmlPullParserException("No start tag found"); } - return createFromXmlInner(r, parser, attrs); + return createFromXmlInner(r, parser, attrs, theme); } /** @@ -136,28 +198,31 @@ public class ColorStateList implements Parcelable { * tag in an XML document, tries to create a ColorStateList from that tag. * * @throws XmlPullParserException if the current tag is not <selector> - * @return A color state list for the current tag. + * @return A new color state list for the current tag. */ - private static ColorStateList createFromXmlInner(Resources r, XmlPullParser parser, - AttributeSet attrs) throws XmlPullParserException, IOException { - final ColorStateList colorStateList; + @NonNull + private static ColorStateList createFromXmlInner(@NonNull Resources r, + @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme) + throws XmlPullParserException, IOException { final String name = parser.getName(); - if (name.equals("selector")) { - colorStateList = new ColorStateList(); - } else { + if (!name.equals("selector")) { throw new XmlPullParserException( - parser.getPositionDescription() + ": invalid drawable tag " + name); + parser.getPositionDescription() + ": invalid color state list tag " + name); } - colorStateList.inflate(r, parser, attrs); + final ColorStateList colorStateList = new ColorStateList(); + colorStateList.inflate(r, parser, attrs, theme); return colorStateList; } /** - * Creates a new ColorStateList that has the same states and - * colors as this one but where each color has the specified alpha value - * (0-255). + * Creates a new ColorStateList that has the same states and colors as this + * one but where each color has the specified alpha value (0-255). + * + * @param alpha The new alpha channel value (0-255). + * @return A new color state list. */ + @NonNull public ColorStateList withAlpha(int alpha) { final int[] colors = new int[mColors.length]; final int len = colors.length; @@ -171,88 +236,154 @@ public class ColorStateList implements Parcelable { /** * Fill in this object based on the contents of an XML "selector" element. */ - private void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + private void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { - int type; - final int innerDepth = parser.getDepth()+1; int depth; + int type; + + int changingConfigurations = 0; + int defaultColor = DEFAULT_COLOR; + + boolean hasUnresolvedAttrs = false; int[][] stateSpecList = ArrayUtils.newUnpaddedArray(int[].class, 20); + int[][] themeAttrsList = new int[stateSpecList.length][]; int[] colorList = new int[stateSpecList.length]; int listSize = 0; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && ((depth=parser.getDepth()) >= innerDepth - || type != XmlPullParser.END_TAG)) { - if (type != XmlPullParser.START_TAG) { + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { + if (type != XmlPullParser.START_TAG || depth > innerDepth + || !parser.getName().equals("item")) { continue; } - if (depth > innerDepth || !parser.getName().equals("item")) { - continue; - } + final TypedArray a = Resources.obtainAttributes(r, theme, attrs, + R.styleable.ColorStateListItem); + final int[] themeAttrs = a.extractThemeAttrs(); + final int baseColor = a.getColor(R.styleable.ColorStateListItem_color, 0); + final float alphaMod = a.getFloat(R.styleable.ColorStateListItem_alpha, 1.0f); + + changingConfigurations |= a.getChangingConfigurations(); - int alphaRes = 0; - float alpha = 1.0f; - int colorRes = 0; - int color = 0xffff0000; - boolean haveColor = false; + a.recycle(); - int i; + // Parse all unrecognized attributes as state specifiers. int j = 0; final int numAttrs = attrs.getAttributeCount(); int[] stateSpec = new int[numAttrs]; - for (i = 0; i < numAttrs; i++) { + for (int i = 0; i < numAttrs; i++) { final int stateResId = attrs.getAttributeNameResource(i); - if (stateResId == 0) break; - if (stateResId == com.android.internal.R.attr.alpha) { - alphaRes = attrs.getAttributeResourceValue(i, 0); - if (alphaRes == 0) { - alpha = attrs.getAttributeFloatValue(i, 1.0f); - } - } else if (stateResId == com.android.internal.R.attr.color) { - colorRes = attrs.getAttributeResourceValue(i, 0); - if (colorRes == 0) { - color = attrs.getAttributeIntValue(i, color); - haveColor = true; - } - } else { - stateSpec[j++] = attrs.getAttributeBooleanValue(i, false) - ? stateResId : -stateResId; + switch (stateResId) { + case R.attr.color: + case R.attr.alpha: + // Recognized attribute, ignore. + break; + default: + stateSpec[j++] = attrs.getAttributeBooleanValue(i, false) + ? stateResId : -stateResId; } } stateSpec = StateSet.trimStateSet(stateSpec, j); - if (colorRes != 0) { - color = r.getColor(colorRes); - } else if (!haveColor) { - throw new XmlPullParserException( - parser.getPositionDescription() - + ": <item> tag requires a 'android:color' attribute."); - } - - if (alphaRes != 0) { - alpha = r.getFloat(alphaRes); - } - // Apply alpha modulation. - final int alphaMod = MathUtils.constrain((int) (Color.alpha(color) * alpha), 0, 255); - color = (color & 0xFFFFFF) | (alphaMod << 24); - + final int color = modulateColorAlpha(baseColor, alphaMod); if (listSize == 0 || stateSpec.length == 0) { - mDefaultColor = color; + defaultColor = color; + } + + if (themeAttrs != null) { + hasUnresolvedAttrs = true; } colorList = GrowingArrayUtils.append(colorList, listSize, color); + themeAttrsList = GrowingArrayUtils.append(themeAttrsList, listSize, themeAttrs); stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec); listSize++; } + mChangingConfigurations = changingConfigurations; + mDefaultColor = defaultColor; + + if (hasUnresolvedAttrs) { + mThemeAttrs = new int[listSize][]; + System.arraycopy(themeAttrsList, 0, mThemeAttrs, 0, listSize); + } else { + mThemeAttrs = null; + } + mColors = new int[listSize]; mStateSpecs = new int[listSize][]; System.arraycopy(colorList, 0, mColors, 0, listSize); System.arraycopy(stateSpecList, 0, mStateSpecs, 0, listSize); + + onColorsChanged(); + } + + /** + * Returns whether a theme can be applied to this color state list, which + * usually indicates that the color state list has unresolved theme + * attributes. + * + * @return whether a theme can be applied to this color state list + */ + public boolean canApplyTheme() { + return mThemeAttrs != null; + } + + /** + * Applies a theme to this color state list. + * + * @param t the theme to apply + */ + public void applyTheme(Theme t) { + if (mThemeAttrs == null) { + return; + } + + boolean hasUnresolvedAttrs = false; + + final int[][] themeAttrsList = mThemeAttrs; + final int N = themeAttrsList.length; + for (int i = 0; i < N; i++) { + if (themeAttrsList[i] != null) { + final TypedArray a = t.resolveAttributes(themeAttrsList[i], + R.styleable.ColorStateListItem); + final int baseColor = a.getColor( + R.styleable.ColorStateListItem_color, mColors[i]); + final float alphaMod = a.getFloat( + R.styleable.ColorStateListItem_alpha, 1.0f); + + mColors[i] = modulateColorAlpha(baseColor, alphaMod); + mChangingConfigurations |= a.getChangingConfigurations(); + + themeAttrsList[i] = a.extractThemeAttrs(themeAttrsList[i]); + if (themeAttrsList[i] != null) { + hasUnresolvedAttrs = true; + } + + a.recycle(); + } + } + + if (!hasUnresolvedAttrs) { + mThemeAttrs = null; + } + + onColorsChanged(); + } + + private int modulateColorAlpha(int baseColor, float alphaMod) { + if (alphaMod == 1.0f) { + return baseColor; + } + + final int baseAlpha = Color.alpha(baseColor); + final int alpha = MathUtils.constrain((int) (baseAlpha * alphaMod + 0.5f), 0, 255); + final int color = (baseColor & 0xFFFFFF) | (alpha << 24); + return color; } /** @@ -275,28 +406,24 @@ public class ColorStateList implements Parcelable { * @return True if this color state list is opaque. */ public boolean isOpaque() { - final int n = mColors.length; - for (int i = 0; i < n; i++) { - if (Color.alpha(mColors[i]) != 0xFF) { - return false; - } - } - return true; + return mIsOpaque; } /** - * Return the color associated with the given set of {@link android.view.View} states. + * Return the color associated with the given set of + * {@link android.view.View} states. * * @param stateSet an array of {@link android.view.View} states - * @param defaultColor the color to return if there's not state spec in this - * {@link ColorStateList} that matches the stateSet. + * @param defaultColor the color to return if there's no matching state + * spec in this {@link ColorStateList} that matches the + * stateSet. * * @return the color associated with that set of states in this {@link ColorStateList}. */ - public int getColorForState(int[] stateSet, int defaultColor) { + public int getColorForState(@Nullable int[] stateSet, int defaultColor) { final int setLength = mStateSpecs.length; for (int i = 0; i < setLength; i++) { - int[] stateSpec = mStateSpecs[i]; + final int[] stateSpec = mStateSpecs[i]; if (StateSet.stateSetMatches(stateSpec, stateSet)) { return mColors[i]; } @@ -314,7 +441,9 @@ public class ColorStateList implements Parcelable { } /** - * Return the states in this {@link ColorStateList}. + * Return the states in this {@link ColorStateList}. The returned array + * should not be modified. + * * @return the states in this {@link ColorStateList} * @hide */ @@ -323,7 +452,9 @@ public class ColorStateList implements Parcelable { } /** - * Return the colors in this {@link ColorStateList}. + * Return the colors in this {@link ColorStateList}. The returned array + * should not be modified. + * * @return the colors in this {@link ColorStateList} * @hide */ @@ -374,11 +505,81 @@ public class ColorStateList implements Parcelable { @Override public String toString() { return "ColorStateList{" + + "mThemeAttrs=" + Arrays.deepToString(mThemeAttrs) + + "mChangingConfigurations=" + mChangingConfigurations + "mStateSpecs=" + Arrays.deepToString(mStateSpecs) + "mColors=" + Arrays.toString(mColors) + "mDefaultColor=" + mDefaultColor + '}'; } + /** + * Updates the default color and opacity. + */ + private void onColorsChanged() { + int defaultColor = DEFAULT_COLOR; + boolean isOpaque = true; + + final int[][] states = mStateSpecs; + final int[] colors = mColors; + final int N = states.length; + if (N > 0) { + defaultColor = colors[0]; + + for (int i = N - 1; i > 0; i--) { + if (states[i].length == 0) { + defaultColor = colors[i]; + break; + } + } + + for (int i = 0; i < N; i++) { + if (Color.alpha(colors[i]) != 0xFF) { + isOpaque = false; + break; + } + } + } + + mDefaultColor = defaultColor; + mIsOpaque = isOpaque; + } + + /** + * @return A factory that can create new instances of this ColorStateList. + */ + ColorStateListFactory getFactory() { + return new ColorStateListFactory(this); + } + + static class ColorStateListFactory extends ConstantState<ColorStateList> { + final ColorStateList mSrc; + + public ColorStateListFactory(ColorStateList src) { + mSrc = src; + } + + @Override + public int getChangingConfigurations() { + return mSrc.mChangingConfigurations; + } + + @Override + public ColorStateList newInstance() { + return mSrc; + } + + @Override + public ColorStateList newInstance(Resources res, Theme theme) { + if (theme == null || !mSrc.canApplyTheme()) { + return mSrc; + } + + final ColorStateList clone = new ColorStateList(mSrc); + clone.applyTheme(theme); + return clone; + } + } + @Override public int describeContents() { return 0; @@ -386,6 +587,9 @@ public class ColorStateList implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { + if (canApplyTheme()) { + Log.w(TAG, "Wrote partially-resolved ColorStateList to parcel!"); + } final int N = mStateSpecs.length; dest.writeInt(N); for (int i = 0; i < N; i++) { diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 73913b6..6fb7299 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -16,21 +16,20 @@ package android.content.res; -import android.animation.Animator; -import android.animation.StateListAnimator; -import android.annotation.NonNull; -import android.util.Pools.SynchronizedPool; -import android.view.ViewDebug; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.animation.Animator; +import android.animation.StateListAnimator; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.ActivityInfo; +import android.content.res.ColorStateList.ColorStateListFactory; import android.graphics.Movie; -import android.graphics.drawable.Drawable; import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable.ConstantState; import android.os.Build; import android.os.Bundle; @@ -40,9 +39,11 @@ import android.util.ArrayMap; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; +import android.util.LongSparseArray; +import android.util.Pools.SynchronizedPool; import android.util.Slog; import android.util.TypedValue; -import android.util.LongSparseArray; +import android.view.ViewDebug; import java.io.IOException; import java.io.InputStream; @@ -97,8 +98,8 @@ public class Resources { private static final LongSparseArray<ConstantState>[] sPreloadedDrawables; private static final LongSparseArray<ConstantState> sPreloadedColorDrawables = new LongSparseArray<ConstantState>(); - private static final LongSparseArray<ColorStateList> sPreloadedColorStateLists - = new LongSparseArray<ColorStateList>(); + private static final LongSparseArray<ColorStateListFactory> sPreloadedColorStateLists + = new LongSparseArray<ColorStateListFactory>(); // Pool of TypedArrays targeted to this Resources object. final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<TypedArray>(5); @@ -116,8 +117,8 @@ public class Resources { new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>(); private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mColorDrawableCache = new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>(); - private final LongSparseArray<WeakReference<ColorStateList>> mColorStateListCache = - new LongSparseArray<WeakReference<ColorStateList>>(); + private final ConfigurationBoundResourceCache<ColorStateList> mColorStateListCache = + new ConfigurationBoundResourceCache<ColorStateList>(this); private final ConfigurationBoundResourceCache<Animator> mAnimatorCache = new ConfigurationBoundResourceCache<Animator>(this); private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache = @@ -140,9 +141,6 @@ public class Resources { private CompatibilityInfo mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; - @SuppressWarnings("unused") - private WeakReference<IBinder> mToken; - static { sPreloadedDrawables = new LongSparseArray[2]; sPreloadedDrawables[0] = new LongSparseArray<ConstantState>(); @@ -223,46 +221,44 @@ public class Resources { /** * Create a new Resources object on top of an existing set of assets in an * AssetManager. - * - * @param assets Previously created AssetManager. - * @param metrics Current display metrics to consider when + * + * @param assets Previously created AssetManager. + * @param metrics Current display metrics to consider when * selecting/computing resource values. - * @param config Desired device configuration to consider when + * @param config Desired device configuration to consider when * selecting/computing resource values (optional). */ public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) { - this(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + this(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO); } /** * Creates a new Resources object with CompatibilityInfo. - * - * @param assets Previously created AssetManager. - * @param metrics Current display metrics to consider when + * + * @param assets Previously created AssetManager. + * @param metrics Current display metrics to consider when * selecting/computing resource values. - * @param config Desired device configuration to consider when + * @param config Desired device configuration to consider when * selecting/computing resource values (optional). * @param compatInfo this resource's compatibility info. Must not be null. - * @param token The Activity token for determining stack affiliation. Usually null. * @hide */ public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config, - CompatibilityInfo compatInfo, IBinder token) { + CompatibilityInfo compatInfo) { mAssets = assets; mMetrics.setToDefaults(); if (compatInfo != null) { mCompatibilityInfo = compatInfo; } - mToken = new WeakReference<IBinder>(token); updateConfiguration(config, metrics); assets.ensureStringBlocks(); } /** * Return a global shared Resources object that provides access to only - * system resources (no application resources), and is not configured for - * the current screen (can not use dimension units, does not change based - * on orientation, etc). + * system resources (no application resources), and is not configured for + * the current screen (can not use dimension units, does not change based + * on orientation, etc). */ public static Resources getSystem() { synchronized (sSync) { @@ -897,20 +893,41 @@ public class Resources { } /** - * Return a color integer associated with a particular resource ID. - * If the resource holds a complex - * {@link android.content.res.ColorStateList}, then the default color from - * the set is returned. + * Returns a color integer associated with a particular resource ID. If the + * resource holds a complex {@link ColorStateList}, then the default color + * from the set is returned. * * @param id The desired resource identifier, as generated by the aapt * tool. This integer encodes the package, type, and resource * entry. The value 0 is an invalid identifier. * - * @throws NotFoundException Throws NotFoundException if the given ID does not exist. + * @throws NotFoundException Throws NotFoundException if the given ID does + * not exist. * - * @return Returns a single color value in the form 0xAARRGGBB. + * @return A single color value in the form 0xAARRGGBB. + * @deprecated Use {@link #getColor(int, Theme)} instead. */ public int getColor(int id) throws NotFoundException { + return getColor(id, null); + } + + /** + * Returns a themed color integer associated with a particular resource ID. + * If the resource holds a complex {@link ColorStateList}, then the default + * color from the set is returned. + * + * @param id The desired resource identifier, as generated by the aapt + * tool. This integer encodes the package, type, and resource + * entry. The value 0 is an invalid identifier. + * @param theme The theme used to style the color attributes, may be + * {@code null}. + * + * @throws NotFoundException Throws NotFoundException if the given ID does + * not exist. + * + * @return A single color value in the form 0xAARRGGBB. + */ + public int getColor(int id, @Nullable Theme theme) throws NotFoundException { TypedValue value; synchronized (mAccessLock) { value = mTmpValue; @@ -919,40 +936,77 @@ public class Resources { } getValue(id, value, true); if (value.type >= TypedValue.TYPE_FIRST_INT - && value.type <= TypedValue.TYPE_LAST_INT) { + && value.type <= TypedValue.TYPE_LAST_INT) { mTmpValue = value; return value.data; } else if (value.type != TypedValue.TYPE_STRING) { throw new NotFoundException( - "Resource ID #0x" + Integer.toHexString(id) + " type #0x" - + Integer.toHexString(value.type) + " is not valid"); + "Resource ID #0x" + Integer.toHexString(id) + " type #0x" + + Integer.toHexString(value.type) + " is not valid"); } mTmpValue = null; } - ColorStateList csl = loadColorStateList(value, id); + + final ColorStateList csl = loadColorStateList(value, id, theme); synchronized (mAccessLock) { if (mTmpValue == null) { mTmpValue = value; } } + return csl.getDefaultColor(); } /** - * Return a color state list associated with a particular resource ID. The - * resource may contain either a single raw color value, or a complex - * {@link android.content.res.ColorStateList} holding multiple possible colors. + * Returns a color state list associated with a particular resource ID. The + * resource may contain either a single raw color value or a complex + * {@link ColorStateList} holding multiple possible colors. * * @param id The desired resource identifier of a {@link ColorStateList}, - * as generated by the aapt tool. This integer encodes the package, type, and resource - * entry. The value 0 is an invalid identifier. + * as generated by the aapt tool. This integer encodes the + * package, type, and resource entry. The value 0 is an invalid + * identifier. * - * @throws NotFoundException Throws NotFoundException if the given ID does not exist. + * @throws NotFoundException Throws NotFoundException if the given ID does + * not exist. * - * @return Returns a ColorStateList object containing either a single - * solid color or multiple colors that can be selected based on a state. + * @return A ColorStateList object containing either a single solid color + * or multiple colors that can be selected based on a state. + * @deprecated Use {@link #getColorStateList(int, Theme)} instead. */ + @Nullable public ColorStateList getColorStateList(int id) throws NotFoundException { + final ColorStateList csl = getColorStateList(id, null); + if (csl != null && csl.canApplyTheme()) { + Log.w(TAG, "ColorStateList " + getResourceName(id) + " has " + + "unresolved theme attributes! Consider using " + + "Resources.getColorStateList(int, Theme) or " + + "Context.getColorStateList(int).", new RuntimeException()); + } + return csl; + } + + /** + * Returns a themed color state list associated with a particular resource + * ID. The resource may contain either a single raw color value or a + * complex {@link ColorStateList} holding multiple possible colors. + * + * @param id The desired resource identifier of a {@link ColorStateList}, + * as generated by the aapt tool. This integer encodes the + * package, type, and resource entry. The value 0 is an invalid + * identifier. + * @param theme The theme used to style the color attributes, may be + * {@code null}. + * + * @throws NotFoundException Throws NotFoundException if the given ID does + * not exist. + * + * @return A themed ColorStateList object containing either a single solid + * color or multiple colors that can be selected based on a state. + */ + @Nullable + public ColorStateList getColorStateList(int id, @Nullable Theme theme) + throws NotFoundException { TypedValue value; synchronized (mAccessLock) { value = mTmpValue; @@ -963,15 +1017,19 @@ public class Resources { } getValue(id, value, true); } - ColorStateList res = loadColorStateList(value, id); + + final ColorStateList res = loadColorStateList(value, id, theme); synchronized (mAccessLock) { if (mTmpValue == null) { mTmpValue = value; } } + return res; } + + /** * Return a boolean associated with a particular resource ID. This can be * used with any integral resource value, and will return true if it is @@ -1843,11 +1901,10 @@ public class Resources { clearDrawableCachesLocked(mDrawableCache, configChanges); clearDrawableCachesLocked(mColorDrawableCache, configChanges); + mColorStateListCache.onConfigurationChange(configChanges); mAnimatorCache.onConfigurationChange(configChanges); mStateListAnimatorCache.onConfigurationChange(configChanges); - mColorStateListCache.clear(); - flushLayoutCache(); } synchronized (sSync) { @@ -2425,6 +2482,9 @@ public class Resources { final String themeKey = theme == null ? "" : theme.mKey; LongSparseArray<WeakReference<ConstantState>> themedCache = caches.get(themeKey); if (themedCache == null) { + // Clean out the caches before we add more. This shouldn't + // happen very often. + pruneCaches(caches); themedCache = new LongSparseArray<WeakReference<ConstantState>>(1); caches.put(themeKey, themedCache); } @@ -2434,12 +2494,46 @@ public class Resources { } /** + * Prunes empty caches from the cache map. + * + * @param caches The map of caches to prune. + */ + private void pruneCaches(ArrayMap<String, + LongSparseArray<WeakReference<ConstantState>>> caches) { + final int N = caches.size(); + for (int i = N - 1; i >= 0; i--) { + final LongSparseArray<WeakReference<ConstantState>> cache = caches.valueAt(i); + if (pruneCache(cache)) { + caches.removeAt(i); + } + } + } + + /** + * Prunes obsolete weak references from a cache, returning {@code true} if + * the cache is empty and should be removed. + * + * @param cache The cache of weak references to prune. + * @return {@code true} if the cache is empty and should be removed. + */ + private boolean pruneCache(LongSparseArray<WeakReference<ConstantState>> cache) { + final int N = cache.size(); + for (int i = N - 1; i >= 0; i--) { + final WeakReference entry = cache.valueAt(i); + if (entry == null || entry.get() == null) { + cache.removeAt(i); + } + } + return cache.size() == 0; + } + + /** * Loads a drawable from XML or resources stream. */ private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) { if (value.string == null) { throw new NotFoundException("Resource \"" + getResourceName(id) + "\" (" - + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value); + + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value); } final String file = value.string.toString(); @@ -2530,7 +2624,8 @@ public class Resources { return null; } - /*package*/ ColorStateList loadColorStateList(TypedValue value, int id) + @Nullable + ColorStateList loadColorStateList(TypedValue value, int id, Theme theme) throws NotFoundException { if (TRACE_FOR_PRELOAD) { // Log only framework resources @@ -2544,101 +2639,107 @@ public class Resources { ColorStateList csl; - if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && - value.type <= TypedValue.TYPE_LAST_COLOR_INT) { - - csl = sPreloadedColorStateLists.get(key); - if (csl != null) { - return csl; + // Handle inline color definitions. + if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT + && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { + final ColorStateListFactory factory = sPreloadedColorStateLists.get(key); + if (factory != null) { + return factory.newInstance(); } csl = ColorStateList.valueOf(value.data); + if (mPreloading) { if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId, "color")) { - sPreloadedColorStateLists.put(key, csl); + sPreloadedColorStateLists.put(key, csl.getFactory()); } } return csl; } - csl = getCachedColorStateList(key); + final ConfigurationBoundResourceCache<ColorStateList> cache = mColorStateListCache; + + csl = cache.get(key, theme); if (csl != null) { return csl; } - csl = sPreloadedColorStateLists.get(key); + final ColorStateListFactory factory = sPreloadedColorStateLists.get(key); + if (factory != null) { + csl = factory.newInstance(this, theme); + } + + if (csl == null) { + csl = loadColorStateListForCookie(value, id, theme); + } + if (csl != null) { - return csl; + if (mPreloading) { + if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId, + "color")) { + sPreloadedColorStateLists.put(key, csl.getFactory()); + } + } else { + cache.put(key, theme, csl.getFactory()); + } } + return csl; + } + + private ColorStateList loadColorStateListForCookie(TypedValue value, int id, Theme theme) { if (value.string == null) { - throw new NotFoundException( - "Resource is not a ColorStateList (color or path): " + value); + throw new UnsupportedOperationException( + "Can't convert to color state list: type=0x" + value.type); } - + final String file = value.string.toString(); + if (TRACE_FOR_MISS_PRELOAD) { + // Log only framework resources + if ((id >>> 24) == 0x1) { + final String name = getResourceName(id); + if (name != null) { + Log.d(TAG, "Loading framework color state list #" + Integer.toHexString(id) + + ": " + name + " at " + file); + } + } + } + + if (DEBUG_LOAD) { + Log.v(TAG, "Loading color state list for cookie " + value.assetCookie + ": " + file); + } + + final ColorStateList csl; + + Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file); if (file.endsWith(".xml")) { - Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file); try { final XmlResourceParser rp = loadXmlResourceParser( - file, id, value.assetCookie, "colorstatelist"); - csl = ColorStateList.createFromXml(this, rp); + file, id, value.assetCookie, "colorstatelist"); + csl = ColorStateList.createFromXml(this, rp, theme); rp.close(); } catch (Exception e) { Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); - NotFoundException rnf = new NotFoundException( - "File " + file + " from color state list resource ID #0x" - + Integer.toHexString(id)); + final NotFoundException rnf = new NotFoundException( + "File " + file + " from color state list resource ID #0x" + + Integer.toHexString(id)); rnf.initCause(e); throw rnf; } - Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); } else { + Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); throw new NotFoundException( "File " + file + " from drawable resource ID #0x" - + Integer.toHexString(id) + ": .xml extension required"); - } - - if (csl != null) { - if (mPreloading) { - if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId, - "color")) { - sPreloadedColorStateLists.put(key, csl); - } - } else { - synchronized (mAccessLock) { - //Log.i(TAG, "Saving cached color state list @ #" + - // Integer.toHexString(key.intValue()) - // + " in " + this + ": " + csl); - mColorStateListCache.put(key, new WeakReference<ColorStateList>(csl)); - } - } + + Integer.toHexString(id) + ": .xml extension required"); } + Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); return csl; } - private ColorStateList getCachedColorStateList(long key) { - synchronized (mAccessLock) { - WeakReference<ColorStateList> wr = mColorStateListCache.get(key); - if (wr != null) { // we have the key - ColorStateList entry = wr.get(); - if (entry != null) { - //Log.i(TAG, "Returning cached color state list @ #" + - // Integer.toHexString(((Integer)key).intValue()) - // + " in " + this + ": " + entry); - return entry; - } else { // our entry has been purged - mColorStateListCache.delete(key); - } - } - } - return null; - } - /*package*/ XmlResourceParser loadXmlResourceParser(int id, String type) throws NotFoundException { synchronized (mAccessLock) { @@ -2715,6 +2816,20 @@ public class Resources { } } + /** + * Obtains styled attributes from the theme, if available, or unstyled + * resources if the theme is null. + * + * @hide + */ + public static TypedArray obtainAttributes( + Resources res, Theme theme, AttributeSet set, int[] attrs) { + if (theme == null) { + return res.obtainAttributes(set, attrs); + } + return theme.obtainStyledAttributes(set, attrs, 0, 0); + } + private Resources() { mAssets = AssetManager.getSystem(); // NOTE: Intentionally leaving this uninitialized (all values set diff --git a/core/java/android/content/res/ResourcesKey.java b/core/java/android/content/res/ResourcesKey.java index 4ae3825..9548d49 100644 --- a/core/java/android/content/res/ResourcesKey.java +++ b/core/java/android/content/res/ResourcesKey.java @@ -16,27 +16,23 @@ package android.content.res; -import android.os.IBinder; +import java.util.Objects; /** @hide */ public final class ResourcesKey { - final String mResDir; - final float mScale; + private final String mResDir; + private final float mScale; private final int mHash; - private final IBinder mToken; public final int mDisplayId; - public final Configuration mOverrideConfiguration = new Configuration(); + public final Configuration mOverrideConfiguration; public ResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, - float scale, IBinder token) { + float scale) { mResDir = resDir; mDisplayId = displayId; - if (overrideConfiguration != null) { - mOverrideConfiguration.setTo(overrideConfiguration); - } + mOverrideConfiguration = overrideConfiguration; mScale = scale; - mToken = token; int hash = 17; hash = 31 * hash + (mResDir == null ? 0 : mResDir.hashCode()); @@ -48,7 +44,8 @@ public final class ResourcesKey { } public boolean hasOverrideConfiguration() { - return !Configuration.EMPTY.equals(mOverrideConfiguration); + return mOverrideConfiguration != null + && !Configuration.EMPTY.equals(mOverrideConfiguration); } @Override @@ -63,17 +60,9 @@ public final class ResourcesKey { } ResourcesKey peer = (ResourcesKey) obj; - if ((mResDir == null) && (peer.mResDir != null)) { - return false; - } - if ((mResDir != null) && (peer.mResDir == null)) { + if (!Objects.equals(mResDir, peer.mResDir)) { return false; } - if ((mResDir != null) && (peer.mResDir != null)) { - if (!mResDir.equals(peer.mResDir)) { - return false; - } - } if (mDisplayId != peer.mDisplayId) { return false; } diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index 02602fb..f15b6b9 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -18,6 +18,7 @@ package android.content.res; import android.annotation.Nullable; import android.graphics.drawable.Drawable; +import android.os.StrictMode; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; @@ -73,7 +74,9 @@ public class TypedArray { /*package*/ TypedValue mValue = new TypedValue(); /** - * Return the number of values in this array. + * Returns the number of values in this array. + * + * @throws RuntimeException if the TypedArray has already been recycled. */ public int length() { if (mRecycled) { @@ -85,6 +88,8 @@ public class TypedArray { /** * Return the number of indices in the array that actually have data. + * + * @throws RuntimeException if the TypedArray has already been recycled. */ public int getIndexCount() { if (mRecycled) { @@ -95,13 +100,14 @@ public class TypedArray { } /** - * Return an index in the array that has data. + * Returns an index in the array that has data. * * @param at The index you would like to returned, ranging from 0 to - * {@link #getIndexCount()}. + * {@link #getIndexCount()}. * * @return The index at the given offset, which can be used with - * {@link #getValue} and related APIs. + * {@link #getValue} and related APIs. + * @throws RuntimeException if the TypedArray has already been recycled. */ public int getIndex(int at) { if (mRecycled) { @@ -112,7 +118,9 @@ public class TypedArray { } /** - * Return the Resources object this array was loaded from. + * Returns the Resources object this array was loaded from. + * + * @throws RuntimeException if the TypedArray has already been recycled. */ public Resources getResources() { if (mRecycled) { @@ -123,12 +131,17 @@ public class TypedArray { } /** - * Retrieve the styled string value for the attribute at <var>index</var>. + * Retrieves the styled string value for the attribute at <var>index</var>. + * <p> + * If the attribute is not a string, this method will attempt to coerce + * it to a string. * * @param index Index of attribute to retrieve. * - * @return CharSequence holding string data. May be styled. Returns - * null if the attribute is not defined. + * @return CharSequence holding string data. May be styled. Returns + * {@code null} if the attribute is not defined or could not be + * coerced to a string. + * @throws RuntimeException if the TypedArray has already been recycled. */ public CharSequence getText(int index) { if (mRecycled) { @@ -144,23 +157,28 @@ public class TypedArray { return loadStringValueAt(index); } - TypedValue v = mValue; + final TypedValue v = mValue; if (getValueAt(index, v)) { - Log.w(Resources.TAG, "Converting to string: " + v); + StrictMode.noteResourceMismatch(v); return v.coerceToString(); } - Log.w(Resources.TAG, "getString of bad type: 0x" - + Integer.toHexString(type)); - return null; + + // We already checked for TYPE_NULL. This should never happen. + throw new RuntimeException("getText of bad type: 0x" + Integer.toHexString(type)); } /** - * Retrieve the string value for the attribute at <var>index</var>. + * Retrieves the string value for the attribute at <var>index</var>. + * <p> + * If the attribute is not a string, this method will attempt to coerce + * it to a string. * * @param index Index of attribute to retrieve. * - * @return String holding string data. Any styling information is - * removed. Returns null if the attribute is not defined. + * @return String holding string data. Any styling information is removed. + * Returns {@code null} if the attribute is not defined or could + * not be coerced to a string. + * @throws RuntimeException if the TypedArray has already been recycled. */ public String getString(int index) { if (mRecycled) { @@ -176,19 +194,19 @@ public class TypedArray { return loadStringValueAt(index).toString(); } - TypedValue v = mValue; + final TypedValue v = mValue; if (getValueAt(index, v)) { - Log.w(Resources.TAG, "Converting to string: " + v); - CharSequence cs = v.coerceToString(); + StrictMode.noteResourceMismatch(v); + final CharSequence cs = v.coerceToString(); return cs != null ? cs.toString() : null; } - Log.w(Resources.TAG, "getString of bad type: 0x" - + Integer.toHexString(type)); - return null; + + // We already checked for TYPE_NULL. This should never happen. + throw new RuntimeException("getString of bad type: 0x" + Integer.toHexString(type)); } /** - * Retrieve the string value for the attribute at <var>index</var>, but + * Retrieves the string value for the attribute at <var>index</var>, but * only if that string comes from an immediate value in an XML file. That * is, this does not allow references to string resources, string * attributes, or conversions from other types. As such, this method @@ -197,9 +215,10 @@ public class TypedArray { * * @param index Index of attribute to retrieve. * - * @return String holding string data. Any styling information is - * removed. Returns null if the attribute is not defined or is not - * an immediate string value. + * @return String holding string data. Any styling information is removed. + * Returns {@code null} if the attribute is not defined or is not + * an immediate string value. + * @throws RuntimeException if the TypedArray has already been recycled. */ public String getNonResourceString(int index) { if (mRecycled) { @@ -220,16 +239,17 @@ public class TypedArray { } /** - * @hide - * Retrieve the string value for the attribute at <var>index</var> that is + * Retrieves the string value for the attribute at <var>index</var> that is * not allowed to change with the given configurations. * * @param index Index of attribute to retrieve. * @param allowedChangingConfigs Bit mask of configurations from - * {@link Configuration}.NATIVE_CONFIG_* that are allowed to change. + * {@link Configuration}.NATIVE_CONFIG_* that are allowed to change. * - * @return String holding string data. Any styling information is - * removed. Returns null if the attribute is not defined. + * @return String holding string data. Any styling information is removed. + * Returns {@code null} if the attribute is not defined. + * @throws RuntimeException if the TypedArray has already been recycled. + * @hide */ public String getNonConfigurationString(int index, int allowedChangingConfigs) { if (mRecycled) { @@ -248,24 +268,33 @@ public class TypedArray { return loadStringValueAt(index).toString(); } - TypedValue v = mValue; + final TypedValue v = mValue; if (getValueAt(index, v)) { - Log.w(Resources.TAG, "Converting to string: " + v); - CharSequence cs = v.coerceToString(); + StrictMode.noteResourceMismatch(v); + final CharSequence cs = v.coerceToString(); return cs != null ? cs.toString() : null; } - Log.w(Resources.TAG, "getString of bad type: 0x" - + Integer.toHexString(type)); - return null; + + // We already checked for TYPE_NULL. This should never happen. + throw new RuntimeException("getNonConfigurationString of bad type: 0x" + + Integer.toHexString(type)); } /** * Retrieve the boolean value for the attribute at <var>index</var>. + * <p> + * If the attribute is an integer value, this method will return whether + * it is equal to zero. If the attribute is not a boolean or integer value, + * this method will attempt to coerce it to an integer using + * {@link Integer#decode(String)} and return whether it is equal to zero. * * @param index Index of attribute to retrieve. - * @param defValue Value to return if the attribute is not defined. + * @param defValue Value to return if the attribute is not defined or + * cannot be coerced to an integer. * - * @return Attribute boolean value, or defValue if not defined. + * @return Boolean value of the attribute, or defValue if the attribute was + * not defined or could not be coerced to an integer. + * @throws RuntimeException if the TypedArray has already been recycled. */ public boolean getBoolean(int index, boolean defValue) { if (mRecycled) { @@ -278,28 +307,33 @@ public class TypedArray { if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT - && type <= TypedValue.TYPE_LAST_INT) { + && type <= TypedValue.TYPE_LAST_INT) { return data[index+AssetManager.STYLE_DATA] != 0; } - TypedValue v = mValue; + final TypedValue v = mValue; if (getValueAt(index, v)) { - Log.w(Resources.TAG, "Converting to boolean: " + v); - return XmlUtils.convertValueToBoolean( - v.coerceToString(), defValue); + StrictMode.noteResourceMismatch(v); + return XmlUtils.convertValueToBoolean(v.coerceToString(), defValue); } - Log.w(Resources.TAG, "getBoolean of bad type: 0x" - + Integer.toHexString(type)); - return defValue; + + // We already checked for TYPE_NULL. This should never happen. + throw new RuntimeException("getBoolean of bad type: 0x" + Integer.toHexString(type)); } /** * Retrieve the integer value for the attribute at <var>index</var>. + * <p> + * If the attribute is not an integer, this method will attempt to coerce + * it to an integer using {@link Integer#decode(String)}. * * @param index Index of attribute to retrieve. - * @param defValue Value to return if the attribute is not defined. + * @param defValue Value to return if the attribute is not defined or + * cannot be coerced to an integer. * - * @return Attribute int value, or defValue if not defined. + * @return Integer value of the attribute, or defValue if the attribute was + * not defined or could not be coerced to an integer. + * @throws RuntimeException if the TypedArray has already been recycled. */ public int getInt(int index, int defValue) { if (mRecycled) { @@ -312,27 +346,31 @@ public class TypedArray { if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT - && type <= TypedValue.TYPE_LAST_INT) { + && type <= TypedValue.TYPE_LAST_INT) { return data[index+AssetManager.STYLE_DATA]; } - TypedValue v = mValue; + final TypedValue v = mValue; if (getValueAt(index, v)) { - Log.w(Resources.TAG, "Converting to int: " + v); - return XmlUtils.convertValueToInt( - v.coerceToString(), defValue); + StrictMode.noteResourceMismatch(v); + return XmlUtils.convertValueToInt(v.coerceToString(), defValue); } - Log.w(Resources.TAG, "getInt of bad type: 0x" - + Integer.toHexString(type)); - return defValue; + + // We already checked for TYPE_NULL. This should never happen. + throw new RuntimeException("getInt of bad type: 0x" + Integer.toHexString(type)); } /** * Retrieve the float value for the attribute at <var>index</var>. + * <p> + * If the attribute is not a float or an integer, this method will attempt + * to coerce it to a float using {@link Float#parseFloat(String)}. * * @param index Index of attribute to retrieve. * - * @return Attribute float value, or defValue if not defined.. + * @return Attribute float value, or defValue if the attribute was + * not defined or could not be coerced to a float. + * @throws RuntimeException if the TypedArray has already been recycled. */ public float getFloat(int index, float defValue) { if (mRecycled) { @@ -347,21 +385,21 @@ public class TypedArray { } else if (type == TypedValue.TYPE_FLOAT) { return Float.intBitsToFloat(data[index+AssetManager.STYLE_DATA]); } else if (type >= TypedValue.TYPE_FIRST_INT - && type <= TypedValue.TYPE_LAST_INT) { + && type <= TypedValue.TYPE_LAST_INT) { return data[index+AssetManager.STYLE_DATA]; } - TypedValue v = mValue; + final TypedValue v = mValue; if (getValueAt(index, v)) { - Log.w(Resources.TAG, "Converting to float: " + v); - CharSequence str = v.coerceToString(); + final CharSequence str = v.coerceToString(); if (str != null) { + StrictMode.noteResourceMismatch(v); return Float.parseFloat(str.toString()); } } - Log.w(Resources.TAG, "getFloat of bad type: 0x" - + Integer.toHexString(type)); - return defValue; + + // We already checked for TYPE_NULL. This should never happen. + throw new RuntimeException("getFloat of bad type: 0x" + Integer.toHexString(type)); } /** @@ -369,12 +407,18 @@ public class TypedArray { * the attribute references a color resource holding a complex * {@link android.content.res.ColorStateList}, then the default color from * the set is returned. + * <p> + * This method will throw an exception if the attribute is defined but is + * not an integer color or color state list. * * @param index Index of attribute to retrieve. * @param defValue Value to return if the attribute is not defined or * not a resource. * * @return Attribute color value, or defValue if not defined. + * @throws RuntimeException if the TypedArray has already been recycled. + * @throws UnsupportedOperationException if the attribute is defined but is + * not an integer color or color state list. */ public int getColor(int index, int defValue) { if (mRecycled) { @@ -387,18 +431,19 @@ public class TypedArray { if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT - && type <= TypedValue.TYPE_LAST_INT) { + && type <= TypedValue.TYPE_LAST_INT) { return data[index+AssetManager.STYLE_DATA]; } else if (type == TypedValue.TYPE_STRING) { final TypedValue value = mValue; if (getValueAt(index, value)) { - ColorStateList csl = mResources.loadColorStateList( - value, value.resourceId); + final ColorStateList csl = mResources.loadColorStateList( + value, value.resourceId, mTheme); return csl.getDefaultColor(); } return defValue; } else if (type == TypedValue.TYPE_ATTRIBUTE) { - throw new RuntimeException("Failed to resolve attribute at index " + index); + throw new UnsupportedOperationException( + "Failed to resolve attribute at index " + index); } throw new UnsupportedOperationException("Can't convert to color: type=0x" @@ -408,12 +453,22 @@ public class TypedArray { /** * Retrieve the ColorStateList for the attribute at <var>index</var>. * The value may be either a single solid color or a reference to - * a color or complex {@link android.content.res.ColorStateList} description. + * a color or complex {@link android.content.res.ColorStateList} + * description. + * <p> + * This method will return {@code null} if the attribute is not defined or + * is not an integer color or color state list. * * @param index Index of attribute to retrieve. * - * @return ColorStateList for the attribute, or null if not defined. + * @return ColorStateList for the attribute, or {@code null} if not + * defined. + * @throws RuntimeException if the attribute if the TypedArray has already + * been recycled. + * @throws UnsupportedOperationException if the attribute is defined but is + * not an integer color or color state list. */ + @Nullable public ColorStateList getColorStateList(int index) { if (mRecycled) { throw new RuntimeException("Cannot make calls to a recycled instance!"); @@ -422,21 +477,28 @@ public class TypedArray { final TypedValue value = mValue; if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { - throw new RuntimeException("Failed to resolve attribute at index " + index); + throw new UnsupportedOperationException( + "Failed to resolve attribute at index " + index); } - return mResources.loadColorStateList(value, value.resourceId); + return mResources.loadColorStateList(value, value.resourceId, mTheme); } return null; } /** * Retrieve the integer value for the attribute at <var>index</var>. + * <p> + * Unlike {@link #getInt(int, int)}, this method will throw an exception if + * the attribute is defined but is not an integer. * * @param index Index of attribute to retrieve. * @param defValue Value to return if the attribute is not defined or * not a resource. * * @return Attribute integer value, or defValue if not defined. + * @throws RuntimeException if the TypedArray has already been recycled. + * @throws UnsupportedOperationException if the attribute is defined but is + * not an integer. */ public int getInteger(int index, int defValue) { if (mRecycled) { @@ -449,10 +511,11 @@ public class TypedArray { if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT - && type <= TypedValue.TYPE_LAST_INT) { + && type <= TypedValue.TYPE_LAST_INT) { return data[index+AssetManager.STYLE_DATA]; } else if (type == TypedValue.TYPE_ATTRIBUTE) { - throw new RuntimeException("Failed to resolve attribute at index " + index); + throw new UnsupportedOperationException( + "Failed to resolve attribute at index " + index); } throw new UnsupportedOperationException("Can't convert to integer: type=0x" @@ -460,17 +523,23 @@ public class TypedArray { } /** - * Retrieve a dimensional unit attribute at <var>index</var>. Unit + * Retrieve a dimensional unit attribute at <var>index</var>. Unit * conversions are based on the current {@link DisplayMetrics} * associated with the resources this {@link TypedArray} object * came from. + * <p> + * This method will throw an exception if the attribute is defined but is + * not a dimension. * * @param index Index of attribute to retrieve. * @param defValue Value to return if the attribute is not defined or * not a resource. * * @return Attribute dimension value multiplied by the appropriate - * metric, or defValue if not defined. + * metric, or defValue if not defined. + * @throws RuntimeException if the TypedArray has already been recycled. + * @throws UnsupportedOperationException if the attribute is defined but is + * not an integer. * * @see #getDimensionPixelOffset * @see #getDimensionPixelSize @@ -487,9 +556,10 @@ public class TypedArray { return defValue; } else if (type == TypedValue.TYPE_DIMENSION) { return TypedValue.complexToDimension( - data[index+AssetManager.STYLE_DATA], mMetrics); + data[index + AssetManager.STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { - throw new RuntimeException("Failed to resolve attribute at index " + index); + throw new UnsupportedOperationException( + "Failed to resolve attribute at index " + index); } throw new UnsupportedOperationException("Can't convert to dimension: type=0x" @@ -502,13 +572,19 @@ public class TypedArray { * {@link #getDimension}, except the returned value is converted to * integer pixels for you. An offset conversion involves simply * truncating the base value to an integer. + * <p> + * This method will throw an exception if the attribute is defined but is + * not a dimension. * * @param index Index of attribute to retrieve. * @param defValue Value to return if the attribute is not defined or * not a resource. * * @return Attribute dimension value multiplied by the appropriate - * metric and truncated to integer pixels, or defValue if not defined. + * metric and truncated to integer pixels, or defValue if not defined. + * @throws RuntimeException if the TypedArray has already been recycled. + * @throws UnsupportedOperationException if the attribute is defined but is + * not an integer. * * @see #getDimension * @see #getDimensionPixelSize @@ -525,9 +601,10 @@ public class TypedArray { return defValue; } else if (type == TypedValue.TYPE_DIMENSION) { return TypedValue.complexToDimensionPixelOffset( - data[index+AssetManager.STYLE_DATA], mMetrics); + data[index + AssetManager.STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { - throw new RuntimeException("Failed to resolve attribute at index " + index); + throw new UnsupportedOperationException( + "Failed to resolve attribute at index " + index); } throw new UnsupportedOperationException("Can't convert to dimension: type=0x" @@ -541,13 +618,19 @@ public class TypedArray { * integer pixels for use as a size. A size conversion involves * rounding the base value, and ensuring that a non-zero base value * is at least one pixel in size. + * <p> + * This method will throw an exception if the attribute is defined but is + * not a dimension. * * @param index Index of attribute to retrieve. * @param defValue Value to return if the attribute is not defined or * not a resource. * * @return Attribute dimension value multiplied by the appropriate - * metric and truncated to integer pixels, or defValue if not defined. + * metric and truncated to integer pixels, or defValue if not defined. + * @throws RuntimeException if the TypedArray has already been recycled. + * @throws UnsupportedOperationException if the attribute is defined but is + * not a dimension. * * @see #getDimension * @see #getDimensionPixelOffset @@ -566,7 +649,8 @@ public class TypedArray { return TypedValue.complexToDimensionPixelSize( data[index+AssetManager.STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { - throw new RuntimeException("Failed to resolve attribute at index " + index); + throw new UnsupportedOperationException( + "Failed to resolve attribute at index " + index); } throw new UnsupportedOperationException("Can't convert to dimension: type=0x" @@ -578,12 +662,18 @@ public class TypedArray { * {@link android.view.ViewGroup}'s layout_width and layout_height * attributes. This is only here for performance reasons; applications * should use {@link #getDimensionPixelSize}. + * <p> + * This method will throw an exception if the attribute is defined but is + * not a dimension or integer (enum). * * @param index Index of the attribute to retrieve. * @param name Textual name of attribute for error reporting. * * @return Attribute dimension value multiplied by the appropriate - * metric and truncated to integer pixels. + * metric and truncated to integer pixels. + * @throws RuntimeException if the TypedArray has already been recycled. + * @throws UnsupportedOperationException if the attribute is defined but is + * not a dimension or integer (enum). */ public int getLayoutDimension(int index, String name) { if (mRecycled) { @@ -600,10 +690,11 @@ public class TypedArray { return TypedValue.complexToDimensionPixelSize( data[index+AssetManager.STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { - throw new RuntimeException("Failed to resolve attribute at index " + index); + throw new UnsupportedOperationException( + "Failed to resolve attribute at index " + index); } - throw new RuntimeException(getPositionDescription() + throw new UnsupportedOperationException(getPositionDescription() + ": You must supply a " + name + " attribute."); } @@ -615,10 +706,11 @@ public class TypedArray { * * @param index Index of the attribute to retrieve. * @param defValue The default value to return if this attribute is not - * default or contains the wrong type of data. + * default or contains the wrong type of data. * * @return Attribute dimension value multiplied by the appropriate - * metric and truncated to integer pixels. + * metric and truncated to integer pixels. + * @throws RuntimeException if the TypedArray has already been recycled. */ public int getLayoutDimension(int index, int defValue) { if (mRecycled) { @@ -633,14 +725,14 @@ public class TypedArray { return data[index+AssetManager.STYLE_DATA]; } else if (type == TypedValue.TYPE_DIMENSION) { return TypedValue.complexToDimensionPixelSize( - data[index+AssetManager.STYLE_DATA], mMetrics); + data[index + AssetManager.STYLE_DATA], mMetrics); } return defValue; } /** - * Retrieve a fractional unit attribute at <var>index</var>. + * Retrieves a fractional unit attribute at <var>index</var>. * * @param index Index of attribute to retrieve. * @param base The base value of this fraction. In other words, a @@ -652,7 +744,10 @@ public class TypedArray { * not a resource. * * @return Attribute fractional value multiplied by the appropriate - * base value, or defValue if not defined. + * base value, or defValue if not defined. + * @throws RuntimeException if the TypedArray has already been recycled. + * @throws UnsupportedOperationException if the attribute is defined but is + * not a fraction. */ public float getFraction(int index, int base, int pbase, float defValue) { if (mRecycled) { @@ -668,7 +763,8 @@ public class TypedArray { return TypedValue.complexToFraction( data[index+AssetManager.STYLE_DATA], base, pbase); } else if (type == TypedValue.TYPE_ATTRIBUTE) { - throw new RuntimeException("Failed to resolve attribute at index " + index); + throw new UnsupportedOperationException( + "Failed to resolve attribute at index " + index); } throw new UnsupportedOperationException("Can't convert to fraction: type=0x" @@ -676,7 +772,7 @@ public class TypedArray { } /** - * Retrieve the resource identifier for the attribute at + * Retrieves the resource identifier for the attribute at * <var>index</var>. Note that attribute resource as resolved when * the overall {@link TypedArray} object is retrieved. As a * result, this function will return the resource identifier of the @@ -688,6 +784,7 @@ public class TypedArray { * not a resource. * * @return Attribute resource identifier, or defValue if not defined. + * @throws RuntimeException if the TypedArray has already been recycled. */ public int getResourceId(int index, int defValue) { if (mRecycled) { @@ -706,13 +803,15 @@ public class TypedArray { } /** - * Retrieve the theme attribute resource identifier for the attribute at + * Retrieves the theme attribute resource identifier for the attribute at * <var>index</var>. * * @param index Index of attribute to retrieve. * @param defValue Value to return if the attribute is not defined or not a - * resource. + * resource. + * * @return Theme attribute resource identifier, or defValue if not defined. + * @throws RuntimeException if the TypedArray has already been recycled. * @hide */ public int getThemeAttributeId(int index, int defValue) { @@ -730,10 +829,16 @@ public class TypedArray { /** * Retrieve the Drawable for the attribute at <var>index</var>. + * <p> + * This method will throw an exception if the attribute is defined but is + * not a color or drawable resource. * * @param index Index of attribute to retrieve. * - * @return Drawable for the attribute, or null if not defined. + * @return Drawable for the attribute, or {@code null} if not defined. + * @throws RuntimeException if the TypedArray has already been recycled. + * @throws UnsupportedOperationException if the attribute is defined but is + * not a color or drawable resource. */ @Nullable public Drawable getDrawable(int index) { @@ -744,7 +849,8 @@ public class TypedArray { final TypedValue value = mValue; if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { - throw new RuntimeException("Failed to resolve attribute at index " + index); + throw new UnsupportedOperationException( + "Failed to resolve attribute at index " + index); } return mResources.loadDrawable(value, value.resourceId, mTheme); } @@ -756,10 +862,15 @@ public class TypedArray { * This gets the resource ID of the selected attribute, and uses * {@link Resources#getTextArray Resources.getTextArray} of the owning * Resources object to retrieve its String[]. + * <p> + * This method will throw an exception if the attribute is defined but is + * not a text array resource. * * @param index Index of attribute to retrieve. * - * @return CharSequence[] for the attribute, or null if not defined. + * @return CharSequence[] for the attribute, or {@code null} if not + * defined. + * @throws RuntimeException if the TypedArray has already been recycled. */ public CharSequence[] getTextArray(int index) { if (mRecycled) { @@ -780,7 +891,8 @@ public class TypedArray { * @param outValue TypedValue object in which to place the attribute's * data. * - * @return Returns true if the value was retrieved, else false. + * @return {@code true} if the value was retrieved, false otherwise. + * @throws RuntimeException if the TypedArray has already been recycled. */ public boolean getValue(int index, TypedValue outValue) { if (mRecycled) { @@ -794,7 +906,9 @@ public class TypedArray { * Returns the type of attribute at the specified index. * * @param index Index of attribute whose type to retrieve. + * * @return Attribute type. + * @throws RuntimeException if the TypedArray has already been recycled. */ public int getType(int index) { if (mRecycled) { @@ -814,6 +928,7 @@ public class TypedArray { * @param index Index of attribute to retrieve. * * @return True if the attribute has a value, false otherwise. + * @throws RuntimeException if the TypedArray has already been recycled. */ public boolean hasValue(int index) { if (mRecycled) { @@ -834,6 +949,7 @@ public class TypedArray { * @param index Index of attribute to retrieve. * * @return True if the attribute has a value or is empty, false otherwise. + * @throws RuntimeException if the TypedArray has already been recycled. */ public boolean hasValueOrEmpty(int index) { if (mRecycled) { @@ -857,6 +973,7 @@ public class TypedArray { * @return Returns a TypedValue object if the attribute is defined, * containing its data; otherwise returns null. (You will not * receive a TypedValue whose type is TYPE_NULL.) + * @throws RuntimeException if the TypedArray has already been recycled. */ public TypedValue peekValue(int index) { if (mRecycled) { @@ -872,6 +989,9 @@ public class TypedArray { /** * Returns a message about the parser state suitable for printing error messages. + * + * @return Human-readable description of current parser state. + * @throws RuntimeException if the TypedArray has already been recycled. */ public String getPositionDescription() { if (mRecycled) { @@ -882,8 +1002,10 @@ public class TypedArray { } /** - * Recycle the TypedArray, to be re-used by a later caller. After calling + * Recycles the TypedArray, to be re-used by a later caller. After calling * this function you must not ever touch the typed array again. + * + * @throws RuntimeException if the TypedArray has already been recycled. */ public void recycle() { if (mRecycled) { @@ -908,9 +1030,19 @@ public class TypedArray { * @return an array of length {@link #getIndexCount()} populated with theme * attributes, or null if there are no theme attributes in the typed * array + * @throws RuntimeException if the TypedArray has already been recycled. * @hide */ + @Nullable public int[] extractThemeAttrs() { + return extractThemeAttrs(null); + } + + /** + * @hide + */ + @Nullable + public int[] extractThemeAttrs(@Nullable int[] scrap) { if (mRecycled) { throw new RuntimeException("Cannot make calls to a recycled instance!"); } @@ -922,6 +1054,7 @@ public class TypedArray { for (int i = 0; i < N; i++) { final int index = i * AssetManager.STYLE_NUM_ENTRIES; if (data[index + AssetManager.STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) { + // Not an attribute, ignore. continue; } @@ -930,13 +1063,20 @@ public class TypedArray { final int attr = data[index + AssetManager.STYLE_DATA]; if (attr == 0) { - // This attribute is useless! + // Useless data, ignore. continue; } + // Ensure we have a usable attribute array. if (attrs == null) { - attrs = new int[N]; + if (scrap != null && scrap.length == N) { + attrs = scrap; + Arrays.fill(attrs, 0); + } else { + attrs = new int[N]; + } } + attrs[i] = attr; } @@ -949,9 +1089,14 @@ public class TypedArray { * * @return Returns a mask of the changing configuration parameters, as * defined by {@link android.content.pm.ActivityInfo}. + * @throws RuntimeException if the TypedArray has already been recycled. * @see android.content.pm.ActivityInfo */ public int getChangingConfigurations() { + if (mRecycled) { + throw new RuntimeException("Cannot make calls to a recycled instance!"); + } + int changingConfig = 0; final int[] data = mData; diff --git a/core/java/android/hardware/camera2/CameraAccessException.java b/core/java/android/hardware/camera2/CameraAccessException.java index 91ef6ca..84e9912 100644 --- a/core/java/android/hardware/camera2/CameraAccessException.java +++ b/core/java/android/hardware/camera2/CameraAccessException.java @@ -28,16 +28,14 @@ import android.util.AndroidException; */ public class CameraAccessException extends AndroidException { /** - * The camera device is in use already - * @hide + * The camera device is in use already. */ public static final int CAMERA_IN_USE = 4; /** - * The system-wide limit for number of open cameras has been reached, - * and more camera devices cannot be opened until previous instances are - * closed. - * @hide + * The system-wide limit for number of open cameras or camera resources has + * been reached, and more camera devices cannot be opened or torch mode + * cannot be turned on until previous instances are closed. */ public static final int MAX_CAMERAS_IN_USE = 5; diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 7cf8fb0..bb45b91 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -424,6 +424,17 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<Rational>("android.control.aeCompensationStep", Rational.class); /** + * <p>Whether the camera device supports {@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock}</p> + * <p>LIMITED or FULL devices will always list <code>true</code></p> + * <p>This key is available on all devices.</p> + * + * @see CaptureRequest#CONTROL_AE_LOCK + */ + @PublicKey + public static final Key<Boolean> CONTROL_AE_LOCK_AVAILABLE = + new Key<Boolean>("android.control.aeLockAvailable", boolean.class); + + /** * <p>List of auto-focus (AF) modes for {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode} that are * supported by this camera device.</p> * <p>Not all the auto-focus modes may be supported by a @@ -471,6 +482,22 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<int[]>("android.control.availableEffects", int[].class); /** + * <p>List of control modes for {@link CaptureRequest#CONTROL_MODE android.control.mode} that are supported by this camera + * device.</p> + * <p>This list contains control modes that can be set for the camera device. + * LEGACY mode devices will always support AUTO mode. LIMITED and FULL + * devices will always support OFF, AUTO modes.</p> + * <p><b>Range of valid values:</b><br> + * Any value listed in {@link CaptureRequest#CONTROL_MODE android.control.mode}</p> + * <p>This key is available on all devices.</p> + * + * @see CaptureRequest#CONTROL_MODE + */ + @PublicKey + public static final Key<int[]> CONTROL_AVAILABLE_MODES = + new Key<int[]>("android.control.availableModes", int[].class); + + /** * <p>List of scene modes for {@link CaptureRequest#CONTROL_SCENE_MODE android.control.sceneMode} that are supported by this camera * device.</p> * <p>This list contains scene modes that can be set for the camera device. @@ -532,6 +559,17 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<int[]>("android.control.awbAvailableModes", int[].class); /** + * <p>Whether the camera device supports {@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock}</p> + * <p>LIMITED or FULL devices will always list <code>true</code></p> + * <p>This key is available on all devices.</p> + * + * @see CaptureRequest#CONTROL_AWB_LOCK + */ + @PublicKey + public static final Key<Boolean> CONTROL_AWB_LOCK_AVAILABLE = + new Key<Boolean>("android.control.awbLockAvailable", boolean.class); + + /** * <p>List of the maximum number of regions that can be used for metering in * auto-exposure (AE), auto-white balance (AWB), and auto-focus (AF); * this corresponds to the the maximum number of elements in @@ -889,10 +927,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <ul> * <li>{@link #LENS_FACING_FRONT FRONT}</li> * <li>{@link #LENS_FACING_BACK BACK}</li> + * <li>{@link #LENS_FACING_EXTERNAL EXTERNAL}</li> * </ul></p> * <p>This key is available on all devices.</p> * @see #LENS_FACING_FRONT * @see #LENS_FACING_BACK + * @see #LENS_FACING_EXTERNAL */ @PublicKey public static final Key<Integer> LENS_FACING = @@ -1069,8 +1109,11 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * android.scaler.availableInputOutputFormatsMap. When using an * input stream, there must be at least one output stream * configured to to receive the reprocessed images.</p> + * <p>When an input stream and some output streams are used in a reprocessing request, + * only the input buffer will be used to produce these output stream buffers, and a + * new sensor image will not be captured.</p> * <p>For example, for Zero Shutter Lag (ZSL) still capture use case, the input - * stream image format will be RAW_OPAQUE, the associated output stream image format + * stream image format will be OPAQUE, the associated output stream image format * should be JPEG.</p> * <p><b>Range of valid values:</b><br></p> * <p>0 or 1.</p> @@ -1080,8 +1123,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> * * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL - * @hide */ + @PublicKey public static final Key<Integer> REQUEST_MAX_NUM_INPUT_STREAMS = new Key<Integer>("android.request.maxNumInputStreams", int.class); @@ -1157,8 +1200,10 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR MANUAL_SENSOR}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING MANUAL_POST_PROCESSING}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_RAW RAW}</li> + * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_OPAQUE_REPROCESSING OPAQUE_REPROCESSING}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS READ_SENSOR_SETTINGS}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE BURST_CAPTURE}</li> + * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING YUV_REPROCESSING}</li> * </ul></p> * <p>This key is available on all devices.</p> * @@ -1167,8 +1212,10 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * @see #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR * @see #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING * @see #REQUEST_AVAILABLE_CAPABILITIES_RAW + * @see #REQUEST_AVAILABLE_CAPABILITIES_OPAQUE_REPROCESSING * @see #REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS * @see #REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE + * @see #REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING */ @PublicKey public static final Key<int[]> REQUEST_AVAILABLE_CAPABILITIES = @@ -1345,10 +1392,10 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>The mapping of image formats that are supported by this * camera device for input streams, to their corresponding output formats.</p> * <p>All camera devices with at least 1 - * android.request.maxNumInputStreams will have at least one + * {@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} will have at least one * available input format.</p> * <p>The camera device will support the following map of formats, - * if its dependent capability is supported:</p> + * if its dependent capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}) is supported:</p> * <table> * <thead> * <tr> @@ -1359,45 +1406,42 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * </thead> * <tbody> * <tr> - * <td align="left">RAW_OPAQUE</td> + * <td align="left">OPAQUE</td> * <td align="left">JPEG</td> - * <td align="left">ZSL</td> + * <td align="left">OPAQUE_REPROCESSING</td> * </tr> * <tr> - * <td align="left">RAW_OPAQUE</td> + * <td align="left">OPAQUE</td> * <td align="left">YUV_420_888</td> - * <td align="left">ZSL</td> - * </tr> - * <tr> - * <td align="left">RAW_OPAQUE</td> - * <td align="left">RAW16</td> - * <td align="left">RAW</td> + * <td align="left">OPAQUE_REPROCESSING</td> * </tr> * <tr> - * <td align="left">RAW16</td> * <td align="left">YUV_420_888</td> - * <td align="left">RAW</td> + * <td align="left">JPEG</td> + * <td align="left">YUV_REPROCESSING</td> * </tr> * <tr> - * <td align="left">RAW16</td> - * <td align="left">JPEG</td> - * <td align="left">RAW</td> + * <td align="left">YUV_420_888</td> + * <td align="left">YUV_420_888</td> + * <td align="left">YUV_REPROCESSING</td> * </tr> * </tbody> * </table> - * <p>For ZSL-capable camera devices, using the RAW_OPAQUE format + * <p>OPAQUE refers to a device-internal format that is not directly application-visible. + * An OPAQUE input or output surface can be acquired by + * OpaqueImageRingBufferQueue#getInputSurface() or + * OpaqueImageRingBufferQueue#getOutputSurface(). + * For a OPAQUE_REPROCESSING-capable camera device, using the OPAQUE format * as either input or output will never hurt maximum frame rate (i.e. - * StreamConfigurationMap#getOutputStallDuration(int,Size) - * for a <code>format =</code> RAW_OPAQUE is always 0).</p> + * StreamConfigurationMap#getOutputStallDuration(klass,Size) is always 0), + * where klass is android.media.OpaqueImageRingBufferQueue.class.</p> * <p>Attempting to configure an input stream with output streams not * listed as available in this map is not valid.</p> * <p>TODO: typedef to ReprocessFormatMap</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> - * <p><b>Full capability</b> - - * Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the - * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> * - * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + * @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS * @hide */ public static final Key<int[]> SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP = diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index b6bb33b..8af3c15 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -109,8 +109,11 @@ public final class CameraManager { * of the state of individual CameraManager instances.</p> * * @param callback the new callback to send camera availability notices to - * @param handler The handler on which the callback should be invoked, or - * {@code null} to use the current thread's {@link android.os.Looper looper}. + * @param handler The handler on which the callback should be invoked, or {@code null} to use + * the current thread's {@link android.os.Looper looper}. + * + * @throws IllegalArgumentException if the handler is {@code null} but the current thread has + * no looper. */ public void registerAvailabilityCallback(AvailabilityCallback callback, Handler handler) { if (handler == null) { @@ -138,6 +141,42 @@ public final class CameraManager { } /** + * Register a callback to be notified about torch mode status. + * + * <p>Registering the same callback again will replace the handler with the + * new one provided.</p> + * + * <p>The first time a callback is registered, it is immediately called + * with the torch mode status of all currently known camera devices.</p> + * + * <p>Since this callback will be registered with the camera service, remember to unregister it + * once it is no longer needed; otherwise the callback will continue to receive events + * indefinitely and it may prevent other resources from being released. Specifically, the + * callbacks will be invoked independently of the general activity lifecycle and independently + * of the state of individual CameraManager instances.</p> + * + * @param callback The new callback to send torch mode status to + * @param handler The handler on which the callback should be invoked, or {@code null} to use + * the current thread's {@link android.os.Looper looper}. + * + * @throws IllegalArgumentException if the handler is {@code null} but the current thread has + * no looper. + */ + public void registerTorchCallback(TorchCallback callback, Handler handler) { + } + + /** + * Remove a previously-added callback; the callback will no longer receive torch mode status + * callbacks. + * + * <p>Removing a callback that isn't registered has no effect.</p> + * + * @param callback The callback to remove from the notification list + */ + public void unregisterTorchCallback(TorchCallback callback) { + } + + /** * <p>Query the capabilities of a camera device. These capabilities are * immutable for a given camera.</p> * @@ -384,6 +423,47 @@ public final class CameraManager { } /** + * Set the flash unit's torch mode of the camera of the given ID without opening the camera + * device. + * + * <p>Use {@link #getCameraIdList} to get the list of available camera devices and use + * {@link #getCameraCharacteristics} to check whether the camera device has a flash unit. + * Note that even if a camera device has a flash unit, turning on the torch mode may fail + * if the camera device or other camera resources needed to turn on the torch mode are in use. + * </p> + * + * <p> If {@link #setTorchMode} is called to turn on or off the torch mode successfully, + * {@link CameraManager.TorchCallback#onTorchModeChanged} will be invoked. + * However, even if turning on the torch mode is successful, the application does not have the + * exclusive ownership of the flash unit or the camera device. The torch mode will be turned + * off and becomes unavailable when the camera device that the flash unit belongs to becomes + * unavailable ({@link CameraManager.TorchCallback#onTorchModeAvailable} will be + * invoked) or when other camera resources to keep the torch on become unavailable ( + * {@link CameraManager.TorchCallback#onTorchModeUnavailable} will be invoked). Also, + * other applications are free to call {@link #setTorchMode} to turn off the torch mode ( + * {@link CameraManager.TorchCallback#onTorchModeChanged} will be invoked). + * + * @param cameraId + * The unique identifier of the camera device that the flash unit belongs to. + * @param enabled + * The desired state of the torch mode for the target camera device. Set to + * {@code true} to turn on the torch mode. Set to {@code false} to turn off the + * torch mode. + * + * @throws CameraAccessException if it failed to access the flash unit. + * {@link CameraAccessException#CAMERA_IN_USE} will be thrown if the camera device + * is in use. {@link CameraAccessException#MAX_CAMERAS_IN_USE} will be thrown if + * other camera resources needed to turn on the torch mode are in use. + * + * @throws IllegalArgumentException if cameraId was null, cameraId doesn't match any currently + * or previously available camera device, or the camera device doesn't have a + * flash unit. + */ + public void setTorchMode(String cameraId, boolean enabled) throws CameraAccessException { + + } + + /** * A callback for camera devices becoming available or * unavailable to open. * @@ -428,6 +508,68 @@ public final class CameraManager { } /** + * A callback for camera flash torch modes becoming available, unavailable, enabled, or + * disabled. + * + * <p>The torch mode becomes available when the camera device it belongs to is no longer in use + * and other camera resources it needs are no longer busy. It becomes unavailable when the + * camera device it belongs to becomes unavailable or other camera resouces it needs become + * busy due to other higher priority camera activities. The torch mode changes when an + * application calls {@link #setTorchMode} successfully. + * + * <p>Extend this callback and pass an instance of the subclass to + * {@link CameraManager#registerTorchCallback} to be notified of such status changes. + * </p> + * + * @see registerTorchCallback + */ + public static abstract class TorchCallback { + /** + * The torch mode of a camera has become available to use. + * + * <p>The default implementation of this method does nothing.</p> + * + * @param cameraId The unique identifier of the camera whose torch mode has become + * available. + */ + public void onTorchModeAvailable(String cameraId) { + // default empty implementation + } + + /** + * A previously-available torch mode of a camera has become unavailable. + * + * <p>If torch mode was previously turned on by calling {@link #setTorchMode}, it will be + * turned off before {@link CameraManager.TorchCallback#onTorchModeUnavailable} is + * invoked. {@link #setTorchMode} will fail until the flash unit becomes available again. + * </p> + * + * <p>The default implementation of this method does nothing.</p> + * + * @param cameraId The unique identifier of the camera whose torch mode has become + * unavailable. + */ + public void onTorchModeUnavailable(String cameraId) { + // default empty implementation + } + + /** + * Torch mode of a camera has been turned on or off through {@link #setTorchMode}. + * + * <p>The default implementation of this method does nothing.</p> + * + * @param cameraId The unique identifier of the camera whose torch mode has been changed. + * + * @param enabled The state that the torch mode of the camera has been changed to. + * {@code true} when the torch mode has been turned on. {@code false} when + * the torch mode has been turned off. + */ + public void onTorchModeChanged(String cameraId, boolean enabled) { + // default empty implementation + } + } + + /** * Return or create the list of currently connected camera devices. * * <p>In case of errors connecting to the camera service, will return an empty list.</p> diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 1b10858..af7d365 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -288,6 +288,13 @@ public abstract class CameraMetadata<TKey> { */ public static final int LENS_FACING_BACK = 1; + /** + * <p>The camera device is an external camera, and has no fixed facing relative to the + * device's screen.</p> + * @see CameraCharacteristics#LENS_FACING + */ + public static final int LENS_FACING_EXTERNAL = 2; + // // Enumeration values for CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES // @@ -432,23 +439,40 @@ public abstract class CameraMetadata<TKey> { public static final int REQUEST_AVAILABLE_CAPABILITIES_RAW = 3; /** - * <p>The camera device supports the Zero Shutter Lag use case.</p> + * <p>The camera device supports the Zero Shutter Lag reprocessing use case.</p> * <ul> - * <li>At least one input stream can be used.</li> - * <li>RAW_OPAQUE is supported as an output/input format</li> - * <li>Using RAW_OPAQUE does not cause a frame rate drop + * <li>One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>.</li> + * <li>OPAQUE is supported as an output/input format, that is, + * StreamConfigurationMap#getOutputSizes(klass) and + * StreamConfigurationMap#getInputSizes(klass) return non empty Size[] and have common + * sizes, where klass is android.media.OpaqueImageRingBufferQueue.class. See + * android.scaler.availableInputOutputFormatsMap for detailed information about + * OPAQUE format.</li> + * <li>android.scaler.availableInputOutputFormatsMap has the required map entries.</li> + * <li>Using OPAQUE does not cause a frame rate drop * relative to the sensor's maximum capture rate (at that - * resolution).</li> - * <li>RAW_OPAQUE will be reprocessable into both YUV_420_888 + * resolution), see android.scaler.availableInputOutputFormatsMap for more details.</li> + * <li>OPAQUE will be reprocessable into both YUV_420_888 * and JPEG formats.</li> - * <li>The maximum available resolution for RAW_OPAQUE streams + * <li>The maximum available resolution for OPAQUE streams * (both input/output) will match the maximum available * resolution of JPEG streams.</li> + * <li>Only below controls are effective for reprocessing requests and + * will be present in capture results, other controls in reprocess + * requests will be ignored by the camera device.<ul> + * <li>android.jpeg.*</li> + * <li>{@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode}</li> + * <li>{@link CaptureRequest#EDGE_MODE android.edge.mode}</li> * </ul> + * </li> + * </ul> + * + * @see CaptureRequest#EDGE_MODE + * @see CaptureRequest#NOISE_REDUCTION_MODE + * @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES - * @hide */ - public static final int REQUEST_AVAILABLE_CAPABILITIES_ZSL = 4; + public static final int REQUEST_AVAILABLE_CAPABILITIES_OPAQUE_REPROCESSING = 4; /** * <p>The camera device supports accurately reporting the sensor settings for many of @@ -508,6 +532,45 @@ public abstract class CameraMetadata<TKey> { */ public static final int REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE = 6; + /** + * <p>The camera device supports the YUV420_888 reprocessing use case, similar as + * OPAQUE_REPROCESSING, This capability requires the camera device to support the + * following:</p> + * <ul> + * <li>One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>.</li> + * <li>YUV420_888 is supported as a common format for both input and output, that is, + * StreamConfigurationMap#getOutputSizes(YUV420_888) and + * StreamConfigurationMap#getInputSizes(YUV420_888) return non empty Size[] and have + * common sizes.</li> + * <li>android.scaler.availableInputOutputFormatsMap has the required map entries.</li> + * <li>Using YUV420_888 does not cause a frame rate drop + * relative to the sensor's maximum capture rate (at that + * resolution), see android.scaler.availableInputOutputFormatsMap for more details.</li> + * <li>YUV420_888 will be reprocessable into both YUV_420_888 + * and JPEG formats.</li> + * <li>The maximum available resolution for YUV420_888 streams + * (both input/output) will match the maximum available + * resolution of JPEG streams.</li> + * <li>Only the below controls are effective for reprocessing requests and will be + * present in capture results. The reprocess requests are from the original capture + * results that are assocaited with the intermidate YUV420_888 output buffers. + * All other controls in the reprocess requests will be ignored by the camera device.<ul> + * <li>android.jpeg.*</li> + * <li>{@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode}</li> + * <li>{@link CaptureRequest#EDGE_MODE android.edge.mode}</li> + * <li>{@link CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR android.reprocess.effectiveExposureFactor}</li> + * </ul> + * </li> + * </ul> + * + * @see CaptureRequest#EDGE_MODE + * @see CaptureRequest#NOISE_REDUCTION_MODE + * @see CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR + * @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + public static final int REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING = 7; + // // Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE // @@ -1823,6 +1886,13 @@ public abstract class CameraMetadata<TKey> { */ public static final int NOISE_REDUCTION_MODE_HIGH_QUALITY = 2; + /** + * <p>MINIMAL noise reduction is applied without reducing frame rate relative to + * sensor output. </p> + * @see CaptureRequest#NOISE_REDUCTION_MODE + */ + public static final int NOISE_REDUCTION_MODE_MINIMAL = 3; + // // Enumeration values for CaptureRequest#SENSOR_TEST_PATTERN_MODE // diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 0849df8..e1b14cc 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -552,6 +552,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * in this matrix result metadata. The transform should keep the magnitude * of the output color values within <code>[0, 1.0]</code> (assuming input color * values is within the normalized range <code>[0, 1.0]</code>), or clipping may occur.</p> + * <p>The valid range of each matrix element varies on different devices, but + * values within [-1.5, 3.0] are guaranteed not to be clipped.</p> * <p><b>Units</b>: Unitless scale factors</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * <p><b>Full capability</b> - @@ -575,6 +577,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * TRANSFORM_MATRIX.</p> * <p>The gains in the result metadata are the gains actually * applied by the camera device to the current frame.</p> + * <p>The valid range of gains varies on different devices, but gains + * between [1.0, 3.0] are guaranteed not to be clipped. Even if a given + * device allows gains below 1.0, this is usually not recommended because + * this can create color artifacts.</p> * <p><b>Units</b>: Unitless gain factors</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * <p><b>Full capability</b> - @@ -1154,7 +1160,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>This control (except for MANUAL) is only effective if * <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code> and any 3A routine is active.</p> * <p>ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} - * contains ZSL. MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} + * contains OPAQUE_REPROCESSING. MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} * contains MANUAL_SENSOR. Other intent values are always supported.</p> * <p><b>Possible values:</b> * <ul> @@ -1239,10 +1245,6 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * update, as if this frame is never captured. This mode can be used in the scenario * where the application doesn't want a 3A manual control capture to affect * the subsequent auto 3A capture results.</p> - * <p>LEGACY mode devices will only support AUTO and USE_SCENE_MODE modes. - * LIMITED mode devices will only support OFF and OFF_KEEP_STATE if they - * support the MANUAL_SENSOR and MANUAL_POST_PROCSESING capabilities. - * FULL mode devices will always support OFF and OFF_KEEP_STATE.</p> * <p><b>Possible values:</b> * <ul> * <li>{@link #CONTROL_MODE_OFF OFF}</li> @@ -1250,9 +1252,12 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <li>{@link #CONTROL_MODE_USE_SCENE_MODE USE_SCENE_MODE}</li> * <li>{@link #CONTROL_MODE_OFF_KEEP_STATE OFF_KEEP_STATE}</li> * </ul></p> + * <p><b>Available values for this device:</b><br> + * {@link CameraCharacteristics#CONTROL_AVAILABLE_MODES android.control.availableModes}</p> * <p>This key is available on all devices.</p> * * @see CaptureRequest#CONTROL_AF_MODE + * @see CameraCharacteristics#CONTROL_AVAILABLE_MODES * @see #CONTROL_MODE_OFF * @see #CONTROL_MODE_AUTO * @see #CONTROL_MODE_USE_SCENE_MODE @@ -1372,6 +1377,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * camera device will use the highest-quality enhancement algorithms, * even if it slows down capture rate. FAST means the camera device will * not slow down capture rate when applying edge enhancement.</p> + * <p>For YUV_REPROCESSING, these FAST/HIGH_QUALITY modes both mean that the camera + * device will apply FAST/HIGH_QUALITY YUV-domain edge enhancement, respectively. + * The camera device may adjust its internal noise reduction parameters for best + * image quality based on the {@link CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR android.reprocess.effectiveExposureFactor}, if it is set.</p> * <p><b>Possible values:</b> * <ul> * <li>{@link #EDGE_MODE_OFF OFF}</li> @@ -1387,6 +1396,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * * @see CameraCharacteristics#EDGE_AVAILABLE_EDGE_MODES * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @see CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR * @see #EDGE_MODE_OFF * @see #EDGE_MODE_FAST * @see #EDGE_MODE_HIGH_QUALITY @@ -1751,18 +1761,28 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> /** * <p>Mode of operation for the noise reduction algorithm.</p> * <p>The noise reduction algorithm attempts to improve image quality by removing - * excessive noise added by the capture process, especially in dark conditions. - * OFF means no noise reduction will be applied by the camera device.</p> + * excessive noise added by the capture process, especially in dark conditions.</p> + * <p>OFF means no noise reduction will be applied by the camera device, for both raw and + * YUV domain.</p> + * <p>MINIMAL means that only sensor raw domain basic noise reduction is enabled ,to remove + * demosaicing or other processing artifacts. For YUV_REPROCESSING, MINIMAL is same as OFF. + * This mode is optional, may not be support by all devices. The application should check + * {@link CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES android.noiseReduction.availableNoiseReductionModes} before using it.</p> * <p>FAST/HIGH_QUALITY both mean camera device determined noise filtering * will be applied. HIGH_QUALITY mode indicates that the camera device * will use the highest-quality noise filtering algorithms, * even if it slows down capture rate. FAST means the camera device will not * slow down capture rate when applying noise filtering.</p> + * <p>For YUV_REPROCESSING, these FAST/HIGH_QUALITY modes both mean that the camera device + * will apply FAST/HIGH_QUALITY YUV domain noise reduction, respectively. The camera device + * may adjust the noise reduction parameters for best image quality based on the + * {@link CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR android.reprocess.effectiveExposureFactor} if it is set.</p> * <p><b>Possible values:</b> * <ul> * <li>{@link #NOISE_REDUCTION_MODE_OFF OFF}</li> * <li>{@link #NOISE_REDUCTION_MODE_FAST FAST}</li> * <li>{@link #NOISE_REDUCTION_MODE_HIGH_QUALITY HIGH_QUALITY}</li> + * <li>{@link #NOISE_REDUCTION_MODE_MINIMAL MINIMAL}</li> * </ul></p> * <p><b>Available values for this device:</b><br> * {@link CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES android.noiseReduction.availableNoiseReductionModes}</p> @@ -1773,9 +1793,11 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL * @see CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES + * @see CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR * @see #NOISE_REDUCTION_MODE_OFF * @see #NOISE_REDUCTION_MODE_FAST * @see #NOISE_REDUCTION_MODE_HIGH_QUALITY + * @see #NOISE_REDUCTION_MODE_MINIMAL */ @PublicKey public static final Key<Integer> NOISE_REDUCTION_MODE = @@ -2416,6 +2438,52 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> public static final Key<Boolean> BLACK_LEVEL_LOCK = new Key<Boolean>("android.blackLevel.lock", boolean.class); + /** + * <p>The amount of exposure time increase factor applied to the original output + * frame by the application processing before sending for reprocessing.</p> + * <p>This is optional, and will be supported if the camera device supports YUV_REPROCESSING + * capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains YUV_REPROCESSING).</p> + * <p>For some YUV reprocessing use cases, the application may choose to filter the original + * output frames to effectively reduce the noise to the same level as a frame that was + * captured with longer exposure time. To be more specific, assuming the original captured + * images were captured with a sensitivity of S and an exposure time of T, the model in + * the camera device is that the amount of noise in the image would be approximately what + * would be expected if the original capture parameters had been a sensitivity of + * S/effectiveExposureFactor and an exposure time of T*effectiveExposureFactor, rather + * than S and T respectively. If the captured images were processed by the application + * before being sent for reprocessing, then the application may have used image processing + * algorithms and/or multi-frame image fusion to reduce the noise in the + * application-processed images (input images). By using the effectiveExposureFactor + * control, the application can communicate to the camera device the actual noise level + * improvement in the application-processed image. With this information, the camera + * device can select appropriate noise reduction and edge enhancement parameters to avoid + * excessive noise reduction ({@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode}) and insufficient edge + * enhancement ({@link CaptureRequest#EDGE_MODE android.edge.mode}) being applied to the reprocessed frames.</p> + * <p>For example, for multi-frame image fusion use case, the application may fuse + * multiple output frames together to a final frame for reprocessing. When N image are + * fused into 1 image for reprocessing, the exposure time increase factor could be up to + * square root of N (based on a simple photon shot noise model). The camera device will + * adjust the reprocessing noise reduction and edge enhancement parameters accordingly to + * produce the best quality images.</p> + * <p>This is relative factor, 1.0 indicates the application hasn't processed the input + * buffer in a way that affects its effective exposure time.</p> + * <p>This control is only effective for YUV reprocessing capture request. For noise + * reduction reprocessing, it is only effective when <code>{@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode} != OFF</code>. + * Similarly, for edge enhancement reprocessing, it is only effective when + * <code>{@link CaptureRequest#EDGE_MODE android.edge.mode} != OFF</code>.</p> + * <p><b>Units</b>: Relative exposure time increase factor.</p> + * <p><b>Range of valid values:</b><br> + * >= 1.0</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CaptureRequest#EDGE_MODE + * @see CaptureRequest#NOISE_REDUCTION_MODE + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + @PublicKey + public static final Key<Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR = + new Key<Float>("android.reprocess.effectiveExposureFactor", float.class); + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 1396940..5bf5b29 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -403,6 +403,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * in this matrix result metadata. The transform should keep the magnitude * of the output color values within <code>[0, 1.0]</code> (assuming input color * values is within the normalized range <code>[0, 1.0]</code>), or clipping may occur.</p> + * <p>The valid range of each matrix element varies on different devices, but + * values within [-1.5, 3.0] are guaranteed not to be clipped.</p> * <p><b>Units</b>: Unitless scale factors</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * <p><b>Full capability</b> - @@ -426,6 +428,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * TRANSFORM_MATRIX.</p> * <p>The gains in the result metadata are the gains actually * applied by the camera device to the current frame.</p> + * <p>The valid range of gains varies on different devices, but gains + * between [1.0, 3.0] are guaranteed not to be clipped. Even if a given + * device allows gains below 1.0, this is usually not recommended because + * this can create color artifacts.</p> * <p><b>Units</b>: Unitless gain factors</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * <p><b>Full capability</b> - @@ -1627,7 +1633,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>This control (except for MANUAL) is only effective if * <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code> and any 3A routine is active.</p> * <p>ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} - * contains ZSL. MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} + * contains OPAQUE_REPROCESSING. MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} * contains MANUAL_SENSOR. Other intent values are always supported.</p> * <p><b>Possible values:</b> * <ul> @@ -1855,10 +1861,6 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * update, as if this frame is never captured. This mode can be used in the scenario * where the application doesn't want a 3A manual control capture to affect * the subsequent auto 3A capture results.</p> - * <p>LEGACY mode devices will only support AUTO and USE_SCENE_MODE modes. - * LIMITED mode devices will only support OFF and OFF_KEEP_STATE if they - * support the MANUAL_SENSOR and MANUAL_POST_PROCSESING capabilities. - * FULL mode devices will always support OFF and OFF_KEEP_STATE.</p> * <p><b>Possible values:</b> * <ul> * <li>{@link #CONTROL_MODE_OFF OFF}</li> @@ -1866,9 +1868,12 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <li>{@link #CONTROL_MODE_USE_SCENE_MODE USE_SCENE_MODE}</li> * <li>{@link #CONTROL_MODE_OFF_KEEP_STATE OFF_KEEP_STATE}</li> * </ul></p> + * <p><b>Available values for this device:</b><br> + * {@link CameraCharacteristics#CONTROL_AVAILABLE_MODES android.control.availableModes}</p> * <p>This key is available on all devices.</p> * * @see CaptureRequest#CONTROL_AF_MODE + * @see CameraCharacteristics#CONTROL_AVAILABLE_MODES * @see #CONTROL_MODE_OFF * @see #CONTROL_MODE_AUTO * @see #CONTROL_MODE_USE_SCENE_MODE @@ -1988,6 +1993,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * camera device will use the highest-quality enhancement algorithms, * even if it slows down capture rate. FAST means the camera device will * not slow down capture rate when applying edge enhancement.</p> + * <p>For YUV_REPROCESSING, these FAST/HIGH_QUALITY modes both mean that the camera + * device will apply FAST/HIGH_QUALITY YUV-domain edge enhancement, respectively. + * The camera device may adjust its internal noise reduction parameters for best + * image quality based on the {@link CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR android.reprocess.effectiveExposureFactor}, if it is set.</p> * <p><b>Possible values:</b> * <ul> * <li>{@link #EDGE_MODE_OFF OFF}</li> @@ -2003,6 +2012,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * * @see CameraCharacteristics#EDGE_AVAILABLE_EDGE_MODES * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @see CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR * @see #EDGE_MODE_OFF * @see #EDGE_MODE_FAST * @see #EDGE_MODE_HIGH_QUALITY @@ -2465,18 +2475,28 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { /** * <p>Mode of operation for the noise reduction algorithm.</p> * <p>The noise reduction algorithm attempts to improve image quality by removing - * excessive noise added by the capture process, especially in dark conditions. - * OFF means no noise reduction will be applied by the camera device.</p> + * excessive noise added by the capture process, especially in dark conditions.</p> + * <p>OFF means no noise reduction will be applied by the camera device, for both raw and + * YUV domain.</p> + * <p>MINIMAL means that only sensor raw domain basic noise reduction is enabled ,to remove + * demosaicing or other processing artifacts. For YUV_REPROCESSING, MINIMAL is same as OFF. + * This mode is optional, may not be support by all devices. The application should check + * {@link CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES android.noiseReduction.availableNoiseReductionModes} before using it.</p> * <p>FAST/HIGH_QUALITY both mean camera device determined noise filtering * will be applied. HIGH_QUALITY mode indicates that the camera device * will use the highest-quality noise filtering algorithms, * even if it slows down capture rate. FAST means the camera device will not * slow down capture rate when applying noise filtering.</p> + * <p>For YUV_REPROCESSING, these FAST/HIGH_QUALITY modes both mean that the camera device + * will apply FAST/HIGH_QUALITY YUV domain noise reduction, respectively. The camera device + * may adjust the noise reduction parameters for best image quality based on the + * {@link CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR android.reprocess.effectiveExposureFactor} if it is set.</p> * <p><b>Possible values:</b> * <ul> * <li>{@link #NOISE_REDUCTION_MODE_OFF OFF}</li> * <li>{@link #NOISE_REDUCTION_MODE_FAST FAST}</li> * <li>{@link #NOISE_REDUCTION_MODE_HIGH_QUALITY HIGH_QUALITY}</li> + * <li>{@link #NOISE_REDUCTION_MODE_MINIMAL MINIMAL}</li> * </ul></p> * <p><b>Available values for this device:</b><br> * {@link CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES android.noiseReduction.availableNoiseReductionModes}</p> @@ -2487,9 +2507,11 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL * @see CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES + * @see CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR * @see #NOISE_REDUCTION_MODE_OFF * @see #NOISE_REDUCTION_MODE_FAST * @see #NOISE_REDUCTION_MODE_HIGH_QUALITY + * @see #NOISE_REDUCTION_MODE_MINIMAL */ @PublicKey public static final Key<Integer> NOISE_REDUCTION_MODE = @@ -3647,6 +3669,52 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { public static final Key<Long> SYNC_FRAME_NUMBER = new Key<Long>("android.sync.frameNumber", long.class); + /** + * <p>The amount of exposure time increase factor applied to the original output + * frame by the application processing before sending for reprocessing.</p> + * <p>This is optional, and will be supported if the camera device supports YUV_REPROCESSING + * capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains YUV_REPROCESSING).</p> + * <p>For some YUV reprocessing use cases, the application may choose to filter the original + * output frames to effectively reduce the noise to the same level as a frame that was + * captured with longer exposure time. To be more specific, assuming the original captured + * images were captured with a sensitivity of S and an exposure time of T, the model in + * the camera device is that the amount of noise in the image would be approximately what + * would be expected if the original capture parameters had been a sensitivity of + * S/effectiveExposureFactor and an exposure time of T*effectiveExposureFactor, rather + * than S and T respectively. If the captured images were processed by the application + * before being sent for reprocessing, then the application may have used image processing + * algorithms and/or multi-frame image fusion to reduce the noise in the + * application-processed images (input images). By using the effectiveExposureFactor + * control, the application can communicate to the camera device the actual noise level + * improvement in the application-processed image. With this information, the camera + * device can select appropriate noise reduction and edge enhancement parameters to avoid + * excessive noise reduction ({@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode}) and insufficient edge + * enhancement ({@link CaptureRequest#EDGE_MODE android.edge.mode}) being applied to the reprocessed frames.</p> + * <p>For example, for multi-frame image fusion use case, the application may fuse + * multiple output frames together to a final frame for reprocessing. When N image are + * fused into 1 image for reprocessing, the exposure time increase factor could be up to + * square root of N (based on a simple photon shot noise model). The camera device will + * adjust the reprocessing noise reduction and edge enhancement parameters accordingly to + * produce the best quality images.</p> + * <p>This is relative factor, 1.0 indicates the application hasn't processed the input + * buffer in a way that affects its effective exposure time.</p> + * <p>This control is only effective for YUV reprocessing capture request. For noise + * reduction reprocessing, it is only effective when <code>{@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode} != OFF</code>. + * Similarly, for edge enhancement reprocessing, it is only effective when + * <code>{@link CaptureRequest#EDGE_MODE android.edge.mode} != OFF</code>.</p> + * <p><b>Units</b>: Relative exposure time increase factor.</p> + * <p><b>Range of valid values:</b><br> + * >= 1.0</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CaptureRequest#EDGE_MODE + * @see CaptureRequest#NOISE_REDUCTION_MODE + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + @PublicKey + public static final Key<Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR = + new Key<Float>("android.reprocess.effectiveExposureFactor", float.class); + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java index 347db05..802b938 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java +++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java @@ -474,6 +474,15 @@ public class LegacyMetadataMapper { m.set(CONTROL_AE_COMPENSATION_STEP, ParamsUtils.createRational(step)); } + + /* + * control.aeLockAvailable + */ + { + boolean aeLockAvailable = p.isAutoExposureLockSupported(); + + m.set(CONTROL_AE_LOCK_AVAILABLE, aeLockAvailable); + } } @@ -571,6 +580,16 @@ public class LegacyMetadataMapper { Log.v(TAG, "mapControlAwb - control.awbAvailableModes set to " + ListUtils.listToString(awbAvail)); } + + + /* + * control.awbLockAvailable + */ + { + boolean awbLockAvailable = p.isAutoWhiteBalanceLockSupported(); + + m.set(CONTROL_AWB_LOCK_AVAILABLE, awbLockAvailable); + } } } @@ -618,17 +637,44 @@ public class LegacyMetadataMapper { /* * android.control.availableSceneModes */ + int maxNumDetectedFaces = p.getMaxNumDetectedFaces(); List<String> sceneModes = p.getSupportedSceneModes(); List<Integer> supportedSceneModes = ArrayUtils.convertStringListToIntList(sceneModes, sLegacySceneModes, sSceneModes); - if (supportedSceneModes == null) { // camera1 doesn't support scene mode settings - supportedSceneModes = new ArrayList<Integer>(); - supportedSceneModes.add(CONTROL_SCENE_MODE_DISABLED); // disabled is always available + + // Special case where the only scene mode listed is AUTO => no scene mode + if (sceneModes != null && sceneModes.size() == 1 && + sceneModes.get(0) == Parameters.SCENE_MODE_AUTO) { + supportedSceneModes = null; } - if (p.getMaxNumDetectedFaces() > 0) { // always supports FACE_PRIORITY when face detecting - supportedSceneModes.add(CONTROL_SCENE_MODE_FACE_PRIORITY); + + boolean sceneModeSupported = true; + if (supportedSceneModes == null && maxNumDetectedFaces == 0) { + sceneModeSupported = false; } - m.set(CONTROL_AVAILABLE_SCENE_MODES, ArrayUtils.toIntArray(supportedSceneModes)); + + if (sceneModeSupported) { + if (supportedSceneModes == null) { + supportedSceneModes = new ArrayList<Integer>(); + } + if (maxNumDetectedFaces > 0) { // always supports FACE_PRIORITY when face detecting + supportedSceneModes.add(CONTROL_SCENE_MODE_FACE_PRIORITY); + } + // Remove all DISABLED occurrences + if (supportedSceneModes.contains(CONTROL_SCENE_MODE_DISABLED)) { + while(supportedSceneModes.remove(new Integer(CONTROL_SCENE_MODE_DISABLED))) {} + } + m.set(CONTROL_AVAILABLE_SCENE_MODES, ArrayUtils.toIntArray(supportedSceneModes)); + } else { + m.set(CONTROL_AVAILABLE_SCENE_MODES, new int[] {CONTROL_SCENE_MODE_DISABLED}); + } + + /* + * android.control.availableModes + */ + m.set(CONTROL_AVAILABLE_MODES, sceneModeSupported ? + new int[] { CONTROL_MODE_AUTO, CONTROL_MODE_USE_SCENE_MODE } : + new int[] { CONTROL_MODE_AUTO }); } private static void mapLens(CameraMetadataNative m, Camera.Parameters p) { diff --git a/core/java/android/hardware/camera2/params/LensShadingMap.java b/core/java/android/hardware/camera2/params/LensShadingMap.java index 9bbc33a..13929b1 100644 --- a/core/java/android/hardware/camera2/params/LensShadingMap.java +++ b/core/java/android/hardware/camera2/params/LensShadingMap.java @@ -238,6 +238,51 @@ public final class LensShadingMap { return HashCodeHelpers.hashCode(mRows, mColumns, elemsHash); } + /** + * Return the LensShadingMap as a string representation. + * + * <p> {@code "LensShadingMap{R:([%f, %f, ... %f], ... [%f, %f, ... %f]), G_even:([%f, %f, ... + * %f], ... [%f, %f, ... %f]), G_odd:([%f, %f, ... %f], ... [%f, %f, ... %f]), B:([%f, %f, ... + * %f], ... [%f, %f, ... %f])}"}, + * where each {@code %f} represents one gain factor and each {@code [%f, %f, ... %f]} represents + * a row of the lens shading map</p> + * + * @return string representation of {@link LensShadingMap} + */ + @Override + public String toString() { + StringBuilder str = new StringBuilder(); + str.append("LensShadingMap{"); + + final String channelPrefix[] = {"R:(", "G_even:(", "G_odd:(", "B:("}; + + for (int ch = 0; ch < COUNT; ch++) { + str.append(channelPrefix[ch]); + + for (int r = 0; r < mRows; r++) { + str.append("["); + for (int c = 0; c < mColumns; c++) { + float gain = getGainFactor(ch, c, r); + str.append(gain); + if (c < mColumns - 1) { + str.append(", "); + } + } + str.append("]"); + if (r < mRows - 1) { + str.append(", "); + } + } + + str.append(")"); + if (ch < COUNT - 1) { + str.append(", "); + } + } + + str.append("}"); + return str.toString(); + } private final int mRows; private final int mColumns; diff --git a/core/java/android/hardware/usb/UsbDevice.java b/core/java/android/hardware/usb/UsbDevice.java index d90e06e..1a42319 100644 --- a/core/java/android/hardware/usb/UsbDevice.java +++ b/core/java/android/hardware/usb/UsbDevice.java @@ -40,6 +40,7 @@ import android.os.Parcelable; public class UsbDevice implements Parcelable { private static final String TAG = "UsbDevice"; + private static final boolean DEBUG = false; private final String mName; private final String mManufacturerName; diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index f64ef87..f283051 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -68,6 +68,8 @@ public class UsbManager { * accessory function is enabled * <li> {@link #USB_FUNCTION_AUDIO_SOURCE} boolean extra indicating whether the * audio source function is enabled + * <li> {@link #USB_FUNCTION_MIDI} boolean extra indicating whether the + * MIDI function is enabled * </ul> * * {@hide} @@ -188,6 +190,14 @@ public class UsbManager { public static final String USB_FUNCTION_AUDIO_SOURCE = "audio_source"; /** + * Name of the MIDI USB function. + * Used in extras for the {@link #ACTION_USB_STATE} broadcast + * + * {@hide} + */ + public static final String USB_FUNCTION_MIDI = "midi"; + + /** * Name of the Accessory USB function. * Used in extras for the {@link #ACTION_USB_STATE} broadcast * diff --git a/core/java/android/midi/IMidiDeviceServer.aidl b/core/java/android/midi/IMidiDeviceServer.aidl new file mode 100644 index 0000000..31fdbbb --- /dev/null +++ b/core/java/android/midi/IMidiDeviceServer.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2014 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.midi; + +import android.os.ParcelFileDescriptor; + +/** @hide */ +interface IMidiDeviceServer +{ + ParcelFileDescriptor openInputPort(int portNumber); + ParcelFileDescriptor openOutputPort(int portNumber); +} diff --git a/core/java/android/midi/IMidiListener.aidl b/core/java/android/midi/IMidiListener.aidl new file mode 100644 index 0000000..b650593 --- /dev/null +++ b/core/java/android/midi/IMidiListener.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2014 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.midi; + +import android.midi.MidiDeviceInfo; + +/** @hide */ +oneway interface IMidiListener +{ + void onDeviceAdded(in MidiDeviceInfo device); + void onDeviceRemoved(in MidiDeviceInfo device); +} diff --git a/core/java/android/midi/IMidiManager.aidl b/core/java/android/midi/IMidiManager.aidl new file mode 100644 index 0000000..575b525 --- /dev/null +++ b/core/java/android/midi/IMidiManager.aidl @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014 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.midi; + +import android.midi.IMidiDeviceServer; +import android.midi.IMidiListener; +import android.midi.MidiDeviceInfo; +import android.os.Bundle; +import android.os.IBinder; + +/** @hide */ +interface IMidiManager +{ + MidiDeviceInfo[] getDeviceList(); + + // for device creation & removal notifications + void registerListener(IBinder token, in IMidiListener listener); + void unregisterListener(IBinder token, in IMidiListener listener); + + // for communicating with MIDI devices + IMidiDeviceServer openDevice(IBinder token, in MidiDeviceInfo device); + + // for implementing virtual MIDI devices + MidiDeviceInfo registerDeviceServer(in IMidiDeviceServer server, int numInputPorts, + int numOutputPorts, in Bundle properties, boolean isPrivate, int type); + void unregisterDeviceServer(in IMidiDeviceServer server); +} diff --git a/core/java/android/midi/MidiDevice.java b/core/java/android/midi/MidiDevice.java new file mode 100644 index 0000000..b91aedf --- /dev/null +++ b/core/java/android/midi/MidiDevice.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2014 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.midi; + +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.util.Log; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; + +/** + * This class is used for sending and receiving data to and from an MIDI device + * Instances of this class are created by {@link MidiManager#openDevice}. + * + * CANDIDATE FOR PUBLIC API + * @hide + */ +public final class MidiDevice { + private static final String TAG = "MidiDevice"; + + private final MidiDeviceInfo mDeviceInfo; + private final IMidiDeviceServer mServer; + + /** + * MidiDevice should only be instantiated by MidiManager + * @hide + */ + public MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server) { + mDeviceInfo = deviceInfo; + mServer = server; + } + + /** + * Returns a {@link MidiDeviceInfo} object, which describes this device. + * + * @return the {@link MidiDeviceInfo} object + */ + public MidiDeviceInfo getInfo() { + return mDeviceInfo; + } + + /** + * Called to open a {@link MidiInputPort} for the specified port number. + * + * @param portNumber the number of the input port to open + * @return the {@link MidiInputPort} + */ + public MidiInputPort openInputPort(int portNumber) { + try { + ParcelFileDescriptor pfd = mServer.openInputPort(portNumber); + if (pfd == null) { + return null; + } + return new MidiInputPort(pfd, portNumber); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in openInputPort"); + return null; + } + } + + /** + * Called to open a {@link MidiOutputPort} for the specified port number. + * + * @param portNumber the number of the output port to open + * @return the {@link MidiOutputPort} + */ + public MidiOutputPort openOutputPort(int portNumber) { + try { + ParcelFileDescriptor pfd = mServer.openOutputPort(portNumber); + if (pfd == null) { + return null; + } + return new MidiOutputPort(pfd, portNumber); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in openOutputPort"); + return null; + } + } + + @Override + public String toString() { + return ("MidiDevice: " + mDeviceInfo.toString()); + } +} diff --git a/core/java/android/midi/MidiDeviceInfo.aidl b/core/java/android/midi/MidiDeviceInfo.aidl new file mode 100644 index 0000000..59be059 --- /dev/null +++ b/core/java/android/midi/MidiDeviceInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2014, 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.midi; + +parcelable MidiDeviceInfo; diff --git a/core/java/android/midi/MidiDeviceInfo.java b/core/java/android/midi/MidiDeviceInfo.java new file mode 100644 index 0000000..dde2669 --- /dev/null +++ b/core/java/android/midi/MidiDeviceInfo.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2014 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.midi; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class contains information to describe a MIDI device. + * For now we only have information that can be retrieved easily for USB devices, + * but we will probably expand this in the future. + * + * This class is just an immutable object to encapsulate the MIDI device description. + * Use the MidiDevice class to actually communicate with devices. + * + * CANDIDATE FOR PUBLIC API + * @hide + */ +public class MidiDeviceInfo implements Parcelable { + + private static final String TAG = "MidiDeviceInfo"; + + /** + * Constant representing USB MIDI devices for {@link #getType} + */ + public static final int TYPE_USB = 1; + + /** + * Constant representing virtual (software based) MIDI devices for {@link #getType} + */ + public static final int TYPE_VIRTUAL = 2; + + private final int mType; // USB or virtual + private final int mId; // unique ID generated by MidiService + private final int mInputPortCount; + private final int mOutputPortCount; + private final Bundle mProperties; + + /** + * Bundle key for the device's manufacturer name property. + * Used with the {@link android.os.Bundle} returned by {@link #getProperties}. + * Matches the USB device manufacturer name string for USB MIDI devices. + */ + public static final String PROPERTY_MANUFACTURER = "manufacturer"; + + /** + * Bundle key for the device's model name property. + * Used with the {@link android.os.Bundle} returned by {@link #getProperties} + * Matches the USB device product name string for USB MIDI devices. + */ + public static final String PROPERTY_MODEL = "model"; + + /** + * Bundle key for the device's serial number property. + * Used with the {@link android.os.Bundle} returned by {@link #getProperties} + * Matches the USB device serial number for USB MIDI devices. + */ + public static final String PROPERTY_SERIAL_NUMBER = "serial_number"; + + /** + * Bundle key for the device's {@link android.hardware.usb.UsbDevice}. + * Only set for USB MIDI devices. + * Used with the {@link android.os.Bundle} returned by {@link #getProperties} + */ + public static final String PROPERTY_USB_DEVICE = "usb_device"; + + /** + * Bundle key for the device's ALSA card number. + * Only set for USB MIDI devices. + * Used with the {@link android.os.Bundle} returned by {@link #getProperties} + */ + public static final String PROPERTY_ALSA_CARD = "alsa_card"; + + /** + * Bundle key for the device's ALSA device number. + * Only set for USB MIDI devices. + * Used with the {@link android.os.Bundle} returned by {@link #getProperties} + */ + public static final String PROPERTY_ALSA_DEVICE = "alsa_device"; + + /** + * MidiDeviceInfo should only be instantiated by MidiService implementation + * @hide + */ + public MidiDeviceInfo(int type, int id, int numInputPorts, int numOutputPorts, + Bundle properties) { + mType = type; + mId = id; + mInputPortCount = numInputPorts; + mOutputPortCount = numOutputPorts; + mProperties = properties; + } + + /** + * Returns the type of the device. + * + * @return the device's type + */ + public int getType() { + return mType; + } + + /** + * Returns the ID of the device. + * This ID is generated by the MIDI service and is not persistent across device unplugs. + * + * @return the device's ID + */ + public int getId() { + return mId; + } + + /** + * Returns the device's number of input ports. + * + * @return the number of input ports + */ + public int getInputPortCount() { + return mInputPortCount; + } + + /** + * Returns the device's number of output ports. + * + * @return the number of output ports + */ + public int getOutputPortCount() { + return mOutputPortCount; + } + + /** + * Returns the {@link android.os.Bundle} containing the device's properties. + * + * @return the device's properties + */ + public Bundle getProperties() { + return mProperties; + } + + @Override + public boolean equals(Object o) { + if (o instanceof MidiDeviceInfo) { + return (((MidiDeviceInfo)o).mId == mId); + } else { + return false; + } + } + + @Override + public int hashCode() { + return mId; + } + + @Override + public String toString() { + return ("MidiDeviceInfo[mType=" + mType + + ",mInputPortCount=" + mInputPortCount + + ",mOutputPortCount=" + mOutputPortCount + + ",mProperties=" + mProperties); + } + + public static final Parcelable.Creator<MidiDeviceInfo> CREATOR = + new Parcelable.Creator<MidiDeviceInfo>() { + public MidiDeviceInfo createFromParcel(Parcel in) { + int type = in.readInt(); + int id = in.readInt(); + int inputPorts = in.readInt(); + int outputPorts = in.readInt(); + Bundle properties = in.readBundle(); + return new MidiDeviceInfo(type, id, inputPorts, outputPorts, properties); + } + + public MidiDeviceInfo[] newArray(int size) { + return new MidiDeviceInfo[size]; + } + }; + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mType); + parcel.writeInt(mId); + parcel.writeInt(mInputPortCount); + parcel.writeInt(mOutputPortCount); + parcel.writeBundle(mProperties); + } +} diff --git a/core/java/android/midi/MidiDeviceServer.java b/core/java/android/midi/MidiDeviceServer.java new file mode 100644 index 0000000..7499934 --- /dev/null +++ b/core/java/android/midi/MidiDeviceServer.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2014 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.midi; + +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.system.OsConstants; +import android.util.Log; + +import java.io.Closeable; +import java.io.IOException; +import java.util.ArrayList; + +/** + * This class is used to provide the implemention of MIDI device. + * Applications may call {@link MidiManager#createDeviceServer} + * to create an instance of this class to implement a virtual MIDI device. + * + * CANDIDATE FOR PUBLIC API + * @hide + */ +public final class MidiDeviceServer implements Closeable { + private static final String TAG = "MidiDeviceServer"; + + private final IMidiManager mMidiManager; + + // MidiDeviceInfo for the device implemented by this server + private MidiDeviceInfo mDeviceInfo; + private int mInputPortCount; + private int mOutputPortCount; + + // output ports for receiving messages from our clients + // we can have only one per port number + private MidiOutputPort[] mInputPortSenders; + + // receivers attached to our input ports + private ArrayList<MidiReceiver>[] mInputPortReceivers; + + // input ports for sending messages to our clients + // we can have multiple outputs per port number + private ArrayList<MidiInputPort>[] mOutputPortReceivers; + + // subclass of MidiInputPort for passing to clients + // that notifies us when the connection has failed + private class ServerInputPort extends MidiInputPort { + ServerInputPort(ParcelFileDescriptor pfd, int portNumber) { + super(pfd, portNumber); + } + + @Override + public void onIOException() { + synchronized (mOutputPortReceivers) { + mOutputPortReceivers[getPortNumber()].clear(); + } + } + } + + // subclass of MidiOutputPort for passing to clients + // that notifies us when the connection has failed + private class ServerOutputPort extends MidiOutputPort { + ServerOutputPort(ParcelFileDescriptor pfd, int portNumber) { + super(pfd, portNumber); + } + + @Override + public void onIOException() { + synchronized (mInputPortSenders) { + mInputPortSenders[getPortNumber()] = null; + } + } + } + + // Binder interface stub for receiving connection requests from clients + private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() { + + @Override + public ParcelFileDescriptor openInputPort(int portNumber) { + if (portNumber < 0 || portNumber >= mInputPortCount) { + Log.e(TAG, "portNumber out of range in openInputPort: " + portNumber); + return null; + } + + ParcelFileDescriptor result = null; + + synchronized (mInputPortSenders) { + if (mInputPortSenders[portNumber] != null) { + Log.d(TAG, "port " + portNumber + " already open"); + return null; + } + + try { + ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair( + OsConstants.SOCK_SEQPACKET); + MidiOutputPort newOutputPort = new ServerOutputPort(pair[0], portNumber); + mInputPortSenders[portNumber] = newOutputPort; + result = pair[1]; + + ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumber]; + synchronized (receivers) { + for (int i = 0; i < receivers.size(); i++) { + newOutputPort.connect(receivers.get(i)); + } + } + } catch (IOException e) { + Log.e(TAG, "unable to create ParcelFileDescriptors in openInputPort"); + return null; + } + } + + return result; + } + + @Override + public ParcelFileDescriptor openOutputPort(int portNumber) { + if (portNumber < 0 || portNumber >= mOutputPortCount) { + Log.e(TAG, "portNumber out of range in openOutputPort: " + portNumber); + return null; + } + synchronized (mOutputPortReceivers) { + try { + ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair( + OsConstants.SOCK_SEQPACKET); + mOutputPortReceivers[portNumber].add(new ServerInputPort(pair[0], portNumber)); + return pair[1]; + } catch (IOException e) { + Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort"); + return null; + } + } + } + }; + + /* package */ MidiDeviceServer(IMidiManager midiManager) { + mMidiManager = midiManager; + } + + /* package */ IMidiDeviceServer getBinderInterface() { + return mServer; + } + + /* package */ void setDeviceInfo(MidiDeviceInfo deviceInfo) { + if (mDeviceInfo != null) { + throw new IllegalStateException("setDeviceInfo should only be called once"); + } + mDeviceInfo = deviceInfo; + mInputPortCount = deviceInfo.getInputPortCount(); + mOutputPortCount = deviceInfo.getOutputPortCount(); + mInputPortSenders = new MidiOutputPort[mInputPortCount]; + + mInputPortReceivers = new ArrayList[mInputPortCount]; + for (int i = 0; i < mInputPortCount; i++) { + mInputPortReceivers[i] = new ArrayList<MidiReceiver>(); + } + + mOutputPortReceivers = new ArrayList[mOutputPortCount]; + for (int i = 0; i < mOutputPortCount; i++) { + mOutputPortReceivers[i] = new ArrayList<MidiInputPort>(); + } + } + + @Override + public void close() throws IOException { + try { + // FIXME - close input and output ports too? + mMidiManager.unregisterDeviceServer(mServer); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in unregisterDeviceServer"); + } + } + + /** + * Returns a {@link MidiDeviceInfo} object, which describes this device. + * + * @return the {@link MidiDeviceInfo} object + */ + public MidiDeviceInfo getInfo() { + return mDeviceInfo; + } + + /** + * Called to open a {@link MidiSender} to allow receiving MIDI messages + * on the device's input port for the specified port number. + * + * @param portNumber the number of the input port + * @return the {@link MidiSender} + */ + public MidiSender openInputPortSender(int portNumber) { + if (portNumber < 0 || portNumber >= mDeviceInfo.getInputPortCount()) { + throw new IllegalArgumentException("portNumber " + portNumber + " out of range"); + } + final int portNumberF = portNumber; + return new MidiSender() { + + @Override + public void connect(MidiReceiver receiver) { + // We always synchronize on mInputPortSenders before receivers if we need to + // synchronize on both. + synchronized (mInputPortSenders) { + ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumberF]; + synchronized (receivers) { + receivers.add(receiver); + MidiOutputPort outputPort = mInputPortSenders[portNumberF]; + if (outputPort != null) { + outputPort.connect(receiver); + } + } + } + } + + @Override + public void disconnect(MidiReceiver receiver) { + // We always synchronize on mInputPortSenders before receivers if we need to + // synchronize on both. + synchronized (mInputPortSenders) { + ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumberF]; + synchronized (receivers) { + receivers.remove(receiver); + MidiOutputPort outputPort = mInputPortSenders[portNumberF]; + if (outputPort != null) { + outputPort.disconnect(receiver); + } + } + } + } + }; + } + + /** + * Called to open a {@link MidiReceiver} to allow sending MIDI messages + * on the virtual device's output port for the specified port number. + * + * @param portNumber the number of the output port + * @return the {@link MidiReceiver} + */ + public MidiReceiver openOutputPortReceiver(int portNumber) { + if (portNumber < 0 || portNumber >= mDeviceInfo.getOutputPortCount()) { + throw new IllegalArgumentException("portNumber " + portNumber + " out of range"); + } + final int portNumberF = portNumber; + return new MidiReceiver() { + + @Override + public void onPost(byte[] msg, int offset, int count, long timestamp) throws IOException { + ArrayList<MidiInputPort> receivers = mOutputPortReceivers[portNumberF]; + synchronized (receivers) { + for (int i = 0; i < receivers.size(); i++) { + // FIXME catch errors and remove dead ones + receivers.get(i).onPost(msg, offset, count, timestamp); + } + } + } + }; + } +} diff --git a/core/java/android/midi/MidiInputPort.java b/core/java/android/midi/MidiInputPort.java new file mode 100644 index 0000000..51c47dd --- /dev/null +++ b/core/java/android/midi/MidiInputPort.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2014 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.midi; + +import android.os.ParcelFileDescriptor; + +import libcore.io.IoUtils; + +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * This class is used for sending data to a port on a MIDI device + * + * CANDIDATE FOR PUBLIC API + * @hide + */ +public class MidiInputPort extends MidiPort implements MidiReceiver { + + private final FileOutputStream mOutputStream; + + // buffer to use for sending messages out our output stream + private final byte[] mBuffer = new byte[MAX_PACKET_SIZE]; + + /* package */ MidiInputPort(ParcelFileDescriptor pfd, int portNumber) { + super(portNumber); + mOutputStream = new ParcelFileDescriptor.AutoCloseOutputStream(pfd); + } + + /** + * Writes a MIDI message to the input port + * + * @param msg byte array containing the message + * @param offset offset of first byte of the message in msg byte array + * @param count size of the message in bytes + * @param timestamp future time to post the message (based on + * {@link java.lang.System#nanoTime} + */ + public void onPost(byte[] msg, int offset, int count, long timestamp) throws IOException { + assert(offset >= 0 && count >= 0 && offset + count <= msg.length); + + synchronized (mBuffer) { + try { + while (count > 0) { + int length = packMessage(msg, offset, count, timestamp, mBuffer); + mOutputStream.write(mBuffer, 0, length); + int sent = getMessageSize(mBuffer, length); + assert(sent >= 0 && sent <= length); + + offset += sent; + count -= sent; + } + } catch (IOException e) { + IoUtils.closeQuietly(mOutputStream); + // report I/O failure + onIOException(); + throw e; + } + } + } + + @Override + public void close() throws IOException { + mOutputStream.close(); + } +} diff --git a/core/java/android/midi/MidiManager.java b/core/java/android/midi/MidiManager.java new file mode 100644 index 0000000..8aa8395 --- /dev/null +++ b/core/java/android/midi/MidiManager.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2014 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.midi; + +import android.content.Context; +import android.os.Binder; +import android.os.IBinder; +import android.os.Bundle; +import android.os.RemoteException; +import android.util.Log; + +import java.io.IOException; +import java.util.HashMap; + +/** + * This class is the public application interface to the MIDI service. + * + * <p>You can obtain an instance of this class by calling + * {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}. + * + * {@samplecode + * MidiManager manager = (MidiManager) getSystemService(Context.MIDI_SERVICE);} + * + * CANDIDATE FOR PUBLIC API + * @hide + */ +public class MidiManager { + private static final String TAG = "MidiManager"; + + private final Context mContext; + private final IMidiManager mService; + private final IBinder mToken = new Binder(); + + private HashMap<DeviceCallback,DeviceListener> mDeviceListeners = + new HashMap<DeviceCallback,DeviceListener>(); + + // Binder stub for receiving device notifications from MidiService + private class DeviceListener extends IMidiListener.Stub { + private DeviceCallback mCallback; + + public DeviceListener(DeviceCallback callback) { + mCallback = callback; + } + + public void onDeviceAdded(MidiDeviceInfo device) { + mCallback.onDeviceAdded(device); + } + + public void onDeviceRemoved(MidiDeviceInfo device) { + mCallback.onDeviceRemoved(device); + } + } + + /** + * Callback interface used for clients to receive MIDI device added and removed notifications + */ + public interface DeviceCallback { + /** + * Called to notify when a new MIDI device has been added + * + * @param device a {@link MidiDeviceInfo} for the newly added device + */ + void onDeviceAdded(MidiDeviceInfo device); + + /** + * Called to notify when a MIDI device has been removed + * + * @param device a {@link MidiDeviceInfo} for the removed device + */ + void onDeviceRemoved(MidiDeviceInfo device); + } + + /** + * @hide + */ + public MidiManager(Context context, IMidiManager service) { + mContext = context; + mService = service; + } + + /** + * Registers a callback to receive notifications when MIDI devices are added and removed. + * + * @param callback a {@link DeviceCallback} for MIDI device notifications + */ + public void registerDeviceCallback(DeviceCallback callback) { + DeviceListener deviceListener = new DeviceListener(callback); + try { + mService.registerListener(mToken, deviceListener); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in registerDeviceListener"); + return; + } + mDeviceListeners.put(callback, deviceListener); + } + + /** + * Unregisters a {@link DeviceCallback}. + * + * @param callback a {@link DeviceCallback} to unregister + */ + public void unregisterDeviceCallback(DeviceCallback callback) { + DeviceListener deviceListener = mDeviceListeners.remove(callback); + if (deviceListener != null) { + try { + mService.unregisterListener(mToken, deviceListener); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in unregisterDeviceListener"); + } + } + } + + /** + * Gets the list of all connected MIDI devices. + * + * @return an array of all MIDI devices + */ + public MidiDeviceInfo[] getDeviceList() { + try { + return mService.getDeviceList(); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getDeviceList"); + return new MidiDeviceInfo[0]; + } + } + + /** + * Opens a MIDI device for reading and writing. + * + * @param deviceInfo a {@link android.midi.MidiDeviceInfo} to open + * @return a {@link MidiDevice} object for the device + */ + public MidiDevice openDevice(MidiDeviceInfo deviceInfo) { + try { + IMidiDeviceServer server = mService.openDevice(mToken, deviceInfo); + if (server == null) { + Log.e(TAG, "could not open device " + deviceInfo); + return null; + } + return new MidiDevice(deviceInfo, server); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in openDevice"); + } + return null; + } + + /** @hide */ + public MidiDeviceServer createDeviceServer(int numInputPorts, int numOutputPorts, + Bundle properties, boolean isPrivate, int type) { + try { + MidiDeviceServer server = new MidiDeviceServer(mService); + MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(), + numInputPorts, numOutputPorts, properties, isPrivate, type); + if (deviceInfo == null) { + Log.e(TAG, "registerVirtualDevice failed"); + return null; + } + server.setDeviceInfo(deviceInfo); + return server; + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in createVirtualDevice"); + return null; + } + } + + /** + * Creates a new MIDI virtual device. + * + * @param numInputPorts number of input ports for the virtual device + * @param numOutputPorts number of output ports for the virtual device + * @param properties a {@link android.os.Bundle} containing properties describing the device + * @param isPrivate true if this device should only be visible and accessible to apps + * with the same UID as the caller + * @return a {@link MidiDeviceServer} object to locally represent the device + */ + public MidiDeviceServer createDeviceServer(int numInputPorts, int numOutputPorts, + Bundle properties, boolean isPrivate) { + return createDeviceServer(numInputPorts, numOutputPorts, properties, + isPrivate, MidiDeviceInfo.TYPE_VIRTUAL); + } + +} diff --git a/core/java/android/midi/MidiOutputPort.java b/core/java/android/midi/MidiOutputPort.java new file mode 100644 index 0000000..332b431 --- /dev/null +++ b/core/java/android/midi/MidiOutputPort.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2014 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.midi; + +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import libcore.io.IoUtils; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; + +/** + * This class is used for receiving data from a port on a MIDI device + * + * CANDIDATE FOR PUBLIC API + * @hide + */ +public class MidiOutputPort extends MidiPort implements MidiSender { + private static final String TAG = "MidiOutputPort"; + + private final FileInputStream mInputStream; + + // array of receiver lists, indexed by port number + private final ArrayList<MidiReceiver> mReceivers = new ArrayList<MidiReceiver>(); + + private int mReceiverCount; // total number of receivers for all ports + + // This thread reads MIDI events from a socket and distributes them to the list of + // MidiReceivers attached to this device. + private final Thread mThread = new Thread() { + @Override + public void run() { + byte[] buffer = new byte[MAX_PACKET_SIZE]; + ArrayList<MidiReceiver> deadReceivers = new ArrayList<MidiReceiver>(); + + try { + while (true) { + // read next event + int count = mInputStream.read(buffer); + if (count < 0) { + break; + } + + int offset = getMessageOffset(buffer, count); + int size = getMessageSize(buffer, count); + long timestamp = getMessageTimeStamp(buffer, count); + + synchronized (mReceivers) { + for (int i = 0; i < mReceivers.size(); i++) { + MidiReceiver receiver = mReceivers.get(i); + try { + receiver.onPost(buffer, offset, size, timestamp); + } catch (IOException e) { + Log.e(TAG, "post failed"); + deadReceivers.add(receiver); + } + } + // remove any receivers that failed + if (deadReceivers.size() > 0) { + for (MidiReceiver receiver: deadReceivers) { + mReceivers.remove(receiver); + mReceiverCount--; + } + deadReceivers.clear(); + } + // exit if we have no receivers left + if (mReceiverCount == 0) { + break; + } + } + } + } catch (IOException e) { + // report I/O failure + Log.e(TAG, "read failed"); + } finally { + IoUtils.closeQuietly(mInputStream); + onIOException(); + } + } + }; + + /* package */ MidiOutputPort(ParcelFileDescriptor pfd, int portNumber) { + super(portNumber); + mInputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd); + } + + /** + * Connects a {@link MidiReceiver} to the output port to allow receiving + * MIDI messages from the port. + * + * @param receiver the receiver to connect + */ + public void connect(MidiReceiver receiver) { + synchronized (mReceivers) { + mReceivers.add(receiver); + if (mReceiverCount++ == 0) { + mThread.start(); + } + } + } + + /** + * Disconnects a {@link MidiReceiver} from the output port. + * + * @param receiver the receiver to connect + */ + public void disconnect(MidiReceiver receiver) { + synchronized (mReceivers) { + if (mReceivers.remove(receiver)) { + mReceiverCount--; + } + } + } + + @Override + public void close() throws IOException { + mInputStream.close(); + } +} diff --git a/core/java/android/midi/MidiPort.java b/core/java/android/midi/MidiPort.java new file mode 100644 index 0000000..7512a90 --- /dev/null +++ b/core/java/android/midi/MidiPort.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2014 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.midi; + +import android.util.Log; + +import java.io.Closeable; + +/** + * This class represents a MIDI input or output port. + * Base class for {@link MidiInputPort} and {@link MidiOutputPort} + * + * CANDIDATE FOR PUBLIC API + * @hide + */ +abstract public class MidiPort implements Closeable { + private static final String TAG = "MidiPort"; + + private final int mPortNumber; + + /** + * Maximum size of a packet that can pass through our ParcelFileDescriptor + */ + protected static final int MAX_PACKET_SIZE = 1024; + + /** + * size of message timestamp in bytes + */ + private static final int TIMESTAMP_SIZE = 8; + + /** + * Maximum amount of MIDI data that can be included in a packet + */ + public static final int MAX_PACKET_DATA_SIZE = MAX_PACKET_SIZE - TIMESTAMP_SIZE; + + + /* package */ MidiPort(int portNumber) { + mPortNumber = portNumber; + } + + /** + * Returns the port number of this port + * + * @return the port's port number + */ + public final int getPortNumber() { + return mPortNumber; + } + + /** + * Called when an IOExeption occurs while sending or receiving data. + * Subclasses can override to be notified of such errors + * + */ + public void onIOException() { + } + + /** + * Utility function for packing a MIDI message to be sent through our ParcelFileDescriptor + * + * message byte array contains variable length MIDI message. + * messageSize is size of variable length MIDI message + * timestamp is message timestamp to pack + * dest is buffer to pack into + * returns size of packed message + */ + protected static int packMessage(byte[] message, int offset, int size, long timestamp, + byte[] dest) { + if (size + TIMESTAMP_SIZE > MAX_PACKET_SIZE) { + size = MAX_PACKET_SIZE - TIMESTAMP_SIZE; + } + // message data goes first + System.arraycopy(message, offset, dest, 0, size); + + // followed by timestamp + for (int i = 0; i < TIMESTAMP_SIZE; i++) { + dest[size++] = (byte)timestamp; + timestamp >>= 8; + } + + return size; + } + + /** + * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor + * returns the offset of the MIDI message in packed buffer + */ + protected static int getMessageOffset(byte[] buffer, int bufferLength) { + // message is at the beginning + return 0; + } + + /** + * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor + * returns size of MIDI data in packed buffer + */ + protected static int getMessageSize(byte[] buffer, int bufferLength) { + // message length is total buffer length minus size of the timestamp + return bufferLength - TIMESTAMP_SIZE; + } + + /** + * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor + * unpacks timestamp from packed buffer + */ + protected static long getMessageTimeStamp(byte[] buffer, int bufferLength) { + // timestamp is at end of the packet + int offset = bufferLength; + long timestamp = 0; + + for (int i = 0; i < TIMESTAMP_SIZE; i++) { + int b = (int)buffer[--offset] & 0xFF; + timestamp = (timestamp << 8) | b; + } + return timestamp; + } +} diff --git a/core/java/android/midi/MidiReceiver.java b/core/java/android/midi/MidiReceiver.java new file mode 100644 index 0000000..fdfe51a --- /dev/null +++ b/core/java/android/midi/MidiReceiver.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 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.midi; + +import java.io.IOException; + +/** + * Interface for receiving data from a MIDI device. + * + * CANDIDATE FOR PUBLIC API + * @hide + */ +public interface MidiReceiver { + /** + * Called to pass MIDI data to the receiver. + * + * NOTE: the msg array parameter is only valid within the context of this call. + * The msg bytes should be copied by the receiver rather than retaining a reference + * to this parameter. + * Also, modifying the contents of the msg array parameter may result in other receivers + * in the same application receiving incorrect values in their onPost() method. + * + * @param msg a byte array containing the MIDI data + * @param offset the offset of the first byte of the data in the byte array + * @param count the number of bytes of MIDI data in the array + * @param timestamp the timestamp of the message (based on {@link java.lang.System#nanoTime} + * @throws IOException + */ + public void onPost(byte[] msg, int offset, int count, long timestamp) throws IOException; +} diff --git a/core/java/android/midi/MidiSender.java b/core/java/android/midi/MidiSender.java new file mode 100644 index 0000000..2b7afad --- /dev/null +++ b/core/java/android/midi/MidiSender.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2014 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.midi; + +/** + * Interface provided by a device to allow attaching + * MidiReceivers to a MIDI device. + * + * CANDIDATE FOR PUBLIC API + * @hide + */ +public interface MidiSender { + /** + * Called to connect a {@link MidiReceiver} to the sender + * + * @param receiver the receiver to connect + */ + public void connect(MidiReceiver receiver); + + /** + * Called to disconnect a {@link MidiReceiver} from the sender + * + * @param receiver the receiver to disconnect + */ + public void disconnect(MidiReceiver receiver); +} diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index cdc8661..1a51808 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -504,6 +504,8 @@ public class ConnectivityManager { return "MOBILE_EMERGENCY"; case TYPE_PROXY: return "PROXY"; + case TYPE_VPN: + return "VPN"; default: return Integer.toString(type); } diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java index 6654577..27096b1 100644 --- a/core/java/android/net/SSLCertificateSocketFactory.java +++ b/core/java/android/net/SSLCertificateSocketFactory.java @@ -159,6 +159,8 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { * instead. The Apache HTTP client is no longer maintained and may be removed in a future * release. Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a> * for further details. + * + * @removed */ @Deprecated public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory( diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 2099c3f..bf3d9aa 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -366,6 +366,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { public String toSafeString() { String scheme = getScheme(); String ssp = getSchemeSpecificPart(); + String authority = null; if (scheme != null) { if (scheme.equalsIgnoreCase("tel") || scheme.equalsIgnoreCase("sip") || scheme.equalsIgnoreCase("sms") || scheme.equalsIgnoreCase("smsto") @@ -384,6 +385,9 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { } } return builder.toString(); + } else if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")) { + ssp = null; + authority = "//" + getAuthority() + "/..."; } } // Not a sensitive scheme, but let's still be conservative about @@ -397,6 +401,9 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { if (ssp != null) { builder.append(ssp); } + if (authority != null) { + builder.append(authority); + } return builder.toString(); } @@ -1742,7 +1749,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { * * @return normalized Uri (never null) * @see {@link android.content.Intent#setData} - * @see {@link #setNormalizedData} + * @see {@link android.content.Intent#setDataAndNormalize} */ public Uri normalizeScheme() { String scheme = getScheme(); diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index b209690..e4b594a 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -91,7 +91,7 @@ public class Build { /** The name of the hardware (from the kernel command line or /proc). */ public static final String HARDWARE = getString("ro.hardware"); - /** A hardware serial number, if available. Alphanumeric only, case-insensitive. */ + /** A hardware serial number, if available. Alphanumeric only, case-insensitive. */ public static final String SERIAL = getString("ro.serialno"); /** @@ -159,7 +159,7 @@ public class Build { /** * The user-visible SDK version of the framework in its raw String * representation; use {@link #SDK_INT} instead. - * + * * @deprecated Use {@link #SDK_INT} to easily get this as an integer. */ @Deprecated @@ -207,25 +207,25 @@ public class Build { * not yet turned into an official release. */ public static final int CUR_DEVELOPMENT = 10000; - + /** * October 2008: The original, first, version of Android. Yay! */ public static final int BASE = 1; - + /** * February 2009: First Android update, officially called 1.1. */ public static final int BASE_1_1 = 2; - + /** * May 2009: Android 1.5. */ public static final int CUPCAKE = 3; - + /** * September 2009: Android 1.6. - * + * * <p>Applications targeting this or a later release will get these * new changes in behavior:</p> * <ul> @@ -247,10 +247,10 @@ public class Build { * </ul> */ public static final int DONUT = 4; - + /** * November 2009: Android 2.0 - * + * * <p>Applications targeting this or a later release will get these * new changes in behavior:</p> * <ul> @@ -267,22 +267,22 @@ public class Build { * </ul> */ public static final int ECLAIR = 5; - + /** * December 2009: Android 2.0.1 */ public static final int ECLAIR_0_1 = 6; - + /** * January 2010: Android 2.1 */ public static final int ECLAIR_MR1 = 7; - + /** * June 2010: Android 2.2 */ public static final int FROYO = 8; - + /** * November 2010: Android 2.3 * @@ -294,7 +294,7 @@ public class Build { * </ul> */ public static final int GINGERBREAD = 9; - + /** * February 2011: Android 2.3.3. */ @@ -339,12 +339,12 @@ public class Build { * </ul> */ public static final int HONEYCOMB = 11; - + /** * May 2011: Android 3.1. */ public static final int HONEYCOMB_MR1 = 12; - + /** * June 2011: Android 3.2. * @@ -597,8 +597,13 @@ public class Build { * Lollipop with an extra sugar coating on the outside! */ public static final int LOLLIPOP_MR1 = 22; + + /** + * M comes after L. + */ + public static final int MNC = CUR_DEVELOPMENT; } - + /** The type of build, like "user" or "eng". */ public static final String TYPE = getString("ro.build.type"); @@ -653,6 +658,7 @@ public class Build { public static boolean isFingerprintConsistent() { final String system = SystemProperties.get("ro.build.fingerprint"); final String vendor = SystemProperties.get("ro.vendor.build.fingerprint"); + final String bootimage = SystemProperties.get("ro.bootimage.build.fingerprint"); if (TextUtils.isEmpty(system)) { Slog.e(TAG, "Required ro.build.fingerprint is empty!"); @@ -667,6 +673,14 @@ public class Build { } } + if (!TextUtils.isEmpty(bootimage)) { + if (!Objects.equals(system, bootimage)) { + Slog.e(TAG, "Mismatched fingerprints; system reported " + system + + " but bootimage reported " + bootimage); + return false; + } + } + return true; } diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index 2d92c7b..d03365b 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -1286,7 +1286,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo * + icount.globalMethodInvocations()); * } * </pre> + * + * @deprecated Instruction counting is no longer supported. */ + @Deprecated public static class InstructionCount { private static final int NUM_INSTR = OpcodeInfo.MAXIMUM_PACKED_VALUE + 1; diff --git a/core/java/android/os/IProcessInfoService.aidl b/core/java/android/os/IProcessInfoService.aidl new file mode 100644 index 0000000..c98daa2 --- /dev/null +++ b/core/java/android/os/IProcessInfoService.aidl @@ -0,0 +1,29 @@ +/* + * Copyright 2015 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.os; + +/** {@hide} */ +interface IProcessInfoService +{ + /** + * For each PID in the given input array, write the current process state + * for that process into the output array, or ActivityManager.PROCESS_STATE_NONEXISTENT + * to indicate that no process with the given PID exists. + */ + void getProcessStatesFromPids(in int[] pids, out int[] states); +} + diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index c6b2151..4e8ec89 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -395,10 +395,17 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * connected to each other. The two sockets are indistinguishable. */ public static ParcelFileDescriptor[] createSocketPair() throws IOException { + return createSocketPair(SOCK_STREAM); + } + + /** + * @hide + */ + public static ParcelFileDescriptor[] createSocketPair(int type) throws IOException { try { final FileDescriptor fd0 = new FileDescriptor(); final FileDescriptor fd1 = new FileDescriptor(); - Os.socketpair(AF_UNIX, SOCK_STREAM, 0, fd0, fd1); + Os.socketpair(AF_UNIX, type, 0, fd0, fd1); return new ParcelFileDescriptor[] { new ParcelFileDescriptor(fd0), new ParcelFileDescriptor(fd1) }; @@ -417,11 +424,18 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * This can also be used to detect remote crashes. */ public static ParcelFileDescriptor[] createReliableSocketPair() throws IOException { + return createReliableSocketPair(SOCK_STREAM); + } + + /** + * @hide + */ + public static ParcelFileDescriptor[] createReliableSocketPair(int type) throws IOException { try { final FileDescriptor[] comm = createCommSocketPair(); final FileDescriptor fd0 = new FileDescriptor(); final FileDescriptor fd1 = new FileDescriptor(); - Os.socketpair(AF_UNIX, SOCK_STREAM, 0, fd0, fd1); + Os.socketpair(AF_UNIX, type, 0, fd0, fd1); return new ParcelFileDescriptor[] { new ParcelFileDescriptor(fd0, comm[0]), new ParcelFileDescriptor(fd1, comm[1]) }; diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 5018711..0b55998 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -145,7 +145,7 @@ public final class StrictMode { * in {@link VmPolicy.Builder#detectAll()}. Apps can still always opt-into * detection using {@link VmPolicy.Builder#detectCleartextNetwork()}. */ - private static final String CLEARTEXT_PROPERTY = "persist.sys.strictmode.nonssl"; + private static final String CLEARTEXT_PROPERTY = "persist.sys.strictmode.clear"; // Only log a duplicate stack trace to the logs every second. private static final long MIN_LOG_INTERVAL_MS = 1000; @@ -184,8 +184,16 @@ public final class StrictMode { */ public static final int DETECT_CUSTOM = 0x08; // for ThreadPolicy + /** + * For StrictMode.noteResourceMismatch() + * + * @hide + */ + public static final int DETECT_RESOURCE_MISMATCH = 0x10; // for ThreadPolicy + private static final int ALL_THREAD_DETECT_BITS = - DETECT_DISK_WRITE | DETECT_DISK_READ | DETECT_NETWORK | DETECT_CUSTOM; + DETECT_DISK_WRITE | DETECT_DISK_READ | DETECT_NETWORK | DETECT_CUSTOM | + DETECT_RESOURCE_MISMATCH; // Byte 2: Process-policy @@ -460,6 +468,22 @@ public final class StrictMode { } /** + * Disable detection of mismatches between defined resource types + * and getter calls. + */ + public Builder permitResourceMismatches() { + return disable(DETECT_RESOURCE_MISMATCH); + } + + /** + * Enable detection of mismatches between defined resource types + * and getter calls. + */ + public Builder detectResourceMismatches() { + return enable(DETECT_RESOURCE_MISMATCH); + } + + /** * Enable detection of disk writes. */ public Builder detectDiskWrites() { @@ -739,8 +763,6 @@ public final class StrictMode { * This inspects both IPv4/IPv6 and TCP/UDP network traffic, but it * may be subject to false positives, such as when STARTTLS * protocols or HTTP proxies are used. - * - * @hide */ public Builder detectCleartextNetwork() { return enable(DETECT_VM_CLEARTEXT_NETWORK); @@ -760,7 +782,6 @@ public final class StrictMode { * detected. * * @see #detectCleartextNetwork() - * @hide */ public Builder penaltyDeathOnCleartextNetwork() { return enable(PENALTY_DEATH_ON_CLEARTEXT_NETWORK); @@ -923,6 +944,15 @@ public final class StrictMode { } /** + * @hide + */ + private static class StrictModeResourceMismatchViolation extends StrictModeViolation { + public StrictModeResourceMismatchViolation(int policyMask, Object tag) { + super(policyMask, DETECT_RESOURCE_MISMATCH, tag != null ? tag.toString() : null); + } + } + + /** * Returns the bitmask of the current thread's policy. * * @return the bitmask of all the DETECT_* and PENALTY_* bits currently enabled @@ -1197,6 +1227,20 @@ public final class StrictMode { startHandlingViolationException(e); } + // Not part of BlockGuard.Policy; just part of StrictMode: + void onResourceMismatch(Object tag) { + if ((mPolicyMask & DETECT_RESOURCE_MISMATCH) == 0) { + return; + } + if (tooManyViolationsThisLoop()) { + return; + } + BlockGuard.BlockGuardPolicyException e = + new StrictModeResourceMismatchViolation(mPolicyMask, tag); + e.fillInStackTrace(); + startHandlingViolationException(e); + } + // Part of BlockGuard.Policy interface: public void onReadFromDisk() { if ((mPolicyMask & DETECT_DISK_READ) == 0) { @@ -2083,6 +2127,26 @@ public final class StrictMode { } /** + * For code to note that a resource was obtained using a type other than + * its defined type. This is a no-op unless the current thread's + * {@link android.os.StrictMode.ThreadPolicy} has + * {@link android.os.StrictMode.ThreadPolicy.Builder#detectResourceMismatches()} + * enabled. + * + * @param tag an object for the exception stack trace that's + * built if when this fires. + * @hide + */ + public static void noteResourceMismatch(Object tag) { + BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); + if (!(policy instanceof AndroidBlockGuardPolicy)) { + // StrictMode not enabled. + return; + } + ((AndroidBlockGuardPolicy) policy).onResourceMismatch(tag); + } + + /** * @hide */ public static void noteDiskRead() { diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java index 30f0c6a..90d30d6 100644 --- a/core/java/android/print/PrintAttributes.java +++ b/core/java/android/print/PrintAttributes.java @@ -45,11 +45,22 @@ public final class PrintAttributes implements Parcelable { private static final int VALID_COLOR_MODES = COLOR_MODE_MONOCHROME | COLOR_MODE_COLOR; + /** Duplex mode: No duplexing. */ + public static final int DUPLEX_MODE_NONE = 1 << 0; + /** Duplex mode: Pages are turned sideways along the long edge - like a book. */ + public static final int DUPLEX_MODE_LONG_EDGE = 1 << 1; + /** Duplex mode: Pages are turned upwards along the short edge - like a notpad. */ + public static final int DUPLEX_MODE_SHORT_EDGE = 1 << 2; + + private static final int VALID_DUPLEX_MODES = + DUPLEX_MODE_NONE | DUPLEX_MODE_LONG_EDGE | DUPLEX_MODE_SHORT_EDGE; + private MediaSize mMediaSize; private Resolution mResolution; private Margins mMinMargins; private int mColorMode; + private int mDuplexMode = DUPLEX_MODE_NONE; PrintAttributes() { /* hide constructor */ @@ -60,6 +71,7 @@ public final class PrintAttributes implements Parcelable { mResolution = (parcel.readInt() == 1) ? Resolution.createFromParcel(parcel) : null; mMinMargins = (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null; mColorMode = parcel.readInt(); + mDuplexMode = parcel.readInt(); } /** @@ -74,7 +86,7 @@ public final class PrintAttributes implements Parcelable { /** * Sets the media size. * - * @param The media size. + * @param mediaSize The media size. * * @hide */ @@ -94,7 +106,7 @@ public final class PrintAttributes implements Parcelable { /** * Sets the resolution. * - * @param The resolution. + * @param resolution The resolution. * * @hide */ @@ -130,7 +142,7 @@ public final class PrintAttributes implements Parcelable { * </strong> * </p> * - * @param The margins. + * @param margins The margins. * * @hide */ @@ -153,7 +165,7 @@ public final class PrintAttributes implements Parcelable { /** * Sets the color mode. * - * @param The color mode. + * @param colorMode The color mode. * * @see #COLOR_MODE_MONOCHROME * @see #COLOR_MODE_COLOR @@ -179,6 +191,35 @@ public final class PrintAttributes implements Parcelable { } /** + * Gets the duplex mode. + * + * @return The duplex mode. + * + * @see #DUPLEX_MODE_NONE + * @see #DUPLEX_MODE_LONG_EDGE + * @see #DUPLEX_MODE_SHORT_EDGE + */ + public int getDuplexMode() { + return mDuplexMode; + } + + /** + * Sets the duplex mode. + * + * @param duplexMode The duplex mode. + * + * @see #DUPLEX_MODE_NONE + * @see #DUPLEX_MODE_LONG_EDGE + * @see #DUPLEX_MODE_SHORT_EDGE + * + * @hide + */ + public void setDuplexMode(int duplexMode) { + enforceValidDuplexMode(duplexMode); + mDuplexMode = duplexMode; + } + + /** * Gets a new print attributes instance which is in portrait orientation, * which is the media size is in portrait and all orientation dependent * attributes such as resolution and margins are properly adjusted. @@ -211,6 +252,7 @@ public final class PrintAttributes implements Parcelable { attributes.setMinMargins(getMinMargins()); attributes.setColorMode(getColorMode()); + attributes.setDuplexMode(getDuplexMode()); return attributes; } @@ -248,6 +290,7 @@ public final class PrintAttributes implements Parcelable { attributes.setMinMargins(getMinMargins()); attributes.setColorMode(getColorMode()); + attributes.setDuplexMode(getDuplexMode()); return attributes; } @@ -273,6 +316,7 @@ public final class PrintAttributes implements Parcelable { parcel.writeInt(0); } parcel.writeInt(mColorMode); + parcel.writeInt(mDuplexMode); } @Override @@ -285,6 +329,7 @@ public final class PrintAttributes implements Parcelable { final int prime = 31; int result = 1; result = prime * result + mColorMode; + result = prime * result + mDuplexMode; result = prime * result + ((mMinMargins == null) ? 0 : mMinMargins.hashCode()); result = prime * result + ((mMediaSize == null) ? 0 : mMediaSize.hashCode()); result = prime * result + ((mResolution == null) ? 0 : mResolution.hashCode()); @@ -306,6 +351,9 @@ public final class PrintAttributes implements Parcelable { if (mColorMode != other.mColorMode) { return false; } + if (mDuplexMode != other.mDuplexMode) { + return false; + } if (mMinMargins == null) { if (other.mMinMargins != null) { return false; @@ -344,6 +392,7 @@ public final class PrintAttributes implements Parcelable { builder.append(", resolution: ").append(mResolution); builder.append(", minMargins: ").append(mMinMargins); builder.append(", colorMode: ").append(colorModeToString(mColorMode)); + builder.append(", duplexMode: ").append(duplexModeToString(mDuplexMode)); builder.append("}"); return builder.toString(); } @@ -354,6 +403,7 @@ public final class PrintAttributes implements Parcelable { mResolution = null; mMinMargins = null; mColorMode = 0; + mDuplexMode = DUPLEX_MODE_NONE; } /** @@ -364,6 +414,7 @@ public final class PrintAttributes implements Parcelable { mResolution = other.mResolution; mMinMargins = other.mMinMargins; mColorMode = other.mColorMode; + mDuplexMode = other.mDuplexMode; } /** @@ -1270,17 +1321,41 @@ public final class PrintAttributes implements Parcelable { case COLOR_MODE_COLOR: { return "COLOR_MODE_COLOR"; } - default: + default: { return "COLOR_MODE_UNKNOWN"; + } + } + } + + static String duplexModeToString(int duplexMode) { + switch (duplexMode) { + case DUPLEX_MODE_NONE: { + return "DUPLEX_MODE_NONE"; + } + case DUPLEX_MODE_LONG_EDGE: { + return "DUPLEX_MODE_LONG_EDGE"; + } + case DUPLEX_MODE_SHORT_EDGE: { + return "DUPLEX_MODE_SHORT_EDGE"; + } + default: { + return "DUPLEX_MODE_UNKNOWN"; + } } } static void enforceValidColorMode(int colorMode) { - if ((colorMode & VALID_COLOR_MODES) == 0 && Integer.bitCount(colorMode) == 1) { + if ((colorMode & VALID_COLOR_MODES) == 0 || Integer.bitCount(colorMode) != 1) { throw new IllegalArgumentException("invalid color mode: " + colorMode); } } + static void enforceValidDuplexMode(int duplexMode) { + if ((duplexMode & VALID_DUPLEX_MODES) == 0 || Integer.bitCount(duplexMode) != 1) { + throw new IllegalArgumentException("invalid duplex mode: " + duplexMode); + } + } + /** * Builder for creating {@link PrintAttributes}. */ @@ -1331,15 +1406,31 @@ public final class PrintAttributes implements Parcelable { * @see PrintAttributes#COLOR_MODE_COLOR */ public Builder setColorMode(int colorMode) { - if (Integer.bitCount(colorMode) > 1) { - throw new IllegalArgumentException("can specify at most one colorMode bit."); - } mAttributes.setColorMode(colorMode); return this; } /** + * Sets the duplex mode. + * + * @param duplexMode A valid duplex mode or zero. + * @return This builder. + * + * @see PrintAttributes#DUPLEX_MODE_NONE + * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE + * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE + */ + public Builder setDuplexMode(int duplexMode) { + mAttributes.setDuplexMode(duplexMode); + return this; + } + + /** * Creates a new {@link PrintAttributes} instance. + * <p> + * If you do not specify a duplex mode, the default + * {@link #DUPLEX_MODE_NONE} will be used. + * </p> * * @return The new instance. */ diff --git a/core/java/android/print/PrinterCapabilitiesInfo.java b/core/java/android/print/PrinterCapabilitiesInfo.java index 806a89d..96f3185 100644 --- a/core/java/android/print/PrinterCapabilitiesInfo.java +++ b/core/java/android/print/PrinterCapabilitiesInfo.java @@ -47,7 +47,8 @@ public final class PrinterCapabilitiesInfo implements Parcelable { private static final int PROPERTY_MEDIA_SIZE = 0; private static final int PROPERTY_RESOLUTION = 1; private static final int PROPERTY_COLOR_MODE = 2; - private static final int PROPERTY_COUNT = 3; + private static final int PROPERTY_DUPLEX_MODE = 3; + private static final int PROPERTY_COUNT = 4; private static final Margins DEFAULT_MARGINS = new Margins(0, 0, 0, 0); @@ -56,6 +57,7 @@ public final class PrinterCapabilitiesInfo implements Parcelable { private List<Resolution> mResolutions; private int mColorModes; + private int mDuplexModes; private final int[] mDefaults = new int[PROPERTY_COUNT]; @@ -106,6 +108,7 @@ public final class PrinterCapabilitiesInfo implements Parcelable { } mColorModes = other.mColorModes; + mDuplexModes = other.mDuplexModes; final int defaultCount = other.mDefaults.length; for (int i = 0; i < defaultCount; i++) { @@ -154,6 +157,19 @@ public final class PrinterCapabilitiesInfo implements Parcelable { } /** + * Gets the bit mask of supported duplex modes. + * + * @return The bit mask of supported duplex modes. + * + * @see PrintAttributes#DUPLEX_MODE_NONE + * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE + * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE + */ + public int getDuplexModes() { + return mDuplexModes; + } + + /** * Gets the default print attributes. * * @return The default attributes. @@ -178,6 +194,11 @@ public final class PrinterCapabilitiesInfo implements Parcelable { builder.setColorMode(colorMode); } + final int duplexMode = mDefaults[PROPERTY_DUPLEX_MODE]; + if (duplexMode > 0) { + builder.setDuplexMode(duplexMode); + } + return builder.build(); } @@ -187,6 +208,7 @@ public final class PrinterCapabilitiesInfo implements Parcelable { readResolutions(parcel); mColorModes = parcel.readInt(); + mDuplexModes = parcel.readInt(); readDefaults(parcel); } @@ -203,6 +225,7 @@ public final class PrinterCapabilitiesInfo implements Parcelable { writeResolutions(parcel); parcel.writeInt(mColorModes); + parcel.writeInt(mDuplexModes); writeDefaults(parcel); } @@ -215,6 +238,7 @@ public final class PrinterCapabilitiesInfo implements Parcelable { result = prime * result + ((mMediaSizes == null) ? 0 : mMediaSizes.hashCode()); result = prime * result + ((mResolutions == null) ? 0 : mResolutions.hashCode()); result = prime * result + mColorModes; + result = prime * result + mDuplexModes; result = prime * result + Arrays.hashCode(mDefaults); return result; } @@ -255,6 +279,9 @@ public final class PrinterCapabilitiesInfo implements Parcelable { if (mColorModes != other.mColorModes) { return false; } + if (mDuplexModes != other.mDuplexModes) { + return false; + } if (!Arrays.equals(mDefaults, other.mDefaults)) { return false; } @@ -269,6 +296,7 @@ public final class PrinterCapabilitiesInfo implements Parcelable { builder.append(", mediaSizes=").append(mMediaSizes); builder.append(", resolutions=").append(mResolutions); builder.append(", colorModes=").append(colorModesToString()); + builder.append(", duplexModes=").append(duplexModesToString()); builder.append("\"}"); return builder.toString(); } @@ -289,6 +317,22 @@ public final class PrinterCapabilitiesInfo implements Parcelable { return builder.toString(); } + private String duplexModesToString() { + StringBuilder builder = new StringBuilder(); + builder.append('['); + int duplexModes = mDuplexModes; + while (duplexModes != 0) { + final int duplexMode = 1 << Integer.numberOfTrailingZeros(duplexModes); + duplexModes &= ~duplexMode; + if (builder.length() > 1) { + builder.append(", "); + } + builder.append(PrintAttributes.duplexModeToString(duplexMode)); + } + builder.append(']'); + return builder.toString(); + } + private void writeMediaSizes(Parcel parcel) { if (mMediaSizes == null) { parcel.writeInt(0); @@ -495,19 +539,51 @@ public final class PrinterCapabilitiesInfo implements Parcelable { currentModes &= ~currentMode; PrintAttributes.enforceValidColorMode(currentMode); } - if ((colorModes & defaultColorMode) == 0) { - throw new IllegalArgumentException("Default color mode not in color modes."); - } - PrintAttributes.enforceValidColorMode(colorModes); + PrintAttributes.enforceValidColorMode(defaultColorMode); mPrototype.mColorModes = colorModes; mPrototype.mDefaults[PROPERTY_COLOR_MODE] = defaultColorMode; return this; } /** + * Sets the duplex modes. + * <p> + * <strong>Required:</strong> No + * </p> + * + * @param duplexModes The duplex mode bit mask. + * @param defaultDuplexMode The default duplex mode. + * @return This builder. + * + * @throws IllegalArgumentException If duplex modes contains an invalid + * mode bit or if the default duplex mode is invalid. + * + * @see PrintAttributes#DUPLEX_MODE_NONE + * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE + * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE + */ + public Builder setDuplexModes(int duplexModes, int defaultDuplexMode) { + int currentModes = duplexModes; + while (currentModes > 0) { + final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes)); + currentModes &= ~currentMode; + PrintAttributes.enforceValidDuplexMode(currentMode); + } + PrintAttributes.enforceValidDuplexMode(defaultDuplexMode); + mPrototype.mDuplexModes = duplexModes; + mPrototype.mDefaults[PROPERTY_DUPLEX_MODE] = defaultDuplexMode; + return this; + } + + /** * Crates a new {@link PrinterCapabilitiesInfo} enforcing that all * required properties have been specified. See individual methods * in this class for reference about required attributes. + * <p> + * <strong>Note:</strong> If you do not add supported duplex modes, + * {@link android.print.PrintAttributes#DUPLEX_MODE_NONE} will set + * as the only supported mode and also as the default duplex mode. + * </p> * * @return A new {@link PrinterCapabilitiesInfo}. * @@ -532,6 +608,10 @@ public final class PrinterCapabilitiesInfo implements Parcelable { if (mPrototype.mDefaults[PROPERTY_COLOR_MODE] == DEFAULT_UNDEFINED) { throw new IllegalStateException("No default color mode specified."); } + if (mPrototype.mDuplexModes == 0) { + setDuplexModes(PrintAttributes.DUPLEX_MODE_NONE, + PrintAttributes.DUPLEX_MODE_NONE); + } if (mPrototype.mMinMargins == null) { throw new IllegalArgumentException("margins cannot be null"); } @@ -558,4 +638,3 @@ public final class PrinterCapabilitiesInfo implements Parcelable { } }; } - diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java index d4c5cfb..3aa526d 100644 --- a/core/java/android/provider/Contacts.java +++ b/core/java/android/provider/Contacts.java @@ -2077,12 +2077,12 @@ public class Contacts { /** * Intents related to the Contacts app UI. - * @deprecated see {@link android.provider.ContactsContract} + * @deprecated Do not use. This is not supported. */ @Deprecated public static final class UI { /** - * @deprecated see {@link android.provider.ContactsContract} + * @deprecated Do not use. This is not supported. */ @Deprecated public UI() { @@ -2090,76 +2090,77 @@ public class Contacts { /** * The action for the default contacts list tab. - * @deprecated see {@link android.provider.ContactsContract} + * @deprecated Do not use. This is not supported. */ @Deprecated - public static final String LIST_DEFAULT = ContactsContract.Intents.UI.LIST_DEFAULT; + public static final String LIST_DEFAULT + = "com.android.contacts.action.LIST_DEFAULT"; /** * The action for the contacts list tab. - * @deprecated see {@link android.provider.ContactsContract} + * @deprecated Do not use. This is not supported. */ @Deprecated public static final String LIST_GROUP_ACTION = - ContactsContract.Intents.UI.LIST_GROUP_ACTION; + "com.android.contacts.action.LIST_GROUP"; /** * When in LIST_GROUP_ACTION mode, this is the group to display. - * @deprecated see {@link android.provider.ContactsContract} + * @deprecated Do not use. This is not supported. */ @Deprecated public static final String GROUP_NAME_EXTRA_KEY = - ContactsContract.Intents.UI.GROUP_NAME_EXTRA_KEY; + "com.android.contacts.extra.GROUP"; /** * The action for the all contacts list tab. - * @deprecated see {@link android.provider.ContactsContract} + * @deprecated Do not use. This is not supported. */ @Deprecated public static final String LIST_ALL_CONTACTS_ACTION = - ContactsContract.Intents.UI.LIST_ALL_CONTACTS_ACTION; + "com.android.contacts.action.LIST_ALL_CONTACTS"; /** * The action for the contacts with phone numbers list tab. - * @deprecated see {@link android.provider.ContactsContract} + * @deprecated Do not use. This is not supported. */ @Deprecated public static final String LIST_CONTACTS_WITH_PHONES_ACTION = - ContactsContract.Intents.UI.LIST_CONTACTS_WITH_PHONES_ACTION; + "com.android.contacts.action.LIST_CONTACTS_WITH_PHONES"; /** * The action for the starred contacts list tab. - * @deprecated see {@link android.provider.ContactsContract} + * @deprecated Do not use. This is not supported. */ @Deprecated public static final String LIST_STARRED_ACTION = - ContactsContract.Intents.UI.LIST_STARRED_ACTION; + "com.android.contacts.action.LIST_STARRED"; /** * The action for the frequent contacts list tab. - * @deprecated see {@link android.provider.ContactsContract} + * @deprecated Do not use. This is not supported. */ @Deprecated public static final String LIST_FREQUENT_ACTION = - ContactsContract.Intents.UI.LIST_FREQUENT_ACTION; + "com.android.contacts.action.LIST_FREQUENT"; /** * The action for the "strequent" contacts list tab. It first lists the starred * contacts in alphabetical order and then the frequent contacts in descending * order of the number of times they have been contacted. - * @deprecated see {@link android.provider.ContactsContract} + * @deprecated Do not use. This is not supported. */ @Deprecated public static final String LIST_STREQUENT_ACTION = - ContactsContract.Intents.UI.LIST_STREQUENT_ACTION; + "com.android.contacts.action.LIST_STREQUENT"; /** * A key for to be used as an intent extra to set the activity * title to a custom String value. - * @deprecated see {@link android.provider.ContactsContract} + * @deprecated Do not use. This is not supported. */ @Deprecated public static final String TITLE_EXTRA_KEY = - ContactsContract.Intents.UI.TITLE_EXTRA_KEY; + "com.android.contacts.extra.TITLE_EXTRA"; /** * Activity Action: Display a filtered list of contacts @@ -2168,20 +2169,20 @@ public class Contacts { * filtering * <p> * Output: Nothing. - * @deprecated see {@link android.provider.ContactsContract} + * @deprecated Do not use. This is not supported. */ @Deprecated public static final String FILTER_CONTACTS_ACTION = - ContactsContract.Intents.UI.FILTER_CONTACTS_ACTION; + "com.android.contacts.action.FILTER_CONTACTS"; /** * Used as an int extra field in {@link #FILTER_CONTACTS_ACTION} * intents to supply the text on which to filter. - * @deprecated see {@link android.provider.ContactsContract} + * @deprecated Do not use. This is not supported. */ @Deprecated public static final String FILTER_TEXT_EXTRA_KEY = - ContactsContract.Intents.UI.FILTER_TEXT_EXTRA_KEY; + "com.android.contacts.extra.FILTER_TEXT"; } /** diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 18a9eb1..67ac043 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -197,12 +197,15 @@ public final class ContactsContract { * API for obtaining a pre-authorized version of a URI that normally requires special * permission (beyond READ_CONTACTS) to read. The caller obtaining the pre-authorized URI * must already have the necessary permissions to access the URI; otherwise a - * {@link SecurityException} will be thrown. + * {@link SecurityException} will be thrown. Unlike {@link Context#grantUriPermission}, + * this can be used to grant permissions that aren't explicitly required for the URI inside + * AndroidManifest.xml. For example, permissions that are only required when reading URIs + * that refer to the user's profile. * </p> * <p> * The authorized URI returned in the bundle contains an expiring token that allows the * caller to execute the query without having the special permissions that would normally - * be required. + * be required. The token expires in five minutes. * </p> * <p> * This API does not access disk, and should be safe to invoke from the UI thread. @@ -226,7 +229,6 @@ public final class ContactsContract { * } * </pre> * </p> - * @hide */ public static final class Authorization { /** @@ -1432,9 +1434,9 @@ public final class ContactsContract { * and {@link #CONTENT_MULTI_VCARD_URI} to indicate that the returned * vcard should not contain a photo. * - * @hide + * This is useful for obtaining a space efficient vcard. */ - public static final String QUERY_PARAMETER_VCARD_NO_PHOTO = "nophoto"; + public static final String QUERY_PARAMETER_VCARD_NO_PHOTO = "no_photo"; /** * Base {@link Uri} for referencing multiple {@link Contacts} entry, @@ -1790,52 +1792,26 @@ public final class ContactsContract { public static final String CONTENT_DIRECTORY = "suggestions"; /** - * Used with {@link Builder#addParameter} to specify what kind of data is - * supplied for the suggestion query. + * Used to specify what kind of data is supplied for the suggestion query. * * @hide */ public static final String PARAMETER_MATCH_NAME = "name"; /** - * Used with {@link Builder#addParameter} to specify what kind of data is - * supplied for the suggestion query. - * - * @hide - */ - public static final String PARAMETER_MATCH_EMAIL = "email"; - - /** - * Used with {@link Builder#addParameter} to specify what kind of data is - * supplied for the suggestion query. - * - * @hide - */ - public static final String PARAMETER_MATCH_PHONE = "phone"; - - /** - * Used with {@link Builder#addParameter} to specify what kind of data is - * supplied for the suggestion query. - * - * @hide - */ - public static final String PARAMETER_MATCH_NICKNAME = "nickname"; - - /** * A convenience builder for aggregation suggestion content URIs. - * - * TODO: change documentation for this class to use the builder. - * @hide */ public static final class Builder { private long mContactId; - private ArrayList<String> mKinds = new ArrayList<String>(); - private ArrayList<String> mValues = new ArrayList<String>(); + private final ArrayList<String> mValues = new ArrayList<String>(); private int mLimit; /** * Optional existing contact ID. If it is not provided, the search - * will be based exclusively on the values supplied with {@link #addParameter}. + * will be based exclusively on the values supplied with {@link #addNameParameter}. + * + * @param contactId contact to find aggregation suggestions for + * @return This Builder object to allow for chaining of calls to builder methods */ public Builder setContactId(long contactId) { this.mContactId = contactId; @@ -1843,28 +1819,31 @@ public final class ContactsContract { } /** - * A value that can be used when searching for an aggregation - * suggestion. + * Add a name to be used when searching for aggregation suggestions. * - * @param kind can be one of - * {@link AggregationSuggestions#PARAMETER_MATCH_NAME}, - * {@link AggregationSuggestions#PARAMETER_MATCH_EMAIL}, - * {@link AggregationSuggestions#PARAMETER_MATCH_NICKNAME}, - * {@link AggregationSuggestions#PARAMETER_MATCH_PHONE} + * @param name name to find aggregation suggestions for + * @return This Builder object to allow for chaining of calls to builder methods */ - public Builder addParameter(String kind, String value) { - if (!TextUtils.isEmpty(value)) { - mKinds.add(kind); - mValues.add(value); - } + public Builder addNameParameter(String name) { + mValues.add(name); return this; } + /** + * Sets the Maximum number of suggested aggregations that should be returned. + * @param limit The maximum number of suggested aggregations + * + * @return This Builder object to allow for chaining of calls to builder methods + */ public Builder setLimit(int limit) { mLimit = limit; return this; } + /** + * Combine all of the options that have been set and return a new {@link Uri} + * object for fetching aggregation suggestions. + */ public Uri build() { android.net.Uri.Builder builder = Contacts.CONTENT_URI.buildUpon(); builder.appendEncodedPath(String.valueOf(mContactId)); @@ -1873,9 +1852,10 @@ public final class ContactsContract { builder.appendQueryParameter("limit", String.valueOf(mLimit)); } - int count = mKinds.size(); + int count = mValues.size(); for (int i = 0; i < count; i++) { - builder.appendQueryParameter("query", mKinds.get(i) + ":" + mValues.get(i)); + builder.appendQueryParameter("query", PARAMETER_MATCH_NAME + + ":" + mValues.get(i)); } return builder.build(); @@ -2214,6 +2194,16 @@ public final class ContactsContract { public static final String CONTACT_ID = "contact_id"; /** + * Persistent unique id for each raw_contact within its account. + * This id is provided by its own data source, and can be used to backup metadata + * to the server. + * This should be unique within each set of account_name/account_type/data_set + * + * @hide + */ + public static final String BACKUP_ID = "backup_id"; + + /** * The data set within the account that this row belongs to. This allows * multiple sync adapters for the same account type to distinguish between * each others' data. @@ -2258,33 +2248,6 @@ public final class ContactsContract { public static final String DELETED = "deleted"; /** - * The "name_verified" flag: "1" means that the name fields on this raw - * contact can be trusted and therefore should be used for the entire - * aggregated contact. - * <p> - * If an aggregated contact contains more than one raw contact with a - * verified name, one of those verified names is chosen at random. - * If an aggregated contact contains no verified names, the - * name is chosen randomly from the constituent raw contacts. - * </p> - * <p> - * Updating this flag from "0" to "1" automatically resets it to "0" on - * all other raw contacts in the same aggregated contact. - * </p> - * <p> - * Sync adapters should only specify a value for this column when - * inserting a raw contact and leave it out when doing an update. - * </p> - * <p> - * The default value is "0" - * </p> - * <p>Type: INTEGER</p> - * - * @hide - */ - public static final String NAME_VERIFIED = "name_verified"; - - /** * The "read-only" flag: "0" by default, "1" if the row cannot be modified or * deleted except by a sync adapter. See {@link ContactsContract#CALLER_IS_SYNCADAPTER}. * <P>Type: INTEGER</P> @@ -2980,7 +2943,6 @@ public final class ContactsContract { DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DELETED); DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, CONTACT_ID); DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, STARRED); - DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, NAME_VERIFIED); android.content.Entity contact = new android.content.Entity(cv); // read data rows until the contact id changes @@ -4006,6 +3968,13 @@ public final class ContactsContract { public static final String MIMETYPE = "mimetype"; /** + * Hash id on the data fields, used for backup and restore. + * + * @hide + */ + public static final String HASH_ID = "hash_id"; + + /** * A reference to the {@link RawContacts#_ID} * that this data belongs to. */ @@ -7839,9 +7808,7 @@ public final class ContactsContract { } /** - * Private API for inquiring about the general status of the provider. - * - * @hide + * API for inquiring about the general status of the provider. */ public static final class ProviderStatus { @@ -7854,8 +7821,6 @@ public final class ContactsContract { /** * The content:// style URI for this table. Requests to this URI can be * performed on the UI thread because they are always unblocking. - * - * @hide */ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "provider_status"); @@ -7863,64 +7828,35 @@ public final class ContactsContract { /** * The MIME-type of {@link #CONTENT_URI} providing a directory of * settings. - * - * @hide */ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/provider_status"; /** * An integer representing the current status of the provider. - * - * @hide */ public static final String STATUS = "status"; /** * Default status of the provider. - * - * @hide */ public static final int STATUS_NORMAL = 0; /** * The status used when the provider is in the process of upgrading. Contacts * are temporarily unaccessible. - * - * @hide */ public static final int STATUS_UPGRADING = 1; /** - * The status used if the provider was in the process of upgrading but ran - * out of storage. The DATA1 column will contain the estimated amount of - * storage required (in bytes). Update status to STATUS_NORMAL to force - * the provider to retry the upgrade. - * - * @hide - */ - public static final int STATUS_UPGRADE_OUT_OF_MEMORY = 2; - - /** * The status used during a locale change. - * - * @hide */ public static final int STATUS_CHANGING_LOCALE = 3; /** * The status that indicates that there are no accounts and no contacts * on the device. - * - * @hide */ public static final int STATUS_NO_ACCOUNTS_NO_CONTACTS = 4; - - /** - * Additional data associated with the status. - * - * @hide - */ - public static final String DATA1 = "data1"; } /** @@ -8125,8 +8061,10 @@ public final class ContactsContract { public static final String EXTRA_TARGET_RECT = "android.provider.extra.TARGET_RECT"; /** - * Extra used to specify size of pivot dialog. - * @hide + * Extra used to specify size of QuickContacts. Not all implementations of QuickContacts + * will respect this extra's value. + * + * One of {@link #MODE_SMALL}, {@link #MODE_MEDIUM}, or {@link #MODE_LARGE}. */ public static final String EXTRA_MODE = "android.provider.extra.MODE"; @@ -8538,102 +8476,6 @@ public final class ContactsContract { public static final String EXTRA_EXCLUDE_MIMES = "exclude_mimes"; /** - * Intents related to the Contacts app UI. - * - * @hide - */ - public static final class UI { - /** - * The action for the default contacts list tab. - */ - public static final String LIST_DEFAULT = - "com.android.contacts.action.LIST_DEFAULT"; - - /** - * The action for the contacts list tab. - */ - public static final String LIST_GROUP_ACTION = - "com.android.contacts.action.LIST_GROUP"; - - /** - * When in LIST_GROUP_ACTION mode, this is the group to display. - */ - public static final String GROUP_NAME_EXTRA_KEY = "com.android.contacts.extra.GROUP"; - - /** - * The action for the all contacts list tab. - */ - public static final String LIST_ALL_CONTACTS_ACTION = - "com.android.contacts.action.LIST_ALL_CONTACTS"; - - /** - * The action for the contacts with phone numbers list tab. - */ - public static final String LIST_CONTACTS_WITH_PHONES_ACTION = - "com.android.contacts.action.LIST_CONTACTS_WITH_PHONES"; - - /** - * The action for the starred contacts list tab. - */ - public static final String LIST_STARRED_ACTION = - "com.android.contacts.action.LIST_STARRED"; - - /** - * The action for the frequent contacts list tab. - */ - public static final String LIST_FREQUENT_ACTION = - "com.android.contacts.action.LIST_FREQUENT"; - - /** - * The action for the "Join Contact" picker. - */ - public static final String PICK_JOIN_CONTACT_ACTION = - "com.android.contacts.action.JOIN_CONTACT"; - - /** - * The action for the "strequent" contacts list tab. It first lists the starred - * contacts in alphabetical order and then the frequent contacts in descending - * order of the number of times they have been contacted. - */ - public static final String LIST_STREQUENT_ACTION = - "com.android.contacts.action.LIST_STREQUENT"; - - /** - * A key for to be used as an intent extra to set the activity - * title to a custom String value. - */ - public static final String TITLE_EXTRA_KEY = - "com.android.contacts.extra.TITLE_EXTRA"; - - /** - * Activity Action: Display a filtered list of contacts - * <p> - * Input: Extra field {@link #FILTER_TEXT_EXTRA_KEY} is the text to use for - * filtering - * <p> - * Output: Nothing. - */ - public static final String FILTER_CONTACTS_ACTION = - "com.android.contacts.action.FILTER_CONTACTS"; - - /** - * Used as an int extra field in {@link #FILTER_CONTACTS_ACTION} - * intents to supply the text on which to filter. - */ - public static final String FILTER_TEXT_EXTRA_KEY = - "com.android.contacts.extra.FILTER_TEXT"; - - /** - * Used with JOIN_CONTACT action to set the target for aggregation. This action type - * uses contact ids instead of contact uris for the sake of backwards compatibility. - * <p> - * Type: LONG - */ - public static final String TARGET_CONTACT_ID_EXTRA_KEY - = "com.android.contacts.action.CONTACT_ID"; - } - - /** * Convenience class that contains string constants used * to create contact {@link android.content.Intent Intents}. */ @@ -8858,10 +8700,8 @@ public final class ContactsContract { * dialog to chose an account * <p> * Type: {@link Account} - * - * @hide */ - public static final String ACCOUNT = "com.android.contacts.extra.ACCOUNT"; + public static final String EXTRA_ACCOUNT = "android.provider.extra.ACCOUNT"; /** * Used to specify the data set within the account in which to create the @@ -8871,10 +8711,8 @@ public final class ContactsContract { * created in the base account, with no data set. * <p> * Type: String - * - * @hide */ - public static final String DATA_SET = "com.android.contacts.extra.DATA_SET"; + public static final String EXTRA_DATA_SET = "android.provider.extra.DATA_SET"; } } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 3e75460..dddbe78 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -132,7 +132,6 @@ public final class Settings { "android.settings.AIRPLANE_MODE_SETTINGS"; /** - * @hide * Activity Action: Modify Airplane mode settings using the users voice. * <p> * In some cases, a matching Activity may not exist, so ensure you safeguard against this. @@ -154,7 +153,6 @@ public final class Settings { * Output: Nothing. */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - @SystemApi public static final String ACTION_VOICE_CONTROL_AIRPLANE_MODE = "android.settings.VOICE_CONTROL_AIRPLANE_MODE"; @@ -308,7 +306,7 @@ public final class Settings { /** * Activity Action: Show settings to allow configuration of - * cast endpoints. + * {@link android.media.routing.MediaRouteService media route providers}. * <p> * In some cases, a matching Activity may not exist, so ensure you * safeguard against this. @@ -991,13 +989,11 @@ public final class Settings { public static final String EXTRA_INPUT_DEVICE_IDENTIFIER = "input_device_identifier"; /** - * @hide * Activity Extra: Enable or disable Airplane Mode. * <p> * This can be passed as an extra field to the {@link #ACTION_VOICE_CONTROL_AIRPLANE_MODE} * intent as a boolean. */ - @SystemApi public static final String EXTRA_AIRPLANE_MODE_ENABLED = "airplane_mode_enabled"; private static final String JID_RESOURCE_PREFIX = "android"; @@ -3657,6 +3653,7 @@ public final class Settings { * A flag containing settings used for biometric weak * @hide */ + @Deprecated public static final String LOCK_BIOMETRIC_WEAK_FLAGS = "lock_biometric_weak_flags"; @@ -3668,7 +3665,11 @@ public final class Settings { /** * Whether autolock is enabled (0 = false, 1 = true) + * + * @deprecated Use {@link android.app.KeyguardManager} to determine the state and security + * level of the keyguard. */ + @Deprecated public static final String LOCK_PATTERN_ENABLED = "lock_pattern_autolock"; /** @@ -3707,6 +3708,7 @@ public final class Settings { * Ids of the user-selected appwidgets on the lockscreen (comma-delimited). * @hide */ + @Deprecated public static final String LOCK_SCREEN_APPWIDGET_IDS = "lock_screen_appwidget_ids"; @@ -3720,6 +3722,7 @@ public final class Settings { * Id of the appwidget shown on the lock screen when appwidgets are disabled. * @hide */ + @Deprecated public static final String LOCK_SCREEN_FALLBACK_APPWIDGET_ID = "lock_screen_fallback_appwidget_id"; @@ -3727,6 +3730,7 @@ public final class Settings { * Index of the lockscreen appwidget to restore, -1 if none. * @hide */ + @Deprecated public static final String LOCK_SCREEN_STICKY_APPWIDGET = "lock_screen_sticky_appwidget"; @@ -5101,6 +5105,7 @@ public final class Settings { * Whether Theater Mode is on. * {@hide} */ + @SystemApi public static final String THEATER_MODE_ON = "theater_mode_on"; /** diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 0cde4f2..65e6988 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -40,15 +40,16 @@ import java.util.Locale; /** * Top-level service of the current global voice interactor, which is providing - * support for hotwording etc. + * support for hotwording, the back-end of a {@link android.app.VoiceInteractor}, etc. * The current VoiceInteractionService that has been selected by the user is kept * always running by the system, to allow it to do things like listen for hotwords - * in the background. + * in the background to instigate voice interactions. * * <p>Because this service is always running, it should be kept as lightweight as * possible. Heavy-weight operations (including showing UI) should be implemented - * in the associated {@link android.service.voice.VoiceInteractionSessionService} - * that only runs while the operation is active. + * in the associated {@link android.service.voice.VoiceInteractionSessionService} when + * an actual voice interaction is taking place, and that service should run in a + * separate process from this one. */ public class VoiceInteractionService extends Service { /** diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 749f813..19d14bf 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -16,7 +16,6 @@ package android.service.voice; -import android.annotation.SystemApi; import android.app.Dialog; import android.app.Instrumentation; import android.content.Context; @@ -54,7 +53,15 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; /** - * An active interaction session, started by a {@link VoiceInteractionService}. + * An active voice interaction session, providing a facility for the implementation + * to interact with the user in the voice interaction layer. This interface is no shown + * by default, but you can request that it be shown with {@link #showWindow()}, which + * will result in a later call to {@link #onCreateContentView()} in which the UI can be + * built + * + * <p>A voice interaction session can be self-contained, ultimately calling {@link #finish} + * when done. It can also initiate voice interactions with applications by calling + * {@link #startVoiceActivity}</p>. */ public abstract class VoiceInteractionSession implements KeyEvent.Callback { static final String TAG = "VoiceInteractionSession"; @@ -168,10 +175,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } }; - /** - * @hide - */ - @SystemApi public static class Request { final IVoiceInteractorRequest mInterface = new IVoiceInteractorRequest.Stub() { @Override @@ -255,10 +258,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } } - /** - * @hide - */ - @SystemApi public static class Caller { final String packageName; final int uid; @@ -354,10 +353,8 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { final MyCallbacks mCallbacks = new MyCallbacks(); /** - * @hide * Information about where interesting parts of the input method UI appear. */ - @SystemApi public static final class Insets { /** * This is the part of the UI that is the main content. It is @@ -477,10 +474,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { mContentFrame = (FrameLayout)mRootView.findViewById(android.R.id.content); } - /** - * @hide - */ - @SystemApi public void showWindow() { if (DEBUG) Log.v(TAG, "Showing window: mWindowAdded=" + mWindowAdded + " mWindowVisible=" + mWindowVisible); @@ -509,10 +502,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } } - /** - * @hide - */ - @SystemApi public void hideWindow() { if (mWindowVisible) { mWindow.hide(); @@ -521,13 +510,11 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } /** - * @hide * You can call this to customize the theme used by your IME's window. * This must be set before {@link #onCreate}, so you * will typically call it in your constructor with the resource ID * of your custom theme. */ - @SystemApi public void setTheme(int theme) { if (mWindow != null) { throw new IllegalStateException("Must be called before onCreate()"); @@ -536,7 +523,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } /** - * @hide * Ask that a new activity be started for voice interaction. This will create a * new dedicated task in the activity manager for this voice interaction session; * this means that {@link Intent#FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_NEW_TASK} @@ -557,7 +543,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { * always have {@link Intent#CATEGORY_VOICE Intent.CATEGORY_VOICE} added to it, since * this is part of a voice interaction. */ - @SystemApi public void startVoiceActivity(Intent intent) { if (mToken == null) { throw new IllegalStateException("Can't call before onCreate()"); @@ -573,19 +558,15 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } /** - * @hide * Convenience for inflating views. */ - @SystemApi public LayoutInflater getLayoutInflater() { return mInflater; } /** - * @hide * Retrieve the window being used to show the session's UI. */ - @SystemApi public Dialog getWindow() { return mWindow; } @@ -631,10 +612,8 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } /** - * @hide * Hook in which to create the session's UI. */ - @SystemApi public View onCreateContentView() { return null; } @@ -647,42 +626,22 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } - /** - * @hide - */ - @SystemApi public boolean onKeyDown(int keyCode, KeyEvent event) { return false; } - /** - * @hide - */ - @SystemApi public boolean onKeyLongPress(int keyCode, KeyEvent event) { return false; } - /** - * @hide - */ - @SystemApi public boolean onKeyUp(int keyCode, KeyEvent event) { return false; } - /** - * @hide - */ - @SystemApi public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { return false; } - /** - * @hide - */ - @SystemApi public void onBackPressed() { finish(); } @@ -697,14 +656,12 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } /** - * @hide * Compute the interesting insets into your UI. The default implementation * uses the entire window frame as the insets. The default touchable * insets are {@link Insets#TOUCHABLE_INSETS_FRAME}. * * @param outInsets Fill in with the current UI insets. */ - @SystemApi public void onComputeInsets(Insets outInsets) { int[] loc = mTmpLocation; View decor = getWindow().getWindow().getDecorView(); @@ -718,8 +675,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } /** - * @hide - * @SystemApi * Called when a task initiated by {@link #startVoiceActivity(android.content.Intent)} * has actually started. * @@ -731,8 +686,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } /** - * @hide - * @SystemApi * Called when the last activity of a task initiated by * {@link #startVoiceActivity(android.content.Intent)} has finished. The default * implementation calls {@link #finish()} on the assumption that this represents @@ -748,8 +701,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } /** - * @hide - * @SystemApi * Request to query for what extended commands the session supports. * * @param caller Who is making the request. @@ -764,8 +715,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } /** - * @hide - * @SystemApi * Request to confirm with the user before proceeding with an unrecoverable operation, * corresponding to a {@link android.app.VoiceInteractor.ConfirmationRequest * VoiceInteractor.ConfirmationRequest}. @@ -781,8 +730,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { Bundle extras); /** - * @hide - * @SystemApi * Request to complete the voice interaction session because the voice activity successfully * completed its interaction using voice. Corresponds to * {@link android.app.VoiceInteractor.CompleteVoiceRequest @@ -804,8 +751,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } /** - * @hide - * @SystemApi * Request to abort the voice interaction session because the voice activity can not * complete its interaction using voice. Corresponds to * {@link android.app.VoiceInteractor.AbortVoiceRequest @@ -824,8 +769,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } /** - * @hide - * @SystemApi * Process an arbitrary extended command from the caller, * corresponding to a {@link android.app.VoiceInteractor.CommandRequest * VoiceInteractor.CommandRequest}. @@ -840,8 +783,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { public abstract void onCommand(Caller caller, Request request, String command, Bundle extras); /** - * @hide - * @SystemApi * Called when the {@link android.app.VoiceInteractor} has asked to cancel a {@link Request} * that was previously delivered to {@link #onConfirm} or {@link #onCommand}. * diff --git a/core/java/android/speech/tts/ITextToSpeechCallback.aidl b/core/java/android/speech/tts/ITextToSpeechCallback.aidl index 899515f..d785c3f 100644 --- a/core/java/android/speech/tts/ITextToSpeechCallback.aidl +++ b/core/java/android/speech/tts/ITextToSpeechCallback.aidl @@ -40,7 +40,7 @@ oneway interface ITextToSpeechCallback { * * @param utteranceId Unique id identifying synthesis request. */ - void onStop(String utteranceId); + void onStop(String utteranceId, boolean isStarted); /** * Tells the client that the synthesis has failed. diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index c59ca8a..06e9ce0 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -2066,10 +2066,10 @@ public class TextToSpeech { private boolean mEstablished; private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() { - public void onStop(String utteranceId) throws RemoteException { + public void onStop(String utteranceId, boolean isStarted) throws RemoteException { UtteranceProgressListener listener = mUtteranceProgressListener; if (listener != null) { - listener.onDone(utteranceId); + listener.onStop(utteranceId, isStarted); } }; diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java index 9bb7f02..02c9a36 100644 --- a/core/java/android/speech/tts/TextToSpeechService.java +++ b/core/java/android/speech/tts/TextToSpeechService.java @@ -455,10 +455,37 @@ public abstract class TextToSpeechService extends Service { private class SynthHandler extends Handler { private SpeechItem mCurrentSpeechItem = null; + private ArrayList<Object> mFlushedObjects = new ArrayList<Object>(); + private boolean mFlushAll; + public SynthHandler(Looper looper) { super(looper); } + private void startFlushingSpeechItems(Object callerIdentity) { + synchronized (mFlushedObjects) { + if (callerIdentity == null) { + mFlushAll = true; + } else { + mFlushedObjects.add(callerIdentity); + } + } + } + private void endFlushingSpeechItems(Object callerIdentity) { + synchronized (mFlushedObjects) { + if (callerIdentity == null) { + mFlushAll = false; + } else { + mFlushedObjects.remove(callerIdentity); + } + } + } + private boolean isFlushed(SpeechItem speechItem) { + synchronized (mFlushedObjects) { + return mFlushAll || mFlushedObjects.contains(speechItem.getCallerIdentity()); + } + } + private synchronized SpeechItem getCurrentSpeechItem() { return mCurrentSpeechItem; } @@ -522,9 +549,13 @@ public abstract class TextToSpeechService extends Service { Runnable runnable = new Runnable() { @Override public void run() { - setCurrentSpeechItem(speechItem); - speechItem.play(); - setCurrentSpeechItem(null); + if (isFlushed(speechItem)) { + speechItem.stop(); + } else { + setCurrentSpeechItem(speechItem); + speechItem.play(); + setCurrentSpeechItem(null); + } } }; Message msg = Message.obtain(this, runnable); @@ -552,12 +583,14 @@ public abstract class TextToSpeechService extends Service { * * Called on a service binder thread. */ - public int stopForApp(Object callerIdentity) { + public int stopForApp(final Object callerIdentity) { if (callerIdentity == null) { return TextToSpeech.ERROR; } - removeCallbacksAndMessages(callerIdentity); + // Flush pending messages from callerIdentity + startFlushingSpeechItems(callerIdentity); + // This stops writing data to the file / or publishing // items to the audio playback handler. // @@ -573,20 +606,39 @@ public abstract class TextToSpeechService extends Service { // Remove any enqueued audio too. mAudioPlaybackHandler.stopForApp(callerIdentity); + // Stop flushing pending messages + Runnable runnable = new Runnable() { + @Override + public void run() { + endFlushingSpeechItems(callerIdentity); + } + }; + sendMessage(Message.obtain(this, runnable)); return TextToSpeech.SUCCESS; } public int stopAll() { + // Order to flush pending messages + startFlushingSpeechItems(null); + // Stop the current speech item unconditionally . SpeechItem current = setCurrentSpeechItem(null); if (current != null) { current.stop(); } - // Remove all other items from the queue. - removeCallbacksAndMessages(null); // Remove all pending playback as well. mAudioPlaybackHandler.stop(); + // Message to stop flushing pending messages + Runnable runnable = new Runnable() { + @Override + public void run() { + endFlushingSpeechItems(null); + } + }; + sendMessage(Message.obtain(this, runnable)); + + return TextToSpeech.SUCCESS; } } @@ -698,7 +750,6 @@ public abstract class TextToSpeechService extends Service { return mCallerIdentity; } - public int getCallerUid() { return mCallerUid; } @@ -752,6 +803,10 @@ public abstract class TextToSpeechService extends Service { protected synchronized boolean isStopped() { return mStopped; } + + protected synchronized boolean isStarted() { + return mStarted; + } } /** @@ -777,7 +832,7 @@ public abstract class TextToSpeechService extends Service { public void dispatchOnStop() { final String utteranceId = getUtteranceId(); if (utteranceId != null) { - mCallbacks.dispatchOnStop(getCallerIdentity(), utteranceId); + mCallbacks.dispatchOnStop(getCallerIdentity(), utteranceId, isStarted()); } } @@ -940,6 +995,8 @@ public abstract class TextToSpeechService extends Service { // turn implies that synthesis would not have started. synthesisCallback.stop(); TextToSpeechService.this.onStop(); + } else { + dispatchOnStop(); } } @@ -1345,11 +1402,11 @@ public abstract class TextToSpeechService extends Service { } } - public void dispatchOnStop(Object callerIdentity, String utteranceId) { + public void dispatchOnStop(Object callerIdentity, String utteranceId, boolean started) { ITextToSpeechCallback cb = getCallbackFor(callerIdentity); if (cb == null) return; try { - cb.onStop(utteranceId); + cb.onStop(utteranceId, started); } catch (RemoteException e) { Log.e(TAG, "Callback onStop failed: " + e); } diff --git a/core/java/android/speech/tts/UtteranceProgressListener.java b/core/java/android/speech/tts/UtteranceProgressListener.java index 6769794..9eb22ef 100644 --- a/core/java/android/speech/tts/UtteranceProgressListener.java +++ b/core/java/android/speech/tts/UtteranceProgressListener.java @@ -60,6 +60,20 @@ public abstract class UtteranceProgressListener { } /** + * Called when an utterance has been stopped while in progress or flushed from the + * synthesis queue. This can happen if client calls {@link TextToSpeech#stop()} + * or use {@link TextToSpeech#QUEUE_FLUSH} as an argument in + * {@link TextToSpeech#speak} or {@link TextToSpeech#synthesizeToFile} methods. + * + * @param utteranceId the utterance ID of the utterance. + * @param isStarted If true, then utterance was interrupted while being synthesized + * and it's output is incomplete. If it's false, then utterance was flushed + * before the synthesis started. + */ + public void onStop(String utteranceId, boolean isStarted) { + } + + /** * Wraps an old deprecated OnUtteranceCompletedListener with a shiny new * progress listener. * @@ -83,6 +97,11 @@ public abstract class UtteranceProgressListener { // Left unimplemented, has no equivalent in the old // API. } + + @Override + public void onStop(String utteranceId, boolean isStarted) { + listener.onUtteranceCompleted(utteranceId); + } }; } } diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 74b7b69..5b07397 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -28,6 +28,8 @@ import android.util.Log; import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; +import java.util.Arrays; + /** * StaticLayout is a Layout for text that will not be edited after it * is laid out. Use {@link DynamicLayout} for text that may change. @@ -161,7 +163,12 @@ public class StaticLayout extends Layout { float spacingadd, boolean includepad, boolean trackpad, float ellipsizedWidth, TextUtils.TruncateAt ellipsize) { - int[] breakOpp = null; + LineBreaks lineBreaks = new LineBreaks(); + // store span end locations + int[] spanEndCache = new int[4]; + // store fontMetrics per span range + // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range) + int[] fmCache = new int[4 * 4]; final String localeLanguageTag = paint.getTextLocale().toLanguageTag(); mLineCount = 0; @@ -186,7 +193,7 @@ public class StaticLayout extends Layout { else paraEnd++; - int firstWidthLineLimit = mLineCount + 1; + int firstWidthLineCount = 1; int firstWidth = outerWidth; int restWidth = outerWidth; @@ -204,9 +211,8 @@ public class StaticLayout extends Layout { // leading margin spans, not just this particular one if (lms instanceof LeadingMarginSpan2) { LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms; - int lmsFirstLine = getLineForOffset(spanned.getSpanStart(lms2)); - firstWidthLineLimit = Math.max(firstWidthLineLimit, - lmsFirstLine + lms2.getLeadingMarginLineCount()); + firstWidthLineCount = Math.max(firstWidthLineCount, + lms2.getLeadingMarginLineCount()); } } @@ -242,34 +248,23 @@ public class StaticLayout extends Layout { int dir = measured.mDir; boolean easy = measured.mEasy; - breakOpp = nLineBreakOpportunities(localeLanguageTag, chs, paraEnd - paraStart, breakOpp); - int breakOppIndex = 0; - - int width = firstWidth; - - float w = 0; - // here is the offset of the starting character of the line we are currently measuring - int here = paraStart; - - // ok is a character offset located after a word separator (space, tab, number...) where - // we would prefer to cut the current line. Equals to here when no such break was found. - int ok = paraStart; - float okWidth = w; - int okAscent = 0, okDescent = 0, okTop = 0, okBottom = 0; - - // fit is a character offset such that the [here, fit[ range fits in the allowed width. - // We will cut the line there if no ok position is found. - int fit = paraStart; - float fitWidth = w; - int fitAscent = 0, fitDescent = 0, fitTop = 0, fitBottom = 0; - // same as fitWidth but not including any trailing whitespace - float fitWidthGraphing = w; - - boolean hasTabOrEmoji = false; - boolean hasTab = false; - TabStops tabStops = null; - + // measurement has to be done before performing line breaking + // but we don't want to recompute fontmetrics or span ranges the + // second time, so we cache those and then use those stored values + int fmCacheCount = 0; + int spanEndCacheCount = 0; for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) { + if (fmCacheCount * 4 >= fmCache.length) { + int[] grow = new int[fmCacheCount * 4 * 2]; + System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4); + fmCache = grow; + } + + if (spanEndCacheCount >= spanEndCache.length) { + int[] grow = new int[spanEndCacheCount * 2]; + System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount); + spanEndCache = grow; + } if (spanned == null) { spanEnd = paraEnd; @@ -285,200 +280,107 @@ public class StaticLayout extends Layout { measured.addStyleRun(paint, spans, spanLen, fm); } - int fmTop = fm.top; - int fmBottom = fm.bottom; - int fmAscent = fm.ascent; - int fmDescent = fm.descent; - - for (int j = spanStart; j < spanEnd; j++) { - char c = chs[j - paraStart]; - - if (c == CHAR_NEW_LINE) { - // intentionally left empty - } else if (c == CHAR_TAB) { - if (hasTab == false) { - hasTab = true; - hasTabOrEmoji = true; - if (spanned != null) { - // First tab this para, check for tabstops - TabStopSpan[] spans = getParagraphSpans(spanned, paraStart, - paraEnd, TabStopSpan.class); - if (spans.length > 0) { - tabStops = new TabStops(TAB_INCREMENT, spans); - } - } - } - if (tabStops != null) { - w = tabStops.nextTab(w); - } else { - w = TabStops.nextDefaultStop(w, TAB_INCREMENT); - } - } else if (c >= CHAR_FIRST_HIGH_SURROGATE && c <= CHAR_LAST_LOW_SURROGATE - && j + 1 < spanEnd) { - int emoji = Character.codePointAt(chs, j - paraStart); - - if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) { - Bitmap bm = EMOJI_FACTORY.getBitmapFromAndroidPua(emoji); - - if (bm != null) { - Paint whichPaint; - - if (spanned == null) { - whichPaint = paint; - } else { - whichPaint = mWorkPaint; - } - - float wid = bm.getWidth() * -whichPaint.ascent() / bm.getHeight(); - - w += wid; - hasTabOrEmoji = true; - j++; - } else { - w += widths[j - paraStart]; - } - } else { - w += widths[j - paraStart]; - } - } else { - w += widths[j - paraStart]; + // the order of storage here (top, bottom, ascent, descent) has to match the code below + // where these values are retrieved + fmCache[fmCacheCount * 4 + 0] = fm.top; + fmCache[fmCacheCount * 4 + 1] = fm.bottom; + fmCache[fmCacheCount * 4 + 2] = fm.ascent; + fmCache[fmCacheCount * 4 + 3] = fm.descent; + fmCacheCount++; + + spanEndCache[spanEndCacheCount] = spanEnd; + spanEndCacheCount++; + } + + // tab stop locations + int[] variableTabStops = null; + if (spanned != null) { + TabStopSpan[] spans = getParagraphSpans(spanned, paraStart, + paraEnd, TabStopSpan.class); + if (spans.length > 0) { + int[] stops = new int[spans.length]; + for (int i = 0; i < spans.length; i++) { + stops[i] = spans[i].getTabStop(); } + Arrays.sort(stops, 0, stops.length); + variableTabStops = stops; + } + } - boolean isSpaceOrTab = c == CHAR_SPACE || c == CHAR_TAB || c == CHAR_ZWSP; + int breakCount = nComputeLineBreaks(localeLanguageTag, chs, widths, paraEnd - paraStart, firstWidth, + firstWidthLineCount, restWidth, variableTabStops, TAB_INCREMENT, false, lineBreaks, + lineBreaks.breaks, lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length); - if (w <= width || isSpaceOrTab) { - fitWidth = w; - if (!isSpaceOrTab) { - fitWidthGraphing = w; - } - fit = j + 1; - - if (fmTop < fitTop) - fitTop = fmTop; - if (fmAscent < fitAscent) - fitAscent = fmAscent; - if (fmDescent > fitDescent) - fitDescent = fmDescent; - if (fmBottom > fitBottom) - fitBottom = fmBottom; - - while (breakOpp[breakOppIndex] != -1 - && breakOpp[breakOppIndex] < j - paraStart + 1) { - breakOppIndex++; - } - boolean isLineBreak = breakOppIndex < breakOpp.length && - breakOpp[breakOppIndex] == j - paraStart + 1; - - if (isLineBreak) { - okWidth = fitWidthGraphing; - ok = j + 1; - - if (fitTop < okTop) - okTop = fitTop; - if (fitAscent < okAscent) - okAscent = fitAscent; - if (fitDescent > okDescent) - okDescent = fitDescent; - if (fitBottom > okBottom) - okBottom = fitBottom; - } - } else { - int endPos; - int above, below, top, bottom; - float currentTextWidth; - - if (ok != here) { - endPos = ok; - above = okAscent; - below = okDescent; - top = okTop; - bottom = okBottom; - currentTextWidth = okWidth; - } else if (fit != here) { - endPos = fit; - above = fitAscent; - below = fitDescent; - top = fitTop; - bottom = fitBottom; - currentTextWidth = fitWidth; - } else { - // must make progress, so take next character - endPos = here + 1; - // but to deal properly with clusters - // take all zero width characters following that - while (endPos < spanEnd && widths[endPos - paraStart] == 0) { - endPos++; - } - above = fmAscent; - below = fmDescent; - top = fmTop; - bottom = fmBottom; - currentTextWidth = widths[here - paraStart]; - } + int[] breaks = lineBreaks.breaks; + float[] lineWidths = lineBreaks.widths; + boolean[] flags = lineBreaks.flags; - int ellipseEnd = endPos; - if (mMaximumVisibleLineCount == 1 && ellipsize == TextUtils.TruncateAt.MIDDLE) { - ellipseEnd = paraEnd; - } - v = out(source, here, ellipseEnd, - above, below, top, bottom, - v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, hasTabOrEmoji, - needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad, - chs, widths, paraStart, ellipsize, ellipsizedWidth, - currentTextWidth, paint, true); - - here = endPos; - j = here - 1; // restart j-span loop from here, compensating for the j++ - ok = fit = here; - w = 0; - fitWidthGraphing = w; - fitAscent = fitDescent = fitTop = fitBottom = 0; - okAscent = okDescent = okTop = okBottom = 0; - - if (--firstWidthLineLimit <= 0) { - width = restWidth; - } + // here is the offset of the starting character of the line we are currently measuring + int here = paraStart; - if (here < spanStart) { - // The text was cut before the beginning of the current span range. - // Exit the span loop, and get spanStart to start over from here. - measured.setPos(here); - spanEnd = here; - break; - } + int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0; + int fmCacheIndex = 0; + int spanEndCacheIndex = 0; + int breakIndex = 0; + for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) { + // retrieve end of span + spanEnd = spanEndCache[spanEndCacheIndex++]; + + // retrieve cached metrics, order matches above + fm.top = fmCache[fmCacheIndex * 4 + 0]; + fm.bottom = fmCache[fmCacheIndex * 4 + 1]; + fm.ascent = fmCache[fmCacheIndex * 4 + 2]; + fm.descent = fmCache[fmCacheIndex * 4 + 3]; + fmCacheIndex++; + + if (fm.top < fmTop) { + fmTop = fm.top; + } + if (fm.ascent < fmAscent) { + fmAscent = fm.ascent; + } + if (fm.descent > fmDescent) { + fmDescent = fm.descent; + } + if (fm.bottom > fmBottom) { + fmBottom = fm.bottom; + } - if (mLineCount >= mMaximumVisibleLineCount) { - return; - } - } + // skip breaks ending before current span range + while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) { + breakIndex++; } - } - if (paraEnd != here && mLineCount < mMaximumVisibleLineCount) { - if ((fitTop | fitBottom | fitDescent | fitAscent) == 0) { - paint.getFontMetricsInt(fm); + while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) { + int endPos = paraStart + breaks[breakIndex]; - fitTop = fm.top; - fitBottom = fm.bottom; - fitAscent = fm.ascent; - fitDescent = fm.descent; - } + boolean moreChars = (endPos < paraEnd); // XXX is this the right way to calculate this? - // Log.e("text", "output rest " + here + " to " + end); - - v = out(source, - here, paraEnd, fitAscent, fitDescent, - fitTop, fitBottom, - v, - spacingmult, spacingadd, chooseHt, - chooseHtv, fm, hasTabOrEmoji, - needMultiply, chdirs, dir, easy, bufEnd, - includepad, trackpad, chs, - widths, paraStart, ellipsize, - ellipsizedWidth, w, paint, paraEnd != bufEnd); - } + v = out(source, here, endPos, + fmAscent, fmDescent, fmTop, fmBottom, + v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, flags[breakIndex], + needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad, + chs, widths, paraStart, ellipsize, ellipsizedWidth, + lineWidths[breakIndex], paint, moreChars); - paraStart = paraEnd; + if (endPos < spanEnd) { + // preserve metrics for current span + fmTop = fm.top; + fmBottom = fm.bottom; + fmAscent = fm.ascent; + fmDescent = fm.descent; + } else { + fmTop = fmBottom = fmAscent = fmDescent = 0; + } + + here = endPos; + breakIndex++; + + if (mLineCount >= mMaximumVisibleLineCount) { + return; + } + } + } if (paraEnd == bufEnd) break; @@ -488,7 +390,7 @@ public class StaticLayout extends Layout { mLineCount < mMaximumVisibleLineCount) { // Log.e("text", "output last " + bufEnd); - measured.setPara(source, bufStart, bufEnd, textDir); + measured.setPara(source, bufEnd, bufEnd, textDir); paint.getFontMetricsInt(fm); @@ -848,15 +750,20 @@ public class StaticLayout extends Layout { void prepare() { mMeasured = MeasuredText.obtain(); } - + void finish() { mMeasured = MeasuredText.recycle(mMeasured); } - // returns an array with terminal sentinel value -1 to indicate end - // this is so that arrays can be recycled instead of allocating new arrays - // every time - private static native int[] nLineBreakOpportunities(String locale, char[] text, int length, int[] recycle); + // populates LineBreaks and returns the number of breaks found + // + // the arrays inside the LineBreaks objects are passed in as well + // to reduce the number of JNI calls in the common case where the + // arrays do not have to be resized + private static native int nComputeLineBreaks(String locale, char[] text, float[] widths, + int length, float firstWidth, int firstWidthLineCount, float restWidth, + int[] variableTabStops, int defaultTabStop, boolean optimize, LineBreaks recycle, + int[] recycleBreaks, float[] recycleWidths, boolean[] recycleFlags, int recycleLength); private int mLineCount; private int mTopPadding, mBottomPadding; @@ -884,18 +791,23 @@ public class StaticLayout extends Layout { private static final int TAB_INCREMENT = 20; // same as Layout, but that's private private static final char CHAR_NEW_LINE = '\n'; - private static final char CHAR_TAB = '\t'; - private static final char CHAR_SPACE = ' '; - private static final char CHAR_ZWSP = '\u200B'; private static final double EXTRA_ROUNDING = 0.5; - private static final int CHAR_FIRST_HIGH_SURROGATE = 0xD800; - private static final int CHAR_LAST_LOW_SURROGATE = 0xDFFF; - /* * This is reused across calls to generate() */ private MeasuredText mMeasured; private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt(); + + // This is used to return three arrays from a single JNI call when + // performing line breaking + /*package*/ static class LineBreaks { + private static final int INITIAL_SIZE = 16; + public int[] breaks = new int[INITIAL_SIZE]; + public float[] widths = new float[INITIAL_SIZE]; + public boolean[] flags = new boolean[INITIAL_SIZE]; // hasTabOrEmoji + // breaks, widths, and flags should all have the same length + } + } diff --git a/core/java/android/text/util/Rfc822Token.java b/core/java/android/text/util/Rfc822Token.java index 0edeeb5..058757a 100644 --- a/core/java/android/text/util/Rfc822Token.java +++ b/core/java/android/text/util/Rfc822Token.java @@ -16,18 +16,21 @@ package android.text.util; +import android.annotation.Nullable; + /** * This class stores an RFC 822-like name, address, and comment, * and provides methods to convert them to quoted strings. */ public class Rfc822Token { + @Nullable private String mName, mAddress, mComment; /** * Creates a new Rfc822Token with the specified name, address, * and comment. */ - public Rfc822Token(String name, String address, String comment) { + public Rfc822Token(@Nullable String name, @Nullable String address, @Nullable String comment) { mName = name; mAddress = address; mComment = comment; @@ -36,6 +39,7 @@ public class Rfc822Token { /** * Returns the name part. */ + @Nullable public String getName() { return mName; } @@ -43,6 +47,7 @@ public class Rfc822Token { /** * Returns the address part. */ + @Nullable public String getAddress() { return mAddress; } @@ -50,6 +55,7 @@ public class Rfc822Token { /** * Returns the comment part. */ + @Nullable public String getComment() { return mComment; } @@ -57,21 +63,21 @@ public class Rfc822Token { /** * Changes the name to the specified name. */ - public void setName(String name) { + public void setName(@Nullable String name) { mName = name; } /** * Changes the address to the specified address. */ - public void setAddress(String address) { + public void setAddress(@Nullable String address) { mAddress = address; } /** * Changes the comment to the specified comment. */ - public void setComment(String comment) { + public void setComment(@Nullable String comment) { mComment = comment; } diff --git a/core/java/android/transition/ChangeScroll.java b/core/java/android/transition/ChangeScroll.java index 39291bf..5a78b94 100644 --- a/core/java/android/transition/ChangeScroll.java +++ b/core/java/android/transition/ChangeScroll.java @@ -28,8 +28,6 @@ import android.view.ViewGroup; /** * This transition captures the scroll properties of targets before and after * the scene change and animates any changes. - * - * @hide */ public class ChangeScroll extends Transition { diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java index 7bd6287..80245ef 100644 --- a/core/java/android/transition/TransitionManager.java +++ b/core/java/android/transition/TransitionManager.java @@ -268,7 +268,12 @@ public class TransitionManager { @Override public boolean onPreDraw() { removeListeners(); - sPendingTransitions.remove(mSceneRoot); + + // Don't start the transition if it's no longer pending. + if (!sPendingTransitions.remove(mSceneRoot)) { + return true; + } + // Add to running list, handle end to remove it final ArrayMap<ViewGroup, ArrayList<Transition>> runningTransitions = getRunningTransitions(); @@ -417,4 +422,24 @@ public class TransitionManager { sceneChangeRunTransition(sceneRoot, transitionClone); } } + + /** + * Ends all pending and ongoing transitions on the specified scene root. + * + * @param sceneRoot The root of the View hierarchy to end transitions on. + * @hide + */ + public static void endTransitions(final ViewGroup sceneRoot) { + sPendingTransitions.remove(sceneRoot); + + final ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot); + if (runningTransitions != null) { + final int count = runningTransitions.size(); + for (int i = 0; i < count; i++) { + final Transition transition = runningTransitions.get(i); + transition.end(); + } + } + + } } diff --git a/core/java/android/util/AttributeSet.java b/core/java/android/util/AttributeSet.java index 74942ba..eb8c168 100644 --- a/core/java/android/util/AttributeSet.java +++ b/core/java/android/util/AttributeSet.java @@ -39,7 +39,7 @@ package android.util; * is more useful in conjunction with compiled XML resources: * * <pre> - * XmlPullParser parser = resources.getXml(myResouce); + * XmlPullParser parser = resources.getXml(myResource); * AttributeSet attributes = Xml.asAttributeSet(parser);</pre> * * <p>The implementation returned here, unlike using diff --git a/core/java/android/util/Spline.java b/core/java/android/util/Spline.java index 41a2e5d..bed3a60 100644 --- a/core/java/android/util/Spline.java +++ b/core/java/android/util/Spline.java @@ -165,7 +165,7 @@ public abstract class Spline { throw new IllegalArgumentException("The control points must have " + "monotonic Y values."); } - float h = FloatMath.hypot(a, b); + float h = (float) Math.hypot(a, b); if (h > 9f) { float t = 3f / h; m[i] = t * a * d[i]; diff --git a/core/java/android/util/StateSet.java b/core/java/android/util/StateSet.java index 2623638..83dfc47 100644 --- a/core/java/android/util/StateSet.java +++ b/core/java/android/util/StateSet.java @@ -36,7 +36,88 @@ import com.android.internal.R; */ public class StateSet { - /** @hide */ public StateSet() {} + /** + * The order here is very important to + * {@link android.view.View#getDrawableState()} + */ + private static final int[][] VIEW_STATE_SETS; + + /** @hide */ + public static final int VIEW_STATE_WINDOW_FOCUSED = 1; + /** @hide */ + public static final int VIEW_STATE_SELECTED = 1 << 1; + /** @hide */ + public static final int VIEW_STATE_FOCUSED = 1 << 2; + /** @hide */ + public static final int VIEW_STATE_ENABLED = 1 << 3; + /** @hide */ + public static final int VIEW_STATE_PRESSED = 1 << 4; + /** @hide */ + public static final int VIEW_STATE_ACTIVATED = 1 << 5; + /** @hide */ + public static final int VIEW_STATE_ACCELERATED = 1 << 6; + /** @hide */ + public static final int VIEW_STATE_HOVERED = 1 << 7; + /** @hide */ + public static final int VIEW_STATE_DRAG_CAN_ACCEPT = 1 << 8; + /** @hide */ + public static final int VIEW_STATE_DRAG_HOVERED = 1 << 9; + + static final int[] VIEW_STATE_IDS = new int[] { + R.attr.state_window_focused, VIEW_STATE_WINDOW_FOCUSED, + R.attr.state_selected, VIEW_STATE_SELECTED, + R.attr.state_focused, VIEW_STATE_FOCUSED, + R.attr.state_enabled, VIEW_STATE_ENABLED, + R.attr.state_pressed, VIEW_STATE_PRESSED, + R.attr.state_activated, VIEW_STATE_ACTIVATED, + R.attr.state_accelerated, VIEW_STATE_ACCELERATED, + R.attr.state_hovered, VIEW_STATE_HOVERED, + R.attr.state_drag_can_accept, VIEW_STATE_DRAG_CAN_ACCEPT, + R.attr.state_drag_hovered, VIEW_STATE_DRAG_HOVERED + }; + + static { + if ((VIEW_STATE_IDS.length / 2) != R.styleable.ViewDrawableStates.length) { + throw new IllegalStateException( + "VIEW_STATE_IDs array length does not match ViewDrawableStates style array"); + } + + final int[] orderedIds = new int[VIEW_STATE_IDS.length]; + for (int i = 0; i < R.styleable.ViewDrawableStates.length; i++) { + final int viewState = R.styleable.ViewDrawableStates[i]; + for (int j = 0; j < VIEW_STATE_IDS.length; j += 2) { + if (VIEW_STATE_IDS[j] == viewState) { + orderedIds[i * 2] = viewState; + orderedIds[i * 2 + 1] = VIEW_STATE_IDS[j + 1]; + } + } + } + + final int NUM_BITS = VIEW_STATE_IDS.length / 2; + VIEW_STATE_SETS = new int[1 << NUM_BITS][]; + for (int i = 0; i < VIEW_STATE_SETS.length; i++) { + final int numBits = Integer.bitCount(i); + final int[] set = new int[numBits]; + int pos = 0; + for (int j = 0; j < orderedIds.length; j += 2) { + if ((i & orderedIds[j + 1]) != 0) { + set[pos++] = orderedIds[j]; + } + } + VIEW_STATE_SETS[i] = set; + } + } + + /** @hide */ + public static int[] get(int mask) { + if (mask >= VIEW_STATE_SETS.length) { + throw new IllegalArgumentException("Invalid state set mask"); + } + return VIEW_STATE_SETS[mask]; + } + + /** @hide */ + public StateSet() {} public static final int[] WILD_CARD = new int[0]; public static final int[] NOTHING = new int[] { 0 }; diff --git a/core/java/android/view/ActionMode.java b/core/java/android/view/ActionMode.java index a359952..ae4b60f 100644 --- a/core/java/android/view/ActionMode.java +++ b/core/java/android/view/ActionMode.java @@ -29,8 +29,21 @@ package android.view; * </div> */ public abstract class ActionMode { + + /** + * The action mode is treated as a Primary mode. This is the default. + * Use with {@link #setType}. + */ + public static final int TYPE_PRIMARY = 0; + /** + * The action mode is treated as a Floating Toolbar. + * Use with {@link #setType}. + */ + public static final int TYPE_FLOATING = 1; + private Object mTag; private boolean mTitleOptionalHint; + private int mType = TYPE_PRIMARY; /** * Set a tag object associated with this ActionMode. @@ -154,6 +167,25 @@ public abstract class ActionMode { public abstract void setCustomView(View view); /** + * Set a type for this action mode. This will affect how the system renders the action mode if + * it has to. + * + * @param type One of {@link #TYPE_PRIMARY} or {@link #TYPE_FLOATING}. + */ + public void setType(int type) { + mType = type; + } + + /** + * Returns the type for this action mode. + * + * @return One of {@link #TYPE_PRIMARY} or {@link #TYPE_FLOATING}. + */ + public int getType() { + return mType; + } + + /** * Invalidate the action mode and refresh menu content. The mode's * {@link ActionMode.Callback} will have its * {@link Callback#onPrepareActionMode(ActionMode, Menu)} method called. diff --git a/core/java/android/view/ContextThemeWrapper.java b/core/java/android/view/ContextThemeWrapper.java index 0afbde9..d9f6054 100644 --- a/core/java/android/view/ContextThemeWrapper.java +++ b/core/java/android/view/ContextThemeWrapper.java @@ -35,13 +35,19 @@ public class ContextThemeWrapper extends ContextWrapper { public ContextThemeWrapper() { super(null); } - - public ContextThemeWrapper(Context base, int themeres) { + + public ContextThemeWrapper(Context base, int themeResId) { + super(base); + mThemeResource = themeResId; + } + + public ContextThemeWrapper(Context base, Resources.Theme theme) { super(base); - mThemeResource = themeres; + mTheme = theme; } - @Override protected void attachBaseContext(Context newBase) { + @Override + protected void attachBaseContext(Context newBase) { super.attachBaseContext(newBase); } diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 60a489b..3cb4666 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -19,19 +19,13 @@ package android.view; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.CanvasProperty; -import android.graphics.DrawFilter; import android.graphics.Matrix; import android.graphics.NinePatch; import android.graphics.Paint; -import android.graphics.PaintFlagsDrawFilter; import android.graphics.Path; import android.graphics.Picture; -import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.RectF; -import android.graphics.Region; -import android.graphics.Shader; -import android.graphics.TemporaryBuffer; import android.text.GraphicsOperations; import android.text.SpannableString; import android.text.SpannedString; @@ -41,14 +35,6 @@ import android.text.TextUtils; * An implementation of Canvas on top of OpenGL ES 2.0. */ class GLES20Canvas extends HardwareCanvas { - private final boolean mOpaque; - protected long mRenderer; - - // The native renderer will be destroyed when this object dies. - // DO NOT overwrite this reference once it is set. - @SuppressWarnings({"unused", "FieldCanBeLocal"}) - private CanvasFinalizer mFinalizer; - private int mWidth; private int mHeight; @@ -58,8 +44,6 @@ class GLES20Canvas extends HardwareCanvas { private Rect mClipBounds; private RectF mPathBounds; - private DrawFilter mFilter; - /////////////////////////////////////////////////////////////////////////// // JNI /////////////////////////////////////////////////////////////////////////// @@ -77,39 +61,10 @@ class GLES20Canvas extends HardwareCanvas { // TODO: Merge with GLES20RecordingCanvas protected GLES20Canvas() { - mOpaque = false; - mRenderer = nCreateDisplayListRenderer(); - setupFinalizer(); - } - - private void setupFinalizer() { - if (mRenderer == 0) { - throw new IllegalStateException("Could not create GLES20Canvas renderer"); - } else { - mFinalizer = new CanvasFinalizer(mRenderer); - } + super(nCreateDisplayListRenderer()); } private static native long nCreateDisplayListRenderer(); - private static native void nResetDisplayListRenderer(long renderer); - private static native void nDestroyRenderer(long renderer); - - private static final class CanvasFinalizer { - private final long mRenderer; - - public CanvasFinalizer(long renderer) { - mRenderer = renderer; - } - - @Override - protected void finalize() throws Throwable { - try { - nDestroyRenderer(mRenderer); - } finally { - super.finalize(); - } - } - } public static void setProperty(String name, String value) { nSetProperty(name, value); @@ -123,7 +78,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public boolean isOpaque() { - return mOpaque; + return false; } @Override @@ -153,7 +108,7 @@ class GLES20Canvas extends HardwareCanvas { * Returns the native OpenGLRenderer object. */ long getRenderer() { - return mRenderer; + return mNativeCanvasWrapper; } /////////////////////////////////////////////////////////////////////////// @@ -165,7 +120,7 @@ class GLES20Canvas extends HardwareCanvas { mWidth = width; mHeight = height; - nSetViewport(mRenderer, width, height); + nSetViewport(mNativeCanvasWrapper, width, height); } private static native void nSetViewport(long renderer, @@ -173,40 +128,38 @@ class GLES20Canvas extends HardwareCanvas { @Override public void setHighContrastText(boolean highContrastText) { - nSetHighContrastText(mRenderer, highContrastText); + nSetHighContrastText(mNativeCanvasWrapper, highContrastText); } private static native void nSetHighContrastText(long renderer, boolean highContrastText); @Override public void insertReorderBarrier() { - nInsertReorderBarrier(mRenderer, true); + nInsertReorderBarrier(mNativeCanvasWrapper, true); } @Override public void insertInorderBarrier() { - nInsertReorderBarrier(mRenderer, false); + nInsertReorderBarrier(mNativeCanvasWrapper, false); } private static native void nInsertReorderBarrier(long renderer, boolean enableReorder); @Override - public int onPreDraw(Rect dirty) { + public void onPreDraw(Rect dirty) { if (dirty != null) { - return nPrepareDirty(mRenderer, dirty.left, dirty.top, dirty.right, dirty.bottom, - mOpaque); + nPrepareDirty(mNativeCanvasWrapper, dirty.left, dirty.top, dirty.right, dirty.bottom); } else { - return nPrepare(mRenderer, mOpaque); + nPrepare(mNativeCanvasWrapper); } } - private static native int nPrepare(long renderer, boolean opaque); - private static native int nPrepareDirty(long renderer, int left, int top, int right, int bottom, - boolean opaque); + private static native void nPrepare(long renderer); + private static native void nPrepareDirty(long renderer, int left, int top, int right, int bottom); @Override public void onPostDraw() { - nFinish(mRenderer); + nFinish(mNativeCanvasWrapper); } private static native void nFinish(long renderer); @@ -216,11 +169,11 @@ class GLES20Canvas extends HardwareCanvas { /////////////////////////////////////////////////////////////////////////// @Override - public int callDrawGLFunction2(long drawGLFunction) { - return nCallDrawGLFunction(mRenderer, drawGLFunction); + public void callDrawGLFunction2(long drawGLFunction) { + nCallDrawGLFunction(mNativeCanvasWrapper, drawGLFunction); } - private static native int nCallDrawGLFunction(long renderer, long drawGLFunction); + private static native void nCallDrawGLFunction(long renderer, long drawGLFunction); /////////////////////////////////////////////////////////////////////////// // Display list @@ -229,12 +182,12 @@ class GLES20Canvas extends HardwareCanvas { protected static native long nFinishRecording(long renderer); @Override - public int drawRenderNode(RenderNode renderNode, Rect dirty, int flags) { - return nDrawRenderNode(mRenderer, renderNode.getNativeDisplayList(), dirty, flags); + public void drawRenderNode(RenderNode renderNode, int flags) { + nDrawRenderNode(mNativeCanvasWrapper, renderNode.getNativeDisplayList(), flags); } - private static native int nDrawRenderNode(long renderer, long renderNode, - Rect dirty, int flags); + private static native void nDrawRenderNode(long renderer, long renderNode, + int flags); /////////////////////////////////////////////////////////////////////////// // Hardware layer @@ -242,332 +195,32 @@ class GLES20Canvas extends HardwareCanvas { void drawHardwareLayer(HardwareLayer layer, float x, float y, Paint paint) { layer.setLayerPaint(paint); - nDrawLayer(mRenderer, layer.getLayerHandle(), x, y); + nDrawLayer(mNativeCanvasWrapper, layer.getLayerHandle(), x, y); } private static native void nDrawLayer(long renderer, long layer, float x, float y); /////////////////////////////////////////////////////////////////////////// - // Support - /////////////////////////////////////////////////////////////////////////// - - private Rect getInternalClipBounds() { - if (mClipBounds == null) mClipBounds = new Rect(); - return mClipBounds; - } - - - private RectF getPathBounds() { - if (mPathBounds == null) mPathBounds = new RectF(); - return mPathBounds; - } - - private float[] getPointStorage() { - if (mPoint == null) mPoint = new float[2]; - return mPoint; - } - - private float[] getLineStorage() { - if (mLine == null) mLine = new float[4]; - return mLine; - } - - /////////////////////////////////////////////////////////////////////////// - // Clipping - /////////////////////////////////////////////////////////////////////////// - - @Override - public boolean clipPath(Path path) { - return nClipPath(mRenderer, path.mNativePath, Region.Op.INTERSECT.nativeInt); - } - - @Override - public boolean clipPath(Path path, Region.Op op) { - return nClipPath(mRenderer, path.mNativePath, op.nativeInt); - } - - private static native boolean nClipPath(long renderer, long path, int op); - - @Override - public boolean clipRect(float left, float top, float right, float bottom) { - return nClipRect(mRenderer, left, top, right, bottom, Region.Op.INTERSECT.nativeInt); - } - - private static native boolean nClipRect(long renderer, float left, float top, - float right, float bottom, int op); - - @Override - public boolean clipRect(float left, float top, float right, float bottom, Region.Op op) { - return nClipRect(mRenderer, left, top, right, bottom, op.nativeInt); - } - - @Override - public boolean clipRect(int left, int top, int right, int bottom) { - return nClipRect(mRenderer, left, top, right, bottom, Region.Op.INTERSECT.nativeInt); - } - - private static native boolean nClipRect(long renderer, int left, int top, - int right, int bottom, int op); - - @Override - public boolean clipRect(Rect rect) { - return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom, - Region.Op.INTERSECT.nativeInt); - } - - @Override - public boolean clipRect(Rect rect, Region.Op op) { - return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom, op.nativeInt); - } - - @Override - public boolean clipRect(RectF rect) { - return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom, - Region.Op.INTERSECT.nativeInt); - } - - @Override - public boolean clipRect(RectF rect, Region.Op op) { - return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom, op.nativeInt); - } - - @Override - public boolean clipRegion(Region region) { - return nClipRegion(mRenderer, region.mNativeRegion, Region.Op.INTERSECT.nativeInt); - } - - @Override - public boolean clipRegion(Region region, Region.Op op) { - return nClipRegion(mRenderer, region.mNativeRegion, op.nativeInt); - } - - private static native boolean nClipRegion(long renderer, long region, int op); - - @Override - public boolean getClipBounds(Rect bounds) { - return nGetClipBounds(mRenderer, bounds); - } - - private static native boolean nGetClipBounds(long renderer, Rect bounds); - - @Override - public boolean quickReject(float left, float top, float right, float bottom, EdgeType type) { - return nQuickReject(mRenderer, left, top, right, bottom); - } - - private static native boolean nQuickReject(long renderer, float left, float top, - float right, float bottom); - - @Override - public boolean quickReject(Path path, EdgeType type) { - RectF pathBounds = getPathBounds(); - path.computeBounds(pathBounds, true); - return nQuickReject(mRenderer, pathBounds.left, pathBounds.top, - pathBounds.right, pathBounds.bottom); - } - - @Override - public boolean quickReject(RectF rect, EdgeType type) { - return nQuickReject(mRenderer, rect.left, rect.top, rect.right, rect.bottom); - } - - /////////////////////////////////////////////////////////////////////////// - // Transformations - /////////////////////////////////////////////////////////////////////////// - - @Override - public void translate(float dx, float dy) { - if (dx != 0.0f || dy != 0.0f) nTranslate(mRenderer, dx, dy); - } - - private static native void nTranslate(long renderer, float dx, float dy); - - @Override - public void skew(float sx, float sy) { - nSkew(mRenderer, sx, sy); - } - - private static native void nSkew(long renderer, float sx, float sy); - - @Override - public void rotate(float degrees) { - nRotate(mRenderer, degrees); - } - - private static native void nRotate(long renderer, float degrees); - - @Override - public void scale(float sx, float sy) { - nScale(mRenderer, sx, sy); - } - - private static native void nScale(long renderer, float sx, float sy); - - @Override - public void setMatrix(Matrix matrix) { - nSetMatrix(mRenderer, matrix == null ? 0 : matrix.native_instance); - } - - private static native void nSetMatrix(long renderer, long matrix); - - @SuppressWarnings("deprecation") - @Override - public void getMatrix(Matrix matrix) { - nGetMatrix(mRenderer, matrix.native_instance); - } - - private static native void nGetMatrix(long renderer, long matrix); - - @Override - public void concat(Matrix matrix) { - if (matrix != null) nConcatMatrix(mRenderer, matrix.native_instance); - } - - private static native void nConcatMatrix(long renderer, long matrix); - - /////////////////////////////////////////////////////////////////////////// - // State management - /////////////////////////////////////////////////////////////////////////// - - @Override - public int save() { - return nSave(mRenderer, Canvas.CLIP_SAVE_FLAG | Canvas.MATRIX_SAVE_FLAG); - } - - @Override - public int save(int saveFlags) { - return nSave(mRenderer, saveFlags); - } - - private static native int nSave(long renderer, int flags); - - @Override - public int saveLayer(RectF bounds, Paint paint, int saveFlags) { - if (bounds != null) { - return saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, paint, saveFlags); - } - - final long nativePaint = paint == null ? 0 : paint.mNativePaint; - return nSaveLayer(mRenderer, nativePaint, saveFlags); - } - - private static native int nSaveLayer(long renderer, long paint, int saveFlags); - - @Override - public int saveLayer(float left, float top, float right, float bottom, Paint paint, - int saveFlags) { - if (left < right && top < bottom) { - final long nativePaint = paint == null ? 0 : paint.mNativePaint; - return nSaveLayer(mRenderer, left, top, right, bottom, nativePaint, saveFlags); - } - return save(saveFlags); - } - - private static native int nSaveLayer(long renderer, float left, float top, - float right, float bottom, long paint, int saveFlags); - - @Override - public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags) { - if (bounds != null) { - return saveLayerAlpha(bounds.left, bounds.top, bounds.right, bounds.bottom, - alpha, saveFlags); - } - return nSaveLayerAlpha(mRenderer, alpha, saveFlags); - } - - private static native int nSaveLayerAlpha(long renderer, int alpha, int saveFlags); - - @Override - public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha, - int saveFlags) { - if (left < right && top < bottom) { - return nSaveLayerAlpha(mRenderer, left, top, right, bottom, alpha, saveFlags); - } - return save(saveFlags); - } - - private static native int nSaveLayerAlpha(long renderer, float left, float top, float right, - float bottom, int alpha, int saveFlags); - - @Override - public void restore() { - nRestore(mRenderer); - } - - private static native void nRestore(long renderer); - - @Override - public void restoreToCount(int saveCount) { - nRestoreToCount(mRenderer, saveCount); - } - - private static native void nRestoreToCount(long renderer, int saveCount); - - @Override - public int getSaveCount() { - return nGetSaveCount(mRenderer); - } - - private static native int nGetSaveCount(long renderer); - - /////////////////////////////////////////////////////////////////////////// - // Filtering - /////////////////////////////////////////////////////////////////////////// - - @Override - public void setDrawFilter(DrawFilter filter) { - mFilter = filter; - if (filter == null) { - nResetPaintFilter(mRenderer); - } else if (filter instanceof PaintFlagsDrawFilter) { - PaintFlagsDrawFilter flagsFilter = (PaintFlagsDrawFilter) filter; - nSetupPaintFilter(mRenderer, flagsFilter.clearBits, flagsFilter.setBits); - } - } - - private static native void nResetPaintFilter(long renderer); - private static native void nSetupPaintFilter(long renderer, int clearBits, int setBits); - - @Override - public DrawFilter getDrawFilter() { - return mFilter; - } - - /////////////////////////////////////////////////////////////////////////// // Drawing /////////////////////////////////////////////////////////////////////////// - @Override - public void drawArc(float left, float top, float right, float bottom, - float startAngle, float sweepAngle, boolean useCenter, Paint paint) { - nDrawArc(mRenderer, left, top, right, bottom, - startAngle, sweepAngle, useCenter, paint.mNativePaint); - } - - private static native void nDrawArc(long renderer, float left, float top, - float right, float bottom, float startAngle, float sweepAngle, - boolean useCenter, long paint); - - @Override - public void drawARGB(int a, int r, int g, int b) { - drawColor((a & 0xFF) << 24 | (r & 0xFF) << 16 | (g & 0xFF) << 8 | (b & 0xFF)); - } - + // TODO: move to Canvas.java @Override public void drawPatch(NinePatch patch, Rect dst, Paint paint) { Bitmap bitmap = patch.getBitmap(); throwIfCannotDraw(bitmap); - final long nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawPatch(mRenderer, bitmap.mNativeBitmap, patch.mNativeChunk, + final long nativePaint = paint == null ? 0 : paint.getNativeInstance(); + nDrawPatch(mNativeCanvasWrapper, bitmap.mNativeBitmap, patch.mNativeChunk, dst.left, dst.top, dst.right, dst.bottom, nativePaint); } + // TODO: move to Canvas.java @Override public void drawPatch(NinePatch patch, RectF dst, Paint paint) { Bitmap bitmap = patch.getBitmap(); throwIfCannotDraw(bitmap); - final long nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawPatch(mRenderer, bitmap.mNativeBitmap, patch.mNativeChunk, + final long nativePaint = paint == null ? 0 : paint.getNativeInstance(); + nDrawPatch(mNativeCanvasWrapper, bitmap.mNativeBitmap, patch.mNativeChunk, dst.left, dst.top, dst.right, dst.bottom, nativePaint); } @@ -575,148 +228,9 @@ class GLES20Canvas extends HardwareCanvas { float left, float top, float right, float bottom, long paint); @Override - public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) { - throwIfCannotDraw(bitmap); - final long nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, nativePaint); - } - - private static native void nDrawBitmap(long renderer, long bitmap, float left, - float top, long paint); - - @Override - public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) { - throwIfCannotDraw(bitmap); - final long nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawBitmap(mRenderer, bitmap.mNativeBitmap, matrix.native_instance, nativePaint); - } - - private static native void nDrawBitmap(long renderer, long bitmap, - long matrix, long paint); - - @Override - public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) { - throwIfCannotDraw(bitmap); - final long nativePaint = paint == null ? 0 : paint.mNativePaint; - - int left, top, right, bottom; - if (src == null) { - left = top = 0; - right = bitmap.getWidth(); - bottom = bitmap.getHeight(); - } else { - left = src.left; - right = src.right; - top = src.top; - bottom = src.bottom; - } - - nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, right, bottom, - dst.left, dst.top, dst.right, dst.bottom, nativePaint); - } - - @Override - public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) { - throwIfCannotDraw(bitmap); - final long nativePaint = paint == null ? 0 : paint.mNativePaint; - - float left, top, right, bottom; - if (src == null) { - left = top = 0; - right = bitmap.getWidth(); - bottom = bitmap.getHeight(); - } else { - left = src.left; - right = src.right; - top = src.top; - bottom = src.bottom; - } - - nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, right, bottom, - dst.left, dst.top, dst.right, dst.bottom, nativePaint); - } - - private static native void nDrawBitmap(long renderer, long bitmap, - float srcLeft, float srcTop, float srcRight, float srcBottom, - float left, float top, float right, float bottom, long paint); - - @Override - public void drawBitmap(int[] colors, int offset, int stride, float x, float y, - int width, int height, boolean hasAlpha, Paint paint) { - if (width < 0) { - throw new IllegalArgumentException("width must be >= 0"); - } - - if (height < 0) { - throw new IllegalArgumentException("height must be >= 0"); - } - - if (Math.abs(stride) < width) { - throw new IllegalArgumentException("abs(stride) must be >= width"); - } - - int lastScanline = offset + (height - 1) * stride; - int length = colors.length; - - if (offset < 0 || (offset + width > length) || lastScanline < 0 || - (lastScanline + width > length)) { - throw new ArrayIndexOutOfBoundsException(); - } - - final long nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawBitmap(mRenderer, colors, offset, stride, x, y, - width, height, hasAlpha, nativePaint); - } - - private static native void nDrawBitmap(long renderer, int[] colors, int offset, int stride, - float x, float y, int width, int height, boolean hasAlpha, long nativePaint); - - @Override - public void drawBitmap(int[] colors, int offset, int stride, int x, int y, - int width, int height, boolean hasAlpha, Paint paint) { - drawBitmap(colors, offset, stride, (float) x, (float) y, width, height, hasAlpha, paint); - } - - @Override - public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, - int vertOffset, int[] colors, int colorOffset, Paint paint) { - throwIfCannotDraw(bitmap); - if (meshWidth < 0 || meshHeight < 0 || vertOffset < 0 || colorOffset < 0) { - throw new ArrayIndexOutOfBoundsException(); - } - - if (meshWidth == 0 || meshHeight == 0) { - return; - } - - final int count = (meshWidth + 1) * (meshHeight + 1); - checkRange(verts.length, vertOffset, count * 2); - - if (colors != null) { - checkRange(colors.length, colorOffset, count); - } - - final long nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawBitmapMesh(mRenderer, bitmap.mNativeBitmap, meshWidth, meshHeight, - verts, vertOffset, colors, colorOffset, nativePaint); - } - - private static native void nDrawBitmapMesh(long renderer, long bitmap, - int meshWidth, int meshHeight, float[] verts, int vertOffset, - int[] colors, int colorOffset, long paint); - - @Override - public void drawCircle(float cx, float cy, float radius, Paint paint) { - nDrawCircle(mRenderer, cx, cy, radius, paint.mNativePaint); - } - - private static native void nDrawCircle(long renderer, float cx, float cy, - float radius, long paint); - - @Override public void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy, CanvasProperty<Float> radius, CanvasProperty<Paint> paint) { - nDrawCircle(mRenderer, cx.getNativeContainer(), cy.getNativeContainer(), + nDrawCircle(mNativeCanvasWrapper, cx.getNativeContainer(), cy.getNativeContainer(), radius.getNativeContainer(), paint.getNativeContainer()); } @@ -727,7 +241,7 @@ class GLES20Canvas extends HardwareCanvas { public void drawRoundRect(CanvasProperty<Float> left, CanvasProperty<Float> top, CanvasProperty<Float> right, CanvasProperty<Float> bottom, CanvasProperty<Float> rx, CanvasProperty<Float> ry, CanvasProperty<Paint> paint) { - nDrawRoundRect(mRenderer, left.getNativeContainer(), top.getNativeContainer(), + nDrawRoundRect(mNativeCanvasWrapper, left.getNativeContainer(), top.getNativeContainer(), right.getNativeContainer(), bottom.getNativeContainer(), rx.getNativeContainer(), ry.getNativeContainer(), paint.getNativeContainer()); @@ -736,73 +250,18 @@ class GLES20Canvas extends HardwareCanvas { private static native void nDrawRoundRect(long renderer, long propLeft, long propTop, long propRight, long propBottom, long propRx, long propRy, long propPaint); - @Override - public void drawColor(int color) { - drawColor(color, PorterDuff.Mode.SRC_OVER); - } - - @Override - public void drawColor(int color, PorterDuff.Mode mode) { - nDrawColor(mRenderer, color, mode.nativeInt); - } - - private static native void nDrawColor(long renderer, int color, int mode); - - @Override - public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) { - float[] line = getLineStorage(); - line[0] = startX; - line[1] = startY; - line[2] = stopX; - line[3] = stopY; - drawLines(line, 0, 4, paint); - } - - @Override - public void drawLines(float[] pts, int offset, int count, Paint paint) { - if (count < 4) return; - - if ((offset | count) < 0 || offset + count > pts.length) { - throw new IllegalArgumentException("The lines array must contain 4 elements per line."); - } - nDrawLines(mRenderer, pts, offset, count, paint.mNativePaint); - } - - private static native void nDrawLines(long renderer, float[] points, - int offset, int count, long paint); - - @Override - public void drawLines(float[] pts, Paint paint) { - drawLines(pts, 0, pts.length, paint); - } - - @Override - public void drawOval(float left, float top, float right, float bottom, Paint paint) { - nDrawOval(mRenderer, left, top, right, bottom, paint.mNativePaint); - } - - private static native void nDrawOval(long renderer, float left, float top, - float right, float bottom, long paint); - - @Override - public void drawPaint(Paint paint) { - final Rect r = getInternalClipBounds(); - nGetClipBounds(mRenderer, r); - drawRect(r.left, r.top, r.right, r.bottom, paint); - } - + // TODO: move this optimization to Canvas.java @Override public void drawPath(Path path, Paint paint) { if (path.isSimplePath) { if (path.rects != null) { - nDrawRects(mRenderer, path.rects.mNativeRegion, paint.mNativePaint); + nDrawRects(mNativeCanvasWrapper, path.rects.mNativeRegion, paint.getNativeInstance()); } } else { - nDrawPath(mRenderer, path.mNativePath, paint.mNativePaint); + super.drawPath(path, paint); } } - private static native void nDrawPath(long renderer, long path, long paint); private static native void nDrawRects(long renderer, long region, long paint); @Override @@ -810,190 +269,4 @@ class GLES20Canvas extends HardwareCanvas { picture.endRecording(); // TODO: Implement rendering } - - @Override - public void drawPoint(float x, float y, Paint paint) { - float[] point = getPointStorage(); - point[0] = x; - point[1] = y; - drawPoints(point, 0, 2, paint); - } - - @Override - public void drawPoints(float[] pts, Paint paint) { - drawPoints(pts, 0, pts.length, paint); - } - - @Override - public void drawPoints(float[] pts, int offset, int count, Paint paint) { - if (count < 2) return; - - nDrawPoints(mRenderer, pts, offset, count, paint.mNativePaint); - } - - private static native void nDrawPoints(long renderer, float[] points, - int offset, int count, long paint); - - // Note: drawPosText just uses implementation in Canvas - - @Override - public void drawRect(float left, float top, float right, float bottom, Paint paint) { - if (left == right || top == bottom) return; - nDrawRect(mRenderer, left, top, right, bottom, paint.mNativePaint); - } - - private static native void nDrawRect(long renderer, float left, float top, - float right, float bottom, long paint); - - @Override - public void drawRect(Rect r, Paint paint) { - drawRect(r.left, r.top, r.right, r.bottom, paint); - } - - @Override - public void drawRect(RectF r, Paint paint) { - drawRect(r.left, r.top, r.right, r.bottom, paint); - } - - @Override - public void drawRGB(int r, int g, int b) { - drawColor(0xFF000000 | (r & 0xFF) << 16 | (g & 0xFF) << 8 | (b & 0xFF)); - } - - @Override - public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, - Paint paint) { - nDrawRoundRect(mRenderer, left, top, right, bottom, rx, ry, paint.mNativePaint); - } - - private static native void nDrawRoundRect(long renderer, float left, float top, - float right, float bottom, float rx, float y, long paint); - - @Override - public void drawText(char[] text, int index, int count, float x, float y, Paint paint) { - if ((index | count | (index + count) | (text.length - index - count)) < 0) { - throw new IndexOutOfBoundsException(); - } - - nDrawText(mRenderer, text, index, count, x, y, - paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); - } - - private static native void nDrawText(long renderer, char[] text, int index, int count, - float x, float y, int bidiFlags, long paint, long typeface); - - @Override - public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) { - if ((start | end | (end - start) | (text.length() - end)) < 0) { - throw new IndexOutOfBoundsException(); - } - if (text instanceof String || text instanceof SpannedString || - text instanceof SpannableString) { - nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mBidiFlags, - paint.mNativePaint, paint.mNativeTypeface); - } else if (text instanceof GraphicsOperations) { - ((GraphicsOperations) text).drawText(this, start, end, x, y, paint); - } else { - char[] buf = TemporaryBuffer.obtain(end - start); - TextUtils.getChars(text, start, end, buf, 0); - nDrawText(mRenderer, buf, 0, end - start, x, y, - paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); - TemporaryBuffer.recycle(buf); - } - } - - @Override - public void drawText(String text, int start, int end, float x, float y, Paint paint) { - if ((start | end | (end - start) | (text.length() - end)) < 0) { - throw new IndexOutOfBoundsException(); - } - - nDrawText(mRenderer, text, start, end, x, y, - paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); - } - - private static native void nDrawText(long renderer, String text, int start, int end, - float x, float y, int bidiFlags, long paint, long typeface); - - @Override - public void drawText(String text, float x, float y, Paint paint) { - nDrawText(mRenderer, text, 0, text.length(), x, y, - paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); - } - - @Override - public void drawTextOnPath(char[] text, int index, int count, Path path, float hOffset, - float vOffset, Paint paint) { - if (index < 0 || index + count > text.length) { - throw new ArrayIndexOutOfBoundsException(); - } - - nDrawTextOnPath(mRenderer, text, index, count, path.mNativePath, hOffset, vOffset, - paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); - } - - private static native void nDrawTextOnPath(long renderer, char[] text, int index, int count, - long path, float hOffset, float vOffset, int bidiFlags, long nativePaint, - long typeface); - - @Override - public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) { - if (text.length() == 0) return; - - nDrawTextOnPath(mRenderer, text, 0, text.length(), path.mNativePath, hOffset, vOffset, - paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface); - } - - private static native void nDrawTextOnPath(long renderer, String text, int start, int end, - long path, float hOffset, float vOffset, int bidiFlags, long nativePaint, - long typeface); - - @Override - public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount, - float x, float y, boolean isRtl, Paint paint) { - if ((index | count | text.length - index - count) < 0) { - throw new IndexOutOfBoundsException(); - } - - nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y, isRtl, - paint.mNativePaint, paint.mNativeTypeface); - } - - private static native void nDrawTextRun(long renderer, char[] text, int index, int count, - int contextIndex, int contextCount, float x, float y, boolean isRtl, long nativePaint, long nativeTypeface); - - @Override - public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd, - float x, float y, boolean isRtl, Paint paint) { - if ((start | end | end - start | text.length() - end) < 0) { - throw new IndexOutOfBoundsException(); - } - - if (text instanceof String || text instanceof SpannedString || - text instanceof SpannableString) { - nDrawTextRun(mRenderer, text.toString(), start, end, contextStart, - contextEnd, x, y, isRtl, paint.mNativePaint, paint.mNativeTypeface); - } else if (text instanceof GraphicsOperations) { - ((GraphicsOperations) text).drawTextRun(this, start, end, - contextStart, contextEnd, x, y, isRtl, paint); - } else { - int contextLen = contextEnd - contextStart; - int len = end - start; - char[] buf = TemporaryBuffer.obtain(contextLen); - TextUtils.getChars(text, contextStart, contextEnd, buf, 0); - nDrawTextRun(mRenderer, buf, start - contextStart, len, 0, contextLen, - x, y, isRtl, paint.mNativePaint, paint.mNativeTypeface); - TemporaryBuffer.recycle(buf); - } - } - - private static native void nDrawTextRun(long renderer, String text, int start, int end, - int contextStart, int contextEnd, float x, float y, boolean isRtl, long nativePaint, long nativeTypeface); - - @Override - public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset, - float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices, - int indexOffset, int indexCount, Paint paint) { - // TODO: Implement - } } diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java index 5e49d8e..5ca5626 100644 --- a/core/java/android/view/GLES20RecordingCanvas.java +++ b/core/java/android/view/GLES20RecordingCanvas.java @@ -56,7 +56,7 @@ class GLES20RecordingCanvas extends GLES20Canvas { } long finishRecording() { - return nFinishRecording(mRenderer); + return nFinishRecording(mNativeCanvasWrapper); } @Override diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java index 18accb8..cdb350f 100644 --- a/core/java/android/view/HardwareCanvas.java +++ b/core/java/android/view/HardwareCanvas.java @@ -29,6 +29,14 @@ import android.graphics.Rect; */ public abstract class HardwareCanvas extends Canvas { + /** + * Pass a reference to the native renderer to our superclass's + * constructor. + */ + protected HardwareCanvas(long renderer) { + super(renderer); + } + @Override public boolean isHardwareAccelerated() { return true; @@ -43,12 +51,10 @@ public abstract class HardwareCanvas extends Canvas { * Invoked before any drawing operation is performed in this canvas. * * @param dirty The dirty rectangle to update, can be null. - * @return {@link RenderNode#STATUS_DREW} if anything was drawn (such as a call to clear - * the canvas). * * @hide */ - public abstract int onPreDraw(Rect dirty); + public abstract void onPreDraw(Rect dirty); /** * Invoked after all drawing operation have been performed. @@ -64,7 +70,7 @@ public abstract class HardwareCanvas extends Canvas { * @param renderNode The RenderNode to replay. */ public void drawRenderNode(RenderNode renderNode) { - drawRenderNode(renderNode, null, RenderNode.FLAG_CLIP_CHILDREN); + drawRenderNode(renderNode, RenderNode.FLAG_CLIP_CHILDREN); } /** @@ -75,12 +81,9 @@ public abstract class HardwareCanvas extends Canvas { * @param flags Optional flags about drawing, see {@link RenderNode} for * the possible flags. * - * @return One of {@link RenderNode#STATUS_DONE} or {@link RenderNode#STATUS_DREW} - * if anything was drawn. - * * @hide */ - public abstract int drawRenderNode(RenderNode renderNode, Rect dirty, int flags); + public abstract void drawRenderNode(RenderNode renderNode, int flags); /** * Draws the specified layer onto this canvas. @@ -101,11 +104,11 @@ public abstract class HardwareCanvas extends Canvas { * * @param drawGLFunction A native function pointer * - * @return {@link RenderNode#STATUS_DONE} - * * @hide */ - public abstract int callDrawGLFunction2(long drawGLFunction); + public void callDrawGLFunction2(long drawGLFunction) { + // Noop - this is done in the display list recorder subclass + } public abstract void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy, CanvasProperty<Float> radius, CanvasProperty<Paint> paint); diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java index a130bda..65ae8a6 100644 --- a/core/java/android/view/HardwareLayer.java +++ b/core/java/android/view/HardwareLayer.java @@ -52,7 +52,7 @@ final class HardwareLayer { * @see View#setLayerPaint(android.graphics.Paint) */ public void setLayerPaint(Paint paint) { - nSetLayerPaint(mFinalizer.get(), paint.mNativePaint); + nSetLayerPaint(mFinalizer.get(), paint.getNativeInstance()); mRenderer.pushLayerUpdate(this); } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 7b20e72..743f6b7 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -81,7 +81,7 @@ interface IWindowManager void addAppToken(int addPos, IApplicationToken token, int groupId, int stackId, int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId, int configChanges, boolean voiceInteraction, boolean launchTaskBehind); - void setAppGroupId(IBinder token, int groupId); + void setAppTask(IBinder token, int taskId); void setAppOrientation(IApplicationToken token, int requestedOrientation); int getAppOrientation(IApplicationToken token); void setFocusedApp(IBinder token, boolean moveFocusNow); diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 7b13e84..d08ab46 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -188,9 +188,6 @@ interface IWindowSession { void wallpaperCommandComplete(IBinder window, in Bundle result); - void setUniverseTransform(IBinder window, float alpha, float offx, float offy, - float dsdx, float dtdx, float dsdy, float dtdy); - /** * Notifies that a rectangle on the screen has been requested. */ diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index 1546877..a5225cb 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -16,22 +16,25 @@ package android.view; -import android.graphics.Canvas; -import android.os.Handler; -import android.os.Message; -import android.os.Trace; -import android.widget.FrameLayout; +import com.android.internal.R; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; +import android.graphics.Canvas; +import android.os.Handler; +import android.os.Message; +import android.os.Trace; import android.util.AttributeSet; import android.util.Log; +import android.util.TypedValue; import android.util.Xml; +import android.widget.FrameLayout; import java.io.IOException; import java.lang.reflect.Constructor; @@ -64,6 +67,7 @@ import java.util.HashMap; * @see Context#getSystemService */ public abstract class LayoutInflater { + private static final String TAG = LayoutInflater.class.getSimpleName(); private static final boolean DEBUG = false; @@ -90,12 +94,16 @@ public abstract class LayoutInflater { private HashMap<String, Boolean> mFilterMap; + private TypedValue mTempValue; + private static final String TAG_MERGE = "merge"; private static final String TAG_INCLUDE = "include"; private static final String TAG_1995 = "blink"; private static final String TAG_REQUEST_FOCUS = "requestFocus"; private static final String TAG_TAG = "tag"; + private static final String ATTR_LAYOUT = "layout"; + private static final int[] ATTRS_THEME = new int[] { com.android.internal.R.attr.theme }; @@ -361,7 +369,7 @@ public abstract class LayoutInflater { * this is the root View; otherwise it is the root of the inflated * XML file. */ - public View inflate(int resource, ViewGroup root) { + public View inflate(int resource, @Nullable ViewGroup root) { return inflate(resource, root, root != null); } @@ -381,7 +389,7 @@ public abstract class LayoutInflater { * this is the root View; otherwise it is the root of the inflated * XML file. */ - public View inflate(XmlPullParser parser, ViewGroup root) { + public View inflate(XmlPullParser parser, @Nullable ViewGroup root) { return inflate(parser, root, root != null); } @@ -402,7 +410,7 @@ public abstract class LayoutInflater { * attachToRoot is true, this is root; otherwise it is the root of * the inflated XML file. */ - public View inflate(int resource, ViewGroup root, boolean attachToRoot) { + public View inflate(int resource, @Nullable ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); if (DEBUG) { Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" (" @@ -439,7 +447,7 @@ public abstract class LayoutInflater { * attachToRoot is true, this is root; otherwise it is the root of * the inflated XML file. */ - public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { + public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate"); @@ -837,7 +845,7 @@ public abstract class LayoutInflater { throws XmlPullParserException, IOException { int type; - final TypedArray ta = mContext.obtainStyledAttributes( + final TypedArray ta = view.getContext().obtainStyledAttributes( attrs, com.android.internal.R.styleable.ViewTag); final int key = ta.getResourceId(com.android.internal.R.styleable.ViewTag_id, 0); final CharSequence value = ta.getText(com.android.internal.R.styleable.ViewTag_value); @@ -856,16 +864,41 @@ public abstract class LayoutInflater { int type; if (parent instanceof ViewGroup) { - final int layout = attrs.getAttributeResourceValue(null, "layout", 0); + Context context = inheritContext ? parent.getContext() : mContext; + + // Apply a theme wrapper, if requested. + final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME); + final int themeResId = ta.getResourceId(0, 0); + if (themeResId != 0) { + context = new ContextThemeWrapper(context, themeResId); + } + ta.recycle(); + + // If the layout is pointing to a theme attribute, we have to + // massage the value to get a resource identifier out of it. + int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0); if (layout == 0) { - final String value = attrs.getAttributeValue(null, "layout"); - if (value == null) { - throw new InflateException("You must specifiy a layout in the" + final String value = attrs.getAttributeValue(null, ATTR_LAYOUT); + if (value == null || value.length() < 1) { + throw new InflateException("You must specify a layout in the" + " include tag: <include layout=\"@layout/layoutID\" />"); - } else { - throw new InflateException("You must specifiy a valid layout " - + "reference. The layout ID " + value + " is not valid."); } + + layout = context.getResources().getIdentifier(value.substring(1), null, null); + } + + // The layout might be referencing a theme attribute. + if (mTempValue == null) { + mTempValue = new TypedValue(); + } + if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) { + layout = mTempValue.resourceId; + } + + if (layout == 0) { + final String value = attrs.getAttributeValue(null, ATTR_LAYOUT); + throw new InflateException("You must specify a valid layout " + + "reference. The layout ID " + value + " is not valid."); } else { final XmlResourceParser childParser = getContext().getResources().getLayout(layout); @@ -893,6 +926,14 @@ public abstract class LayoutInflater { inheritContext); final ViewGroup group = (ViewGroup) parent; + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.Include); + final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID); + final int visibility = a.getInt(R.styleable.Include_visibility, -1); + final boolean hasWidth = a.hasValue(R.styleable.Include_layout_width); + final boolean hasHeight = a.hasValue(R.styleable.Include_layout_height); + a.recycle(); + // We try to load the layout params set in the <include /> tag. If // they don't exist, we will rely on the layout params set in the // included XML file. @@ -902,28 +943,21 @@ public abstract class LayoutInflater { // successfully loaded layout params from the <include /> tag, // false means we need to rely on the included layout params. ViewGroup.LayoutParams params = null; - try { - params = group.generateLayoutParams(attrs); - } catch (RuntimeException e) { - params = group.generateLayoutParams(childAttrs); - } finally { - if (params != null) { - view.setLayoutParams(params); + if (hasWidth && hasHeight) { + try { + params = group.generateLayoutParams(attrs); + } catch (RuntimeException e) { + // Ignore, just fail over to child attrs. } } + if (params == null) { + params = group.generateLayoutParams(childAttrs); + } + view.setLayoutParams(params); // Inflate all children. rInflate(childParser, view, childAttrs, true, true); - // Attempt to override the included layout's android:id with the - // one set on the <include /> tag itself. - TypedArray a = mContext.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.View, 0, 0); - int id = a.getResourceId(com.android.internal.R.styleable.View_id, View.NO_ID); - // While we're at it, let's try to override android:visibility. - int visibility = a.getInt(com.android.internal.R.styleable.View_visibility, -1); - a.recycle(); - if (id != View.NO_ID) { view.setId(id); } diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java index 47f72a8..09eb486 100644 --- a/core/java/android/view/RenderNode.java +++ b/core/java/android/view/RenderNode.java @@ -305,7 +305,7 @@ public class RenderNode { } public boolean setLayerPaint(Paint paint) { - return nSetLayerPaint(mNativeRenderNode, paint != null ? paint.mNativePaint : 0); + return nSetLayerPaint(mNativeRenderNode, paint != null ? paint.getNativeInstance() : 0); } public boolean setClipBounds(@Nullable Rect rect) { diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 33ce517..83b8100 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -322,7 +322,6 @@ public class Surface implements Parcelable { * @return A canvas for drawing into the surface. * * @throws IllegalStateException If the canvas cannot be locked. - * @hide */ public Canvas lockHardwareCanvas() { synchronized (mLock) { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 6928b2c..5a8b265 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -66,6 +66,7 @@ import android.util.LongSparseLongArray; import android.util.Pools.SynchronizedPool; import android.util.Property; import android.util.SparseArray; +import android.util.StateSet; import android.util.SuperNotCalledException; import android.util.TypedValue; import android.view.ContextMenu.ContextMenuInfo; @@ -439,7 +440,7 @@ import java.util.concurrent.atomic.AtomicInteger; * </p> * * <p> - * To intiate a layout, call {@link #requestLayout}. This method is typically + * To initiate a layout, call {@link #requestLayout}. This method is typically * called by a view on itself when it believes that is can no longer fit within * its current bounds. * </p> @@ -1438,140 +1439,87 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET; - /** - * The order here is very important to {@link #getDrawableState()} - */ - private static final int[][] VIEW_STATE_SETS; - - static final int VIEW_STATE_WINDOW_FOCUSED = 1; - static final int VIEW_STATE_SELECTED = 1 << 1; - static final int VIEW_STATE_FOCUSED = 1 << 2; - static final int VIEW_STATE_ENABLED = 1 << 3; - static final int VIEW_STATE_PRESSED = 1 << 4; - static final int VIEW_STATE_ACTIVATED = 1 << 5; - static final int VIEW_STATE_ACCELERATED = 1 << 6; - static final int VIEW_STATE_HOVERED = 1 << 7; - static final int VIEW_STATE_DRAG_CAN_ACCEPT = 1 << 8; - static final int VIEW_STATE_DRAG_HOVERED = 1 << 9; - - static final int[] VIEW_STATE_IDS = new int[] { - R.attr.state_window_focused, VIEW_STATE_WINDOW_FOCUSED, - R.attr.state_selected, VIEW_STATE_SELECTED, - R.attr.state_focused, VIEW_STATE_FOCUSED, - R.attr.state_enabled, VIEW_STATE_ENABLED, - R.attr.state_pressed, VIEW_STATE_PRESSED, - R.attr.state_activated, VIEW_STATE_ACTIVATED, - R.attr.state_accelerated, VIEW_STATE_ACCELERATED, - R.attr.state_hovered, VIEW_STATE_HOVERED, - R.attr.state_drag_can_accept, VIEW_STATE_DRAG_CAN_ACCEPT, - R.attr.state_drag_hovered, VIEW_STATE_DRAG_HOVERED - }; - static { - if ((VIEW_STATE_IDS.length/2) != R.styleable.ViewDrawableStates.length) { - throw new IllegalStateException( - "VIEW_STATE_IDs array length does not match ViewDrawableStates style array"); - } - int[] orderedIds = new int[VIEW_STATE_IDS.length]; - for (int i = 0; i < R.styleable.ViewDrawableStates.length; i++) { - int viewState = R.styleable.ViewDrawableStates[i]; - for (int j = 0; j<VIEW_STATE_IDS.length; j += 2) { - if (VIEW_STATE_IDS[j] == viewState) { - orderedIds[i * 2] = viewState; - orderedIds[i * 2 + 1] = VIEW_STATE_IDS[j + 1]; - } - } - } - final int NUM_BITS = VIEW_STATE_IDS.length / 2; - VIEW_STATE_SETS = new int[1 << NUM_BITS][]; - for (int i = 0; i < VIEW_STATE_SETS.length; i++) { - int numBits = Integer.bitCount(i); - int[] set = new int[numBits]; - int pos = 0; - for (int j = 0; j < orderedIds.length; j += 2) { - if ((i & orderedIds[j+1]) != 0) { - set[pos++] = orderedIds[j]; - } - } - VIEW_STATE_SETS[i] = set; - } - - EMPTY_STATE_SET = VIEW_STATE_SETS[0]; - WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[VIEW_STATE_WINDOW_FOCUSED]; - SELECTED_STATE_SET = VIEW_STATE_SETS[VIEW_STATE_SELECTED]; - SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED]; - FOCUSED_STATE_SET = VIEW_STATE_SETS[VIEW_STATE_FOCUSED]; - FOCUSED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_FOCUSED]; - FOCUSED_SELECTED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_SELECTED | VIEW_STATE_FOCUSED]; - FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED - | VIEW_STATE_FOCUSED]; - ENABLED_STATE_SET = VIEW_STATE_SETS[VIEW_STATE_ENABLED]; - ENABLED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_ENABLED]; - ENABLED_SELECTED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_SELECTED | VIEW_STATE_ENABLED]; - ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED - | VIEW_STATE_ENABLED]; - ENABLED_FOCUSED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_FOCUSED | VIEW_STATE_ENABLED]; - ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_FOCUSED - | VIEW_STATE_ENABLED]; - ENABLED_FOCUSED_SELECTED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_SELECTED | VIEW_STATE_FOCUSED - | VIEW_STATE_ENABLED]; - ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED - | VIEW_STATE_FOCUSED| VIEW_STATE_ENABLED]; - - PRESSED_STATE_SET = VIEW_STATE_SETS[VIEW_STATE_PRESSED]; - PRESSED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_PRESSED]; - PRESSED_SELECTED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_SELECTED | VIEW_STATE_PRESSED]; - PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED - | VIEW_STATE_PRESSED]; - PRESSED_FOCUSED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_FOCUSED | VIEW_STATE_PRESSED]; - PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_FOCUSED - | VIEW_STATE_PRESSED]; - PRESSED_FOCUSED_SELECTED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_SELECTED | VIEW_STATE_FOCUSED - | VIEW_STATE_PRESSED]; - PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED - | VIEW_STATE_FOCUSED | VIEW_STATE_PRESSED]; - PRESSED_ENABLED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_ENABLED | VIEW_STATE_PRESSED]; - PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_ENABLED - | VIEW_STATE_PRESSED]; - PRESSED_ENABLED_SELECTED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_SELECTED | VIEW_STATE_ENABLED - | VIEW_STATE_PRESSED]; - PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED - | VIEW_STATE_ENABLED | VIEW_STATE_PRESSED]; - PRESSED_ENABLED_FOCUSED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_FOCUSED | VIEW_STATE_ENABLED - | VIEW_STATE_PRESSED]; - PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_FOCUSED - | VIEW_STATE_ENABLED | VIEW_STATE_PRESSED]; - PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_SELECTED | VIEW_STATE_FOCUSED - | VIEW_STATE_ENABLED | VIEW_STATE_PRESSED]; - PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[ - VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED - | VIEW_STATE_FOCUSED| VIEW_STATE_ENABLED - | VIEW_STATE_PRESSED]; + EMPTY_STATE_SET = StateSet.get(0); + + WINDOW_FOCUSED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_WINDOW_FOCUSED); + + SELECTED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_SELECTED); + SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED); + + FOCUSED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_FOCUSED); + FOCUSED_WINDOW_FOCUSED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_FOCUSED); + FOCUSED_SELECTED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_FOCUSED); + FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED + | StateSet.VIEW_STATE_FOCUSED); + + ENABLED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_ENABLED); + ENABLED_WINDOW_FOCUSED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_ENABLED); + ENABLED_SELECTED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_ENABLED); + ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED + | StateSet.VIEW_STATE_ENABLED); + ENABLED_FOCUSED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_FOCUSED | StateSet.VIEW_STATE_ENABLED); + ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_FOCUSED + | StateSet.VIEW_STATE_ENABLED); + ENABLED_FOCUSED_SELECTED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_FOCUSED + | StateSet.VIEW_STATE_ENABLED); + ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED + | StateSet.VIEW_STATE_FOCUSED| StateSet.VIEW_STATE_ENABLED); + + PRESSED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_PRESSED); + PRESSED_WINDOW_FOCUSED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_PRESSED); + PRESSED_SELECTED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_PRESSED); + PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED + | StateSet.VIEW_STATE_PRESSED); + PRESSED_FOCUSED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_FOCUSED | StateSet.VIEW_STATE_PRESSED); + PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_FOCUSED + | StateSet.VIEW_STATE_PRESSED); + PRESSED_FOCUSED_SELECTED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_FOCUSED + | StateSet.VIEW_STATE_PRESSED); + PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED + | StateSet.VIEW_STATE_FOCUSED | StateSet.VIEW_STATE_PRESSED); + PRESSED_ENABLED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED); + PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_ENABLED + | StateSet.VIEW_STATE_PRESSED); + PRESSED_ENABLED_SELECTED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_ENABLED + | StateSet.VIEW_STATE_PRESSED); + PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED + | StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED); + PRESSED_ENABLED_FOCUSED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_FOCUSED | StateSet.VIEW_STATE_ENABLED + | StateSet.VIEW_STATE_PRESSED); + PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_FOCUSED + | StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED); + PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_FOCUSED + | StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED); + PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get( + StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED + | StateSet.VIEW_STATE_FOCUSED| StateSet.VIEW_STATE_ENABLED + | StateSet.VIEW_STATE_PRESSED); } /** @@ -1977,7 +1925,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, static final int LAYOUT_DIRECTION_RESOLVED_DEFAULT = LAYOUT_DIRECTION_LTR; /** - * Text direction is inherited thru {@link ViewGroup} + * Text direction is inherited through {@link ViewGroup} */ public static final int TEXT_DIRECTION_INHERIT = 0; @@ -2545,7 +2493,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Flag for {@link #setSystemUiVisibility(int)}: View would like its window - * to be layed out as if it has requested + * to be laid out as if it has requested * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, even if it currently hasn't. This * allows it to avoid artifacts when switching in and out of that mode, at * the expense that some of its user interface may be covered by screen @@ -2557,7 +2505,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Flag for {@link #setSystemUiVisibility(int)}: View would like its window - * to be layed out as if it has requested + * to be laid out as if it has requested * {@link #SYSTEM_UI_FLAG_FULLSCREEN}, even if it currently hasn't. This * allows it to avoid artifacts when switching in and out of that mode, at * the expense that some of its user interface may be covered by screen @@ -2596,6 +2544,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public static final int SYSTEM_UI_FLAG_IMMERSIVE_STICKY = 0x00001000; /** + * Flag for {@link #setSystemUiVisibility(int)}: Requests the status bar to draw in a mode that + * is compatible with light status bar backgrounds. + * + * <p>For this to take effect, the window must request + * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS + * FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} but not + * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS + * FLAG_TRANSLUCENT_STATUS}. + * + * @see android.R.attr#windowHasLightStatusBar + */ + public static final int SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0x00002000; + + /** * @deprecated Use {@link #SYSTEM_UI_FLAG_LOW_PROFILE} instead. */ public static final int STATUS_BAR_HIDDEN = SYSTEM_UI_FLAG_LOW_PROFILE; @@ -4508,6 +4470,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (scrollabilityCache.scrollBar == null) { scrollabilityCache.scrollBar = new ScrollBarDrawable(); + scrollabilityCache.scrollBar.setCallback(this); + scrollabilityCache.scrollBar.setState(getDrawableState()); } final boolean fadeScrollbars = a.getBoolean(R.styleable.View_fadeScrollbars, true); @@ -4619,11 +4583,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Register a callback to be invoked when the scroll position of this view - * changed. + * Register a callback to be invoked when the scroll X or Y positions of + * this view change. + * <p> + * <b>Note:</b> Some views handle scrolling independently from View and may + * have their own separate listeners for scroll-type events. For example, + * {@link android.widget.ListView ListView} allows clients to register an + * {@link android.widget.ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener) AbsListView.OnScrollListener} + * to listen for changes in list scroll position. * - * @param l The callback that will run. - * @hide Only used internally. + * @param l The listener to notify when the scroll X or Y position changes. + * @see android.view.View#getScrollX() + * @see android.view.View#getScrollY() */ public void setOnScrollChangeListener(OnScrollChangeListener l) { getListenerInfo().mOnScrollChangeListener = l; @@ -5123,7 +5094,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Returns true if this view has focus iteself, or is the ancestor of the + * Returns true if this view has focus itself, or is the ancestor of the * view that has focus. * * @return True if this view has or contains focus, false otherwise. @@ -5220,7 +5191,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * populate the text content of the event source including its descendants, * and last calls * {@link ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)} - * on its parent to resuest sending of the event to interested parties. + * on its parent to request sending of the event to interested parties. * <p> * If an {@link AccessibilityDelegate} has been specified via calling * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its @@ -5270,8 +5241,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #sendAccessibilityEvent(int) * * Note: Called from the default {@link AccessibilityDelegate}. + * + * @hide */ - void sendAccessibilityEventInternal(int eventType) { + public void sendAccessibilityEventInternal(int eventType) { if (AccessibilityManager.getInstance(mContext).isEnabled()) { sendAccessibilityEventUnchecked(AccessibilityEvent.obtain(eventType)); } @@ -5304,8 +5277,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #sendAccessibilityEventUnchecked(AccessibilityEvent) * * Note: Called from the default {@link AccessibilityDelegate}. + * + * @hide */ - void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) { + public void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) { if (!isShown()) { return; } @@ -5355,8 +5330,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #dispatchPopulateAccessibilityEvent(AccessibilityEvent) * * Note: Called from the default {@link AccessibilityDelegate}. + * + * @hide */ - boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { + public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { onPopulateAccessibilityEvent(event); return false; } @@ -5404,8 +5381,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #onPopulateAccessibilityEvent(AccessibilityEvent) * * Note: Called from the default {@link AccessibilityDelegate}. + * + * @hide */ - void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { + public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { } /** @@ -5446,8 +5425,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #onInitializeAccessibilityEvent(AccessibilityEvent) * * Note: Called from the default {@link AccessibilityDelegate}. + * + * @hide */ - void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { event.setSource(this); event.setClassName(View.class.getName()); event.setPackageName(getContext().getPackageName()); @@ -5502,8 +5483,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * @see #createAccessibilityNodeInfo() + * + * @hide */ - AccessibilityNodeInfo createAccessibilityNodeInfoInternal() { + public AccessibilityNodeInfo createAccessibilityNodeInfoInternal() { AccessibilityNodeProvider provider = getAccessibilityNodeProvider(); if (provider != null) { return provider.createAccessibilityNodeInfo(View.NO_ID); @@ -5620,8 +5603,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo) * * Note: Called from the default {@link AccessibilityDelegate}. + * + * @hide */ - void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { Rect bounds = mAttachInfo.mTmpInvalRect; getDrawingRect(bounds); @@ -6691,7 +6676,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @RemotableViewMethod public void setVisibility(@Visibility int visibility) { setFlags(visibility, VISIBILITY_MASK); - if (mBackground != null) mBackground.setVisible(visibility == VISIBLE, false); } /** @@ -7394,8 +7378,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * including this view if it is focusable itself) to views. This method * adds all focusable views regardless if we are in touch mode or * only views focusable in touch mode if we are in touch mode or - * only views that can take accessibility focus if accessibility is enabeld - * depending on the focusable mode paramater. + * only views that can take accessibility focus if accessibility is enabled + * depending on the focusable mode parameter. * * @param views Focusable views found so far or null if all we are interested is * the number of focusables. @@ -7681,7 +7665,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Call this to try to give focus to a specific view or to one of its descendants. This is a - * special variant of {@link #requestFocus() } that will allow views that are not focuable in + * special variant of {@link #requestFocus() } that will allow views that are not focusable in * touch mode to request focus when they are touched. * * @return Whether this view or one of its descendants actually took focus. @@ -8083,7 +8067,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * Note: Called from the default {@link AccessibilityDelegate}. * - * @hide Until we've refactored all accessibility delegation methods. + * @hide */ public boolean performAccessibilityActionInternal(int action, Bundle arguments) { if (isNestedScrollingEnabled() @@ -8728,20 +8712,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Called when the visibility of the view or an ancestor of the view is changed. - * @param changedView The view whose visibility changed. Could be 'this' or - * an ancestor view. - * @param visibility The new visibility of changedView: {@link #VISIBLE}, - * {@link #INVISIBLE} or {@link #GONE}. + * Called when the visibility of the view or an ancestor of the view has + * changed. + * + * @param changedView The view whose visibility changed. May be + * {@code this} or an ancestor view. + * @param visibility The new visibility, one of {@link #VISIBLE}, + * {@link #INVISIBLE} or {@link #GONE}. */ protected void onVisibilityChanged(@NonNull View changedView, @Visibility int visibility) { - if (visibility == VISIBLE) { + final boolean visible = visibility == VISIBLE && getVisibility() == VISIBLE; + if (visible) { if (mAttachInfo != null) { initialAwakenScrollBars(); } else { mPrivateFlags |= PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH; } } + + final Drawable dr = mBackground; + if (dr != null && visible != dr.isVisible()) { + dr.setVisible(visible, false); + } } /** @@ -8864,7 +8856,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Called when the current configuration of the resources being used * by the application have changed. You can use this to decide when * to reload resources that can changed based on orientation and other - * configuration characterstics. You only need to use this if you are + * configuration characteristics. You only need to use this if you are * not relying on the normal {@link android.app.Activity} mechanism of * recreating the activity instance upon a configuration change. * @@ -9871,9 +9863,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Interface definition for a callback to be invoked when the scroll - * position of a view changes. + * X or Y positions of a view change. + * <p> + * <b>Note:</b> Some views handle scrolling independently from View and may + * have their own separate listeners for scroll-type events. For example, + * {@link android.widget.ListView ListView} allows clients to register an + * {@link android.widget.ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener) AbsListView.OnScrollListener} + * to listen for changes in list scroll position. * - * @hide Only used internally. + * @see #setOnScrollChangeListener(View.OnScrollChangeListener) */ public interface OnScrollChangeListener { /** @@ -10533,7 +10531,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <p>Note that if the view is backed by a * {@link #setLayerType(int, android.graphics.Paint) layer} and is associated with a * {@link #setLayerPaint(android.graphics.Paint) layer paint}, setting an alpha value less than - * 1.0 will supercede the alpha of the layer paint.</p> + * 1.0 will supersede the alpha of the layer paint.</p> * * @param alpha The opacity of the view. * @@ -11581,7 +11579,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * </p> * * <p> - * This method should be invoked everytime a subclass directly updates the + * This method should be invoked every time a subclass directly updates the * scroll parameters. * </p> * @@ -11620,7 +11618,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * </p> * * <p> - * This method should be invoked everytime a subclass directly updates the + * This method should be invoked every time a subclass directly updates the * scroll parameters. * </p> * @@ -11628,7 +11626,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * should start; when the delay is 0, the animation starts * immediately * - * @param invalidate Wheter this method should call invalidate + * @param invalidate Whether this method should call invalidate * * @return true if the animation is played, false otherwise * @@ -11648,6 +11646,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (scrollCache.scrollBar == null) { scrollCache.scrollBar = new ScrollBarDrawable(); + scrollCache.scrollBar.setCallback(this); + scrollCache.scrollBar.setState(getDrawableState()); } if (isHorizontalScrollBarEnabled() || isVerticalScrollBarEnabled()) { @@ -12533,7 +12533,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Define whether scrollbars will fade when the view is not scrolling. * - * @param fadeScrollbars wheter to enable fading + * @param fadeScrollbars whether to enable fading * * @attr ref android.R.styleable#View_fadeScrollbars */ @@ -13385,7 +13385,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * after onDetachedFromWindow(). * * If you override this you *MUST* call super.onDetachedFromWindowInternal()! - * The super method should be called at the end of the overriden method to ensure + * The super method should be called at the end of the overridden method to ensure * subclasses are destroyed first * * @hide @@ -13786,7 +13786,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <p>Note: if this view's parent addStateFromChildren property is enabled and this * property is enabled, an exception will be thrown.</p> * - * <p>Note: if the child view uses and updates additionnal states which are unknown to the + * <p>Note: if the child view uses and updates additional states which are unknown to the * parent, these states should not be affected by this method.</p> * * @param enabled True to enable duplication of the parent's drawable state, false @@ -13827,7 +13827,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * </ul> * * <p>If this view has an alpha value set to < 1.0 by calling - * {@link #setAlpha(float)}, the alpha value of the layer's paint is superceded + * {@link #setAlpha(float)}, the alpha value of the layer's paint is superseded * by this view's alpha value.</p> * * <p>Refer to the documentation of {@link #LAYER_TYPE_NONE}, @@ -13895,7 +13895,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * </ul> * * <p>If this view has an alpha value set to < 1.0 by calling {@link #setAlpha(float)}, the - * alpha value of the layer's paint is superceded by this view's alpha value.</p> + * alpha value of the layer's paint is superseded by this view's alpha value.</p> * * @param paint The paint used to compose the layer. This argument is optional * and can be null. It is ignored when the layer type is @@ -14799,10 +14799,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, void setDisplayListProperties(RenderNode renderNode) { if (renderNode != null) { renderNode.setHasOverlappingRendering(hasOverlappingRendering()); - if (mParent instanceof ViewGroup) { - renderNode.setClipToBounds( - (((ViewGroup) mParent).mGroupFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0); - } + renderNode.setClipToBounds(mParent instanceof ViewGroup + && ((ViewGroup) mParent).getClipChildren()); + float alpha = 1; if (mParent instanceof ViewGroup && (((ViewGroup) mParent).mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) { @@ -15137,7 +15136,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } else { mPrivateFlags &= ~PFLAG_DIRTY_MASK; - ((HardwareCanvas) canvas).drawRenderNode(renderNode, null, flags); + ((HardwareCanvas) canvas).drawRenderNode(renderNode, flags); } } } else if (cache != null) { @@ -15960,7 +15959,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #drawableStateChanged() */ protected boolean verifyDrawable(Drawable who) { - return who == mBackground; + return who == mBackground || (mScrollCache != null && mScrollCache.scrollBar == who); } /** @@ -15975,13 +15974,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see Drawable#setState(int[]) */ protected void drawableStateChanged() { + final int[] state = getDrawableState(); + final Drawable d = mBackground; if (d != null && d.isStateful()) { - d.setState(getDrawableState()); + d.setState(state); + } + + if (mScrollCache != null) { + final Drawable scrollBar = mScrollCache.scrollBar; + if (scrollBar != null && scrollBar.isStateful()) { + scrollBar.setState(state); + } } if (mStateListAnimator != null) { - mStateListAnimator.setState(getDrawableState()); + mStateListAnimator.setState(state); } } @@ -16079,26 +16087,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int privateFlags = mPrivateFlags; int viewStateIndex = 0; - if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= VIEW_STATE_PRESSED; - if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= VIEW_STATE_ENABLED; - if (isFocused()) viewStateIndex |= VIEW_STATE_FOCUSED; - if ((privateFlags & PFLAG_SELECTED) != 0) viewStateIndex |= VIEW_STATE_SELECTED; - if (hasWindowFocus()) viewStateIndex |= VIEW_STATE_WINDOW_FOCUSED; - if ((privateFlags & PFLAG_ACTIVATED) != 0) viewStateIndex |= VIEW_STATE_ACTIVATED; + if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= StateSet.VIEW_STATE_PRESSED; + if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= StateSet.VIEW_STATE_ENABLED; + if (isFocused()) viewStateIndex |= StateSet.VIEW_STATE_FOCUSED; + if ((privateFlags & PFLAG_SELECTED) != 0) viewStateIndex |= StateSet.VIEW_STATE_SELECTED; + if (hasWindowFocus()) viewStateIndex |= StateSet.VIEW_STATE_WINDOW_FOCUSED; + if ((privateFlags & PFLAG_ACTIVATED) != 0) viewStateIndex |= StateSet.VIEW_STATE_ACTIVATED; if (mAttachInfo != null && mAttachInfo.mHardwareAccelerationRequested && HardwareRenderer.isAvailable()) { // This is set if HW acceleration is requested, even if the current // process doesn't allow it. This is just to allow app preview // windows to better match their app. - viewStateIndex |= VIEW_STATE_ACCELERATED; + viewStateIndex |= StateSet.VIEW_STATE_ACCELERATED; } - if ((privateFlags & PFLAG_HOVERED) != 0) viewStateIndex |= VIEW_STATE_HOVERED; + if ((privateFlags & PFLAG_HOVERED) != 0) viewStateIndex |= StateSet.VIEW_STATE_HOVERED; final int privateFlags2 = mPrivateFlags2; - if ((privateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0) viewStateIndex |= VIEW_STATE_DRAG_CAN_ACCEPT; - if ((privateFlags2 & PFLAG2_DRAG_HOVERED) != 0) viewStateIndex |= VIEW_STATE_DRAG_HOVERED; + if ((privateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0) { + viewStateIndex |= StateSet.VIEW_STATE_DRAG_CAN_ACCEPT; + } + if ((privateFlags2 & PFLAG2_DRAG_HOVERED) != 0) { + viewStateIndex |= StateSet.VIEW_STATE_DRAG_HOVERED; + } - drawableState = VIEW_STATE_SETS[viewStateIndex]; + drawableState = StateSet.get(viewStateIndex); //noinspection ConstantIfStatement if (false) { @@ -16186,6 +16198,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * If the view has a ColorDrawable background, returns the color of that + * drawable. + * + * @return The color of the ColorDrawable background, if set, otherwise 0. + */ + public int getBackgroundColor() { + if (mBackground instanceof ColorDrawable) { + return ((ColorDrawable) mBackground).getColor(); + } + return 0; + } + + /** * Set the background to a given resource. The resource should refer to * a Drawable object or 0 to remove the background. * @param resid The identifier of the resource. @@ -16648,8 +16673,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Return if the padding as been set thru relative values - * {@link #setPaddingRelative(int, int, int, int)} or thru + * Return if the padding has been set through relative values + * {@link #setPaddingRelative(int, int, int, int)} or through * @attr ref android.R.styleable#View_paddingStart or * @attr ref android.R.styleable#View_paddingEnd * @@ -17273,7 +17298,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * The specified key should be an id declared in the resources of the * application to ensure it is unique (see the <a - * href={@docRoot}guide/topics/resources/more-resources.html#Id">ID resource type</a>). + * href="{@docRoot}guide/topics/resources/more-resources.html#Id">ID resource type</a>). * Keys identified as belonging to * the Android framework or not associated with any package will cause * an {@link IllegalArgumentException} to be thrown. @@ -17571,7 +17596,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <p> * Measure the view and its content to determine the measured width and the * measured height. This method is invoked by {@link #measure(int, int)} and - * should be overriden by subclasses to provide accurate and efficient + * should be overridden by subclasses to provide accurate and efficient * measurement of their contents. * </p> * @@ -17684,37 +17709,40 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Utility to reconcile a desired size and state, with constraints imposed - * by a MeasureSpec. Will take the desired size, unless a different size - * is imposed by the constraints. The returned value is a compound integer, + * by a MeasureSpec. Will take the desired size, unless a different size + * is imposed by the constraints. The returned value is a compound integer, * with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and - * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the resulting - * size is smaller than the size the view wants to be. + * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the + * resulting size is smaller than the size the view wants to be. * - * @param size How big the view wants to be - * @param measureSpec Constraints imposed by the parent + * @param size How big the view wants to be. + * @param measureSpec Constraints imposed by the parent. + * @param childMeasuredState Size information bit mask for the view's + * children. * @return Size information bit mask as defined by - * {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}. + * {@link #MEASURED_SIZE_MASK} and + * {@link #MEASURED_STATE_TOO_SMALL}. */ public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { - int result = size; - int specMode = MeasureSpec.getMode(measureSpec); - int specSize = MeasureSpec.getSize(measureSpec); + final int specMode = MeasureSpec.getMode(measureSpec); + final int specSize = MeasureSpec.getSize(measureSpec); + final int result; switch (specMode) { - case MeasureSpec.UNSPECIFIED: - result = size; - break; - case MeasureSpec.AT_MOST: - if (specSize < size) { - result = specSize | MEASURED_STATE_TOO_SMALL; - } else { + case MeasureSpec.AT_MOST: + if (specSize < size) { + result = specSize | MEASURED_STATE_TOO_SMALL; + } else { + result = size; + } + break; + case MeasureSpec.EXACTLY: + result = specSize; + break; + case MeasureSpec.UNSPECIFIED: + default: result = size; - } - break; - case MeasureSpec.EXACTLY: - result = specSize; - break; } - return result | (childMeasuredState&MEASURED_STATE_MASK); + return result | (childMeasuredState & MEASURED_STATE_MASK); } /** @@ -18177,7 +18205,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * appearance as the given View. The default also positions the center of the drag shadow * directly under the touch point. If no View is provided (the constructor with no parameters * is used), and {@link #onProvideShadowMetrics(Point,Point) onProvideShadowMetrics()} and - * {@link #onDrawShadow(Canvas) onDrawShadow()} are not overriden, then the + * {@link #onDrawShadow(Canvas) onDrawShadow()} are not overridden, then the * default is an invisible drag shadow. * <p> * You are not required to use the View you provide to the constructor as the basis of the diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 719e780..879fe19 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -771,8 +771,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @see #onRequestSendAccessibilityEvent(View, AccessibilityEvent) * * Note: Called from the default {@link View.AccessibilityDelegate}. + * + * @hide */ - boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { + public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { return true; } @@ -2650,8 +2652,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + /** @hide */ @Override - boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { + public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { boolean handled = false; if (includeForAccessibility()) { handled = super.dispatchPopulateAccessibilityEventInternal(event); @@ -2678,8 +2681,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return false; } + /** @hide */ @Override - void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfoInternal(info); if (mAttachInfo != null) { final ArrayList<View> childrenForAccessibility = mAttachInfo.mTempArrayList; @@ -2694,8 +2698,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + /** @hide */ @Override - void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { super.onInitializeAccessibilityEventInternal(event); event.setClassName(ViewGroup.class.getName()); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index e4d82b1..f78c018 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -472,8 +472,10 @@ public final class ViewRootImpl implements ViewParent, // Compute surface insets required to draw at specified Z value. // TODO: Use real shadow insets for a constant max Z. - final int surfaceInset = (int) Math.ceil(view.getZ() * 2); - attrs.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset); + if (!attrs.hasManualSurfaceInsets) { + final int surfaceInset = (int) Math.ceil(view.getZ() * 2); + attrs.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset); + } CompatibilityInfo compatibilityInfo = mDisplayAdjustments.getCompatibilityInfo(); mTranslator = compatibilityInfo.getTranslator(); @@ -760,6 +762,7 @@ public final class ViewRootImpl implements ViewParent, final int oldInsetRight = mWindowAttributes.surfaceInsets.right; final int oldInsetBottom = mWindowAttributes.surfaceInsets.bottom; final int oldSoftInputMode = mWindowAttributes.softInputMode; + final boolean oldHasManualSurfaceInsets = mWindowAttributes.hasManualSurfaceInsets; // Keep track of the actual window flags supplied by the client. mClientWindowLayoutFlags = attrs.flags; @@ -786,6 +789,7 @@ public final class ViewRootImpl implements ViewParent, // Restore old surface insets. mWindowAttributes.surfaceInsets.set( oldInsetLeft, oldInsetTop, oldInsetRight, oldInsetBottom); + mWindowAttributes.hasManualSurfaceInsets = oldHasManualSurfaceInsets; applyKeepScreenOnFlag(mWindowAttributes); @@ -1326,6 +1330,11 @@ public final class ViewRootImpl implements ViewParent, } } + // Non-visible windows can't hold accessibility focus. + if (mAttachInfo.mWindowVisibility != View.VISIBLE) { + host.clearAccessibilityFocus(); + } + // Execute enqueued actions on every traversal in case a detached view enqueued an action getRunQueue().executeActions(mAttachInfo.mHandler); diff --git a/core/java/android/view/ViewStub.java b/core/java/android/view/ViewStub.java index d68a860..9f9ed5b 100644 --- a/core/java/android/view/ViewStub.java +++ b/core/java/android/view/ViewStub.java @@ -69,8 +69,8 @@ import java.lang.ref.WeakReference; */ @RemoteView public final class ViewStub extends View { - private int mLayoutResource = 0; private int mInflatedId; + private int mLayoutResource; private WeakReference<View> mInflatedViewRef; @@ -78,7 +78,7 @@ public final class ViewStub extends View { private OnInflateListener mInflateListener; public ViewStub(Context context) { - initialize(context); + this(context, 0); } /** @@ -88,38 +88,29 @@ public final class ViewStub extends View { * @param layoutResource The reference to a layout resource that will be inflated. */ public ViewStub(Context context, int layoutResource) { + this(context, null); + mLayoutResource = layoutResource; - initialize(context); } public ViewStub(Context context, AttributeSet attrs) { this(context, attrs, 0); } - @SuppressWarnings({"UnusedDeclaration"}) public ViewStub(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - TypedArray a = context.obtainStyledAttributes( - attrs, com.android.internal.R.styleable.ViewStub, defStyleAttr, defStyleRes); + super(context); + final TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.ViewStub, defStyleAttr, defStyleRes); mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID); mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0); - - a.recycle(); - - a = context.obtainStyledAttributes( - attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); - mID = a.getResourceId(R.styleable.View_id, NO_ID); + mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID); a.recycle(); - initialize(context); - } - - private void initialize(Context context) { - mContext = context; setVisibility(GONE); setWillNotDraw(true); } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 094a8a1..740cb5d 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -501,13 +501,6 @@ public interface WindowManager extends ViewManager { public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24; /** - * Window type: Behind the universe of the real windows. - * In multiuser systems shows on all users' windows. - * @hide - */ - public static final int TYPE_UNIVERSE_BACKGROUND = FIRST_SYSTEM_WINDOW+25; - - /** * Window type: Display overlay window. Used to simulate secondary display devices. * In multiuser systems shows on all users' windows. * @hide @@ -1332,6 +1325,16 @@ public interface WindowManager extends ViewManager { * @hide */ public final Rect surfaceInsets = new Rect(); + + /** + * Whether the surface insets have been manually set. When set to + * {@code false}, the view root will automatically determine the + * appropriate surface insets. + * + * @see #surfaceInsets + * @hide + */ + public boolean hasManualSurfaceInsets; /** * The desired bitmap format. May be one of the constants in @@ -1628,6 +1631,7 @@ public interface WindowManager extends ViewManager { out.writeInt(surfaceInsets.top); out.writeInt(surfaceInsets.right); out.writeInt(surfaceInsets.bottom); + out.writeInt(hasManualSurfaceInsets ? 1 : 0); out.writeInt(needsMenuKey); } @@ -1676,6 +1680,7 @@ public interface WindowManager extends ViewManager { surfaceInsets.top = in.readInt(); surfaceInsets.right = in.readInt(); surfaceInsets.bottom = in.readInt(); + hasManualSurfaceInsets = in.readInt() != 0; needsMenuKey = in.readInt(); } @@ -1858,6 +1863,11 @@ public interface WindowManager extends ViewManager { changes |= SURFACE_INSETS_CHANGED; } + if (hasManualSurfaceInsets != o.hasManualSurfaceInsets) { + hasManualSurfaceInsets = o.hasManualSurfaceInsets; + changes |= SURFACE_INSETS_CHANGED; + } + if (needsMenuKey != o.needsMenuKey) { needsMenuKey = o.needsMenuKey; changes |= NEEDS_MENU_KEY_CHANGED; @@ -1966,8 +1976,11 @@ public interface WindowManager extends ViewManager { if (userActivityTimeout >= 0) { sb.append(" userActivityTimeout=").append(userActivityTimeout); } - if (!surfaceInsets.equals(Insets.NONE)) { + if (!surfaceInsets.equals(Insets.NONE) || hasManualSurfaceInsets) { sb.append(" surfaceInsets=").append(surfaceInsets); + if (hasManualSurfaceInsets) { + sb.append(" (manual)"); + } } if (needsMenuKey != NEEDS_MENU_UNSET) { sb.append(" needsMenuKey="); diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index a14c766..ed17e3f 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -213,15 +213,15 @@ public final class WindowManagerGlobal { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } - final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; + final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); - } else { + } else if (ActivityManager.isHighEndGfx()) { // If there's no parent and we're running on L or above (or in the // system context), assume we want hardware acceleration. final Context context = view.getContext(); - if (context != null - && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) { + if (context != null && context.getApplicationInfo().targetSdkVersion + >= Build.VERSION_CODES.LOLLIPOP) { wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } } diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index b8e94ee..ff1fde7 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -579,13 +579,6 @@ public interface WindowManagerPolicy { public int getMaxWallpaperLayer(); /** - * Return the window layer at which windows appear above the normal - * universe (that is no longer impacted by the universe background - * transform). - */ - public int getAboveUniverseLayer(); - - /** * Return the display width available after excluding any screen * decorations that can never be removed. That is, system bar or * button bar. diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index b5afdf7..6096d7d 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -721,7 +721,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @return Whether the refresh succeeded. */ public boolean refresh() { - return refresh(false); + return refresh(true); } /** diff --git a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java index 5bef71f..3ff099a 100644 --- a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java +++ b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java @@ -203,43 +203,20 @@ public class InputMethodSubtypeArray { } private static byte[] compress(final byte[] data) { - ByteArrayOutputStream resultStream = null; - GZIPOutputStream zipper = null; - try { - resultStream = new ByteArrayOutputStream(); - zipper = new GZIPOutputStream(resultStream); + try (final ByteArrayOutputStream resultStream = new ByteArrayOutputStream(); + final GZIPOutputStream zipper = new GZIPOutputStream(resultStream)) { zipper.write(data); - } catch(IOException e) { + zipper.finish(); + return resultStream.toByteArray(); + } catch(Exception e) { + Slog.e(TAG, "Failed to compress the data.", e); return null; - } finally { - try { - if (zipper != null) { - zipper.close(); - } - } catch (IOException e) { - zipper = null; - Slog.e(TAG, "Failed to close the stream.", e); - // swallowed, not propagated back to the caller - } - try { - if (resultStream != null) { - resultStream.close(); - } - } catch (IOException e) { - resultStream = null; - Slog.e(TAG, "Failed to close the stream.", e); - // swallowed, not propagated back to the caller - } } - return resultStream != null ? resultStream.toByteArray() : null; } private static byte[] decompress(final byte[] data, final int expectedSize) { - ByteArrayInputStream inputStream = null; - GZIPInputStream unzipper = null; - try { - inputStream = new ByteArrayInputStream(data); - unzipper = new GZIPInputStream(inputStream); + try (final ByteArrayInputStream inputStream = new ByteArrayInputStream(data); + final GZIPInputStream unzipper = new GZIPInputStream(inputStream)) { final byte [] result = new byte[expectedSize]; int totalReadBytes = 0; while (totalReadBytes < result.length) { @@ -254,25 +231,9 @@ public class InputMethodSubtypeArray { return null; } return result; - } catch(IOException e) { + } catch(Exception e) { + Slog.e(TAG, "Failed to decompress the data.", e); return null; - } finally { - try { - if (unzipper != null) { - unzipper.close(); - } - } catch (IOException e) { - Slog.e(TAG, "Failed to close the stream.", e); - // swallowed, not propagated back to the caller - } - try { - if (inputStream != null) { - inputStream.close(); - } - } catch (IOException e) { - Slog.e(TAG, "Failed to close the stream.", e); - // swallowed, not propagated back to the caller - } } } } diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java index eca96f9..6d5fac1 100644 --- a/core/java/android/webkit/CookieManager.java +++ b/core/java/android/webkit/CookieManager.java @@ -31,10 +31,7 @@ public abstract class CookieManager { } /** - * Gets the singleton CookieManager instance. If this method is used - * before the application instantiates a {@link WebView} instance, - * {@link CookieSyncManager#createInstance CookieSyncManager.createInstance(Context)} - * must be called first. + * Gets the singleton CookieManager instance. * * @return the singleton CookieManager instance */ diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java index 768dc9f..4737e9b 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -70,13 +70,14 @@ public class WebChromeClient { } /** - * Notify the host application that the current page would - * like to show a custom View. This is used for Fullscreen - * video playback; see "HTML5 Video support" documentation on + * Notify the host application that the current page has entered full + * screen mode. The host application must show the custom View which + * contains the web contents — video or other HTML content — + * in full screen mode. Also see "Full screen support" documentation on * {@link WebView}. * @param view is the View object to be shown. - * @param callback is the callback to be invoked if and when the view - * is dismissed. + * @param callback invoke this callback to request the page to exit + * full screen mode. */ public void onShowCustomView(View view, CustomViewCallback callback) {}; @@ -96,8 +97,10 @@ public class WebChromeClient { CustomViewCallback callback) {}; /** - * Notify the host application that the current page would - * like to hide its custom view. + * Notify the host application that the current page has exited full + * screen mode. The host application must hide the custom View, ie. the + * View passed to {@link #onShowCustomView} when the content entered fullscreen. + * Also see "Full screen support" documentation on {@link WebView}. */ public void onHideCustomView() {} @@ -377,10 +380,9 @@ public class WebChromeClient { } /** - * When the user starts to playback a video element, it may take time for enough - * data to be buffered before the first frames can be rendered. While this buffering - * is taking place, the ChromeClient can use this function to provide a View to be - * displayed. For example, the ChromeClient could show a spinner animation. + * Obtains a View to be displayed while buffering of full screen video is taking + * place. The host application can override this method to provide a View + * containing a spinner or similar. * * @return View The View to be displayed whilst the video is loading. */ diff --git a/core/java/android/webkit/WebResourceRequest.java b/core/java/android/webkit/WebResourceRequest.java index 2185658..07402b3 100644 --- a/core/java/android/webkit/WebResourceRequest.java +++ b/core/java/android/webkit/WebResourceRequest.java @@ -42,6 +42,8 @@ public interface WebResourceRequest { /** * Gets whether a gesture (such as a click) was associated with the request. + * For security reasons in certain situations this method may return false even though the + * sequence of events which caused the request to be created was initiated by a user gesture. * * @return whether a gesture was associated with the request. */ diff --git a/core/java/android/webkit/WebResourceResponse.java b/core/java/android/webkit/WebResourceResponse.java index ad6e9aa..f487a4e 100644 --- a/core/java/android/webkit/WebResourceResponse.java +++ b/core/java/android/webkit/WebResourceResponse.java @@ -17,6 +17,7 @@ package android.webkit; import java.io.InputStream; +import java.io.StringBufferInputStream; import java.util.Map; /** @@ -40,13 +41,14 @@ public class WebResourceResponse { * * @param mimeType the resource response's MIME type, for example text/html * @param encoding the resource response's encoding - * @param data the input stream that provides the resource response's data + * @param data the input stream that provides the resource response's data. Must not be a + * StringBufferInputStream. */ public WebResourceResponse(String mimeType, String encoding, InputStream data) { mMimeType = mimeType; mEncoding = encoding; - mInputStream = data; + setData(data); } /** @@ -62,7 +64,8 @@ public class WebResourceResponse { * and not empty. * @param responseHeaders the resource response's headers represented as a mapping of header * name -> header value. - * @param data the input stream that provides the resource response's data + * @param data the input stream that provides the resource response's data. Must not be a + * StringBufferInputStream. */ public WebResourceResponse(String mimeType, String encoding, int statusCode, String reasonPhrase, Map<String, String> responseHeaders, InputStream data) { @@ -178,9 +181,16 @@ public class WebResourceResponse { * Sets the input stream that provides the resource response's data. Callers * must implement {@link InputStream#read(byte[]) InputStream.read(byte[])}. * - * @param data the input stream that provides the resource response's data + * @param data the input stream that provides the resource response's data. Must not be a + * StringBufferInputStream. */ public void setData(InputStream data) { + // If data is (or is a subclass of) StringBufferInputStream + if (data != null && StringBufferInputStream.class.isAssignableFrom(data.getClass())) { + throw new IllegalArgumentException("StringBufferInputStream is deprecated and must " + + "not be passed to a WebResourceResponse"); + } + mInputStream = data; } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index bab1f3b..4a7cc6d 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -233,12 +233,48 @@ import java.util.Map; * * <h3>HTML5 Video support</h3> * - * <p>In order to support inline HTML5 video in your application, you need to have hardware - * acceleration turned on, and set a {@link android.webkit.WebChromeClient}. For full screen support, - * implementations of {@link WebChromeClient#onShowCustomView(View, WebChromeClient.CustomViewCallback)} - * and {@link WebChromeClient#onHideCustomView()} are required, - * {@link WebChromeClient#getVideoLoadingProgressView()} is optional. + * <p>In order to support inline HTML5 video in your application you need to have hardware + * acceleration turned on. * </p> + * + * <h3>Full screen support</h3> + * + * <p>In order to support full screen — for video or other HTML content — you need to set a + * {@link android.webkit.WebChromeClient} and implement both + * {@link WebChromeClient#onShowCustomView(View, WebChromeClient.CustomViewCallback)} + * and {@link WebChromeClient#onHideCustomView()}. If the implementation of either of these two methods is + * missing then the web contents will not be allowed to enter full screen. Optionally you can implement + * {@link WebChromeClient#getVideoLoadingProgressView()} to customize the View displayed whilst a video + * is loading. + * </p> + * + * <h3>Layout size</h3> + * <p> + * It is recommended to set the WebView layout height to a fixed value or to + * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} instead of using + * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}. + * When using {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} + * for the height none of the WebView's parents should use a + * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} layout height since that could result in + * incorrect sizing of the views. + * </p> + * + * <p>Setting the WebView's height to {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} + * enables the following behaviors: + * <ul> + * <li>The HTML body layout height is set to a fixed value. This means that elements with a height + * relative to the HTML body may not be sized correctly. </li> + * <li>For applications targetting {@link android.os.Build.VERSION_CODES#KITKAT} and earlier SDKs the + * HTML viewport meta tag will be ignored in order to preserve backwards compatibility. </li> + * </ul> + * </p> + * + * <p> + * Using a layout width of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} is not + * supported. If such a width is used the WebView will attempt to use the width of the parent + * instead. + * </p> + * */ // Implementation notes. // The WebView is a thin API class that delegates its public API to a backend WebViewProvider @@ -2043,7 +2079,7 @@ public class WebView extends AbsoluteLayout } public boolean super_performAccessibilityAction(int action, Bundle arguments) { - return WebView.super.performAccessibilityAction(action, arguments); + return WebView.super.performAccessibilityActionInternal(action, arguments); } public boolean super_performLongClick() { @@ -2351,22 +2387,25 @@ public class WebView extends AbsoluteLayout return mProvider.getViewDelegate().shouldDelayChildPressedState(); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(WebView.class.getName()); mProvider.getViewDelegate().onInitializeAccessibilityNodeInfo(info); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(WebView.class.getName()); mProvider.getViewDelegate().onInitializeAccessibilityEvent(event); } + /** @hide */ @Override - public boolean performAccessibilityAction(int action, Bundle arguments) { + public boolean performAccessibilityActionInternal(int action, Bundle arguments) { return mProvider.getViewDelegate().performAccessibilityAction(action, arguments); } diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java index d52dd60..1cc899d 100644 --- a/core/java/android/webkit/WebViewClient.java +++ b/core/java/android/webkit/WebViewClient.java @@ -50,7 +50,9 @@ public class WebViewClient { * is called once for each main frame load so a page with iframes or * framesets will call onPageStarted one time for the main frame. This also * means that onPageStarted will not be called when the contents of an - * embedded frame changes, i.e. clicking a link whose target is an iframe. + * embedded frame changes, i.e. clicking a link whose target is an iframe, + * it will also not be called for fragment navigations (navigations to + * #fragment_id). * * @param view The WebView that is initiating the callback. * @param url The url to be loaded. diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index 474ef42..cafe053 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -117,12 +117,8 @@ public final class WebViewFactory { StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "providerClass.newInstance()"); try { - try { - sProviderInstance = providerClass.getConstructor(WebViewDelegate.class) - .newInstance(new WebViewDelegate()); - } catch (Exception e) { - sProviderInstance = providerClass.newInstance(); - } + sProviderInstance = providerClass.getConstructor(WebViewDelegate.class) + .newInstance(new WebViewDelegate()); if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance); return sProviderInstance; } catch (Exception e) { diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 1e269a3..897749f 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -578,6 +578,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te private boolean mIsChildViewEnabled; /** + * The cached drawable state for the selector. Accounts for child enabled + * state, but otherwise identical to the view's own drawable state. + */ + private int[] mSelectorState; + + /** * The last scroll state reported to clients through {@link OnScrollListener}. */ private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE; @@ -1466,8 +1472,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these. } + /** @hide */ @Override - public void sendAccessibilityEvent(int eventType) { + public void sendAccessibilityEventInternal(int eventType) { // Since this class calls onScrollChanged even if the mFirstPosition and the // child count have not changed we will avoid sending duplicate accessibility // events. @@ -1482,18 +1489,20 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mLastAccessibilityScrollEventToIndex = lastVisiblePosition; } } - super.sendAccessibilityEvent(eventType); + super.sendAccessibilityEventInternal(eventType); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(AbsListView.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(AbsListView.class.getName()); if (isEnabled()) { if (canScrollUp()) { @@ -1522,9 +1531,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } + /** @hide */ @Override - public boolean performAccessibilityAction(int action, Bundle arguments) { - if (super.performAccessibilityAction(action, arguments)) { + public boolean performAccessibilityActionInternal(int action, Bundle arguments) { + if (super.performAccessibilityActionInternal(action, arguments)) { return true; } switch (action) { @@ -2785,7 +2795,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te void updateSelectorState() { if (mSelector != null) { if (shouldShowSelector()) { - mSelector.setState(getDrawableState()); + mSelector.setState(getDrawableStateForSelector()); } else { mSelector.setState(StateSet.NOTHING); } @@ -2798,12 +2808,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te updateSelectorState(); } - @Override - protected int[] onCreateDrawableState(int extraSpace) { + private int[] getDrawableStateForSelector() { // If the child view is enabled then do the default behavior. if (mIsChildViewEnabled) { // Common case - return super.onCreateDrawableState(extraSpace); + return super.getDrawableState(); } // The selector uses this View's drawable state. The selected child view @@ -2811,10 +2820,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te // states. final int enabledState = ENABLED_STATE_SET[0]; - // If we don't have any extra space, it will return one of the static state arrays, - // and clearing the enabled state on those arrays is a bad thing! If we specify - // we need extra space, it will create+copy into a new array that safely mutable. - int[] state = super.onCreateDrawableState(extraSpace + 1); + // If we don't have any extra space, it will return one of the static + // state arrays, and clearing the enabled state on those arrays is a + // bad thing! If we specify we need extra space, it will create+copy + // into a new array that is safely mutable. + final int[] state = onCreateDrawableState(1); + int enabledPos = -1; for (int i = state.length - 1; i >= 0; i--) { if (state[i] == enabledState) { diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index a3ce808..1f7be63 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -715,15 +715,17 @@ public abstract class AbsSeekBar extends ProgressBar { return super.onKeyDown(keyCode, event); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(AbsSeekBar.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(AbsSeekBar.class.getName()); if (isEnabled()) { @@ -737,9 +739,10 @@ public abstract class AbsSeekBar extends ProgressBar { } } + /** @hide */ @Override - public boolean performAccessibilityAction(int action, Bundle arguments) { - if (super.performAccessibilityAction(action, arguments)) { + public boolean performAccessibilityActionInternal(int action, Bundle arguments) { + if (super.performAccessibilityActionInternal(action, arguments)) { return true; } if (!isEnabled()) { diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java index 6a4ad75..5d8d48f 100644 --- a/core/java/android/widget/AbsSpinner.java +++ b/core/java/android/widget/AbsSpinner.java @@ -73,13 +73,12 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> { initAbsSpinner(); final TypedArray a = context.obtainStyledAttributes( - attrs, com.android.internal.R.styleable.AbsSpinner, defStyleAttr, defStyleRes); + attrs, R.styleable.AbsSpinner, defStyleAttr, defStyleRes); - CharSequence[] entries = a.getTextArray(R.styleable.AbsSpinner_entries); + final CharSequence[] entries = a.getTextArray(R.styleable.AbsSpinner_entries); if (entries != null) { - ArrayAdapter<CharSequence> adapter = - new ArrayAdapter<CharSequence>(context, - R.layout.simple_spinner_item, entries); + final ArrayAdapter<CharSequence> adapter = new ArrayAdapter<CharSequence>( + context, R.layout.simple_spinner_item, entries); adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item); setAdapter(adapter); } @@ -471,15 +470,17 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> { } } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(AbsSpinner.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(AbsSpinner.class.getName()); } } diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java index 18f15a0..94827dd 100644 --- a/core/java/android/widget/ActionMenuPresenter.java +++ b/core/java/android/widget/ActionMenuPresenter.java @@ -645,9 +645,10 @@ public class ActionMenuPresenter extends BaseMenuPresenter return false; } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setCanOpenPopup(true); } diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java index 0a8a01f..403e4ac 100644 --- a/core/java/android/widget/ActionMenuView.java +++ b/core/java/android/widget/ActionMenuView.java @@ -116,11 +116,14 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - mPresenter.updateMenuView(false); - if (mPresenter != null && mPresenter.isOverflowMenuShowing()) { - mPresenter.hideOverflowMenu(); - mPresenter.showOverflowMenu(); + if (mPresenter != null) { + mPresenter.updateMenuView(false); + + if (mPresenter.isOverflowMenuShowing()) { + mPresenter.hideOverflowMenu(); + mPresenter.showOverflowMenu(); + } } } @@ -700,7 +703,8 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo return result; } - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + /** @hide */ + public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { return false; } diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java index 5e2394c..428b6ce 100644 --- a/core/java/android/widget/AdapterView.java +++ b/core/java/android/widget/AdapterView.java @@ -929,8 +929,9 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { } } + /** @hide */ @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { View selectedView = getSelectedView(); if (selectedView != null && selectedView.getVisibility() == VISIBLE && selectedView.dispatchPopulateAccessibilityEvent(event)) { @@ -939,9 +940,10 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { return false; } + /** @hide */ @Override - public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { - if (super.onRequestSendAccessibilityEvent(child, event)) { + public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { + if (super.onRequestSendAccessibilityEventInternal(child, event)) { // Add a record for ourselves as well. AccessibilityEvent record = AccessibilityEvent.obtain(); onInitializeAccessibilityEvent(record); @@ -953,9 +955,10 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { return false; } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(AdapterView.class.getName()); info.setScrollable(isScrollableForAccessibility()); View selectedView = getSelectedView(); @@ -964,9 +967,10 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { } } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(AdapterView.class.getName()); event.setScrollable(isScrollableForAccessibility()); View selectedView = getSelectedView(); diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java index 1bc2f4b..96eb0e2 100644 --- a/core/java/android/widget/AdapterViewAnimator.java +++ b/core/java/android/widget/AdapterViewAnimator.java @@ -1084,15 +1084,17 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> public void fyiWillBeAdvancedByHostKThx() { } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(AdapterViewAnimator.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(AdapterViewAnimator.class.getName()); } } diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java index 285dee8..a7ba617 100644 --- a/core/java/android/widget/AdapterViewFlipper.java +++ b/core/java/android/widget/AdapterViewFlipper.java @@ -304,15 +304,17 @@ public class AdapterViewFlipper extends AdapterViewAnimator { updateRunning(false); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(AdapterViewFlipper.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(AdapterViewFlipper.class.getName()); } } diff --git a/core/java/android/widget/ArrayAdapter.java b/core/java/android/widget/ArrayAdapter.java index 97926a7..aff5e29 100644 --- a/core/java/android/widget/ArrayAdapter.java +++ b/core/java/android/widget/ArrayAdapter.java @@ -17,7 +17,9 @@ package android.widget; import android.content.Context; +import android.content.res.Resources; import android.util.Log; +import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -44,7 +46,8 @@ import java.util.List; * or to have some of data besides toString() results fill the views, * override {@link #getView(int, View, ViewGroup)} to return the type of view you want. */ -public class ArrayAdapter<T> extends BaseAdapter implements Filterable { +public class ArrayAdapter<T> extends BaseAdapter implements Filterable, + Spinner.ThemedSpinnerAdapter { /** * Contains the list of objects that represent the data of this ArrayAdapter. * The content of this list is referred to as "the array" in the documentation. @@ -93,6 +96,9 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable { private LayoutInflater mInflater; + /** Layout inflater used for {@link #getDropDownView(int, View, ViewGroup)}. */ + private LayoutInflater mDropDownInflater; + /** * Constructor * @@ -101,7 +107,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable { * instantiating views. */ public ArrayAdapter(Context context, int resource) { - init(context, resource, 0, new ArrayList<T>()); + this(context, resource, 0, new ArrayList<T>()); } /** @@ -113,7 +119,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable { * @param textViewResourceId The id of the TextView within the layout resource to be populated */ public ArrayAdapter(Context context, int resource, int textViewResourceId) { - init(context, resource, textViewResourceId, new ArrayList<T>()); + this(context, resource, textViewResourceId, new ArrayList<T>()); } /** @@ -125,7 +131,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable { * @param objects The objects to represent in the ListView. */ public ArrayAdapter(Context context, int resource, T[] objects) { - init(context, resource, 0, Arrays.asList(objects)); + this(context, resource, 0, Arrays.asList(objects)); } /** @@ -138,7 +144,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable { * @param objects The objects to represent in the ListView. */ public ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects) { - init(context, resource, textViewResourceId, Arrays.asList(objects)); + this(context, resource, textViewResourceId, Arrays.asList(objects)); } /** @@ -150,7 +156,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable { * @param objects The objects to represent in the ListView. */ public ArrayAdapter(Context context, int resource, List<T> objects) { - init(context, resource, 0, objects); + this(context, resource, 0, objects); } /** @@ -163,7 +169,11 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable { * @param objects The objects to represent in the ListView. */ public ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects) { - init(context, resource, textViewResourceId, objects); + mContext = context; + mInflater = LayoutInflater.from(context); + mResource = mDropDownResource = resource; + mObjects = objects; + mFieldId = textViewResourceId; } /** @@ -305,14 +315,6 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable { mNotifyOnChange = notifyOnChange; } - private void init(Context context, int resource, int textViewResourceId, List<T> objects) { - mContext = context; - mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mResource = mDropDownResource = resource; - mObjects = objects; - mFieldId = textViewResourceId; - } - /** * Returns the context associated with this array adapter. The context is used * to create views from the resource passed to the constructor. @@ -359,16 +361,16 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable { * {@inheritDoc} */ public View getView(int position, View convertView, ViewGroup parent) { - return createViewFromResource(position, convertView, parent, mResource); + return createViewFromResource(mInflater, position, convertView, parent, mResource); } - private View createViewFromResource(int position, View convertView, ViewGroup parent, - int resource) { + private View createViewFromResource(LayoutInflater inflater, int position, View convertView, + ViewGroup parent, int resource) { View view; TextView text; if (convertView == null) { - view = mInflater.inflate(resource, parent, false); + view = inflater.inflate(resource, parent, false); } else { view = convertView; } @@ -408,11 +410,40 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable { } /** + * Sets the {@link Resources.Theme} against which drop-down views are + * inflated. + * <p> + * By default, drop-down views are inflated against the theme of the + * {@link Context} passed to the adapter's constructor. + * + * @param theme the theme against which to inflate drop-down views or + * {@code null} to use the theme from the adapter's context + * @see #getDropDownView(int, View, ViewGroup) + */ + @Override + public void setDropDownViewTheme(Resources.Theme theme) { + if (theme == null) { + mDropDownInflater = null; + } else if (theme == mInflater.getContext().getTheme()) { + mDropDownInflater = mInflater; + } else { + final Context context = new ContextThemeWrapper(mContext, theme); + mDropDownInflater = LayoutInflater.from(context); + } + } + + @Override + public Resources.Theme getDropDownViewTheme() { + return mDropDownInflater == null ? null : mDropDownInflater.getContext().getTheme(); + } + + /** * {@inheritDoc} */ @Override public View getDropDownView(int position, View convertView, ViewGroup parent) { - return createViewFromResource(position, convertView, parent, mDropDownResource); + final LayoutInflater inflater = mDropDownInflater == null ? mInflater : mDropDownInflater; + return createViewFromResource(inflater, position, convertView, parent, mDropDownResource); } /** diff --git a/core/java/android/widget/Button.java b/core/java/android/widget/Button.java index 1663620..90d77f9 100644 --- a/core/java/android/widget/Button.java +++ b/core/java/android/widget/Button.java @@ -111,15 +111,17 @@ public class Button extends TextView { super(context, attrs, defStyleAttr, defStyleRes); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(Button.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(Button.class.getName()); } } diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java index ed59ea6..d58da8f 100644 --- a/core/java/android/widget/CalendarView.java +++ b/core/java/android/widget/CalendarView.java @@ -501,13 +501,15 @@ public class CalendarView extends FrameLayout { mDelegate.onConfigurationChanged(newConfig); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { event.setClassName(CalendarView.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { info.setClassName(CalendarView.class.getName()); } diff --git a/core/java/android/widget/CheckBox.java b/core/java/android/widget/CheckBox.java index 71438c9..b1fb338 100644 --- a/core/java/android/widget/CheckBox.java +++ b/core/java/android/widget/CheckBox.java @@ -72,15 +72,17 @@ public class CheckBox extends CompoundButton { super(context, attrs, defStyleAttr, defStyleRes); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(CheckBox.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(CheckBox.class.getName()); } } diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java index 69969a9..477862b 100644 --- a/core/java/android/widget/CheckedTextView.java +++ b/core/java/android/widget/CheckedTextView.java @@ -435,16 +435,18 @@ public class CheckedTextView extends TextView implements Checkable { } } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(CheckedTextView.class.getName()); event.setChecked(mChecked); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(CheckedTextView.class.getName()); info.setCheckable(true); info.setChecked(mChecked); diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java index f94789d..05fc6a1 100644 --- a/core/java/android/widget/Chronometer.java +++ b/core/java/android/widget/Chronometer.java @@ -281,15 +281,17 @@ public class Chronometer extends TextView { } } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(Chronometer.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(Chronometer.class.getName()); } } diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java index 447ccc2..41a3915 100644 --- a/core/java/android/widget/CompoundButton.java +++ b/core/java/android/widget/CompoundButton.java @@ -324,16 +324,18 @@ public abstract class CompoundButton extends Button implements Checkable { } } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(CompoundButton.class.getName()); event.setChecked(mChecked); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(CompoundButton.class.getName()); info.setCheckable(true); info.setChecked(mChecked); diff --git a/core/java/android/widget/CursorAdapter.java b/core/java/android/widget/CursorAdapter.java index d4c5be0..8e318fd 100644 --- a/core/java/android/widget/CursorAdapter.java +++ b/core/java/android/widget/CursorAdapter.java @@ -17,11 +17,14 @@ package android.widget; import android.content.Context; +import android.content.res.Resources; import android.database.ContentObserver; import android.database.Cursor; import android.database.DataSetObserver; import android.os.Handler; import android.util.Log; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -35,7 +38,7 @@ import android.view.ViewGroup; * columns. */ public abstract class CursorAdapter extends BaseAdapter implements Filterable, - CursorFilter.CursorFilterClient { + CursorFilter.CursorFilterClient, Spinner.ThemedSpinnerAdapter { /** * This field should be made private, so it is hidden from the SDK. * {@hide} @@ -57,6 +60,11 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable, */ protected Context mContext; /** + * Context used for {@link #getDropDownView(int, View, ViewGroup)}. + * {@hide} + */ + protected Context mDropDownContext; + /** * This field should be made private, so it is hidden from the SDK. * {@hide} */ @@ -185,6 +193,33 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable, } /** + * Sets the {@link Resources.Theme} against which drop-down views are + * inflated. + * <p> + * By default, drop-down views are inflated against the theme of the + * {@link Context} passed to the adapter's constructor. + * + * @param theme the theme against which to inflate drop-down views or + * {@code null} to use the theme from the adapter's context + * @see #newDropDownView(Context, Cursor, ViewGroup) + */ + @Override + public void setDropDownViewTheme(Resources.Theme theme) { + if (theme == null) { + mDropDownContext = null; + } else if (theme == mContext.getTheme()) { + mDropDownContext = mContext; + } else { + mDropDownContext = new ContextThemeWrapper(mContext, theme); + } + } + + @Override + public Resources.Theme getDropDownViewTheme() { + return mDropDownContext == null ? null : mDropDownContext.getTheme(); + } + + /** * Returns the cursor. * @return the cursor. */ @@ -258,20 +293,21 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable, @Override public View getDropDownView(int position, View convertView, ViewGroup parent) { if (mDataValid) { + final Context context = mDropDownContext == null ? mContext : mDropDownContext; mCursor.moveToPosition(position); - View v; + final View v; if (convertView == null) { - v = newDropDownView(mContext, mCursor, parent); + v = newDropDownView(context, mCursor, parent); } else { v = convertView; } - bindView(v, mContext, mCursor); + bindView(v, context, mCursor); return v; } else { return null; } } - + /** * Makes a new view to hold the data pointed to by cursor. * @param context Interface to application's global information diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index 0ca08e1..0f0cd2e 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -283,26 +283,30 @@ public class DatePicker extends FrameLayout { return mDelegate.isEnabled(); } + /** @hide */ @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { return mDelegate.dispatchPopulateAccessibilityEvent(event); } + /** @hide */ @Override - public void onPopulateAccessibilityEvent(AccessibilityEvent event) { - super.onPopulateAccessibilityEvent(event); + public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { + super.onPopulateAccessibilityEventInternal(event); mDelegate.onPopulateAccessibilityEvent(event); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); mDelegate.onInitializeAccessibilityEvent(event); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); mDelegate.onInitializeAccessibilityNodeInfo(info); } diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java index e75643ab..a3b834e 100644 --- a/core/java/android/widget/DatePickerCalendarDelegate.java +++ b/core/java/android/widget/DatePickerCalendarDelegate.java @@ -145,8 +145,8 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i // Use Theme attributes if possible final int dayOfWeekTextAppearanceResId = a.getResourceId( - R.styleable.DatePicker_dayOfWeekTextAppearance, -1); - if (dayOfWeekTextAppearanceResId != -1) { + R.styleable.DatePicker_dayOfWeekTextAppearance, 0); + if (dayOfWeekTextAppearanceResId != 0) { mDayOfWeekView.setTextAppearance(context, dayOfWeekTextAppearanceResId); } @@ -157,8 +157,8 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i final int headerSelectedTextColor = a.getColor( R.styleable.DatePicker_headerSelectedTextColor, defaultHighlightColor); final int monthTextAppearanceResId = a.getResourceId( - R.styleable.DatePicker_headerMonthTextAppearance, -1); - if (monthTextAppearanceResId != -1) { + R.styleable.DatePicker_headerMonthTextAppearance, 0); + if (monthTextAppearanceResId != 0) { mHeaderMonthTextView.setTextAppearance(context, monthTextAppearanceResId); } mHeaderMonthTextView.setTextColor(ColorStateList.addFirstIfMissing( @@ -166,18 +166,18 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i headerSelectedTextColor)); final int dayOfMonthTextAppearanceResId = a.getResourceId( - R.styleable.DatePicker_headerDayOfMonthTextAppearance, -1); - if (dayOfMonthTextAppearanceResId != -1) { + R.styleable.DatePicker_headerDayOfMonthTextAppearance, 0); + if (dayOfMonthTextAppearanceResId != 0) { mHeaderDayOfMonthTextView.setTextAppearance(context, dayOfMonthTextAppearanceResId); } mHeaderDayOfMonthTextView.setTextColor(ColorStateList.addFirstIfMissing( mHeaderDayOfMonthTextView.getTextColors(), R.attr.state_selected, headerSelectedTextColor)); - final int yearTextAppearanceResId = a.getResourceId( - R.styleable.DatePicker_headerYearTextAppearance, -1); - if (yearTextAppearanceResId != -1) { - mHeaderYearTextView.setTextAppearance(context, yearTextAppearanceResId); + final int headerYearTextAppearanceResId = a.getResourceId( + R.styleable.DatePicker_headerYearTextAppearance, 0); + if (headerYearTextAppearanceResId != 0) { + mHeaderYearTextView.setTextAppearance(context, headerYearTextAppearanceResId); } mHeaderYearTextView.setTextColor(ColorStateList.addFirstIfMissing( mHeaderYearTextView.getTextColors(), R.attr.state_selected, @@ -193,16 +193,23 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i mYearPickerView = new YearPickerView(mContext); mYearPickerView.init(this); - final int yearSelectedCircleColor = a.getColor(R.styleable.DatePicker_yearListSelectorColor, - defaultHighlightColor); - mYearPickerView.setYearSelectedCircleColor(yearSelectedCircleColor); + final ColorStateList yearBackgroundColor = a.getColorStateList( + R.styleable.DatePicker_yearListSelectorColor); + mYearPickerView.setYearBackgroundColor(yearBackgroundColor); + + final int yearTextAppearanceResId = a.getResourceId( + R.styleable.DatePicker_yearListItemTextAppearance, 0); + if (yearTextAppearanceResId != 0) { + mYearPickerView.setYearTextAppearance(yearTextAppearanceResId); + } final ColorStateList calendarTextColor = a.getColorStateList( R.styleable.DatePicker_calendarTextColor); - final int calendarSelectedTextColor = a.getColor( - R.styleable.DatePicker_calendarSelectedTextColor, defaultHighlightColor); - mDayPickerView.setCalendarTextColor(ColorStateList.addFirstIfMissing( - calendarTextColor, R.attr.state_selected, calendarSelectedTextColor)); + mDayPickerView.setCalendarTextColor(calendarTextColor); + + final ColorStateList calendarDayBackgroundColor = a.getColorStateList( + R.styleable.DatePicker_calendarDayBackgroundColor); + mDayPickerView.setCalendarDayBackgroundColor(calendarDayBackgroundColor); mDayPickerDescription = res.getString(R.string.day_picker_description); mSelectDay = res.getString(R.string.select_day); diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java index 7db3fb9..65af45d 100644 --- a/core/java/android/widget/DayPickerView.java +++ b/core/java/android/widget/DayPickerView.java @@ -305,6 +305,10 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener { mAdapter.setCalendarTextColor(colors); } + void setCalendarDayBackgroundColor(ColorStateList dayBackgroundColor) { + mAdapter.setCalendarDayBackgroundColor(dayBackgroundColor); + } + void setCalendarTextAppearance(int resId) { mAdapter.setCalendarTextAppearance(resId); } @@ -459,9 +463,10 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener { mYearFormat = new SimpleDateFormat("yyyy", Locale.getDefault()); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setItemCount(-1); } @@ -476,22 +481,26 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener { /** * Necessary for accessibility, to ensure we support "scrolling" forward and backward * in the month list. + * + * @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD); info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD); } /** * When scroll forward/backward events are received, announce the newly scrolled-to month. + * + * @hide */ @Override - public boolean performAccessibilityAction(int action, Bundle arguments) { + public boolean performAccessibilityActionInternal(int action, Bundle arguments) { if (action != AccessibilityNodeInfo.ACTION_SCROLL_FORWARD && action != AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) { - return super.performAccessibilityAction(action, arguments); + return super.performAccessibilityActionInternal(action, arguments); } // Figure out what month is showing. diff --git a/core/java/android/widget/DigitalClock.java b/core/java/android/widget/DigitalClock.java index b6c1e5b..372bdb3 100644 --- a/core/java/android/widget/DigitalClock.java +++ b/core/java/android/widget/DigitalClock.java @@ -115,16 +115,18 @@ public class DigitalClock extends TextView { } } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); //noinspection deprecation event.setClassName(DigitalClock.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); //noinspection deprecation info.setClassName(DigitalClock.class.getName()); } diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java index a8ff562..f54beb5 100644 --- a/core/java/android/widget/EditText.java +++ b/core/java/android/widget/EditText.java @@ -122,20 +122,23 @@ public class EditText extends TextView { super.setEllipsize(ellipsis); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(EditText.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(EditText.class.getName()); } + /** @hide */ @Override - public boolean performAccessibilityAction(int action, Bundle arguments) { + public boolean performAccessibilityActionInternal(int action, Bundle arguments) { switch (action) { case AccessibilityNodeInfo.ACTION_SET_TEXT: { CharSequence text = (arguments != null) ? arguments.getCharSequence( @@ -147,7 +150,7 @@ public class EditText extends TextView { return true; } default: { - return super.performAccessibilityAction(action, arguments); + return super.performAccessibilityActionInternal(action, arguments); } } } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 936da32..d5166f3 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -1402,7 +1402,7 @@ public class Editor { blockDisplayList.setLeftTopRightBottom(left, top, right, bottom); } - ((HardwareCanvas) canvas).drawRenderNode(blockDisplayList, null, + ((HardwareCanvas) canvas).drawRenderNode(blockDisplayList, 0 /* no child clipping, our TextView parent enforces it */); endOfPreviousBlock = blockEndLine; diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java index 70089e0..675dc9b 100644 --- a/core/java/android/widget/ExpandableListView.java +++ b/core/java/android/widget/ExpandableListView.java @@ -1341,15 +1341,17 @@ public class ExpandableListView extends ListView { } } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(ExpandableListView.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(ExpandableListView.class.getName()); } } diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java index d974c29..b5782fc 100644 --- a/core/java/android/widget/FrameLayout.java +++ b/core/java/android/widget/FrameLayout.java @@ -18,6 +18,7 @@ package android.widget; import java.util.ArrayList; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.res.ColorStateList; @@ -203,11 +204,15 @@ public class FrameLayout extends ViewGroup { } @Override - @RemotableViewMethod - public void setVisibility(@Visibility int visibility) { - super.setVisibility(visibility); - if (mForeground != null) { - mForeground.setVisible(visibility == VISIBLE, false); + protected void onVisibilityChanged(@NonNull View changedView, @Visibility int visibility) { + super.onVisibilityChanged(changedView, visibility); + + final Drawable dr = mForeground; + if (dr != null) { + final boolean visible = visibility == VISIBLE && getVisibility() == VISIBLE; + if (visible != dr.isVisible()) { + dr.setVisible(visible, false); + } } } @@ -704,15 +709,17 @@ public class FrameLayout extends ViewGroup { } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(FrameLayout.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(FrameLayout.class.getName()); } diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java index f7c839f..3c428b0 100644 --- a/core/java/android/widget/Gallery.java +++ b/core/java/android/widget/Gallery.java @@ -1373,15 +1373,17 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(Gallery.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(Gallery.class.getName()); info.setScrollable(mItemCount > 1); if (isEnabled()) { @@ -1394,9 +1396,10 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList } } + /** @hide */ @Override - public boolean performAccessibilityAction(int action, Bundle arguments) { - if (super.performAccessibilityAction(action, arguments)) { + public boolean performAccessibilityActionInternal(int action, Bundle arguments) { + if (super.performAccessibilityActionInternal(action, arguments)) { return true; } switch (action) { diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index 161ae7e..cc925eb 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -1190,15 +1190,17 @@ public class GridLayout extends ViewGroup { } } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(GridLayout.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(GridLayout.class.getName()); } diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java index efd6fc0..f7ce57b 100644 --- a/core/java/android/widget/GridView.java +++ b/core/java/android/widget/GridView.java @@ -2341,15 +2341,17 @@ public class GridView extends AbsListView { return result; } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(GridView.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(GridView.class.getName()); final int columnsCount = getNumColumns(); diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 1b93b97..f1fa1b6 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -762,9 +762,10 @@ public class HorizontalScrollView extends FrameLayout { awakenScrollBars(); } + /** @hide */ @Override - public boolean performAccessibilityAction(int action, Bundle arguments) { - if (super.performAccessibilityAction(action, arguments)) { + public boolean performAccessibilityActionInternal(int action, Bundle arguments) { + if (super.performAccessibilityActionInternal(action, arguments)) { return true; } switch (action) { @@ -794,9 +795,10 @@ public class HorizontalScrollView extends FrameLayout { return false; } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(HorizontalScrollView.class.getName()); final int scrollRange = getScrollRange(); if (scrollRange > 0) { @@ -810,9 +812,10 @@ public class HorizontalScrollView extends FrameLayout { } } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(HorizontalScrollView.class.getName()); event.setScrollable(getScrollRange() > 0); event.setScrollX(mScrollX); diff --git a/core/java/android/widget/ImageButton.java b/core/java/android/widget/ImageButton.java index 3a20628..3b6825d 100644 --- a/core/java/android/widget/ImageButton.java +++ b/core/java/android/widget/ImageButton.java @@ -92,15 +92,17 @@ public class ImageButton extends ImageView { return false; } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(ImageButton.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(ImageButton.class.getName()); } } diff --git a/core/java/android/widget/ImageSwitcher.java b/core/java/android/widget/ImageSwitcher.java index c048970..0910eb0 100644 --- a/core/java/android/widget/ImageSwitcher.java +++ b/core/java/android/widget/ImageSwitcher.java @@ -56,15 +56,17 @@ public class ImageSwitcher extends ViewSwitcher showNext(); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(ImageSwitcher.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(ImageSwitcher.class.getName()); } } diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index c68bfca..fbad314 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -239,9 +239,10 @@ public class ImageView extends View { return (getBackground() != null && getBackground().getCurrent() != null); } + /** @hide */ @Override - public void onPopulateAccessibilityEvent(AccessibilityEvent event) { - super.onPopulateAccessibilityEvent(event); + public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { + super.onPopulateAccessibilityEventInternal(event); CharSequence contentDescription = getContentDescription(); if (!TextUtils.isEmpty(contentDescription)) { event.getText().add(contentDescription); @@ -1418,15 +1419,17 @@ public class ImageView extends View { } } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(ImageView.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(ImageView.class.getName()); } } diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java index 6476cdc..28e50c4 100644 --- a/core/java/android/widget/LinearLayout.java +++ b/core/java/android/widget/LinearLayout.java @@ -1806,15 +1806,17 @@ public class LinearLayout extends ViewGroup { return p instanceof LinearLayout.LayoutParams; } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(LinearLayout.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(LinearLayout.class.getName()); } diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index ba6f061..df0d1fd 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -3878,15 +3878,17 @@ public class ListView extends AbsListView { return false; } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(ListView.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(ListView.class.getName()); final int rowsCount = getCount(); diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java index f1aaa4d..4cafe72 100644 --- a/core/java/android/widget/MediaController.java +++ b/core/java/android/widget/MediaController.java @@ -625,15 +625,17 @@ public class MediaController extends FrameLayout { super.setEnabled(enabled); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(MediaController.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(MediaController.class.getName()); } diff --git a/core/java/android/widget/MultiAutoCompleteTextView.java b/core/java/android/widget/MultiAutoCompleteTextView.java index cbd01b0..6954eea 100644 --- a/core/java/android/widget/MultiAutoCompleteTextView.java +++ b/core/java/android/widget/MultiAutoCompleteTextView.java @@ -202,15 +202,17 @@ public class MultiAutoCompleteTextView extends AutoCompleteTextView { editable.replace(start, end, mTokenizer.terminateToken(text)); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(MultiAutoCompleteTextView.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(MultiAutoCompleteTextView.class.getName()); } diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java index ee17b78..16dc26d 100644 --- a/core/java/android/widget/NumberPicker.java +++ b/core/java/android/widget/NumberPicker.java @@ -1558,9 +1558,10 @@ public class NumberPicker extends LinearLayout { } } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(NumberPicker.class.getName()); event.setScrollable(true); event.setScrollY((mMinValue + mValue) * mSelectorElementHeight); diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java index 2708398..06ac1c3 100644 --- a/core/java/android/widget/PopupMenu.java +++ b/core/java/android/widget/PopupMenu.java @@ -115,6 +115,29 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { } /** + * Sets the gravity used to align the popup window to its anchor view. + * <p> + * If the popup is showing, calling this method will take effect only + * the next time the popup is shown. + * + * @param gravity the gravity used to align the popup window + * + * @see #getGravity() + */ + public void setGravity(int gravity) { + mPopup.setGravity(gravity); + } + + /** + * @return the gravity used to align the popup window to its anchor view + * + * @see #setGravity(int) + */ + public int getGravity() { + return mPopup.getGravity(); + } + + /** * Returns an {@link OnTouchListener} that can be added to the anchor view * to implement drag-to-open behavior. * <p> diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 5419ab6..a929f3d 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -18,17 +18,20 @@ package android.widget; import com.android.internal.R; -import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; -import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.StateListDrawable; import android.os.Build; import android.os.IBinder; +import android.transition.Transition; +import android.transition.Transition.EpicenterCallback; +import android.transition.TransitionInflater; +import android.transition.TransitionManager; +import android.transition.TransitionSet; import android.util.AttributeSet; import android.view.Gravity; import android.view.KeyEvent; @@ -41,12 +44,13 @@ import android.view.ViewTreeObserver.OnScrollChangedListener; import android.view.WindowManager; import java.lang.ref.WeakReference; +import java.util.List; /** * <p>A popup window that can be used to display an arbitrary view. The popup * window is a floating container that appears on top of the current * activity.</p> - * + * * @see android.widget.AutoCompleteTextView * @see android.widget.Spinner */ @@ -58,7 +62,7 @@ public class PopupWindow { * it doesn't. */ public static final int INPUT_METHOD_FROM_FOCUSABLE = 0; - + /** * Mode for {@link #setInputMethodMode(int)}: this popup always needs to * work with an input method, regardless of whether it is focusable. This @@ -66,7 +70,7 @@ public class PopupWindow { * the input method while it is shown. */ public static final int INPUT_METHOD_NEEDED = 1; - + /** * Mode for {@link #setInputMethodMode(int)}: this popup never needs to * work with an input method, regardless of whether it is focusable. This @@ -77,14 +81,32 @@ public class PopupWindow { private static final int DEFAULT_ANCHORED_GRAVITY = Gravity.TOP | Gravity.START; + /** + * Default animation style indicating that separate animations should be + * used for top/bottom anchoring states. + */ + private static final int ANIMATION_STYLE_DEFAULT = -1; + + private final int[] mDrawingLocation = new int[2]; + private final int[] mScreenLocation = new int[2]; + private final Rect mTempRect = new Rect(); + private final Rect mAnchorBounds = new Rect(); + private Context mContext; private WindowManager mWindowManager; - + private boolean mIsShowing; private boolean mIsDropdown; + /** View that handles event dispatch and content transitions. */ + private PopupDecorView mDecorView; + + /** View that holds the popup background. May be the content view. */ + private View mBackgroundView; + + /** The contents of the popup. */ private View mContentView; - private View mPopupView; + private boolean mFocusable; private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE; private int mSoftInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED; @@ -114,49 +136,67 @@ public class PopupWindow { private float mElevation; - private int[] mDrawingLocation = new int[2]; - private int[] mScreenLocation = new int[2]; - private Rect mTempRect = new Rect(); - private Drawable mBackground; private Drawable mAboveAnchorBackgroundDrawable; private Drawable mBelowAnchorBackgroundDrawable; - // Temporary animation centers. Should be moved into window params? - private int mAnchorRelativeX; - private int mAnchorRelativeY; + private Transition mEnterTransition; + private Transition mExitTransition; private boolean mAboveAnchor; private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; - + private OnDismissListener mOnDismissListener; private boolean mIgnoreCheekPress = false; - private int mAnimationStyle = -1; - + private int mAnimationStyle = ANIMATION_STYLE_DEFAULT; + private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] { com.android.internal.R.attr.state_above_anchor }; private WeakReference<View> mAnchor; - private final OnScrollChangedListener mOnScrollChangedListener = - new OnScrollChangedListener() { - @Override - public void onScrollChanged() { - final View anchor = mAnchor != null ? mAnchor.get() : null; - if (anchor != null && mPopupView != null) { - final WindowManager.LayoutParams p = (WindowManager.LayoutParams) - mPopupView.getLayoutParams(); - - updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff, - mAnchoredGravity)); - update(p.x, p.y, -1, -1, true); - } + private final EpicenterCallback mEpicenterCallback = new EpicenterCallback() { + @Override + public Rect onGetEpicenter(Transition transition) { + final View anchor = mAnchor.get(); + final View decor = mDecorView; + if (anchor == null || decor == null) { + return null; + } + + final Rect anchorBounds = mAnchorBounds; + final int[] anchorLocation = mAnchor.get().getLocationOnScreen(); + final int[] popupLocation = mDecorView.getLocationOnScreen(); + + // Compute the position of the anchor relative to the popup. + anchorBounds.set(0, 0, anchor.getWidth(), anchor.getHeight()); + anchorBounds.offset(anchorLocation[0] - popupLocation[0], + anchorLocation[1] - popupLocation[1]); + + return anchorBounds; + } + }; + + private final OnScrollChangedListener mOnScrollChangedListener = new OnScrollChangedListener() { + @Override + public void onScrollChanged() { + final View anchor = mAnchor != null ? mAnchor.get() : null; + if (anchor != null && mDecorView != null) { + final WindowManager.LayoutParams p = (WindowManager.LayoutParams) + mDecorView.getLayoutParams(); + + updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff, + mAnchoredGravity)); + update(p.x, p.y, -1, -1, true); } - }; + } + }; - private int mAnchorXoff, mAnchorYoff, mAnchoredGravity; + private int mAnchorXoff; + private int mAnchorYoff; + private int mAnchoredGravity; private boolean mOverlapAnchor; private boolean mPopupViewInitialLayoutDirectionInherited; @@ -187,15 +227,15 @@ public class PopupWindow { public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } - + /** * <p>Create a new, empty, non focusable popup window of dimension (0,0).</p> - * + * * <p>The popup does not provide a background.</p> */ public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { mContext = context; - mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); + mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.PopupWindow, defStyleAttr, defStyleRes); @@ -203,11 +243,34 @@ public class PopupWindow { mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0); mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false); - final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, -1); - mAnimationStyle = animStyle == R.style.Animation_PopupWindow ? -1 : animStyle; + // Preserve default behavior from Gingerbread. If the animation is + // undefined or explicitly specifies the Gingerbread animation style, + // use a sentinel value. + if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupAnimationStyle)) { + final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, 0); + if (animStyle == R.style.Animation_PopupWindow) { + mAnimationStyle = ANIMATION_STYLE_DEFAULT; + } else { + mAnimationStyle = animStyle; + } + } else { + mAnimationStyle = ANIMATION_STYLE_DEFAULT; + } + + final Transition enterTransition = getTransition(a.getResourceId( + R.styleable.PopupWindow_popupEnterTransition, 0)); + final Transition exitTransition; + if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupExitTransition)) { + exitTransition = getTransition(a.getResourceId( + R.styleable.PopupWindow_popupExitTransition, 0)); + } else { + exitTransition = enterTransition == null ? null : enterTransition.clone(); + } a.recycle(); + setEnterTransition(enterTransition); + setExitTransition(exitTransition); setBackgroundDrawable(bg); } @@ -288,6 +351,37 @@ public class PopupWindow { setFocusable(focusable); } + public void setEnterTransition(Transition enterTransition) { + mEnterTransition = enterTransition; + + if (mEnterTransition != null) { + mEnterTransition.setEpicenterCallback(mEpicenterCallback); + } + } + + public void setExitTransition(Transition exitTransition) { + mExitTransition = exitTransition; + + if (mExitTransition != null) { + mExitTransition.setEpicenterCallback(mEpicenterCallback); + } + } + + private Transition getTransition(int resId) { + if (resId != 0 && resId != R.transition.no_transition) { + final TransitionInflater inflater = TransitionInflater.from(mContext); + final Transition transition = inflater.inflateTransition(resId); + if (transition != null) { + final boolean isEmpty = transition instanceof TransitionSet + && ((TransitionSet) transition).getTransitionCount() == 0; + if (!isEmpty) { + return transition; + } + } + } + return null; + } + /** * Return the drawable used as the popup window's background. * @@ -381,7 +475,7 @@ public class PopupWindow { * Set the flag on popup to ignore cheek press events; by default this flag * is set to false * which means the popup will not ignore cheek press dispatch events. - * + * * <p>If the popup is showing, calling this method will take effect only * the next time the popup is shown or through a manual call to one of * the {@link #update()} methods.</p> @@ -391,7 +485,7 @@ public class PopupWindow { public void setIgnoreCheekPress() { mIgnoreCheekPress = true; } - + /** * <p>Change the animation style resource for this popup.</p> @@ -403,13 +497,13 @@ public class PopupWindow { * @param animationStyle animation style to use when the popup appears * and disappears. Set to -1 for the default animation, 0 for no * animation, or a resource identifier for an explicit animation. - * + * * @see #update() */ public void setAnimationStyle(int animationStyle) { mAnimationStyle = animationStyle; } - + /** * <p>Return the view used as the content of the popup window.</p> * @@ -493,7 +587,7 @@ public class PopupWindow { * @param focusable true if the popup should grab focus, false otherwise. * * @see #isFocusable() - * @see #isShowing() + * @see #isShowing() * @see #update() */ public void setFocusable(boolean focusable) { @@ -502,23 +596,23 @@ public class PopupWindow { /** * Return the current value in {@link #setInputMethodMode(int)}. - * + * * @see #setInputMethodMode(int) */ public int getInputMethodMode() { return mInputMethodMode; - + } - + /** * Control how the popup operates with an input method: one of * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED}, * or {@link #INPUT_METHOD_NOT_NEEDED}. - * + * * <p>If the popup is showing, calling this method will take effect only * the next time the popup is shown or through a manual call to one of * the {@link #update()} methods.</p> - * + * * @see #getInputMethodMode() * @see #update() */ @@ -549,12 +643,12 @@ public class PopupWindow { public int getSoftInputMode() { return mSoftInputMode; } - + /** * <p>Indicates whether the popup window receives touch events.</p> - * + * * @return true if the popup is touchable, false otherwise - * + * * @see #setTouchable(boolean) */ public boolean isTouchable() { @@ -573,7 +667,7 @@ public class PopupWindow { * @param touchable true if the popup should receive touch events, false otherwise * * @see #isTouchable() - * @see #isShowing() + * @see #isShowing() * @see #update() */ public void setTouchable(boolean touchable) { @@ -583,9 +677,9 @@ public class PopupWindow { /** * <p>Indicates whether the popup window will be informed of touch events * outside of its window.</p> - * + * * @return true if the popup is outside touchable, false otherwise - * + * * @see #setOutsideTouchable(boolean) */ public boolean isOutsideTouchable() { @@ -606,7 +700,7 @@ public class PopupWindow { * touch events, false otherwise * * @see #isOutsideTouchable() - * @see #isShowing() + * @see #isShowing() * @see #update() */ public void setOutsideTouchable(boolean touchable) { @@ -615,9 +709,9 @@ public class PopupWindow { /** * <p>Indicates whether clipping of the popup window is enabled.</p> - * + * * @return true if the clipping is enabled, false otherwise - * + * * @see #setClippingEnabled(boolean) */ public boolean isClippingEnabled() { @@ -628,13 +722,13 @@ public class PopupWindow { * <p>Allows the popup window to extend beyond the bounds of the screen. By default the * window is clipped to the screen boundaries. Setting this to false will allow windows to be * accurately positioned.</p> - * + * * <p>If the popup is showing, calling this method will take effect only * the next time the popup is shown or through a manual call to one of * the {@link #update()} methods.</p> * * @param enabled false if the window should be allowed to extend outside of the screen - * @see #isShowing() + * @see #isShowing() * @see #isClippingEnabled() * @see #update() */ @@ -662,12 +756,12 @@ public class PopupWindow { void setAllowScrollingAnchorParent(boolean enabled) { mAllowScrollingAnchorParent = enabled; } - + /** * <p>Indicates whether the popup window supports splitting touches.</p> - * + * * @return true if the touch splitting is enabled, false otherwise - * + * * @see #setSplitTouchEnabled(boolean) */ public boolean isSplitTouchEnabled() { @@ -796,7 +890,7 @@ public class PopupWindow { * window manager by the popup. By default these are 0, meaning that * the current width or height is requested as an explicit size from * the window manager. You can supply - * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or + * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or * {@link ViewGroup.LayoutParams#MATCH_PARENT} to have that measure * spec supplied instead, replacing the absolute width and height that * has been set in the popup.</p> @@ -817,7 +911,7 @@ public class PopupWindow { mWidthMode = widthSpec; mHeightMode = heightSpec; } - + /** * <p>Return this popup's height MeasureSpec</p> * @@ -838,7 +932,7 @@ public class PopupWindow { * @param height the height MeasureSpec of the popup * * @see #getHeight() - * @see #isShowing() + * @see #isShowing() */ public void setHeight(int height) { mHeight = height; @@ -849,7 +943,7 @@ public class PopupWindow { * * @return the width MeasureSpec of the popup * - * @see #setWidth(int) + * @see #setWidth(int) */ public int getWidth() { return mWidth; @@ -871,6 +965,34 @@ public class PopupWindow { } /** + * Sets whether the popup window should overlap its anchor view when + * displayed as a drop-down. + * <p> + * If the popup is showing, calling this method will take effect only + * the next time the popup is shown. + * + * @param overlapAnchor Whether the popup should overlap its anchor. + * + * @see #getOverlapAnchor() + * @see #isShowing() + */ + public void setOverlapAnchor(boolean overlapAnchor) { + mOverlapAnchor = overlapAnchor; + } + + /** + * Returns whether the popup window should overlap its anchor view when + * displayed as a drop-down. + * + * @return Whether the popup should overlap its anchor. + * + * @see #setOverlapAnchor(boolean) + */ + public boolean getOverlapAnchor() { + return mOverlapAnchor; + } + + /** * <p>Indicate whether this popup window is showing on screen.</p> * * @return true if the popup is showing, false otherwise @@ -887,7 +1009,7 @@ public class PopupWindow { * a gravity of {@link android.view.Gravity#NO_GRAVITY} is similar to specifying * <code>Gravity.LEFT | Gravity.TOP</code>. * </p> - * + * * @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from * @param gravity the gravity which controls the placement of the popup window * @param x the popup's x location offset @@ -913,32 +1035,34 @@ public class PopupWindow { return; } + TransitionManager.endTransitions(mDecorView); + unregisterForScrollChanged(); mIsShowing = true; mIsDropdown = false; - WindowManager.LayoutParams p = createPopupLayout(token); - p.windowAnimations = computeAnimationResource(); - + final WindowManager.LayoutParams p = createPopupLayoutParams(token); preparePopup(p); - if (gravity == Gravity.NO_GRAVITY) { - gravity = Gravity.TOP | Gravity.START; + + // Only override the default if some gravity was specified. + if (gravity != Gravity.NO_GRAVITY) { + p.gravity = gravity; } - p.gravity = gravity; + p.x = x; p.y = y; - if (mHeightMode < 0) p.height = mLastHeight = mHeightMode; - if (mWidthMode < 0) p.width = mLastWidth = mWidthMode; + invokePopup(p); } /** - * <p>Display the content view in a popup window anchored to the bottom-left + * Display the content view in a popup window anchored to the bottom-left * corner of the anchor view. If there is not enough room on screen to show * the popup in its entirety, this method tries to find a parent scroll - * view to scroll. If no parent scroll view can be scrolled, the bottom-left - * corner of the popup is pinned at the top left corner of the anchor view.</p> + * view to scroll. If no parent scroll view can be scrolled, the + * bottom-left corner of the popup is pinned at the top left corner of the + * anchor view. * * @param anchor the view on which to pin the popup window * @@ -949,14 +1073,15 @@ public class PopupWindow { } /** - * <p>Display the content view in a popup window anchored to the bottom-left + * Display the content view in a popup window anchored to the bottom-left * corner of the anchor view offset by the specified x and y coordinates. - * If there is not enough room on screen to show - * the popup in its entirety, this method tries to find a parent scroll - * view to scroll. If no parent scroll view can be scrolled, the bottom-left - * corner of the popup is pinned at the top left corner of the anchor view.</p> - * <p>If the view later scrolls to move <code>anchor</code> to a different - * location, the popup will be moved correspondingly.</p> + * If there is not enough room on screen to show the popup in its entirety, + * this method tries to find a parent scroll view to scroll. If no parent + * scroll view can be scrolled, the bottom-left corner of the popup is + * pinned at the top left corner of the anchor view. + * <p> + * If the view later scrolls to move <code>anchor</code> to a different + * location, the popup will be moved correspondingly. * * @param anchor the view on which to pin the popup window * @param xoff A horizontal offset from the anchor in pixels @@ -969,14 +1094,17 @@ public class PopupWindow { } /** - * <p>Display the content view in a popup window anchored to the bottom-left - * corner of the anchor view offset by the specified x and y coordinates. - * If there is not enough room on screen to show - * the popup in its entirety, this method tries to find a parent scroll - * view to scroll. If no parent scroll view can be scrolled, the bottom-left - * corner of the popup is pinned at the top left corner of the anchor view.</p> - * <p>If the view later scrolls to move <code>anchor</code> to a different - * location, the popup will be moved correspondingly.</p> + * Displays the content view in a popup window anchored to the corner of + * another view. The window is positioned according to the specified + * gravity and offset by the specified x and y coordinates. + * <p> + * If there is not enough room on screen to show the popup in its entirety, + * this method tries to find a parent scroll view to scroll. If no parent + * view can be scrolled, the specified vertical gravity will be ignored and + * the popup will anchor itself such that it is visible. + * <p> + * If the view later scrolls to move <code>anchor</code> to a different + * location, the popup will be moved correspondingly. * * @param anchor the view on which to pin the popup window * @param xoff A horizontal offset from the anchor in pixels @@ -990,20 +1118,18 @@ public class PopupWindow { return; } + TransitionManager.endTransitions(mDecorView); + registerForScrollChanged(anchor, xoff, yoff, gravity); mIsShowing = true; mIsDropdown = true; - WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken()); + final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken()); preparePopup(p); - updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, gravity)); - - if (mHeightMode < 0) p.height = mLastHeight = mHeightMode; - if (mWidthMode < 0) p.width = mLastWidth = mWidthMode; - - p.windowAnimations = computeAnimationResource(); + final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff, gravity); + updateAboveAnchor(aboveAnchor); invokePopup(p); } @@ -1018,12 +1144,12 @@ public class PopupWindow { // do the job. if (mAboveAnchorBackgroundDrawable != null) { if (mAboveAnchor) { - mPopupView.setBackground(mAboveAnchorBackgroundDrawable); + mDecorView.setBackground(mAboveAnchorBackgroundDrawable); } else { - mPopupView.setBackground(mBelowAnchorBackgroundDrawable); + mDecorView.setBackground(mBelowAnchorBackgroundDrawable); } } else { - mPopupView.refreshDrawableState(); + mDecorView.refreshDrawableState(); } } } @@ -1045,10 +1171,9 @@ public class PopupWindow { } /** - * <p>Prepare the popup by embedding in into a new ViewGroup if the - * background drawable is not null. If embedding is required, the layout - * parameters' height is modified to take into account the background's - * padding.</p> + * Prepare the popup by embedding it into a new ViewGroup if the background + * drawable is not null. If embedding is required, the layout parameters' + * height is modified to take into account the background's padding. * * @param p the layout parameters of the popup's content view */ @@ -1058,36 +1183,79 @@ public class PopupWindow { + "calling setContentView() before attempting to show the popup."); } + // When a background is available, we embed the content view within + // another view that owns the background drawable. if (mBackground != null) { - final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); - int height = ViewGroup.LayoutParams.MATCH_PARENT; - if (layoutParams != null && - layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) { - height = ViewGroup.LayoutParams.WRAP_CONTENT; - } - - // when a background is available, we embed the content view - // within another view that owns the background drawable - PopupViewContainer popupViewContainer = new PopupViewContainer(mContext); - PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, height - ); - popupViewContainer.setBackground(mBackground); - popupViewContainer.addView(mContentView, listParams); - - mPopupView = popupViewContainer; + mBackgroundView = createBackgroundView(mContentView); + mBackgroundView.setBackground(mBackground); } else { - mPopupView = mContentView; + mBackgroundView = mContentView; } - mPopupView.setElevation(mElevation); + mDecorView = createDecorView(mBackgroundView); + + // The background owner should be elevated so that it casts a shadow. + mBackgroundView.setElevation(mElevation); + + // We may wrap that in another view, so we'll need to manually specify + // the surface insets. + final int surfaceInset = (int) Math.ceil(mBackgroundView.getZ() * 2); + p.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset); + p.hasManualSurfaceInsets = true; + mPopupViewInitialLayoutDirectionInherited = - (mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT); + (mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT); mPopupWidth = p.width; mPopupHeight = p.height; } /** + * Wraps a content view in a PopupViewContainer. + * + * @param contentView the content view to wrap + * @return a PopupViewContainer that wraps the content view + */ + private PopupBackgroundView createBackgroundView(View contentView) { + final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); + final int height; + if (layoutParams != null && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) { + height = ViewGroup.LayoutParams.WRAP_CONTENT; + } else { + height = ViewGroup.LayoutParams.MATCH_PARENT; + } + + final PopupBackgroundView backgroundView = new PopupBackgroundView(mContext); + final PopupBackgroundView.LayoutParams listParams = new PopupBackgroundView.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, height); + backgroundView.addView(contentView, listParams); + + return backgroundView; + } + + /** + * Wraps a content view in a FrameLayout. + * + * @param contentView the content view to wrap + * @return a FrameLayout that wraps the content view + */ + private PopupDecorView createDecorView(View contentView) { + final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); + final int height; + if (layoutParams != null && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) { + height = ViewGroup.LayoutParams.WRAP_CONTENT; + } else { + height = ViewGroup.LayoutParams.MATCH_PARENT; + } + + final PopupDecorView decorView = new PopupDecorView(mContext); + decorView.addView(contentView, ViewGroup.LayoutParams.MATCH_PARENT, height); + decorView.setClipChildren(false); + decorView.setClipToPadding(false); + + return decorView; + } + + /** * <p>Invoke the popup window by adding the content view to the window * manager.</p> * @@ -1099,16 +1267,34 @@ public class PopupWindow { if (mContext != null) { p.packageName = mContext.getPackageName(); } - mPopupView.setFitsSystemWindows(mLayoutInsetDecor); + + final View rootView = mContentView.getRootView(); + rootView.setFitsSystemWindows(mLayoutInsetDecor); setLayoutDirectionFromAnchor(); - mWindowManager.addView(mPopupView, p); + + mWindowManager.addView(rootView, p); + + // Postpone enter transition until the scene root has been laid out. + if (mEnterTransition != null) { + mEnterTransition.addTarget(mBackgroundView); + mEnterTransition.addListener(new Transition.TransitionListenerAdapter() { + @Override + public void onTransitionEnd(Transition transition) { + transition.removeListener(this); + transition.removeTarget(mBackgroundView); + } + }); + + mDecorView.getViewTreeObserver().addOnGlobalLayoutListener( + new PostLayoutTransitionListener(mDecorView, mEnterTransition)); + } } private void setLayoutDirectionFromAnchor() { if (mAnchor != null) { View anchor = mAnchor.get(); if (anchor != null && mPopupViewInitialLayoutDirectionInherited) { - mPopupView.setLayoutDirection(anchor.getLayoutDirection()); + mDecorView.setLayoutDirection(anchor.getLayoutDirection()); } } } @@ -1120,26 +1306,39 @@ public class PopupWindow { * * @return the layout parameters to pass to the window manager */ - private WindowManager.LayoutParams createPopupLayout(IBinder token) { - // generates the layout parameters for the drop down - // we want a fixed size view located at the bottom left of the anchor - WindowManager.LayoutParams p = new WindowManager.LayoutParams(); - // these gravity settings put the view at the top left corner of the - // screen. The view is then positioned to the appropriate location - // by setting the x and y offsets to match the anchor's bottom - // left corner + private WindowManager.LayoutParams createPopupLayoutParams(IBinder token) { + final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); + + // These gravity settings put the view at the top left corner of the + // screen. The view is then positioned to the appropriate location by + // setting the x and y offsets to match the anchor's bottom-left + // corner. p.gravity = Gravity.START | Gravity.TOP; - p.width = mLastWidth = mWidth; - p.height = mLastHeight = mHeight; + p.flags = computeFlags(p.flags); + p.type = mWindowLayoutType; + p.token = token; + p.softInputMode = mSoftInputMode; + p.windowAnimations = computeAnimationResource(); + if (mBackground != null) { p.format = mBackground.getOpacity(); } else { p.format = PixelFormat.TRANSLUCENT; } - p.flags = computeFlags(p.flags); - p.type = mWindowLayoutType; - p.token = token; - p.softInputMode = mSoftInputMode; + + if (mHeightMode < 0) { + p.height = mLastHeight = mHeightMode; + } else { + p.height = mLastHeight = mHeight; + } + + if (mWidthMode < 0) { + p.width = mLastWidth = mWidthMode; + } else { + p.width = mLastWidth = mWidth; + } + + // Used for debugging. p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); return p; @@ -1193,7 +1392,7 @@ public class PopupWindow { } private int computeAnimationResource() { - if (mAnimationStyle == -1) { + if (mAnimationStyle == ANIMATION_STYLE_DEFAULT) { if (mIsDropdown) { return mAboveAnchor ? com.android.internal.R.style.Animation_DropDownUp @@ -1212,7 +1411,7 @@ public class PopupWindow { * <p> * The height must have been set on the layout parameters prior to calling * this method. - * + * * @param anchor the view on which the popup window must be anchored * @param p the layout parameters used to display the drop down * @param xoff horizontal offset used to adjust for background padding @@ -1310,19 +1509,15 @@ public class PopupWindow { p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL; - // Compute the position of the anchor relative to the popup. - mAnchorRelativeX = mDrawingLocation[0] - p.x + anchorHeight / 2; - mAnchorRelativeY = mDrawingLocation[1] - p.y + anchorWidth / 2; - return onTop; } - + /** * Returns the maximum height that is available for the popup to be * completely shown. It is recommended that this height be the maximum for * the popup's height, otherwise it is possible that the popup will be * clipped. - * + * * @param anchor The view on which the popup window must be anchored. * @return The maximum available height for the popup to be completely * shown. @@ -1345,14 +1540,14 @@ public class PopupWindow { public int getMaxAvailableHeight(View anchor, int yOffset) { return getMaxAvailableHeight(anchor, yOffset, false); } - + /** * Returns the maximum height that is available for the popup to be * completely shown, optionally ignoring any bottom decorations such as * the input method. It is recommended that this height be the maximum for * the popup's height, otherwise it is possible that the popup will be * clipped. - * + * * @param anchor The view on which the popup window must be anchored. * @param yOffset y offset from the view's bottom edge * @param ignoreBottomDecorations if true, the height returned will be @@ -1360,7 +1555,7 @@ public class PopupWindow { * bottom decorations * @return The maximum available height for the popup to be completely * shown. - * + * * @hide Pending API council approval. */ public int getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations) { @@ -1369,7 +1564,7 @@ public class PopupWindow { final int[] anchorPos = mDrawingLocation; anchor.getLocationOnScreen(anchorPos); - + int bottomEdge = displayFrame.bottom; if (ignoreBottomDecorations) { Resources res = anchor.getContext().getResources(); @@ -1382,49 +1577,83 @@ public class PopupWindow { int returnedHeight = Math.max(distanceToBottom, distanceToTop); if (mBackground != null) { mBackground.getPadding(mTempRect); - returnedHeight -= mTempRect.top + mTempRect.bottom; + returnedHeight -= mTempRect.top + mTempRect.bottom; } - + return returnedHeight; } - + /** - * <p>Dispose of the popup window. This method can be invoked only after - * {@link #showAsDropDown(android.view.View)} has been executed. Failing that, calling - * this method will have no effect.</p> + * Disposes of the popup window. This method can be invoked only after + * {@link #showAsDropDown(android.view.View)} has been executed. Failing + * that, calling this method will have no effect. * - * @see #showAsDropDown(android.view.View) + * @see #showAsDropDown(android.view.View) */ public void dismiss() { - if (isShowing() && mPopupView != null) { - mIsShowing = false; + if (!isShowing()) { + return; + } + + unregisterForScrollChanged(); - unregisterForScrollChanged(); + mIsShowing = false; - try { - mWindowManager.removeViewImmediate(mPopupView); - } finally { - if (mPopupView != mContentView && mPopupView instanceof ViewGroup) { - ((ViewGroup) mPopupView).removeView(mContentView); - } - mPopupView = null; + if (mExitTransition != null) { + // Cache the content view, since it may change without notice. + final View contentView = mContentView; - if (mOnDismissListener != null) { - mOnDismissListener.onDismiss(); + mExitTransition.addTarget(mBackgroundView); + mExitTransition.addListener(new Transition.TransitionListenerAdapter() { + @Override + public void onTransitionEnd(Transition transition) { + transition.removeListener(this); + transition.removeTarget(mBackgroundView); + + dismissImmediate(contentView); } + }); + + TransitionManager.beginDelayedTransition(mDecorView, mExitTransition); + + // Transition to invisible. + mBackgroundView.setVisibility(View.INVISIBLE); + } else { + dismissImmediate(mContentView); + } + + if (mOnDismissListener != null) { + mOnDismissListener.onDismiss(); + } + } + + /** + * Removes the popup from the window manager and tears down the supporting + * view hierarchy, if necessary. + */ + private void dismissImmediate(View contentView) { + try { + mWindowManager.removeViewImmediate(mDecorView); + } finally { + mDecorView.removeView(mBackgroundView); + mDecorView = null; + + if (mBackgroundView != contentView) { + ((ViewGroup) mBackgroundView).removeView(contentView); } + mBackgroundView = null; } } /** * Sets the listener to be called when the window is dismissed. - * + * * @param onDismissListener The listener. */ public void setOnDismissListener(OnDismissListener onDismissListener) { mOnDismissListener = onDismissListener; } - + /** * Updates the state of the popup window, if it is currently being displayed, * from the currently set state. This includes: @@ -1436,12 +1665,12 @@ public class PopupWindow { if (!isShowing() || mContentView == null) { return; } - - WindowManager.LayoutParams p = (WindowManager.LayoutParams) - mPopupView.getLayoutParams(); - + + final WindowManager.LayoutParams p = + (WindowManager.LayoutParams) mDecorView.getLayoutParams(); + boolean update = false; - + final int newAnim = computeAnimationResource(); if (newAnim != p.windowAnimations) { p.windowAnimations = newAnim; @@ -1456,7 +1685,7 @@ public class PopupWindow { if (update) { setLayoutDirectionFromAnchor(); - mWindowManager.updateViewLayout(mPopupView, p); + mWindowManager.updateViewLayout(mDecorView, p); } } @@ -1469,11 +1698,11 @@ public class PopupWindow { * @param height the new height */ public void update(int width, int height) { - WindowManager.LayoutParams p = (WindowManager.LayoutParams) - mPopupView.getLayoutParams(); + final WindowManager.LayoutParams p = + (WindowManager.LayoutParams) mDecorView.getLayoutParams(); update(p.x, p.y, width, height, false); } - + /** * <p>Updates the position and the dimension of the popup window. Width and * height can be set to -1 to update location only. Calling this function @@ -1517,7 +1746,8 @@ public class PopupWindow { return; } - WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams(); + final WindowManager.LayoutParams p = + (WindowManager.LayoutParams) mDecorView.getLayoutParams(); boolean update = force; @@ -1557,7 +1787,7 @@ public class PopupWindow { if (update) { setLayoutDirectionFromAnchor(); - mWindowManager.updateViewLayout(mPopupView, p); + mWindowManager.updateViewLayout(mDecorView, p); } } @@ -1571,7 +1801,7 @@ public class PopupWindow { * @param height the new height, can be -1 to ignore */ public void update(View anchor, int width, int height) { - update(anchor, false, 0, 0, true, width, height, mAnchoredGravity); + update(anchor, false, 0, 0, true, width, height); } /** @@ -1590,30 +1820,26 @@ public class PopupWindow { * @param height the new height, can be -1 to ignore */ public void update(View anchor, int xoff, int yoff, int width, int height) { - update(anchor, true, xoff, yoff, true, width, height, mAnchoredGravity); + update(anchor, true, xoff, yoff, true, width, height); } private void update(View anchor, boolean updateLocation, int xoff, int yoff, - boolean updateDimension, int width, int height, int gravity) { + boolean updateDimension, int width, int height) { if (!isShowing() || mContentView == null) { return; } - WeakReference<View> oldAnchor = mAnchor; - final boolean needsUpdate = updateLocation - && (mAnchorXoff != xoff || mAnchorYoff != yoff); + final WeakReference<View> oldAnchor = mAnchor; + final boolean needsUpdate = updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff); if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) { - registerForScrollChanged(anchor, xoff, yoff, gravity); + registerForScrollChanged(anchor, xoff, yoff, mAnchoredGravity); } else if (needsUpdate) { // No need to register again if this is a DropDown, showAsDropDown already did. mAnchorXoff = xoff; mAnchorYoff = yoff; - mAnchoredGravity = gravity; } - WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams(); - if (updateDimension) { if (width == -1) { width = mPopupWidth; @@ -1627,11 +1853,12 @@ public class PopupWindow { } } - int x = p.x; - int y = p.y; - + final WindowManager.LayoutParams p = + (WindowManager.LayoutParams) mDecorView.getLayoutParams(); + final int x = p.x; + final int y = p.y; if (updateLocation) { - updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, gravity)); + updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, mAnchoredGravity)); } else { updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff, mAnchoredGravity)); @@ -1651,23 +1878,22 @@ public class PopupWindow { } private void unregisterForScrollChanged() { - WeakReference<View> anchorRef = mAnchor; - View anchor = null; - if (anchorRef != null) { - anchor = anchorRef.get(); - } + final WeakReference<View> anchorRef = mAnchor; + final View anchor = anchorRef == null ? null : anchorRef.get(); if (anchor != null) { - ViewTreeObserver vto = anchor.getViewTreeObserver(); + final ViewTreeObserver vto = anchor.getViewTreeObserver(); vto.removeOnScrollChangedListener(mOnScrollChangedListener); } + mAnchor = null; } private void registerForScrollChanged(View anchor, int xoff, int yoff, int gravity) { unregisterForScrollChanged(); - mAnchor = new WeakReference<View>(anchor); - ViewTreeObserver vto = anchor.getViewTreeObserver(); + mAnchor = new WeakReference<>(anchor); + + final ViewTreeObserver vto = anchor.getViewTreeObserver(); if (vto != null) { vto.addOnScrollChangedListener(mOnScrollChangedListener); } @@ -1677,23 +1903,49 @@ public class PopupWindow { mAnchoredGravity = gravity; } - private class PopupViewContainer extends FrameLayout { - private static final String TAG = "PopupWindow.PopupViewContainer"; + /** + * Layout listener used to run a transition immediately after a view is + * laid out. Forces the view to transition from invisible to visible. + */ + private static class PostLayoutTransitionListener implements + ViewTreeObserver.OnGlobalLayoutListener { + private final ViewGroup mSceneRoot; + private final Transition mTransition; - public PopupViewContainer(Context context) { - super(context); + public PostLayoutTransitionListener(ViewGroup sceneRoot, Transition transition) { + mSceneRoot = sceneRoot; + mTransition = transition; } @Override - protected int[] onCreateDrawableState(int extraSpace) { - if (mAboveAnchor) { - // 1 more needed for the above anchor state - final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); - View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET); - return drawableState; - } else { - return super.onCreateDrawableState(extraSpace); + public void onGlobalLayout() { + final ViewTreeObserver observer = mSceneRoot.getViewTreeObserver(); + if (observer == null) { + // View has been detached. + return; } + + observer.removeOnGlobalLayoutListener(this); + + // Set all targets to be initially invisible. + final List<View> targets = mTransition.getTargets(); + final int N = targets.size(); + for (int i = 0; i < N; i++) { + targets.get(i).setVisibility(View.INVISIBLE); + } + + TransitionManager.beginDelayedTransition(mSceneRoot, mTransition); + + // Transition targets to visible. + for (int i = 0; i < N; i++) { + targets.get(i).setVisibility(View.VISIBLE); + } + } + } + + private class PopupDecorView extends FrameLayout { + public PopupDecorView(Context context) { + super(context); } @Override @@ -1703,15 +1955,14 @@ public class PopupWindow { return super.dispatchKeyEvent(event); } - if (event.getAction() == KeyEvent.ACTION_DOWN - && event.getRepeatCount() == 0) { - KeyEvent.DispatcherState state = getKeyDispatcherState(); + if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { + final KeyEvent.DispatcherState state = getKeyDispatcherState(); if (state != null) { state.startTracking(event, this); } return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { - KeyEvent.DispatcherState state = getKeyDispatcherState(); + final KeyEvent.DispatcherState state = getKeyDispatcherState(); if (state != null && state.isTracking(event) && !event.isCanceled()) { dismiss(); return true; @@ -1735,7 +1986,7 @@ public class PopupWindow { public boolean onTouchEvent(MotionEvent event) { final int x = (int) event.getX(); final int y = (int) event.getY(); - + if ((event.getAction() == MotionEvent.ACTION_DOWN) && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) { dismiss(); @@ -1747,16 +1998,22 @@ public class PopupWindow { return super.onTouchEvent(event); } } + } + + private class PopupBackgroundView extends FrameLayout { + public PopupBackgroundView(Context context) { + super(context); + } @Override - public void sendAccessibilityEvent(int eventType) { - // clinets are interested in the content not the container, make it event source - if (mContentView != null) { - mContentView.sendAccessibilityEvent(eventType); + protected int[] onCreateDrawableState(int extraSpace) { + if (mAboveAnchor) { + final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); + View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET); + return drawableState; } else { - super.sendAccessibilityEvent(eventType); + return super.onCreateDrawableState(extraSpace); } } } - } diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index de1bbc7..d32cb10 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -1825,17 +1825,19 @@ public class ProgressBar extends View { mAttached = false; } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(ProgressBar.class.getName()); event.setItemCount(mMax); event.setCurrentItemIndex(mProgress); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(ProgressBar.class.getName()); } diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java index 23fa402..d0e8081 100644 --- a/core/java/android/widget/QuickContactBadge.java +++ b/core/java/android/widget/QuickContactBadge.java @@ -304,15 +304,17 @@ public class QuickContactBadge extends ImageView implements OnClickListener { } } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(QuickContactBadge.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(QuickContactBadge.class.getName()); } diff --git a/core/java/android/widget/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java index 11fda2c..4b061d3 100644 --- a/core/java/android/widget/RadialTimePickerView.java +++ b/core/java/android/widget/RadialTimePickerView.java @@ -23,18 +23,22 @@ import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.content.Context; +import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; +import android.graphics.Path; import android.graphics.Rect; +import android.graphics.Region; import android.graphics.Typeface; import android.os.Bundle; import android.util.AttributeSet; import android.util.IntArray; import android.util.Log; import android.util.MathUtils; +import android.util.StateSet; import android.util.TypedValue; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; @@ -56,8 +60,8 @@ import java.util.Locale; * * @hide */ -public class RadialTimePickerView extends View implements View.OnTouchListener { - private static final String TAG = "ClockView"; +public class RadialTimePickerView extends View { + private static final String TAG = "RadialTimePickerView"; private static final boolean DEBUG = false; @@ -82,12 +86,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { // Transparent alpha level private static final int ALPHA_TRANSPARENT = 0; - // Alpha level of color for selector. - private static final int ALPHA_SELECTOR = 60; // was 51 - - private static final float COSINE_30_DEGREES = ((float) Math.sqrt(3)) * 0.5f; - private static final float SINE_30_DEGREES = 0.5f; - private static final int DEGREES_FOR_ONE_HOUR = 30; private static final int DEGREES_FOR_ONE_MINUTE = 6; @@ -97,7 +95,27 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { private static final int CENTER_RADIUS = 2; - private static int[] sSnapPrefer30sMap = new int[361]; + private static final int FADE_OUT_DURATION = 500; + private static final int FADE_IN_DURATION = 500; + + private static final int[] SNAP_PREFER_30S_MAP = new int[361]; + + private static final int NUM_POSITIONS = 12; + private static final float[] COS_30 = new float[NUM_POSITIONS]; + private static final float[] SIN_30 = new float[NUM_POSITIONS]; + + static { + // Prepare mapping to snap touchable degrees to selectable degrees. + preparePrefer30sMap(); + + final double increment = 2.0 * Math.PI / NUM_POSITIONS; + double angle = Math.PI / 2.0; + for (int i = 0; i < NUM_POSITIONS; i++) { + COS_30[i] = (float) Math.cos(angle); + SIN_30[i] = (float) Math.sin(angle); + angle += increment; + } + } private final InvalidateUpdateListener mInvalidateUpdateListener = new InvalidateUpdateListener(); @@ -108,7 +126,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { private final String[] mMinutesTexts = new String[12]; private final Paint[] mPaint = new Paint[2]; - private final int[] mColor = new int[2]; private final IntHolder[] mAlpha = new IntHolder[2]; private final Paint mPaintCenter = new Paint(); @@ -122,26 +139,18 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { private final Typeface mTypeface; - private final float[] mCircleRadius = new float[3]; - private final float[] mTextSize = new float[2]; - private final float[][] mTextGridHeights = new float[2][7]; - private final float[][] mTextGridWidths = new float[2][7]; + private final float[][] mOuterTextX = new float[2][12]; + private final float[][] mOuterTextY = new float[2][12]; - private final float[] mInnerTextGridHeights = new float[7]; - private final float[] mInnerTextGridWidths = new float[7]; + private final float[] mInnerTextX = new float[12]; + private final float[] mInnerTextY = new float[12]; - private final float[] mCircleRadiusMultiplier = new float[2]; private final float[] mNumbersRadiusMultiplier = new float[3]; private final float[] mTextSizeMultiplier = new float[3]; - private final float[] mAnimationRadiusMultiplier = new float[3]; - - private final float mTransitionMidRadiusMultiplier; - private final float mTransitionEndRadiusMultiplier; - private final int[] mLineLength = new int[3]; private final int[] mSelectionRadius = new int[3]; private final float mSelectionRadiusMultiplier; @@ -152,7 +161,7 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { private final RadialPickerTouchHelper mTouchHelper; - private float mInnerTextSize; + private ColorStateList mNumbersTextColor; private boolean mIs24HourMode; private boolean mShowHours; @@ -163,8 +172,9 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { */ private boolean mIsOnInnerCircle; - private int mXCenter; - private int mYCenter; + private float mXCenter; + private float mYCenter; + private float mCircleRadius; private int mMinHypotenuseForInnerNumber; private int mMaxHypotenuseForOuterNumber; @@ -186,11 +196,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance); } - static { - // Prepare mapping to snap touchable degrees to selectable degrees. - preparePrefer30sMap(); - } - /** * Split up the 360 degrees of the circle among the 60 selectable values. Assigns a larger * selectable area to each of the 12 visible values, such that the ratio of space apportioned @@ -225,7 +230,7 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { // Iterate through the input. for (int degrees = 0; degrees < 361; degrees++) { // Save the input-output mapping. - sSnapPrefer30sMap[degrees] = snappedOutputDegrees; + SNAP_PREFER_30S_MAP[degrees] = snappedOutputDegrees; // If this is the last input for the specified output, calculate the next output and // the next expected count. if (count == expectedCount) { @@ -252,10 +257,10 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { * mapping. */ private static int snapPrefer30s(int degrees) { - if (sSnapPrefer30sMap == null) { + if (SNAP_PREFER_30S_MAP == null) { return -1; } - return sSnapPrefer30sMap[degrees]; + return SNAP_PREFER_30S_MAP[degrees]; } /** @@ -327,63 +332,56 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { } } - final int numbersTextColor = a.getColor(R.styleable.TimePicker_numbersTextColor, - res.getColor(R.color.timepicker_default_text_color_material)); + mNumbersTextColor = a.getColorStateList( + R.styleable.TimePicker_numbersTextColor); mPaint[HOURS] = new Paint(); mPaint[HOURS].setAntiAlias(true); mPaint[HOURS].setTextAlign(Paint.Align.CENTER); - mColor[HOURS] = numbersTextColor; mPaint[MINUTES] = new Paint(); mPaint[MINUTES].setAntiAlias(true); mPaint[MINUTES].setTextAlign(Paint.Align.CENTER); - mColor[MINUTES] = numbersTextColor; - mPaintCenter.setColor(numbersTextColor); + final ColorStateList selectorColors = a.getColorStateList( + R.styleable.TimePicker_numbersSelectorColor); + final int selectorActivatedColor = selectorColors.getColorForState( + StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0); + + mPaintCenter.setColor(selectorActivatedColor); mPaintCenter.setAntiAlias(true); - mPaintCenter.setTextAlign(Paint.Align.CENTER); + + final int textActivatedColor = mNumbersTextColor.getColorForState( + StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0); mPaintSelector[HOURS][SELECTOR_CIRCLE] = new Paint(); mPaintSelector[HOURS][SELECTOR_CIRCLE].setAntiAlias(true); - mColorSelector[HOURS][SELECTOR_CIRCLE] = a.getColor( - R.styleable.TimePicker_numbersSelectorColor, - R.color.timepicker_default_selector_color_material); + mColorSelector[HOURS][SELECTOR_CIRCLE] = selectorActivatedColor; mPaintSelector[HOURS][SELECTOR_DOT] = new Paint(); mPaintSelector[HOURS][SELECTOR_DOT].setAntiAlias(true); - mColorSelector[HOURS][SELECTOR_DOT] = a.getColor( - R.styleable.TimePicker_numbersSelectorColor, - R.color.timepicker_default_selector_color_material); + mColorSelector[HOURS][SELECTOR_DOT] = textActivatedColor; mPaintSelector[HOURS][SELECTOR_LINE] = new Paint(); mPaintSelector[HOURS][SELECTOR_LINE].setAntiAlias(true); mPaintSelector[HOURS][SELECTOR_LINE].setStrokeWidth(2); - mColorSelector[HOURS][SELECTOR_LINE] = a.getColor( - R.styleable.TimePicker_numbersSelectorColor, - R.color.timepicker_default_selector_color_material); + mColorSelector[HOURS][SELECTOR_LINE] = selectorActivatedColor; mPaintSelector[MINUTES][SELECTOR_CIRCLE] = new Paint(); mPaintSelector[MINUTES][SELECTOR_CIRCLE].setAntiAlias(true); - mColorSelector[MINUTES][SELECTOR_CIRCLE] = a.getColor( - R.styleable.TimePicker_numbersSelectorColor, - R.color.timepicker_default_selector_color_material); + mColorSelector[MINUTES][SELECTOR_CIRCLE] = selectorActivatedColor; mPaintSelector[MINUTES][SELECTOR_DOT] = new Paint(); mPaintSelector[MINUTES][SELECTOR_DOT].setAntiAlias(true); - mColorSelector[MINUTES][SELECTOR_DOT] = a.getColor( - R.styleable.TimePicker_numbersSelectorColor, - R.color.timepicker_default_selector_color_material); + mColorSelector[MINUTES][SELECTOR_DOT] = textActivatedColor; mPaintSelector[MINUTES][SELECTOR_LINE] = new Paint(); mPaintSelector[MINUTES][SELECTOR_LINE].setAntiAlias(true); mPaintSelector[MINUTES][SELECTOR_LINE].setStrokeWidth(2); - mColorSelector[MINUTES][SELECTOR_LINE] = a.getColor( - R.styleable.TimePicker_numbersSelectorColor, - R.color.timepicker_default_selector_color_material); + mColorSelector[MINUTES][SELECTOR_LINE] = selectorActivatedColor; mPaintBackground.setColor(a.getColor(R.styleable.TimePicker_numbersBackgroundColor, - res.getColor(R.color.timepicker_default_numbers_background_color_material))); + context.getColor(R.color.timepicker_default_numbers_background_color_material))); mPaintBackground.setAntiAlias(true); if (DEBUG) { @@ -409,22 +407,10 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { initHoursAndMinutesText(); initData(); - mTransitionMidRadiusMultiplier = Float.parseFloat( - res.getString(R.string.timepicker_transition_mid_radius_multiplier)); - mTransitionEndRadiusMultiplier = Float.parseFloat( - res.getString(R.string.timepicker_transition_end_radius_multiplier)); - - mTextGridHeights[HOURS] = new float[7]; - mTextGridHeights[MINUTES] = new float[7]; - - mSelectionRadiusMultiplier = Float.parseFloat( - res.getString(R.string.timepicker_selection_radius_multiplier)); + mSelectionRadiusMultiplier = res.getFloat(R.dimen.timepicker_selection_radius_multiplier); a.recycle(); - setOnTouchListener(this); - setClickable(true); - // Initial values final Calendar calendar = Calendar.getInstance(Locale.getDefault()); final int currentHour = calendar.get(Calendar.HOUR_OF_DAY); @@ -436,21 +422,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { setHapticFeedbackEnabled(true); } - /** - * Measure the view to end up as a square, based on the minimum of the height and width. - */ - @Override - public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int measuredWidth = MeasureSpec.getSize(widthMeasureSpec); - int widthMode = MeasureSpec.getMode(widthMeasureSpec); - int measuredHeight = MeasureSpec.getSize(heightMeasureSpec); - int heightMode = MeasureSpec.getMode(heightMeasureSpec); - int minDimension = Math.min(measuredWidth, measuredHeight); - - super.onMeasure(MeasureSpec.makeMeasureSpec(minDimension, widthMode), - MeasureSpec.makeMeasureSpec(minDimension, heightMode)); - } - public void initialize(int hour, int minute, boolean is24HourMode) { if (mIs24HourMode != is24HourMode) { mIs24HourMode = is24HourMode; @@ -512,7 +483,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { mIsOnInnerCircle = isOnInnerCircle; initData(); - updateLayoutData(); mTouchHelper.invalidateRoot(); } @@ -601,24 +571,32 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { } public void showHours(boolean animate) { - if (mShowHours) return; + if (mShowHours) { + return; + } + mShowHours = true; + if (animate) { startMinutesToHoursAnimation(); } + initData(); - updateLayoutData(); invalidate(); } public void showMinutes(boolean animate) { - if (!mShowHours) return; + if (!mShowHours) { + return; + } + mShowHours = false; + if (animate) { startHoursToMinutesAnimation(); } + initData(); - updateLayoutData(); invalidate(); } @@ -645,93 +623,71 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { final Resources res = getResources(); - if (mShowHours) { - if (mIs24HourMode) { - mCircleRadiusMultiplier[HOURS] = Float.parseFloat( - res.getString(R.string.timepicker_circle_radius_multiplier_24HourMode)); - mNumbersRadiusMultiplier[HOURS] = Float.parseFloat( - res.getString(R.string.timepicker_numbers_radius_multiplier_outer)); - mTextSizeMultiplier[HOURS] = Float.parseFloat( - res.getString(R.string.timepicker_text_size_multiplier_outer)); - - mNumbersRadiusMultiplier[HOURS_INNER] = Float.parseFloat( - res.getString(R.string.timepicker_numbers_radius_multiplier_inner)); - mTextSizeMultiplier[HOURS_INNER] = Float.parseFloat( - res.getString(R.string.timepicker_text_size_multiplier_inner)); - } else { - mCircleRadiusMultiplier[HOURS] = Float.parseFloat( - res.getString(R.string.timepicker_circle_radius_multiplier)); - mNumbersRadiusMultiplier[HOURS] = Float.parseFloat( - res.getString(R.string.timepicker_numbers_radius_multiplier_normal)); - mTextSizeMultiplier[HOURS] = Float.parseFloat( - res.getString(R.string.timepicker_text_size_multiplier_normal)); - } + if (mIs24HourMode) { + mNumbersRadiusMultiplier[HOURS] = res.getFloat( + R.dimen.timepicker_numbers_radius_multiplier_outer); + mTextSizeMultiplier[HOURS] = res.getFloat( + R.dimen.timepicker_text_size_multiplier_outer); + + mNumbersRadiusMultiplier[HOURS_INNER] = res.getFloat( + R.dimen.timepicker_numbers_radius_multiplier_inner); + mTextSizeMultiplier[HOURS_INNER] = res.getFloat( + R.dimen.timepicker_text_size_multiplier_inner); } else { - mCircleRadiusMultiplier[MINUTES] = Float.parseFloat( - res.getString(R.string.timepicker_circle_radius_multiplier)); - mNumbersRadiusMultiplier[MINUTES] = Float.parseFloat( - res.getString(R.string.timepicker_numbers_radius_multiplier_normal)); - mTextSizeMultiplier[MINUTES] = Float.parseFloat( - res.getString(R.string.timepicker_text_size_multiplier_normal)); - } - - mAnimationRadiusMultiplier[HOURS] = 1; - mAnimationRadiusMultiplier[HOURS_INNER] = 1; - mAnimationRadiusMultiplier[MINUTES] = 1; - - mAlpha[HOURS].setValue(mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT); - mAlpha[MINUTES].setValue(mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE); - - mAlphaSelector[HOURS][SELECTOR_CIRCLE].setValue( - mShowHours ? ALPHA_SELECTOR : ALPHA_TRANSPARENT); - mAlphaSelector[HOURS][SELECTOR_DOT].setValue( - mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT); - mAlphaSelector[HOURS][SELECTOR_LINE].setValue( - mShowHours ? ALPHA_SELECTOR : ALPHA_TRANSPARENT); - - mAlphaSelector[MINUTES][SELECTOR_CIRCLE].setValue( - mShowHours ? ALPHA_TRANSPARENT : ALPHA_SELECTOR); - mAlphaSelector[MINUTES][SELECTOR_DOT].setValue( - mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE); - mAlphaSelector[MINUTES][SELECTOR_LINE].setValue( - mShowHours ? ALPHA_TRANSPARENT : ALPHA_SELECTOR); + mNumbersRadiusMultiplier[HOURS] = res.getFloat( + R.dimen.timepicker_numbers_radius_multiplier_normal); + mTextSizeMultiplier[HOURS] = res.getFloat( + R.dimen.timepicker_text_size_multiplier_normal); + } + + mNumbersRadiusMultiplier[MINUTES] = res.getFloat( + R.dimen.timepicker_numbers_radius_multiplier_normal); + mTextSizeMultiplier[MINUTES] = res.getFloat( + R.dimen.timepicker_text_size_multiplier_normal); + + final int hoursAlpha = mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT; + mAlpha[HOURS].setValue(hoursAlpha); + mAlphaSelector[HOURS][SELECTOR_CIRCLE].setValue(hoursAlpha); + mAlphaSelector[HOURS][SELECTOR_DOT].setValue(hoursAlpha); + mAlphaSelector[HOURS][SELECTOR_LINE].setValue(hoursAlpha); + + final int minutesAlpha = mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE; + mAlpha[MINUTES].setValue(minutesAlpha); + mAlphaSelector[MINUTES][SELECTOR_CIRCLE].setValue(minutesAlpha); + mAlphaSelector[MINUTES][SELECTOR_DOT].setValue(minutesAlpha); + mAlphaSelector[MINUTES][SELECTOR_LINE].setValue(minutesAlpha); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - updateLayoutData(); - } + if (!changed) { + return; + } - private void updateLayoutData() { mXCenter = getWidth() / 2; mYCenter = getHeight() / 2; + mCircleRadius = Math.min(mXCenter, mYCenter); - final int min = Math.min(mXCenter, mYCenter); - - mCircleRadius[HOURS] = min * mCircleRadiusMultiplier[HOURS]; - mCircleRadius[HOURS_INNER] = min * mCircleRadiusMultiplier[HOURS]; - mCircleRadius[MINUTES] = min * mCircleRadiusMultiplier[MINUTES]; - - mMinHypotenuseForInnerNumber = (int) (mCircleRadius[HOURS] + mMinHypotenuseForInnerNumber = (int) (mCircleRadius * mNumbersRadiusMultiplier[HOURS_INNER]) - mSelectionRadius[HOURS]; - mMaxHypotenuseForOuterNumber = (int) (mCircleRadius[HOURS] + mMaxHypotenuseForOuterNumber = (int) (mCircleRadius * mNumbersRadiusMultiplier[HOURS]) + mSelectionRadius[HOURS]; - mHalfwayHypotenusePoint = (int) (mCircleRadius[HOURS] + mHalfwayHypotenusePoint = (int) (mCircleRadius * ((mNumbersRadiusMultiplier[HOURS] + mNumbersRadiusMultiplier[HOURS_INNER]) / 2)); - mTextSize[HOURS] = mCircleRadius[HOURS] * mTextSizeMultiplier[HOURS]; - mTextSize[MINUTES] = mCircleRadius[MINUTES] * mTextSizeMultiplier[MINUTES]; + mTextSize[HOURS] = mCircleRadius * mTextSizeMultiplier[HOURS]; + mTextSize[MINUTES] = mCircleRadius * mTextSizeMultiplier[MINUTES]; if (mIs24HourMode) { - mInnerTextSize = mCircleRadius[HOURS] * mTextSizeMultiplier[HOURS_INNER]; + mTextSize[HOURS_INNER] = mCircleRadius * mTextSizeMultiplier[HOURS_INNER]; } - calculateGridSizesHours(); - calculateGridSizesMinutes(); + calculatePositionsHours(); + calculatePositionsMinutes(); - mSelectionRadius[HOURS] = (int) (mCircleRadius[HOURS] * mSelectionRadiusMultiplier); + mSelectionRadius[HOURS] = (int) (mCircleRadius * mSelectionRadiusMultiplier); mSelectionRadius[HOURS_INNER] = mSelectionRadius[HOURS]; - mSelectionRadius[MINUTES] = (int) (mCircleRadius[MINUTES] * mSelectionRadiusMultiplier); + mSelectionRadius[MINUTES] = (int) (mCircleRadius * mSelectionRadiusMultiplier); mTouchHelper.invalidateRoot(); } @@ -744,25 +700,48 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { canvas.save(); } - calculateGridSizesHours(); - calculateGridSizesMinutes(); - drawCircleBackground(canvas); - drawSelector(canvas); - - drawTextElements(canvas, mTextSize[HOURS], mTypeface, mOuterTextHours, - mTextGridWidths[HOURS], mTextGridHeights[HOURS], mPaint[HOURS], - mColor[HOURS], mAlpha[HOURS].getValue()); - if (mIs24HourMode && mInnerTextHours != null) { - drawTextElements(canvas, mInnerTextSize, mTypeface, mInnerTextHours, - mInnerTextGridWidths, mInnerTextGridHeights, mPaint[HOURS], - mColor[HOURS], mAlpha[HOURS].getValue()); + final int hoursAlpha = mAlpha[HOURS].getValue(); + if (hoursAlpha > 0) { + // Draw the hour selector under the elements. + drawSelector(canvas, mIsOnInnerCircle ? HOURS_INNER : HOURS, null); + + // Draw outer hours. + drawTextElements(canvas, mTextSize[HOURS], mTypeface, mOuterTextHours, + mOuterTextX[HOURS], mOuterTextY[HOURS], mPaint[HOURS], hoursAlpha, + !mIsOnInnerCircle, mSelectionDegrees[HOURS], false); + + // Draw inner hours (12-23) for 24-hour time. + if (mIs24HourMode && mInnerTextHours != null) { + drawTextElements(canvas, mTextSize[HOURS_INNER], mTypeface, mInnerTextHours, + mInnerTextX, mInnerTextY, mPaint[HOURS], hoursAlpha, + mIsOnInnerCircle, mSelectionDegrees[HOURS], false); + } } - drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mOuterTextMinutes, - mTextGridWidths[MINUTES], mTextGridHeights[MINUTES], mPaint[MINUTES], - mColor[MINUTES], mAlpha[MINUTES].getValue()); + final int minutesAlpha = mAlpha[MINUTES].getValue(); + if (minutesAlpha > 0) { + drawSelector(canvas, MINUTES, mSelectorPath); + + // Exclude the selector region, then draw minutes with no + // activated states. + canvas.save(Canvas.CLIP_SAVE_FLAG); + canvas.clipPath(mSelectorPath, Region.Op.DIFFERENCE); + drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mOuterTextMinutes, + mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], minutesAlpha, + false, 0, false); + canvas.restore(); + + // Intersect the selector region, then draw minutes with only + // activated states. + canvas.save(Canvas.CLIP_SAVE_FLAG); + canvas.clipPath(mSelectorPath, Region.Op.INTERSECT); + drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mOuterTextMinutes, + mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], minutesAlpha, + true, mSelectionDegrees[MINUTES], true); + canvas.restore(); + } drawCenter(canvas); @@ -774,31 +753,27 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { } private void drawCircleBackground(Canvas canvas) { - canvas.drawCircle(mXCenter, mYCenter, mCircleRadius[HOURS], mPaintBackground); + canvas.drawCircle(mXCenter, mYCenter, mCircleRadius, mPaintBackground); } private void drawCenter(Canvas canvas) { canvas.drawCircle(mXCenter, mYCenter, CENTER_RADIUS, mPaintCenter); } - private void drawSelector(Canvas canvas) { - drawSelector(canvas, mIsOnInnerCircle ? HOURS_INNER : HOURS); - drawSelector(canvas, MINUTES); - } - private int getMultipliedAlpha(int argb, int alpha) { return (int) (Color.alpha(argb) * (alpha / 255.0) + 0.5); } - private void drawSelector(Canvas canvas, int index) { + private final Path mSelectorPath = new Path(); + + private void drawSelector(Canvas canvas, int index, Path selectorPath) { // Calculate the current radius at which to place the selection circle. - mLineLength[index] = (int) (mCircleRadius[index] - * mNumbersRadiusMultiplier[index] * mAnimationRadiusMultiplier[index]); + mLineLength[index] = (int) (mCircleRadius * mNumbersRadiusMultiplier[index]); - double selectionRadians = Math.toRadians(mSelectionDegrees[index]); + final double selectionRadians = Math.toRadians(mSelectionDegrees[index]); - int pointX = mXCenter + (int) (mLineLength[index] * Math.sin(selectionRadians)); - int pointY = mYCenter - (int) (mLineLength[index] * Math.cos(selectionRadians)); + float pointX = mXCenter + (int) (mLineLength[index] * Math.sin(selectionRadians)); + float pointY = mYCenter - (int) (mLineLength[index] * Math.cos(selectionRadians)); int color; int alpha; @@ -812,23 +787,29 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { paint.setAlpha(getMultipliedAlpha(color, alpha)); canvas.drawCircle(pointX, pointY, mSelectionRadius[index], paint); - // Draw the dot if needed - if (mSelectionDegrees[index] % 30 != 0) { + // If needed, set up the clip path for later. + if (selectorPath != null) { + mSelectorPath.reset(); + mSelectorPath.addCircle(pointX, pointY, mSelectionRadius[index], Path.Direction.CCW); + } + + // Draw the dot if needed. + final boolean shouldDrawDot = mSelectionDegrees[index] % 30 != 0; + if (shouldDrawDot) { // We're not on a direct tick color = mColorSelector[index % 2][SELECTOR_DOT]; alpha = mAlphaSelector[index % 2][SELECTOR_DOT].getValue(); paint = mPaintSelector[index % 2][SELECTOR_DOT]; paint.setColor(color); paint.setAlpha(getMultipliedAlpha(color, alpha)); - canvas.drawCircle(pointX, pointY, (mSelectionRadius[index] * 2 / 7), paint); - } else { - // We're not drawing the dot, so shorten the line to only go as far as the edge of the - // selection circle - int lineLength = mLineLength[index] - mSelectionRadius[index]; - pointX = mXCenter + (int) (lineLength * Math.sin(selectionRadians)); - pointY = mYCenter - (int) (lineLength * Math.cos(selectionRadians)); + canvas.drawCircle(pointX, pointY, (mSelectionRadius[index] * 0.125f), paint); } + // Shorten the line to only go to the edge of the selection circle. + final int lineLength = mLineLength[index] - mSelectionRadius[index]; + pointX = mXCenter + (int) (lineLength * Math.sin(selectionRadians)); + pointY = mYCenter - (int) (lineLength * Math.cos(selectionRadians)); + // Draw the line color = mColorSelector[index % 2][SELECTOR_LINE]; alpha = mAlphaSelector[index % 2][SELECTOR_LINE].getValue(); @@ -840,15 +821,15 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { private void drawDebug(Canvas canvas) { // Draw outer numbers circle - final float outerRadius = mCircleRadius[HOURS] * mNumbersRadiusMultiplier[HOURS]; + final float outerRadius = mCircleRadius * mNumbersRadiusMultiplier[HOURS]; canvas.drawCircle(mXCenter, mYCenter, outerRadius, mPaintDebug); // Draw inner numbers circle - final float innerRadius = mCircleRadius[HOURS] * mNumbersRadiusMultiplier[HOURS_INNER]; + final float innerRadius = mCircleRadius * mNumbersRadiusMultiplier[HOURS_INNER]; canvas.drawCircle(mXCenter, mYCenter, innerRadius, mPaintDebug); // Draw outer background circle - canvas.drawCircle(mXCenter, mYCenter, mCircleRadius[HOURS], mPaintDebug); + canvas.drawCircle(mXCenter, mYCenter, mCircleRadius, mPaintDebug); // Draw outer rectangle for circles float left = mXCenter - outerRadius; @@ -858,10 +839,10 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { canvas.drawRect(left, top, right, bottom, mPaintDebug); // Draw outer rectangle for background - left = mXCenter - mCircleRadius[HOURS]; - top = mYCenter - mCircleRadius[HOURS]; - right = mXCenter + mCircleRadius[HOURS]; - bottom = mYCenter + mCircleRadius[HOURS]; + left = mXCenter - mCircleRadius; + top = mYCenter - mCircleRadius; + right = mXCenter + mCircleRadius; + bottom = mYCenter + mCircleRadius; canvas.drawRect(left, top, right, bottom, mPaintDebug); // Draw outer view rectangle @@ -888,185 +869,104 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { canvas.drawText(selected, x, y, paint); } - private void calculateGridSizesHours() { + private void calculatePositionsHours() { // Calculate the text positions - float numbersRadius = mCircleRadius[HOURS] - * mNumbersRadiusMultiplier[HOURS] * mAnimationRadiusMultiplier[HOURS]; + final float numbersRadius = mCircleRadius * mNumbersRadiusMultiplier[HOURS]; // Calculate the positions for the 12 numbers in the main circle. - calculateGridSizes(mPaint[HOURS], numbersRadius, mXCenter, mYCenter, - mTextSize[HOURS], mTextGridHeights[HOURS], mTextGridWidths[HOURS]); + calculatePositions(mPaint[HOURS], numbersRadius, mXCenter, mYCenter, + mTextSize[HOURS], mOuterTextX[HOURS], mOuterTextY[HOURS]); // If we have an inner circle, calculate those positions too. if (mIs24HourMode) { - float innerNumbersRadius = mCircleRadius[HOURS_INNER] - * mNumbersRadiusMultiplier[HOURS_INNER] - * mAnimationRadiusMultiplier[HOURS_INNER]; + final float innerNumbersRadius = mCircleRadius + * mNumbersRadiusMultiplier[HOURS_INNER]; - calculateGridSizes(mPaint[HOURS], innerNumbersRadius, mXCenter, mYCenter, - mInnerTextSize, mInnerTextGridHeights, mInnerTextGridWidths); + calculatePositions(mPaint[HOURS], innerNumbersRadius, mXCenter, mYCenter, + mTextSize[HOURS_INNER], mInnerTextX, mInnerTextY); } } - private void calculateGridSizesMinutes() { + private void calculatePositionsMinutes() { // Calculate the text positions - float numbersRadius = mCircleRadius[MINUTES] - * mNumbersRadiusMultiplier[MINUTES] * mAnimationRadiusMultiplier[MINUTES]; + final float numbersRadius = mCircleRadius * mNumbersRadiusMultiplier[MINUTES]; // Calculate the positions for the 12 numbers in the main circle. - calculateGridSizes(mPaint[MINUTES], numbersRadius, mXCenter, mYCenter, - mTextSize[MINUTES], mTextGridHeights[MINUTES], mTextGridWidths[MINUTES]); + calculatePositions(mPaint[MINUTES], numbersRadius, mXCenter, mYCenter, + mTextSize[MINUTES], mOuterTextX[MINUTES], mOuterTextY[MINUTES]); } - /** * Using the trigonometric Unit Circle, calculate the positions that the text will need to be * drawn at based on the specified circle radius. Place the values in the textGridHeights and * textGridWidths parameters. */ - private static void calculateGridSizes(Paint paint, float numbersRadius, float xCenter, - float yCenter, float textSize, float[] textGridHeights, float[] textGridWidths) { - /* - * The numbers need to be drawn in a 7x7 grid, representing the points on the Unit Circle. - */ - final float offset1 = numbersRadius; - // cos(30) = a / r => r * cos(30) - final float offset2 = numbersRadius * COSINE_30_DEGREES; - // sin(30) = o / r => r * sin(30) - final float offset3 = numbersRadius * SINE_30_DEGREES; - + private static void calculatePositions(Paint paint, float radius, float xCenter, float yCenter, + float textSize, float[] x, float[] y) { + // Adjust yCenter to account for the text's baseline. paint.setTextSize(textSize); - // We'll need yTextBase to be slightly lower to account for the text's baseline. yCenter -= (paint.descent() + paint.ascent()) / 2; - textGridHeights[0] = yCenter - offset1; - textGridWidths[0] = xCenter - offset1; - textGridHeights[1] = yCenter - offset2; - textGridWidths[1] = xCenter - offset2; - textGridHeights[2] = yCenter - offset3; - textGridWidths[2] = xCenter - offset3; - textGridHeights[3] = yCenter; - textGridWidths[3] = xCenter; - textGridHeights[4] = yCenter + offset3; - textGridWidths[4] = xCenter + offset3; - textGridHeights[5] = yCenter + offset2; - textGridWidths[5] = xCenter + offset2; - textGridHeights[6] = yCenter + offset1; - textGridWidths[6] = xCenter + offset1; + for (int i = 0; i < NUM_POSITIONS; i++) { + x[i] = xCenter - radius * COS_30[i]; + y[i] = yCenter - radius * SIN_30[i]; + } } /** * Draw the 12 text values at the positions specified by the textGrid parameters. */ private void drawTextElements(Canvas canvas, float textSize, Typeface typeface, String[] texts, - float[] textGridWidths, float[] textGridHeights, Paint paint, int color, int alpha) { + float[] textX, float[] textY, Paint paint, int alpha, boolean showActivated, + int activatedDegrees, boolean activatedOnly) { paint.setTextSize(textSize); paint.setTypeface(typeface); - paint.setColor(color); - paint.setAlpha(getMultipliedAlpha(color, alpha)); - canvas.drawText(texts[0], textGridWidths[3], textGridHeights[0], paint); - canvas.drawText(texts[1], textGridWidths[4], textGridHeights[1], paint); - canvas.drawText(texts[2], textGridWidths[5], textGridHeights[2], paint); - canvas.drawText(texts[3], textGridWidths[6], textGridHeights[3], paint); - canvas.drawText(texts[4], textGridWidths[5], textGridHeights[4], paint); - canvas.drawText(texts[5], textGridWidths[4], textGridHeights[5], paint); - canvas.drawText(texts[6], textGridWidths[3], textGridHeights[6], paint); - canvas.drawText(texts[7], textGridWidths[2], textGridHeights[5], paint); - canvas.drawText(texts[8], textGridWidths[1], textGridHeights[4], paint); - canvas.drawText(texts[9], textGridWidths[0], textGridHeights[3], paint); - canvas.drawText(texts[10], textGridWidths[1], textGridHeights[2], paint); - canvas.drawText(texts[11], textGridWidths[2], textGridHeights[1], paint); - } - // Used for animating the hours by changing their radius - @SuppressWarnings("unused") - private void setAnimationRadiusMultiplierHours(float animationRadiusMultiplier) { - mAnimationRadiusMultiplier[HOURS] = animationRadiusMultiplier; - mAnimationRadiusMultiplier[HOURS_INNER] = animationRadiusMultiplier; - } + // The activated index can touch a range of elements. + final float activatedIndex = activatedDegrees / (360.0f / NUM_POSITIONS); + final int activatedFloor = (int) activatedIndex; + final int activatedCeil = ((int) Math.ceil(activatedIndex)) % NUM_POSITIONS; - // Used for animating the minutes by changing their radius - @SuppressWarnings("unused") - private void setAnimationRadiusMultiplierMinutes(float animationRadiusMultiplier) { - mAnimationRadiusMultiplier[MINUTES] = animationRadiusMultiplier; - } + for (int i = 0; i < 12; i++) { + final boolean activated = (activatedFloor == i || activatedCeil == i); + if (activatedOnly && !activated) { + continue; + } - private static ObjectAnimator getRadiusDisappearAnimator(Object target, - String radiusPropertyName, InvalidateUpdateListener updateListener, - float midRadiusMultiplier, float endRadiusMultiplier) { - Keyframe kf0, kf1, kf2; - float midwayPoint = 0.2f; - int duration = 500; - - kf0 = Keyframe.ofFloat(0f, 1); - kf1 = Keyframe.ofFloat(midwayPoint, midRadiusMultiplier); - kf2 = Keyframe.ofFloat(1f, endRadiusMultiplier); - PropertyValuesHolder radiusDisappear = PropertyValuesHolder.ofKeyframe( - radiusPropertyName, kf0, kf1, kf2); - - ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder( - target, radiusDisappear).setDuration(duration); - animator.addUpdateListener(updateListener); - return animator; - } + final int stateMask = StateSet.VIEW_STATE_ENABLED + | (showActivated && activated ? StateSet.VIEW_STATE_ACTIVATED : 0); + final int color = mNumbersTextColor.getColorForState(StateSet.get(stateMask), 0); + paint.setColor(color); + paint.setAlpha(getMultipliedAlpha(color, alpha)); - private static ObjectAnimator getRadiusReappearAnimator(Object target, - String radiusPropertyName, InvalidateUpdateListener updateListener, - float midRadiusMultiplier, float endRadiusMultiplier) { - Keyframe kf0, kf1, kf2, kf3; - float midwayPoint = 0.2f; - int duration = 500; - - // Set up animator for reappearing. - float delayMultiplier = 0.25f; - float transitionDurationMultiplier = 1f; - float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier; - int totalDuration = (int) (duration * totalDurationMultiplier); - float delayPoint = (delayMultiplier * duration) / totalDuration; - midwayPoint = 1 - (midwayPoint * (1 - delayPoint)); - - kf0 = Keyframe.ofFloat(0f, endRadiusMultiplier); - kf1 = Keyframe.ofFloat(delayPoint, endRadiusMultiplier); - kf2 = Keyframe.ofFloat(midwayPoint, midRadiusMultiplier); - kf3 = Keyframe.ofFloat(1f, 1); - PropertyValuesHolder radiusReappear = PropertyValuesHolder.ofKeyframe( - radiusPropertyName, kf0, kf1, kf2, kf3); - - ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder( - target, radiusReappear).setDuration(totalDuration); - animator.addUpdateListener(updateListener); - return animator; + canvas.drawText(texts[i], textX[i], textY[i], paint); + } } private static ObjectAnimator getFadeOutAnimator(IntHolder target, int startAlpha, int endAlpha, InvalidateUpdateListener updateListener) { - int duration = 500; - ObjectAnimator animator = ObjectAnimator.ofInt(target, "value", startAlpha, endAlpha); - animator.setDuration(duration); + final ObjectAnimator animator = ObjectAnimator.ofInt(target, "value", startAlpha, endAlpha); + animator.setDuration(FADE_OUT_DURATION); animator.addUpdateListener(updateListener); - return animator; } private static ObjectAnimator getFadeInAnimator(IntHolder target, int startAlpha, int endAlpha, InvalidateUpdateListener updateListener) { - Keyframe kf0, kf1, kf2; - int duration = 500; - - // Set up animator for reappearing. - float delayMultiplier = 0.25f; - float transitionDurationMultiplier = 1f; - float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier; - int totalDuration = (int) (duration * totalDurationMultiplier); - float delayPoint = (delayMultiplier * duration) / totalDuration; + final float delayMultiplier = 0.25f; + final float transitionDurationMultiplier = 1f; + final float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier; + final int totalDuration = (int) (FADE_IN_DURATION * totalDurationMultiplier); + final float delayPoint = (delayMultiplier * FADE_IN_DURATION) / totalDuration; + final Keyframe kf0, kf1, kf2; kf0 = Keyframe.ofInt(0f, startAlpha); kf1 = Keyframe.ofInt(delayPoint, startAlpha); kf2 = Keyframe.ofInt(1f, endAlpha); - PropertyValuesHolder fadeIn = PropertyValuesHolder.ofKeyframe("value", kf0, kf1, kf2); + final PropertyValuesHolder fadeIn = PropertyValuesHolder.ofKeyframe("value", kf0, kf1, kf2); - ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder( - target, fadeIn).setDuration(totalDuration); + final ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(target, fadeIn); + animator.setDuration(totalDuration); animator.addUpdateListener(updateListener); return animator; } @@ -1080,29 +980,23 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { private void startHoursToMinutesAnimation() { if (mHoursToMinutesAnims.size() == 0) { - mHoursToMinutesAnims.add(getRadiusDisappearAnimator(this, - "animationRadiusMultiplierHours", mInvalidateUpdateListener, - mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier)); mHoursToMinutesAnims.add(getFadeOutAnimator(mAlpha[HOURS], ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_CIRCLE], - ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); + ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_DOT], ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_LINE], - ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); + ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); - mHoursToMinutesAnims.add(getRadiusReappearAnimator(this, - "animationRadiusMultiplierMinutes", mInvalidateUpdateListener, - mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier)); mHoursToMinutesAnims.add(getFadeInAnimator(mAlpha[MINUTES], ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener)); mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_CIRCLE], - ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener)); + ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener)); mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_DOT], ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener)); mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_LINE], - ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener)); + ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener)); } if (mTransition != null && mTransition.isRunning()) { @@ -1115,29 +1009,23 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { private void startMinutesToHoursAnimation() { if (mMinuteToHoursAnims.size() == 0) { - mMinuteToHoursAnims.add(getRadiusDisappearAnimator(this, - "animationRadiusMultiplierMinutes", mInvalidateUpdateListener, - mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier)); mMinuteToHoursAnims.add(getFadeOutAnimator(mAlpha[MINUTES], ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_CIRCLE], - ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); + ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_DOT], ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_LINE], - ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); + ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener)); - mMinuteToHoursAnims.add(getRadiusReappearAnimator(this, - "animationRadiusMultiplierHours", mInvalidateUpdateListener, - mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier)); mMinuteToHoursAnims.add(getFadeInAnimator(mAlpha[HOURS], ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener)); mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_CIRCLE], - ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener)); + ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener)); mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_DOT], ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener)); mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_LINE], - ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener)); + ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener)); } if (mTransition != null && mTransition.isRunning()) { @@ -1148,20 +1036,21 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { mTransition.start(); } - private int getDegreesFromXY(float x, float y) { + private int getDegreesFromXY(float x, float y, boolean constrainOutside) { final double hypotenuse = Math.sqrt( (y - mYCenter) * (y - mYCenter) + (x - mXCenter) * (x - mXCenter)); // Basic check if we're outside the range of the disk - if (hypotenuse > mCircleRadius[HOURS]) { + if (constrainOutside && hypotenuse > mCircleRadius) { return -1; } + // Check if (mIs24HourMode && mShowHours) { if (hypotenuse >= mMinHypotenuseForInnerNumber && hypotenuse <= mHalfwayHypotenusePoint) { mIsOnInnerCircle = true; - } else if (hypotenuse <= mMaxHypotenuseForOuterNumber + } else if ((hypotenuse <= mMaxHypotenuseForOuterNumber || !constrainOutside) && hypotenuse >= mHalfwayHypotenusePoint) { mIsOnInnerCircle = false; } else { @@ -1169,11 +1058,12 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { } } else { final int index = (mShowHours) ? HOURS : MINUTES; - final float length = (mCircleRadius[index] * mNumbersRadiusMultiplier[index]); - final int distanceToNumber = (int) Math.abs(hypotenuse - length); + final float length = (mCircleRadius * mNumbersRadiusMultiplier[index]); + final int distanceToNumber = (int) (hypotenuse - length); final int maxAllowedDistance = - (int) (mCircleRadius[index] * (1 - mNumbersRadiusMultiplier[index])); - if (distanceToNumber > maxAllowedDistance) { + (int) (mCircleRadius * (1 - mNumbersRadiusMultiplier[index])); + if (distanceToNumber < -maxAllowedDistance + || (constrainOutside && distanceToNumber > maxAllowedDistance)) { return -1; } } @@ -1203,7 +1093,7 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { boolean mChangedDuringTouch = false; @Override - public boolean onTouch(View v, MotionEvent event) { + public boolean onTouchEvent(MotionEvent event) { if (!mInputEnabled) { return true; } @@ -1240,7 +1130,7 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { // Calling getDegreesFromXY has side effects, so cache // whether we used to be on the inner circle. final boolean wasOnInnerCircle = mIsOnInnerCircle; - final int degrees = getDegreesFromXY(x, y); + final int degrees = getDegreesFromXY(x, y, false); if (degrees == -1) { return false; } @@ -1385,7 +1275,7 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { // Calling getDegreesXY() has side-effects, so we need to cache the // current inner circle value and restore after the call. final boolean wasOnInnerCircle = mIsOnInnerCircle; - final int degrees = getDegreesFromXY(x, y); + final int degrees = getDegreesFromXY(x, y, true); final boolean isOnInnerCircle = mIsOnInnerCircle; mIsOnInnerCircle = wasOnInnerCircle; @@ -1541,16 +1431,16 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { if (type == TYPE_HOUR) { final boolean innerCircle = mIs24HourMode && value > 0 && value <= 12; if (innerCircle) { - centerRadius = mCircleRadius[HOURS_INNER] * mNumbersRadiusMultiplier[HOURS_INNER]; + centerRadius = mCircleRadius * mNumbersRadiusMultiplier[HOURS_INNER]; radius = mSelectionRadius[HOURS_INNER]; } else { - centerRadius = mCircleRadius[HOURS] * mNumbersRadiusMultiplier[HOURS]; + centerRadius = mCircleRadius * mNumbersRadiusMultiplier[HOURS]; radius = mSelectionRadius[HOURS]; } degrees = getDegreesForHour(value); } else if (type == TYPE_MINUTE) { - centerRadius = mCircleRadius[MINUTES] * mNumbersRadiusMultiplier[MINUTES]; + centerRadius = mCircleRadius * mNumbersRadiusMultiplier[MINUTES]; degrees = getDegreesForMinute(value); radius = mSelectionRadius[MINUTES]; } else { diff --git a/core/java/android/widget/RadioButton.java b/core/java/android/widget/RadioButton.java index afc4830..82280b4 100644 --- a/core/java/android/widget/RadioButton.java +++ b/core/java/android/widget/RadioButton.java @@ -79,15 +79,17 @@ public class RadioButton extends CompoundButton { } } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(RadioButton.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(RadioButton.class.getName()); } } diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java index 78d05b0..c0f60eb 100644 --- a/core/java/android/widget/RadioGroup.java +++ b/core/java/android/widget/RadioGroup.java @@ -240,15 +240,17 @@ public class RadioGroup extends LinearLayout { return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(RadioGroup.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(RadioGroup.class.getName()); } diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java index 82b490e..2d0649d 100644 --- a/core/java/android/widget/RatingBar.java +++ b/core/java/android/widget/RatingBar.java @@ -331,15 +331,17 @@ public class RatingBar extends AbsSeekBar { super.setMax(max); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(RatingBar.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(RatingBar.class.getName()); } } diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java index 5b604cd..71f4da0 100644 --- a/core/java/android/widget/RelativeLayout.java +++ b/core/java/android/widget/RelativeLayout.java @@ -201,7 +201,6 @@ public class RelativeLayout extends ViewGroup { private static final int VALUE_NOT_SET = Integer.MIN_VALUE; private View mBaselineView = null; - private boolean mHasBaselineAlignedChild; private int mGravity = Gravity.START | Gravity.TOP; private final Rect mContentBounds = new Rect(); @@ -417,8 +416,6 @@ public class RelativeLayout extends ViewGroup { height = myHeight; } - mHasBaselineAlignedChild = false; - View ignore = null; int gravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; final boolean horizontalGravity = gravity != Gravity.START && gravity != 0; @@ -473,11 +470,11 @@ public class RelativeLayout extends ViewGroup { final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion; for (int i = 0; i < count; i++) { - View child = views[i]; + final View child = views[i]; if (child.getVisibility() != GONE) { - LayoutParams params = (LayoutParams) child.getLayoutParams(); - - applyVerticalSizeRules(params, myHeight); + final LayoutParams params = (LayoutParams) child.getLayoutParams(); + + applyVerticalSizeRules(params, myHeight, child.getBaseline()); measureChild(child, params, myWidth, myHeight); if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) { offsetVerticalAxis = true; @@ -519,26 +516,6 @@ public class RelativeLayout extends ViewGroup { } } - if (mHasBaselineAlignedChild) { - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - if (child.getVisibility() != GONE) { - LayoutParams params = (LayoutParams) child.getLayoutParams(); - alignBaseline(child, params); - - if (child != ignore || verticalGravity) { - left = Math.min(left, params.mLeft - params.leftMargin); - top = Math.min(top, params.mTop - params.topMargin); - } - - if (child != ignore || horizontalGravity) { - right = Math.max(right, params.mRight + params.rightMargin); - bottom = Math.max(bottom, params.mBottom + params.bottomMargin); - } - } - } - } - if (isWrapContentWidth) { // Width already has left padding in it since it was calculated by looking at // the right of each child view @@ -638,39 +615,22 @@ public class RelativeLayout extends ViewGroup { params.mRight -= offsetWidth; } } - } - setMeasuredDimension(width, height); - } - - private void alignBaseline(View child, LayoutParams params) { - final int layoutDirection = getLayoutDirection(); - int[] rules = params.getRules(layoutDirection); - int anchorBaseline = getRelatedViewBaseline(rules, ALIGN_BASELINE); - - if (anchorBaseline != -1) { - LayoutParams anchorParams = getRelatedViewParams(rules, ALIGN_BASELINE); - if (anchorParams != null) { - int offset = anchorParams.mTop + anchorBaseline; - int baseline = child.getBaseline(); - if (baseline != -1) { - offset -= baseline; - } - int height = params.mBottom - params.mTop; - params.mTop = offset; - params.mBottom = params.mTop + height; + // Use the bottom-most view as the baseline. + View baselineView = null; + int baseline = 0; + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + final int childBaseline = child.getBaseline(); + if (childBaseline >= baseline) { + baselineView = child; + baseline = childBaseline; } } + mBaselineView = baselineView; - if (mBaselineView == null) { - mBaselineView = child; - } else { - LayoutParams lp = (LayoutParams) mBaselineView.getLayoutParams(); - if (params.mTop < lp.mTop || (params.mTop == lp.mTop && params.mLeft < lp.mLeft)) { - mBaselineView = child; - } - } + setMeasuredDimension(width, height); } /** @@ -950,8 +910,20 @@ public class RelativeLayout extends ViewGroup { } } - private void applyVerticalSizeRules(LayoutParams childParams, int myHeight) { - int[] rules = childParams.getRules(); + private void applyVerticalSizeRules(LayoutParams childParams, int myHeight, int myBaseline) { + final int[] rules = childParams.getRules(); + + // Baseline alignment overrides any explicitly specified top or bottom. + int baselineOffset = getRelatedViewBaselineOffset(rules); + if (baselineOffset != -1) { + if (myBaseline != -1) { + baselineOffset -= myBaseline; + } + childParams.mTop = baselineOffset; + childParams.mBottom = VALUE_NOT_SET; + return; + } + RelativeLayout.LayoutParams anchorParams; childParams.mTop = VALUE_NOT_SET; @@ -1000,10 +972,6 @@ public class RelativeLayout extends ViewGroup { childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin; } } - - if (rules[ALIGN_BASELINE] != 0) { - mHasBaselineAlignedChild = true; - } } private View getRelatedView(int[] rules, int relation) { @@ -1038,10 +1006,17 @@ public class RelativeLayout extends ViewGroup { return null; } - private int getRelatedViewBaseline(int[] rules, int relation) { - View v = getRelatedView(rules, relation); + private int getRelatedViewBaselineOffset(int[] rules) { + final View v = getRelatedView(rules, ALIGN_BASELINE); if (v != null) { - return v.getBaseline(); + final int baseline = v.getBaseline(); + if (baseline != -1) { + final ViewGroup.LayoutParams params = v.getLayoutParams(); + if (params instanceof LayoutParams) { + final LayoutParams anchorParams = (LayoutParams) v.getLayoutParams(); + return anchorParams.mTop + baseline; + } + } } return -1; } @@ -1104,8 +1079,9 @@ public class RelativeLayout extends ViewGroup { return new LayoutParams(p); } + /** @hide */ @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { if (mTopToBottomLeftToRightSet == null) { mTopToBottomLeftToRightSet = new TreeSet<View>(new TopToBottomLeftToRightComparator()); } @@ -1127,15 +1103,17 @@ public class RelativeLayout extends ViewGroup { return false; } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(RelativeLayout.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(RelativeLayout.class.getName()); } diff --git a/core/java/android/widget/ResourceCursorAdapter.java b/core/java/android/widget/ResourceCursorAdapter.java index 7341c2c..100f919 100644 --- a/core/java/android/widget/ResourceCursorAdapter.java +++ b/core/java/android/widget/ResourceCursorAdapter.java @@ -17,7 +17,9 @@ package android.widget; import android.content.Context; +import android.content.res.Resources; import android.database.Cursor; +import android.view.ContextThemeWrapper; import android.view.View; import android.view.ViewGroup; import android.view.LayoutInflater; @@ -31,9 +33,10 @@ public abstract class ResourceCursorAdapter extends CursorAdapter { private int mLayout; private int mDropDownLayout; - + private LayoutInflater mInflater; - + private LayoutInflater mDropDownInflater; + /** * Constructor the enables auto-requery. * @@ -52,8 +55,9 @@ public abstract class ResourceCursorAdapter extends CursorAdapter { super(context, c); mLayout = mDropDownLayout = layout; mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mDropDownInflater = mInflater; } - + /** * Constructor with default behavior as per * {@link CursorAdapter#CursorAdapter(Context, Cursor, boolean)}; it is recommended @@ -74,6 +78,7 @@ public abstract class ResourceCursorAdapter extends CursorAdapter { super(context, c, autoRequery); mLayout = mDropDownLayout = layout; mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mDropDownInflater = mInflater; } /** @@ -91,11 +96,37 @@ public abstract class ResourceCursorAdapter extends CursorAdapter { super(context, c, flags); mLayout = mDropDownLayout = layout; mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mDropDownInflater = mInflater; + } + + /** + * Sets the {@link android.content.res.Resources.Theme} against which drop-down views are + * inflated. + * <p> + * By default, drop-down views are inflated against the theme of the + * {@link Context} passed to the adapter's constructor. + * + * @param theme the theme against which to inflate drop-down views or + * {@code null} to use the theme from the adapter's context + * @see #newDropDownView(Context, Cursor, ViewGroup) + */ + @Override + public void setDropDownViewTheme(Resources.Theme theme) { + super.setDropDownViewTheme(theme); + + if (theme == null) { + mDropDownInflater = null; + } else if (theme == mInflater.getContext().getTheme()) { + mDropDownInflater = mInflater; + } else { + final Context context = new ContextThemeWrapper(mContext, theme); + mDropDownInflater = LayoutInflater.from(context); + } } /** * Inflates view(s) from the specified XML file. - * + * * @see android.widget.CursorAdapter#newView(android.content.Context, * android.database.Cursor, ViewGroup) */ @@ -106,7 +137,7 @@ public abstract class ResourceCursorAdapter extends CursorAdapter { @Override public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) { - return mInflater.inflate(mDropDownLayout, parent, false); + return mDropDownInflater.inflate(mDropDownLayout, parent, false); } /** diff --git a/core/java/android/widget/ScrollBarDrawable.java b/core/java/android/widget/ScrollBarDrawable.java index 10e9ff4..b12c581 100644 --- a/core/java/android/widget/ScrollBarDrawable.java +++ b/core/java/android/widget/ScrollBarDrawable.java @@ -23,62 +23,73 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; /** - * This is only used by View for displaying its scroll bars. It should probably + * This is only used by View for displaying its scroll bars. It should probably * be moved in to the view package since it is used in that lower-level layer. * For now, we'll hide it so it can be cleaned up later. + * * {@hide} */ -public class ScrollBarDrawable extends Drawable { - private static final int[] STATE_ENABLED = new int[] { android.R.attr.state_enabled }; - +public class ScrollBarDrawable extends Drawable implements Drawable.Callback { private Drawable mVerticalTrack; private Drawable mHorizontalTrack; private Drawable mVerticalThumb; private Drawable mHorizontalThumb; + private int mRange; private int mOffset; private int mExtent; + private boolean mVertical; - private boolean mChanged; + private boolean mBoundsChanged; private boolean mRangeChanged; - private final Rect mTempBounds = new Rect(); private boolean mAlwaysDrawHorizontalTrack; private boolean mAlwaysDrawVerticalTrack; - public ScrollBarDrawable() { - } + private int mAlpha = 255; + private boolean mHasSetAlpha; + + private ColorFilter mColorFilter; + private boolean mHasSetColorFilter; /** - * Indicate whether the horizontal scrollbar track should always be drawn regardless of the - * extent. Defaults to false. + * Indicate whether the horizontal scrollbar track should always be drawn + * regardless of the extent. Defaults to false. + * + * @param alwaysDrawTrack Whether the track should always be drawn * - * @param alwaysDrawTrack Set to true if the track should always be drawn + * @see #getAlwaysDrawHorizontalTrack() */ public void setAlwaysDrawHorizontalTrack(boolean alwaysDrawTrack) { mAlwaysDrawHorizontalTrack = alwaysDrawTrack; } /** - * Indicate whether the vertical scrollbar track should always be drawn regardless of the - * extent. Defaults to false. + * Indicate whether the vertical scrollbar track should always be drawn + * regardless of the extent. Defaults to false. * - * @param alwaysDrawTrack Set to true if the track should always be drawn + * @param alwaysDrawTrack Whether the track should always be drawn + * + * @see #getAlwaysDrawVerticalTrack() */ public void setAlwaysDrawVerticalTrack(boolean alwaysDrawTrack) { mAlwaysDrawVerticalTrack = alwaysDrawTrack; } /** - * Indicates whether the vertical scrollbar track should always be drawn regardless of the - * extent. + * @return whether the vertical scrollbar track should always be drawn + * regardless of the extent. + * + * @see #setAlwaysDrawVerticalTrack(boolean) */ public boolean getAlwaysDrawVerticalTrack() { return mAlwaysDrawVerticalTrack; } /** - * Indicates whether the horizontal scrollbar track should always be drawn regardless of the - * extent. + * @return whether the horizontal scrollbar track should always be drawn + * regardless of the extent. + * + * @see #setAlwaysDrawHorizontalTrack(boolean) */ public boolean getAlwaysDrawHorizontalTrack() { return mAlwaysDrawHorizontalTrack; @@ -86,17 +97,18 @@ public class ScrollBarDrawable extends Drawable { public void setParameters(int range, int offset, int extent, boolean vertical) { if (mVertical != vertical) { - mChanged = true; + mVertical = vertical; + + mBoundsChanged = true; } if (mRange != range || mOffset != offset || mExtent != extent) { + mRange = range; + mOffset = offset; + mExtent = extent; + mRangeChanged = true; } - - mRange = range; - mOffset = offset; - mExtent = extent; - mVertical = vertical; } @Override @@ -112,27 +124,29 @@ public class ScrollBarDrawable extends Drawable { drawThumb = false; } - Rect r = getBounds(); + final Rect r = getBounds(); if (canvas.quickReject(r.left, r.top, r.right, r.bottom, Canvas.EdgeType.AA)) { return; } + if (drawTrack) { drawTrack(canvas, r, vertical); } if (drawThumb) { - int size = vertical ? r.height() : r.width(); - int thickness = vertical ? r.width() : r.height(); - int length = Math.round((float) size * extent / range); - int offset = Math.round((float) (size - length) * mOffset / (range - extent)); + final int size = vertical ? r.height() : r.width(); + final int thickness = vertical ? r.width() : r.height(); + final int minLength = thickness * 2; - // avoid the tiny thumb - int minLength = thickness * 2; + // Avoid the tiny thumb. + int length = Math.round((float) size * extent / range); if (length < minLength) { length = minLength; } - // avoid the too-big thumb - if (offset + length > size) { + + // Avoid the too-big thumb. + int offset = Math.round((float) (size - length) * mOffset / (range - extent)); + if (offset > size - length) { offset = size - length; } @@ -143,80 +157,128 @@ public class ScrollBarDrawable extends Drawable { @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); - mChanged = true; + mBoundsChanged = true; } - protected void drawTrack(Canvas canvas, Rect bounds, boolean vertical) { - Drawable track; + @Override + public boolean isStateful() { + return (mVerticalTrack != null && mVerticalTrack.isStateful()) + || (mVerticalThumb != null && mVerticalThumb.isStateful()) + || (mHorizontalTrack != null && mHorizontalTrack.isStateful()) + || (mHorizontalThumb != null && mHorizontalThumb.isStateful()) + || super.isStateful(); + } + + @Override + protected boolean onStateChange(int[] state) { + boolean changed = super.onStateChange(state); + if (mVerticalTrack != null) { + changed |= mVerticalTrack.setState(state); + } + if (mVerticalThumb != null) { + changed |= mVerticalThumb.setState(state); + } + if (mHorizontalTrack != null) { + changed |= mHorizontalTrack.setState(state); + } + if (mHorizontalThumb != null) { + changed |= mHorizontalThumb.setState(state); + } + return changed; + } + + private void drawTrack(Canvas canvas, Rect bounds, boolean vertical) { + final Drawable track; if (vertical) { track = mVerticalTrack; } else { track = mHorizontalTrack; } + if (track != null) { - if (mChanged) { + if (mBoundsChanged) { track.setBounds(bounds); } track.draw(canvas); } } - protected void drawThumb(Canvas canvas, Rect bounds, int offset, int length, boolean vertical) { - final Rect thumbRect = mTempBounds; - final boolean changed = mRangeChanged || mChanged; - if (changed) { - if (vertical) { - thumbRect.set(bounds.left, bounds.top + offset, - bounds.right, bounds.top + offset + length); - } else { - thumbRect.set(bounds.left + offset, bounds.top, - bounds.left + offset + length, bounds.bottom); - } - } - + private void drawThumb(Canvas canvas, Rect bounds, int offset, int length, boolean vertical) { + final boolean changed = mRangeChanged || mBoundsChanged; if (vertical) { if (mVerticalThumb != null) { final Drawable thumb = mVerticalThumb; - if (changed) thumb.setBounds(thumbRect); + if (changed) { + thumb.setBounds(bounds.left, bounds.top + offset, + bounds.right, bounds.top + offset + length); + } + thumb.draw(canvas); } } else { if (mHorizontalThumb != null) { final Drawable thumb = mHorizontalThumb; - if (changed) thumb.setBounds(thumbRect); + if (changed) { + thumb.setBounds(bounds.left + offset, bounds.top, + bounds.left + offset + length, bounds.bottom); + } + thumb.draw(canvas); } } } public void setVerticalThumbDrawable(Drawable thumb) { - if (thumb != null) { - thumb.setState(STATE_ENABLED); - mVerticalThumb = thumb; + if (mVerticalThumb != null) { + mVerticalThumb.setCallback(null); } + + propagateCurrentState(thumb); + mVerticalThumb = thumb; } public void setVerticalTrackDrawable(Drawable track) { - if (track != null) { - track.setState(STATE_ENABLED); + if (mVerticalTrack != null) { + mVerticalTrack.setCallback(null); } + + propagateCurrentState(track); mVerticalTrack = track; } public void setHorizontalThumbDrawable(Drawable thumb) { - if (thumb != null) { - thumb.setState(STATE_ENABLED); - mHorizontalThumb = thumb; + if (mHorizontalThumb != null) { + mHorizontalThumb.setCallback(null); } + + propagateCurrentState(thumb); + mHorizontalThumb = thumb; } public void setHorizontalTrackDrawable(Drawable track) { - if (track != null) { - track.setState(STATE_ENABLED); + if (mHorizontalTrack != null) { + mHorizontalTrack.setCallback(null); } + + propagateCurrentState(track); mHorizontalTrack = track; } + private void propagateCurrentState(Drawable d) { + if (d != null) { + d.setState(getState()); + d.setCallback(this); + + if (mHasSetAlpha) { + d.setAlpha(mAlpha); + } + + if (mHasSetColorFilter) { + d.setColorFilter(mColorFilter); + } + } + } + public int getSize(boolean vertical) { if (vertical) { return mVerticalTrack != null ? mVerticalTrack.getIntrinsicWidth() : @@ -229,6 +291,9 @@ public class ScrollBarDrawable extends Drawable { @Override public void setAlpha(int alpha) { + mAlpha = alpha; + mHasSetAlpha = true; + if (mVerticalTrack != null) { mVerticalTrack.setAlpha(alpha); } @@ -245,12 +310,14 @@ public class ScrollBarDrawable extends Drawable { @Override public int getAlpha() { - // All elements should have same alpha, just return one of them - return mVerticalThumb.getAlpha(); + return mAlpha; } @Override public void setColorFilter(ColorFilter cf) { + mColorFilter = cf; + mHasSetColorFilter = true; + if (mVerticalTrack != null) { mVerticalTrack.setColorFilter(cf); } @@ -266,11 +333,31 @@ public class ScrollBarDrawable extends Drawable { } @Override + public ColorFilter getColorFilter() { + return mColorFilter; + } + + @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } @Override + public void invalidateDrawable(Drawable who) { + invalidateSelf(); + } + + @Override + public void scheduleDrawable(Drawable who, Runnable what, long when) { + scheduleSelf(what, when); + } + + @Override + public void unscheduleDrawable(Drawable who, Runnable what) { + unscheduleSelf(what); + } + + @Override public String toString() { return "ScrollBarDrawable: range=" + mRange + " offset=" + mOffset + " extent=" + mExtent + (mVertical ? " V" : " H"); diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index a90b392..1098419 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -809,9 +809,10 @@ public class ScrollView extends FrameLayout { awakenScrollBars(); } + /** @hide */ @Override - public boolean performAccessibilityAction(int action, Bundle arguments) { - if (super.performAccessibilityAction(action, arguments)) { + public boolean performAccessibilityActionInternal(int action, Bundle arguments) { + if (super.performAccessibilityActionInternal(action, arguments)) { return true; } if (!isEnabled()) { @@ -838,9 +839,10 @@ public class ScrollView extends FrameLayout { return false; } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(ScrollView.class.getName()); if (isEnabled()) { final int scrollRange = getScrollRange(); @@ -856,9 +858,10 @@ public class ScrollView extends FrameLayout { } } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(ScrollView.class.getName()); final boolean scrollable = getScrollRange() > 0; event.setScrollable(scrollable); diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java index 4ee6418..a1f361a 100644 --- a/core/java/android/widget/SearchView.java +++ b/core/java/android/widget/SearchView.java @@ -1325,15 +1325,17 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { setIconified(false); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(SearchView.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(SearchView.class.getName()); } diff --git a/core/java/android/widget/SeekBar.java b/core/java/android/widget/SeekBar.java index dc7c04c..aa5c055 100644 --- a/core/java/android/widget/SeekBar.java +++ b/core/java/android/widget/SeekBar.java @@ -124,15 +124,17 @@ public class SeekBar extends AbsSeekBar { } } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(SeekBar.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(SeekBar.class.getName()); } } diff --git a/core/java/android/widget/SimpleAdapter.java b/core/java/android/widget/SimpleAdapter.java index 98bcfff..a656712 100644 --- a/core/java/android/widget/SimpleAdapter.java +++ b/core/java/android/widget/SimpleAdapter.java @@ -17,6 +17,8 @@ package android.widget; import android.content.Context; +import android.content.res.Resources; +import android.view.ContextThemeWrapper; import android.view.View; import android.view.ViewGroup; import android.view.LayoutInflater; @@ -40,14 +42,14 @@ import java.util.Map; * If the returned value is false, the following views are then tried in order: * <ul> * <li> A view that implements Checkable (e.g. CheckBox). The expected bind value is a boolean. - * <li> TextView. The expected bind value is a string and {@link #setViewText(TextView, String)} + * <li> TextView. The expected bind value is a string and {@link #setViewText(TextView, String)} * is invoked. - * <li> ImageView. The expected bind value is a resource id or a string and - * {@link #setViewImage(ImageView, int)} or {@link #setViewImage(ImageView, String)} is invoked. + * <li> ImageView. The expected bind value is a resource id or a string and + * {@link #setViewImage(ImageView, int)} or {@link #setViewImage(ImageView, String)} is invoked. * </ul> * If no appropriate binding can be found, an {@link IllegalStateException} is thrown. */ -public class SimpleAdapter extends BaseAdapter implements Filterable { +public class SimpleAdapter extends BaseAdapter implements Filterable, Spinner.ThemedSpinnerAdapter { private int[] mTo; private String[] mFrom; private ViewBinder mViewBinder; @@ -58,12 +60,15 @@ public class SimpleAdapter extends BaseAdapter implements Filterable { private int mDropDownResource; private LayoutInflater mInflater; + /** Layout inflater used for {@link #getDropDownView(int, View, ViewGroup)}. */ + private LayoutInflater mDropDownInflater; + private SimpleFilter mFilter; private ArrayList<Map<String, ?>> mUnfilteredData; /** * Constructor - * + * * @param context The context where the View associated with this SimpleAdapter is running * @param data A List of Maps. Each entry in the List corresponds to one row in the list. The * Maps contain the data for each row, and should include all the entries specified in @@ -85,7 +90,6 @@ public class SimpleAdapter extends BaseAdapter implements Filterable { mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } - /** * @see android.widget.Adapter#getCount() */ @@ -111,14 +115,14 @@ public class SimpleAdapter extends BaseAdapter implements Filterable { * @see android.widget.Adapter#getView(int, View, ViewGroup) */ public View getView(int position, View convertView, ViewGroup parent) { - return createViewFromResource(position, convertView, parent, mResource); + return createViewFromResource(mInflater, position, convertView, parent, mResource); } - private View createViewFromResource(int position, View convertView, + private View createViewFromResource(LayoutInflater inflater, int position, View convertView, ViewGroup parent, int resource) { View v; if (convertView == null) { - v = mInflater.inflate(resource, parent, false); + v = inflater.inflate(resource, parent, false); } else { v = convertView; } @@ -135,12 +139,41 @@ public class SimpleAdapter extends BaseAdapter implements Filterable { * @see #getDropDownView(int, android.view.View, android.view.ViewGroup) */ public void setDropDownViewResource(int resource) { - this.mDropDownResource = resource; + mDropDownResource = resource; + } + + /** + * Sets the {@link android.content.res.Resources.Theme} against which drop-down views are + * inflated. + * <p> + * By default, drop-down views are inflated against the theme of the + * {@link Context} passed to the adapter's constructor. + * + * @param theme the theme against which to inflate drop-down views or + * {@code null} to use the theme from the adapter's context + * @see #getDropDownView(int, View, ViewGroup) + */ + @Override + public void setDropDownViewTheme(Resources.Theme theme) { + if (theme == null) { + mDropDownInflater = null; + } else if (theme == mInflater.getContext().getTheme()) { + mDropDownInflater = mInflater; + } else { + final Context context = new ContextThemeWrapper(mInflater.getContext(), theme); + mDropDownInflater = LayoutInflater.from(context); + } + } + + @Override + public Resources.Theme getDropDownViewTheme() { + return mDropDownInflater == null ? null : mDropDownInflater.getContext().getTheme(); } @Override public View getDropDownView(int position, View convertView, ViewGroup parent) { - return createViewFromResource(position, convertView, parent, mDropDownResource); + return createViewFromResource( + mDropDownInflater, position, convertView, parent, mDropDownResource); } private void bindView(int position, View view) { diff --git a/core/java/android/widget/SimpleMonthAdapter.java b/core/java/android/widget/SimpleMonthAdapter.java index 24ebb2c..c807d56 100644 --- a/core/java/android/widget/SimpleMonthAdapter.java +++ b/core/java/android/widget/SimpleMonthAdapter.java @@ -39,6 +39,7 @@ class SimpleMonthAdapter extends BaseAdapter { private Calendar mSelectedDay = Calendar.getInstance(); private ColorStateList mCalendarTextColors = ColorStateList.valueOf(Color.BLACK); + private ColorStateList mCalendarDayBackgroundColor = ColorStateList.valueOf(Color.MAGENTA); private OnDaySelectedListener mOnDaySelectedListener; private int mFirstDayOfWeek; @@ -88,6 +89,10 @@ class SimpleMonthAdapter extends BaseAdapter { mCalendarTextColors = colors; } + void setCalendarDayBackgroundColor(ColorStateList dayBackgroundColor) { + mCalendarDayBackgroundColor = dayBackgroundColor; + } + /** * Sets the text color, size, style, hint color, and highlight color from * the specified TextAppearance resource. This is mostly copied from @@ -144,9 +149,11 @@ class SimpleMonthAdapter extends BaseAdapter { v.setClickable(true); v.setOnDayClickListener(mOnDayClickListener); - if (mCalendarTextColors != null) { - v.setTextColor(mCalendarTextColors); - } + v.setMonthTextColor(mCalendarTextColors); + v.setDayOfWeekTextColor(mCalendarTextColors); + v.setDayTextColor(mCalendarTextColors); + + v.setDayBackgroundColor(mCalendarDayBackgroundColor); } final int minMonth = mMinDate.get(Calendar.MONTH); diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java index d2a37ac..58ad515 100644 --- a/core/java/android/widget/SimpleMonthView.java +++ b/core/java/android/widget/SimpleMonthView.java @@ -27,12 +27,13 @@ import android.graphics.Paint.Style; import android.graphics.Rect; import android.graphics.Typeface; import android.os.Bundle; +import android.text.TextPaint; import android.text.format.DateFormat; import android.text.format.DateUtils; import android.text.format.Time; import android.util.AttributeSet; import android.util.IntArray; -import android.util.MathUtils; +import android.util.StateSet; import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityEvent; @@ -44,7 +45,6 @@ import com.android.internal.widget.ExploreByTouchHelper; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Formatter; -import java.util.List; import java.util.Locale; /** @@ -52,8 +52,7 @@ import java.util.Locale; * within the specified month. */ class SimpleMonthView extends View { - private static final int DEFAULT_HEIGHT = 32; - private static final int MIN_HEIGHT = 10; + private static final int MIN_ROW_HEIGHT = 10; private static final int DEFAULT_SELECTED_DAY = -1; private static final int DEFAULT_WEEK_START = Calendar.SUNDAY; @@ -61,18 +60,21 @@ class SimpleMonthView extends View { private static final int DEFAULT_NUM_ROWS = 6; private static final int MAX_NUM_ROWS = 6; - private static final int SELECTED_CIRCLE_ALPHA = 60; - - private static final int DAY_SEPARATOR_WIDTH = 1; - private final Formatter mFormatter; private final StringBuilder mStringBuilder; - private final int mMiniDayNumberTextSize; - private final int mMonthLabelTextSize; - private final int mMonthDayLabelTextSize; - private final int mMonthHeaderSize; - private final int mDaySelectedCircleSize; + private final int mMonthTextSize; + private final int mDayOfWeekTextSize; + private final int mDayTextSize; + + /** Height of the header containing the month and day of week labels. */ + private final int mMonthHeaderHeight; + + private final TextPaint mMonthPaint = new TextPaint(); + private final TextPaint mDayOfWeekPaint = new TextPaint(); + private final TextPaint mDayPaint = new TextPaint(); + + private final Paint mDayBackgroundPaint = new Paint(); /** Single-letter (when available) formatter for the day of week label. */ private SimpleDateFormat mDayFormatter = new SimpleDateFormat("EEEEE", Locale.getDefault()); @@ -81,14 +83,7 @@ class SimpleMonthView extends View { private int mPadding = 0; private String mDayOfWeekTypeface; - private String mMonthTitleTypeface; - - private Paint mDayNumberPaint; - private Paint mDayNumberDisabledPaint; - private Paint mDayNumberSelectedPaint; - - private Paint mMonthTitlePaint; - private Paint mMonthDayLabelPaint; + private String mMonthTypeface; private int mMonth; private int mYear; @@ -97,13 +92,13 @@ class SimpleMonthView extends View { private int mWidth; // The height this view should draw at in pixels, set by height param - private int mRowHeight = DEFAULT_HEIGHT; + private final int mRowHeight; // If this view contains the today private boolean mHasToday = false; // Which day is selected [0-6] or -1 if no day is selected - private int mSelectedDay = -1; + private int mActivatedDay = -1; // Which day is today [0-6] or -1 if no day is today private int mToday = DEFAULT_SELECTED_DAY; @@ -142,6 +137,8 @@ class SimpleMonthView extends View { private int mDisabledTextColor; private int mSelectedDayColor; + private ColorStateList mDayTextColor; + public SimpleMonthView(Context context) { this(context, null); } @@ -159,22 +156,21 @@ class SimpleMonthView extends View { final Resources res = context.getResources(); mDayOfWeekTypeface = res.getString(R.string.day_of_week_label_typeface); - mMonthTitleTypeface = res.getString(R.string.sans_serif); + mMonthTypeface = res.getString(R.string.sans_serif); mStringBuilder = new StringBuilder(50); mFormatter = new Formatter(mStringBuilder, Locale.getDefault()); - mMiniDayNumberTextSize = res.getDimensionPixelSize(R.dimen.datepicker_day_number_size); - mMonthLabelTextSize = res.getDimensionPixelSize(R.dimen.datepicker_month_label_size); - mMonthDayLabelTextSize = res.getDimensionPixelSize( + mDayTextSize = res.getDimensionPixelSize(R.dimen.datepicker_day_number_size); + mMonthTextSize = res.getDimensionPixelSize(R.dimen.datepicker_month_label_size); + mDayOfWeekTextSize = res.getDimensionPixelSize( R.dimen.datepicker_month_day_label_text_size); - mMonthHeaderSize = res.getDimensionPixelOffset( + mMonthHeaderHeight = res.getDimensionPixelOffset( R.dimen.datepicker_month_list_item_header_height); - mDaySelectedCircleSize = res.getDimensionPixelSize( - R.dimen.datepicker_day_number_select_circle_radius); - mRowHeight = (res.getDimensionPixelOffset(R.dimen.datepicker_view_animator_height) - - mMonthHeaderSize) / MAX_NUM_ROWS; + mRowHeight = Math.max(MIN_ROW_HEIGHT, + (res.getDimensionPixelOffset(R.dimen.datepicker_view_animator_height) + - mMonthHeaderHeight) / MAX_NUM_ROWS); // Set up accessibility components. mTouchHelper = new MonthViewTouchHelper(this); @@ -182,8 +178,32 @@ class SimpleMonthView extends View { setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); mLockAccessibilityDelegate = true; - // Sets up any standard paints that will be used - initView(); + initPaints(); + } + + /** + * Sets up the text and style properties for painting. + */ + private void initPaints() { + mMonthPaint.setAntiAlias(true); + mMonthPaint.setTextSize(mMonthTextSize); + mMonthPaint.setTypeface(Typeface.create(mMonthTypeface, Typeface.BOLD)); + mMonthPaint.setTextAlign(Align.CENTER); + mMonthPaint.setStyle(Style.FILL); + + mDayOfWeekPaint.setAntiAlias(true); + mDayOfWeekPaint.setTextSize(mDayOfWeekTextSize); + mDayOfWeekPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.BOLD)); + mDayOfWeekPaint.setTextAlign(Align.CENTER); + mDayOfWeekPaint.setStyle(Style.FILL); + + mDayBackgroundPaint.setAntiAlias(true); + mDayBackgroundPaint.setStyle(Style.FILL); + + mDayPaint.setAntiAlias(true); + mDayPaint.setTextSize(mDayTextSize); + mDayPaint.setTextAlign(Align.CENTER); + mDayPaint.setStyle(Style.FILL); } @Override @@ -193,22 +213,28 @@ class SimpleMonthView extends View { mDayFormatter = new SimpleDateFormat("EEEEE", newConfig.locale); } - void setTextColor(ColorStateList colors) { - final Resources res = getContext().getResources(); + void setMonthTextColor(ColorStateList monthTextColor) { + final int enabledColor = monthTextColor.getColorForState(ENABLED_STATE_SET, 0); + mMonthPaint.setColor(enabledColor); + invalidate(); + } - mNormalTextColor = colors.getColorForState(ENABLED_STATE_SET, - res.getColor(R.color.datepicker_default_normal_text_color_holo_light)); - mMonthTitlePaint.setColor(mNormalTextColor); - mMonthDayLabelPaint.setColor(mNormalTextColor); + void setDayOfWeekTextColor(ColorStateList dayOfWeekTextColor) { + final int enabledColor = dayOfWeekTextColor.getColorForState(ENABLED_STATE_SET, 0); + mDayOfWeekPaint.setColor(enabledColor); + invalidate(); + } - mDisabledTextColor = colors.getColorForState(EMPTY_STATE_SET, - res.getColor(R.color.datepicker_default_disabled_text_color_holo_light)); - mDayNumberDisabledPaint.setColor(mDisabledTextColor); + void setDayTextColor(ColorStateList dayTextColor) { + mDayTextColor = dayTextColor; + invalidate(); + } - mSelectedDayColor = colors.getColorForState(ENABLED_SELECTED_STATE_SET, - res.getColor(R.color.holo_blue_light)); - mDayNumberSelectedPaint.setColor(mSelectedDayColor); - mDayNumberSelectedPaint.setAlpha(SELECTED_CIRCLE_ALPHA); + void setDayBackgroundColor(ColorStateList dayBackgroundColor) { + final int activatedColor = dayBackgroundColor.getColorForState( + StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0); + mDayBackgroundPaint.setColor(activatedColor); + invalidate(); } @Override @@ -246,52 +272,6 @@ class SimpleMonthView extends View { return true; } - /** - * Sets up the text and style properties for painting. - */ - private void initView() { - mMonthTitlePaint = new Paint(); - mMonthTitlePaint.setAntiAlias(true); - mMonthTitlePaint.setColor(mNormalTextColor); - mMonthTitlePaint.setTextSize(mMonthLabelTextSize); - mMonthTitlePaint.setTypeface(Typeface.create(mMonthTitleTypeface, Typeface.BOLD)); - mMonthTitlePaint.setTextAlign(Align.CENTER); - mMonthTitlePaint.setStyle(Style.FILL); - mMonthTitlePaint.setFakeBoldText(true); - - mMonthDayLabelPaint = new Paint(); - mMonthDayLabelPaint.setAntiAlias(true); - mMonthDayLabelPaint.setColor(mNormalTextColor); - mMonthDayLabelPaint.setTextSize(mMonthDayLabelTextSize); - mMonthDayLabelPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.NORMAL)); - mMonthDayLabelPaint.setTextAlign(Align.CENTER); - mMonthDayLabelPaint.setStyle(Style.FILL); - mMonthDayLabelPaint.setFakeBoldText(true); - - mDayNumberSelectedPaint = new Paint(); - mDayNumberSelectedPaint.setAntiAlias(true); - mDayNumberSelectedPaint.setColor(mSelectedDayColor); - mDayNumberSelectedPaint.setAlpha(SELECTED_CIRCLE_ALPHA); - mDayNumberSelectedPaint.setTextAlign(Align.CENTER); - mDayNumberSelectedPaint.setStyle(Style.FILL); - mDayNumberSelectedPaint.setFakeBoldText(true); - - mDayNumberPaint = new Paint(); - mDayNumberPaint.setAntiAlias(true); - mDayNumberPaint.setTextSize(mMiniDayNumberTextSize); - mDayNumberPaint.setTextAlign(Align.CENTER); - mDayNumberPaint.setStyle(Style.FILL); - mDayNumberPaint.setFakeBoldText(false); - - mDayNumberDisabledPaint = new Paint(); - mDayNumberDisabledPaint.setAntiAlias(true); - mDayNumberDisabledPaint.setColor(mDisabledTextColor); - mDayNumberDisabledPaint.setTextSize(mMiniDayNumberTextSize); - mDayNumberDisabledPaint.setTextAlign(Align.CENTER); - mDayNumberDisabledPaint.setStyle(Style.FILL); - mDayNumberDisabledPaint.setFakeBoldText(false); - } - @Override protected void onDraw(Canvas canvas) { drawMonthTitle(canvas); @@ -323,11 +303,7 @@ class SimpleMonthView extends View { */ void setMonthParams(int selectedDay, int month, int year, int weekStart, int enabledDayStart, int enabledDayEnd) { - if (mRowHeight < MIN_HEIGHT) { - mRowHeight = MIN_HEIGHT; - } - - mSelectedDay = selectedDay; + mActivatedDay = selectedDay; if (isValidMonth(month)) { mMonth = month; @@ -415,7 +391,7 @@ class SimpleMonthView extends View { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows - + mMonthHeaderSize); + + mMonthHeaderHeight); } @Override @@ -437,21 +413,28 @@ class SimpleMonthView extends View { private void drawMonthTitle(Canvas canvas) { final float x = (mWidth + 2 * mPadding) / 2f; - final float y = (mMonthHeaderSize - mMonthDayLabelTextSize) / 2f; - canvas.drawText(getMonthAndYearString(), x, y, mMonthTitlePaint); + + // Centered on the upper half of the month header. + final float lineHeight = mMonthPaint.ascent() + mMonthPaint.descent(); + final float y = mMonthHeaderHeight * 0.25f - lineHeight / 2f; + + canvas.drawText(getMonthAndYearString(), x, y, mMonthPaint); } private void drawWeekDayLabels(Canvas canvas) { - final int y = mMonthHeaderSize - (mMonthDayLabelTextSize / 2); - final int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2); + final float dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2); + + // Centered on the lower half of the month header. + final float lineHeight = mDayOfWeekPaint.ascent() + mDayOfWeekPaint.descent(); + final float y = mMonthHeaderHeight * 0.75f - lineHeight / 2f; for (int i = 0; i < mNumDays; i++) { final int calendarDay = (i + mWeekStart) % mNumDays; mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay); final String dayLabel = mDayFormatter.format(mDayLabelCalendar.getTime()); - final int x = (2 * i + 1) * dayWidthHalf + mPadding; - canvas.drawText(dayLabel, x, y, mMonthDayLabelPaint); + final float x = (2 * i + 1) * dayWidthHalf + mPadding; + canvas.drawText(dayLabel, x, y, mDayOfWeekPaint); } } @@ -459,26 +442,40 @@ class SimpleMonthView extends View { * Draws the month days. */ private void drawDays(Canvas canvas) { - int y = (((mRowHeight + mMiniDayNumberTextSize) / 2) - DAY_SEPARATOR_WIDTH) - + mMonthHeaderSize; - int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2); - int j = findDayOffset(); - for (int day = 1; day <= mNumCells; day++) { - int x = (2 * j + 1) * dayWidthHalf + mPadding; - if (mSelectedDay == day) { - canvas.drawCircle(x, y - (mMiniDayNumberTextSize / 3), mDaySelectedCircleSize, - mDayNumberSelectedPaint); + final int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2); + + // Centered within the row. + final float lineHeight = mDayOfWeekPaint.ascent() + mDayOfWeekPaint.descent(); + float y = mMonthHeaderHeight + (mRowHeight - lineHeight) / 2f; + + for (int day = 1, j = findDayOffset(); day <= mNumCells; day++) { + final int x = (2 * j + 1) * dayWidthHalf + mPadding; + int stateMask = 0; + + if (day >= mEnabledDayStart && day <= mEnabledDayEnd) { + stateMask |= StateSet.VIEW_STATE_ENABLED; } - if (mHasToday && mToday == day) { - mDayNumberPaint.setColor(mSelectedDayColor); - } else { - mDayNumberPaint.setColor(mNormalTextColor); + if (mActivatedDay == day) { + stateMask |= StateSet.VIEW_STATE_ACTIVATED; + + // Adjust the circle to be centered the row. + final float rowCenterY = y + lineHeight / 2; + canvas.drawCircle(x, rowCenterY, mRowHeight / 2, + mDayBackgroundPaint); } - final Paint paint = (day < mEnabledDayStart || day > mEnabledDayEnd) ? - mDayNumberDisabledPaint : mDayNumberPaint; - canvas.drawText(String.format("%d", day), x, y, paint); + + final int[] stateSet = StateSet.get(stateMask); + final int dayTextColor = mDayTextColor.getColorForState(stateSet, 0); + mDayPaint.setColor(dayTextColor); + + final boolean isDayToday = mHasToday && mToday == day; + mDayPaint.setFakeBoldText(isDayToday); + + canvas.drawText(String.format("%d", day), x, y, mDayPaint); + j++; + if (j == mNumDays) { j = 0; y += mRowHeight; @@ -504,7 +501,7 @@ class SimpleMonthView extends View { return -1; } // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels - int row = (int) (y - mMonthHeaderSize) / mRowHeight; + int row = (int) (y - mMonthHeaderHeight) / mRowHeight; int column = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding)); int day = column - findDayOffset() + 1; @@ -628,7 +625,7 @@ class SimpleMonthView extends View { node.setBoundsInParent(mTempRect); node.addAction(AccessibilityNodeInfo.ACTION_CLICK); - if (virtualViewId == mSelectedDay) { + if (virtualViewId == mActivatedDay) { node.setSelected(true); } @@ -654,7 +651,7 @@ class SimpleMonthView extends View { */ private void getItemBounds(int day, Rect rect) { final int offsetX = mPadding; - final int offsetY = mMonthHeaderSize; + final int offsetY = mMonthHeaderHeight; final int cellHeight = mRowHeight; final int cellWidth = ((mWidth - (2 * mPadding)) / mNumDays); final int index = ((day - 1) + findDayOffset()); @@ -679,7 +676,7 @@ class SimpleMonthView extends View { final CharSequence date = DateFormat.format(DATE_FORMAT, mTempCalendar.getTimeInMillis()); - if (day == mSelectedDay) { + if (day == mActivatedDay) { return getContext().getString(R.string.item_is_selected, date); } diff --git a/core/java/android/widget/SlidingDrawer.java b/core/java/android/widget/SlidingDrawer.java index ec06c02..e3f4a10 100644 --- a/core/java/android/widget/SlidingDrawer.java +++ b/core/java/android/widget/SlidingDrawer.java @@ -837,15 +837,17 @@ public class SlidingDrawer extends ViewGroup { } } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(SlidingDrawer.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(SlidingDrawer.class.getName()); } diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java index 0d76239..a6e0e6d 100644 --- a/core/java/android/widget/Spinner.java +++ b/core/java/android/widget/Spinner.java @@ -16,11 +16,15 @@ package android.widget; +import com.android.internal.R; + +import android.annotation.Nullable; import android.annotation.Widget; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; +import android.content.res.Resources; import android.content.res.TypedArray; import android.database.DataSetObserver; import android.graphics.Rect; @@ -30,6 +34,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; import android.util.Log; +import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; @@ -74,17 +79,22 @@ public class Spinner extends AbsSpinner implements OnClickListener { * Use a dropdown anchored to the Spinner for selecting spinner options. */ public static final int MODE_DROPDOWN = 1; - + /** * Use the theme-supplied value to select the dropdown mode. */ private static final int MODE_THEME = -1; + /** Context used to inflate the popup window or dialog. */ + private Context mPopupContext; + /** Forwarding listener used to implement drag-to-open. */ private ForwardingListener mForwardingListener; + /** Temporary holder for setAdapter() calls from the super constructor. */ + private SpinnerAdapter mTempAdapter; + private SpinnerPopup mPopup; - private DropDownAdapter mTempAdapter; int mDropDownWidth; private int mGravity; @@ -184,69 +194,120 @@ public class Spinner extends AbsSpinner implements OnClickListener { * @see #MODE_DIALOG * @see #MODE_DROPDOWN */ - public Spinner( - Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, int mode) { + public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, + int mode) { + this(context, attrs, defStyleAttr, defStyleRes, mode, null); + } + + /** + * Constructs a new spinner with the given context's theme, the supplied + * attribute set, default styles, popup mode (one of {@link #MODE_DIALOG} + * or {@link #MODE_DROPDOWN}), and the context against which the popup + * should be inflated. + * + * @param context The context against which the view is inflated, which + * provides access to the current theme, resources, etc. + * @param attrs The attributes of the XML tag that is inflating the view. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default + * values for the view. Can be 0 to not look for + * defaults. + * @param defStyleRes A resource identifier of a style resource that + * supplies default values for the view, used only if + * defStyleAttr is 0 or can not be found in the theme. + * Can be 0 to not look for defaults. + * @param mode Constant describing how the user will select choices from + * the spinner. + * @param popupContext The context against which the dialog or dropdown + * popup will be inflated. Can be null to use the view + * context. If set, this will override any value + * specified by + * {@link android.R.styleable#Spinner_popupTheme}. + * + * @see #MODE_DIALOG + * @see #MODE_DROPDOWN + */ + public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, int mode, + Context popupContext) { super(context, attrs, defStyleAttr, defStyleRes); final TypedArray a = context.obtainStyledAttributes( - attrs, com.android.internal.R.styleable.Spinner, defStyleAttr, defStyleRes); + attrs, R.styleable.Spinner, defStyleAttr, defStyleRes); - if (mode == MODE_THEME) { - mode = a.getInt(com.android.internal.R.styleable.Spinner_spinnerMode, MODE_DIALOG); - } - - switch (mode) { - case MODE_DIALOG: { - mPopup = new DialogPopup(); - break; + if (popupContext != null) { + mPopupContext = popupContext; + } else { + final int popupThemeResId = a.getResourceId(R.styleable.Spinner_popupTheme, 0); + if (popupThemeResId != 0) { + mPopupContext = new ContextThemeWrapper(context, popupThemeResId); + } else { + mPopupContext = context; + } } - case MODE_DROPDOWN: { - final DropdownPopup popup = new DropdownPopup(context, attrs, defStyleAttr, defStyleRes); + if (mode == MODE_THEME) { + mode = a.getInt(R.styleable.Spinner_spinnerMode, MODE_DIALOG); + } - mDropDownWidth = a.getLayoutDimension( - com.android.internal.R.styleable.Spinner_dropDownWidth, - ViewGroup.LayoutParams.WRAP_CONTENT); - popup.setBackgroundDrawable(a.getDrawable( - com.android.internal.R.styleable.Spinner_popupBackground)); + switch (mode) { + case MODE_DIALOG: { + mPopup = new DialogPopup(); + mPopup.setPromptText(a.getString(R.styleable.Spinner_prompt)); + break; + } - mPopup = popup; - mForwardingListener = new ForwardingListener(this) { - @Override - public ListPopupWindow getPopup() { - return popup; - } + case MODE_DROPDOWN: { + final DropdownPopup popup = new DropdownPopup( + mPopupContext, attrs, defStyleAttr, defStyleRes); + final TypedArray pa = mPopupContext.obtainStyledAttributes( + attrs, R.styleable.Spinner, defStyleAttr, defStyleRes); + mDropDownWidth = pa.getLayoutDimension(R.styleable.Spinner_dropDownWidth, + ViewGroup.LayoutParams.WRAP_CONTENT); + popup.setBackgroundDrawable(pa.getDrawable(R.styleable.Spinner_popupBackground)); + popup.setPromptText(a.getString(R.styleable.Spinner_prompt)); + pa.recycle(); + + mPopup = popup; + mForwardingListener = new ForwardingListener(this) { + @Override + public ListPopupWindow getPopup() { + return popup; + } - @Override - public boolean onForwardingStarted() { - if (!mPopup.isShowing()) { - mPopup.show(getTextDirection(), getTextAlignment()); + @Override + public boolean onForwardingStarted() { + if (!mPopup.isShowing()) { + mPopup.show(getTextDirection(), getTextAlignment()); + } + return true; } - return true; - } - }; - break; - } + }; + break; + } } - mGravity = a.getInt(com.android.internal.R.styleable.Spinner_gravity, Gravity.CENTER); - - mPopup.setPromptText(a.getString(com.android.internal.R.styleable.Spinner_prompt)); - + mGravity = a.getInt(R.styleable.Spinner_gravity, Gravity.CENTER); mDisableChildrenWhenDisabled = a.getBoolean( - com.android.internal.R.styleable.Spinner_disableChildrenWhenDisabled, false); + R.styleable.Spinner_disableChildrenWhenDisabled, false); a.recycle(); // Base constructor can call setAdapter before we initialize mPopup. // Finish setting things up if this happened. if (mTempAdapter != null) { - mPopup.setAdapter(mTempAdapter); + setAdapter(mTempAdapter); mTempAdapter = null; } } /** + * @return the context used to inflate the Spinner's popup or dialog window + */ + public Context getPopupContext() { + return mPopupContext; + } + + /** * Set the background drawable for the spinner's popup window of choices. * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes. * @@ -259,7 +320,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { Log.e(TAG, "setPopupBackgroundDrawable: incompatible spinner mode; ignoring..."); return; } - ((DropdownPopup) mPopup).setBackgroundDrawable(background); + mPopup.setBackgroundDrawable(background); } /** @@ -271,7 +332,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { * @attr ref android.R.styleable#Spinner_popupBackground */ public void setPopupBackgroundResource(int resId) { - setPopupBackgroundDrawable(getContext().getDrawable(resId)); + setPopupBackgroundDrawable(getPopupContext().getDrawable(resId)); } /** @@ -410,9 +471,17 @@ public class Spinner extends AbsSpinner implements OnClickListener { } /** - * Sets the Adapter used to provide the data which backs this Spinner. + * Sets the {@link SpinnerAdapter} used to provide the data which backs + * this Spinner. + * <p> + * If this Spinner has a popup theme set in XML via the + * {@link android.R.styleable#Spinner_popupTheme popupTheme} attribute, the + * adapter should inflate drop-down views using the same theme. The easiest + * way to achieve this is by using {@link #getPopupContext()} to obtain a + * layout inflater for use in + * {@link SpinnerAdapter#getDropDownView(int, View, ViewGroup)}. * <p> - * Note that Spinner overrides {@link Adapter#getViewTypeCount()} on the + * Spinner overrides {@link Adapter#getViewTypeCount()} on the * Adapter associated with this view. Calling * {@link Adapter#getItemViewType(int) getItemViewType(int)} on the object * returned from {@link #getAdapter()} will always return 0. Calling @@ -429,6 +498,13 @@ public class Spinner extends AbsSpinner implements OnClickListener { */ @Override public void setAdapter(SpinnerAdapter adapter) { + // The super constructor may call setAdapter before we're prepared. + // Postpone doing anything until we've finished construction. + if (mPopup == null) { + mTempAdapter = adapter; + return; + } + super.setAdapter(adapter); mRecycler.clear(); @@ -439,11 +515,8 @@ public class Spinner extends AbsSpinner implements OnClickListener { throw new IllegalArgumentException("Spinner adapter view type count must be 1"); } - if (mPopup != null) { - mPopup.setAdapter(new DropDownAdapter(adapter)); - } else { - mTempAdapter = new DropDownAdapter(adapter); - } + final Context popupContext = mPopupContext == null ? mContext : mPopupContext; + mPopup.setAdapter(new DropDownAdapter(adapter, popupContext.getTheme())); } @Override @@ -692,15 +765,17 @@ public class Spinner extends AbsSpinner implements OnClickListener { dialog.dismiss(); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(Spinner.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(Spinner.class.getName()); if (mAdapter != null) { @@ -847,14 +922,26 @@ public class Spinner extends AbsSpinner implements OnClickListener { private ListAdapter mListAdapter; /** - * <p>Creates a new ListAdapter wrapper for the specified adapter.</p> + * Creates a new ListAdapter wrapper for the specified adapter. * - * @param adapter the Adapter to transform into a ListAdapter + * @param adapter the SpinnerAdapter to transform into a ListAdapter + * @param dropDownTheme the theme against which to inflate drop-down + * views, may be {@null} to use default theme */ - public DropDownAdapter(SpinnerAdapter adapter) { - this.mAdapter = adapter; + public DropDownAdapter(@Nullable SpinnerAdapter adapter, + @Nullable Resources.Theme dropDownTheme) { + mAdapter = adapter; + if (adapter instanceof ListAdapter) { - this.mListAdapter = (ListAdapter) adapter; + mListAdapter = (ListAdapter) adapter; + } + + if (dropDownTheme != null && adapter instanceof Spinner.ThemedSpinnerAdapter) { + final Spinner.ThemedSpinnerAdapter themedAdapter = + (Spinner.ThemedSpinnerAdapter) adapter; + if (themedAdapter.getDropDownViewTheme() == null) { + themedAdapter.setDropDownViewTheme(dropDownTheme); + } } } @@ -970,7 +1057,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { public int getVerticalOffset(); public int getHorizontalOffset(); } - + private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener { private AlertDialog mPopup; private ListAdapter mListAdapter; @@ -994,7 +1081,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { public void setPromptText(CharSequence hintText) { mPrompt = hintText; } - + public CharSequence getHintText() { return mPrompt; } @@ -1003,7 +1090,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { if (mListAdapter == null) { return; } - AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + AlertDialog.Builder builder = new AlertDialog.Builder(getPopupContext()); if (mPrompt != null) { builder.setTitle(mPrompt); } @@ -1179,4 +1266,21 @@ public class Spinner extends AbsSpinner implements OnClickListener { } } } + + public interface ThemedSpinnerAdapter { + /** + * Sets the {@link Resources.Theme} against which drop-down views are + * inflated. + * + * @param theme the context against which to inflate drop-down views + * @see SpinnerAdapter#getDropDownView(int, View, ViewGroup) + */ + public void setDropDownViewTheme(Resources.Theme theme); + + /** + * @return The {@link Resources.Theme} against which drop-down views are + * inflated. + */ + public Resources.Theme getDropDownViewTheme(); + } } diff --git a/core/java/android/widget/SpinnerAdapter.java b/core/java/android/widget/SpinnerAdapter.java index 91504cf..f99f45b 100644 --- a/core/java/android/widget/SpinnerAdapter.java +++ b/core/java/android/widget/SpinnerAdapter.java @@ -22,16 +22,17 @@ import android.view.ViewGroup; /** * Extended {@link Adapter} that is the bridge between a * {@link android.widget.Spinner} and its data. A spinner adapter allows to - * define two different views: one that shows the data in the spinner itself and - * one that shows the data in the drop down list when the spinner is pressed.</p> + * define two different views: one that shows the data in the spinner itself + * and one that shows the data in the drop down list when the spinner is + * pressed. */ public interface SpinnerAdapter extends Adapter { /** - * <p>Get a {@link android.view.View} that displays in the drop down popup - * the data at the specified position in the data set.</p> + * Gets a {@link android.view.View} that displays in the drop down popup + * the data at the specified position in the data set. * - * @param position index of the item whose view we want. - * @param convertView the old view to reuse, if possible. Note: You should + * @param position index of the item whose view we want. + * @param convertView the old view to reuse, if possible. Note: You should * check that this view is non-null and of an appropriate type before * using. If it is not possible to convert this view to display the * correct data, this method can create a new view. diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java index 9e168b8..2aa1c09 100644 --- a/core/java/android/widget/StackView.java +++ b/core/java/android/widget/StackView.java @@ -1224,15 +1224,17 @@ public class StackView extends AdapterViewAnimator { measureChildren(); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(StackView.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(StackView.class.getName()); info.setScrollable(getChildCount() > 1); if (isEnabled()) { @@ -1245,9 +1247,10 @@ public class StackView extends AdapterViewAnimator { } } + /** @hide */ @Override - public boolean performAccessibilityAction(int action, Bundle arguments) { - if (super.performAccessibilityAction(action, arguments)) { + public boolean performAccessibilityActionInternal(int action, Bundle arguments) { + if (super.performAccessibilityActionInternal(action, arguments)) { return true; } if (!isEnabled()) { diff --git a/core/java/android/widget/SuggestionsAdapter.java b/core/java/android/widget/SuggestionsAdapter.java index 6349347..4323851 100644 --- a/core/java/android/widget/SuggestionsAdapter.java +++ b/core/java/android/widget/SuggestionsAdapter.java @@ -145,8 +145,9 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene * copied to the query text field. * <p> * - * @param refine which queries to refine. Possible values are {@link #REFINE_NONE}, - * {@link #REFINE_BY_ENTRY}, and {@link #REFINE_ALL}. + * @param refineWhat which queries to refine. Possible values are + * {@link #REFINE_NONE}, {@link #REFINE_BY_ENTRY}, and + * {@link #REFINE_ALL}. */ public void setQueryRefinement(int refineWhat) { mQueryRefinement = refineWhat; @@ -327,7 +328,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene // First check TEXT_2_URL CharSequence text2 = getStringOrNull(cursor, mText2UrlCol); if (text2 != null) { - text2 = formatUrl(text2); + text2 = formatUrl(context, text2); } else { text2 = getStringOrNull(cursor, mText2Col); } @@ -372,12 +373,12 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene } } - private CharSequence formatUrl(CharSequence url) { + private CharSequence formatUrl(Context context, CharSequence url) { if (mUrlColor == null) { // Lazily get the URL color from the current theme. TypedValue colorValue = new TypedValue(); - mContext.getTheme().resolveAttribute(R.attr.textColorSearchUrl, colorValue, true); - mUrlColor = mContext.getResources().getColorStateList(colorValue.resourceId); + context.getTheme().resolveAttribute(R.attr.textColorSearchUrl, colorValue, true); + mUrlColor = context.getResources().getColorStateList(colorValue.resourceId); } SpannableString text = new SpannableString(url); @@ -502,6 +503,30 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene } /** + * This method is overridden purely to provide a bit of protection against + * flaky content providers. + * + * @see android.widget.CursorAdapter#getDropDownView(int, View, ViewGroup) + */ + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) { + try { + return super.getDropDownView(position, convertView, parent); + } catch (RuntimeException e) { + Log.w(LOG_TAG, "Search suggestions cursor threw exception.", e); + // Put exception string in item title + final Context context = mDropDownContext == null ? mContext : mDropDownContext; + final View v = newDropDownView(context, mCursor, parent); + if (v != null) { + final ChildViewCache views = (ChildViewCache) v.getTag(); + final TextView tv = views.mText1; + tv.setText(e.toString()); + } + return v; + } + } + + /** * Gets a drawable given a value provided by a suggestion provider. * * This value could be just the string value of a resource id @@ -570,7 +595,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene OpenResourceIdResult r = mProviderContext.getContentResolver().getResourceId(uri); try { - return r.r.getDrawable(r.id, mContext.getTheme()); + return r.r.getDrawable(r.id, mProviderContext.getTheme()); } catch (Resources.NotFoundException ex) { throw new FileNotFoundException("Resource does not exist: " + uri); } diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java index 7a22224..a282cf5 100644 --- a/core/java/android/widget/Switch.java +++ b/core/java/android/widget/Switch.java @@ -17,6 +17,7 @@ package android.widget; import android.animation.ObjectAnimator; +import android.annotation.Nullable; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; @@ -24,6 +25,7 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Insets; import android.graphics.Paint; +import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.Region.Op; @@ -84,7 +86,17 @@ public class Switch extends CompoundButton { private static final int MONOSPACE = 3; private Drawable mThumbDrawable; + private ColorStateList mThumbTintList = null; + private PorterDuff.Mode mThumbTintMode = null; + private boolean mHasThumbTint = false; + private boolean mHasThumbTintMode = false; + private Drawable mTrackDrawable; + private ColorStateList mTrackTintList = null; + private PorterDuff.Mode mTrackTintMode = null; + private boolean mHasTrackTint = false; + private boolean mHasTrackTintMode = false; + private int mThumbTextPadding; private int mSwitchMinWidth; private int mSwitchPadding; @@ -473,6 +485,86 @@ public class Switch extends CompoundButton { } /** + * Applies a tint to the track drawable. Does not modify the current + * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. + * <p> + * Subsequent calls to {@link #setTrackDrawable(Drawable)} will + * automatically mutate the drawable and apply the specified tint and tint + * mode using {@link Drawable#setTintList(ColorStateList)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * + * @attr ref android.R.styleable#Switch_trackTint + * @see #getTrackTintList() + * @see Drawable#setTintList(ColorStateList) + */ + public void setTrackTintList(@Nullable ColorStateList tint) { + mTrackTintList = tint; + mHasTrackTint = true; + + applyTrackTint(); + } + + /** + * @return the tint applied to the track drawable + * @attr ref android.R.styleable#Switch_trackTint + * @see #setTrackTintList(ColorStateList) + */ + @Nullable + public ColorStateList getTrackTintList() { + return mTrackTintList; + } + + /** + * Specifies the blending mode used to apply the tint specified by + * {@link #setTrackTintList(ColorStateList)}} to the track drawable. + * The default mode is {@link PorterDuff.Mode#SRC_IN}. + * + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * @attr ref android.R.styleable#Switch_trackTintMode + * @see #getTrackTintMode() + * @see Drawable#setTintMode(PorterDuff.Mode) + */ + public void setTrackTintMode(@Nullable PorterDuff.Mode tintMode) { + mTrackTintMode = tintMode; + mHasTrackTintMode = true; + + applyTrackTint(); + } + + /** + * @return the blending mode used to apply the tint to the track + * drawable + * @attr ref android.R.styleable#Switch_trackTintMode + * @see #setTrackTintMode(PorterDuff.Mode) + */ + @Nullable + public PorterDuff.Mode getTrackTintMode() { + return mTrackTintMode; + } + + private void applyTrackTint() { + if (mTrackDrawable != null && (mHasTrackTint || mHasTrackTintMode)) { + mTrackDrawable = mTrackDrawable.mutate(); + + if (mHasTrackTint) { + mTrackDrawable.setTintList(mTrackTintList); + } + + if (mHasTrackTintMode) { + mTrackDrawable.setTintMode(mTrackTintMode); + } + + // The drawable (or one of its children) may not have been + // stateful before applying the tint, so let's try again. + if (mTrackDrawable.isStateful()) { + mTrackDrawable.setState(getDrawableState()); + } + } + } + + /** * Set the drawable used for the switch "thumb" - the piece that the user * can physically touch and drag along the track. * @@ -516,6 +608,86 @@ public class Switch extends CompoundButton { } /** + * Applies a tint to the thumb drawable. Does not modify the current + * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. + * <p> + * Subsequent calls to {@link #setThumbDrawable(Drawable)} will + * automatically mutate the drawable and apply the specified tint and tint + * mode using {@link Drawable#setTintList(ColorStateList)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * + * @attr ref android.R.styleable#Switch_thumbTint + * @see #getThumbTintList() + * @see Drawable#setTintList(ColorStateList) + */ + public void setThumbTintList(@Nullable ColorStateList tint) { + mThumbTintList = tint; + mHasThumbTint = true; + + applyThumbTint(); + } + + /** + * @return the tint applied to the thumb drawable + * @attr ref android.R.styleable#Switch_thumbTint + * @see #setThumbTintList(ColorStateList) + */ + @Nullable + public ColorStateList getThumbTintList() { + return mThumbTintList; + } + + /** + * Specifies the blending mode used to apply the tint specified by + * {@link #setThumbTintList(ColorStateList)}} to the thumb drawable. + * The default mode is {@link PorterDuff.Mode#SRC_IN}. + * + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * @attr ref android.R.styleable#Switch_thumbTintMode + * @see #getThumbTintMode() + * @see Drawable#setTintMode(PorterDuff.Mode) + */ + public void setThumbTintMode(@Nullable PorterDuff.Mode tintMode) { + mThumbTintMode = tintMode; + mHasThumbTintMode = true; + + applyThumbTint(); + } + + /** + * @return the blending mode used to apply the tint to the thumb + * drawable + * @attr ref android.R.styleable#Switch_thumbTintMode + * @see #setThumbTintMode(PorterDuff.Mode) + */ + @Nullable + public PorterDuff.Mode getThumbTintMode() { + return mThumbTintMode; + } + + private void applyThumbTint() { + if (mThumbDrawable != null && (mHasThumbTint || mHasThumbTintMode)) { + mThumbDrawable = mThumbDrawable.mutate(); + + if (mHasThumbTint) { + mThumbDrawable.setTintList(mThumbTintList); + } + + if (mHasThumbTintMode) { + mThumbDrawable.setTintMode(mThumbTintMode); + } + + // The drawable (or one of its children) may not have been + // stateful before applying the tint, so let's try again. + if (mThumbDrawable.isStateful()) { + mThumbDrawable.setState(getDrawableState()); + } + } + } + + /** * Specifies whether the track should be split by the thumb. When true, * the thumb's optical bounds will be clipped out of the track drawable, * then the thumb will be drawn into the resulting gap. @@ -665,9 +837,10 @@ public class Switch extends CompoundButton { } } + /** @hide */ @Override - public void onPopulateAccessibilityEvent(AccessibilityEvent event) { - super.onPopulateAccessibilityEvent(event); + public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { + super.onPopulateAccessibilityEventInternal(event); final CharSequence text = isChecked() ? mTextOn : mTextOff; if (text != null) { @@ -1180,15 +1353,17 @@ public class Switch extends CompoundButton { } } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(Switch.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(Switch.class.getName()); CharSequence switchText = isChecked() ? mTextOn : mTextOff; if (!TextUtils.isEmpty(switchText)) { diff --git a/core/java/android/widget/TabHost.java b/core/java/android/widget/TabHost.java index 89df51a..3c3389a 100644 --- a/core/java/android/widget/TabHost.java +++ b/core/java/android/widget/TabHost.java @@ -173,8 +173,9 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); } } + /** @hide */ @Override - public void sendAccessibilityEvent(int eventType) { + public void sendAccessibilityEventInternal(int eventType) { /* avoid super class behavior - TabWidget sends the right events */ } @@ -383,15 +384,17 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); } } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(TabHost.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(TabHost.class.getName()); } diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java index 47a5449..bddee91 100644 --- a/core/java/android/widget/TabWidget.java +++ b/core/java/android/widget/TabWidget.java @@ -401,8 +401,9 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { } } + /** @hide */ @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { onPopulateAccessibilityEvent(event); // Dispatch only to the selected tab. if (mSelectedTab != -1) { @@ -414,28 +415,31 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { return false; } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(TabWidget.class.getName()); event.setItemCount(getTabCount()); event.setCurrentItemIndex(mSelectedTab); } + /** @hide */ @Override - public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { + public void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) { // this class fires events only when tabs are focused or selected if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED && isFocused()) { event.recycle(); return; } - super.sendAccessibilityEventUnchecked(event); + super.sendAccessibilityEventUncheckedInternal(event); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(TabWidget.class.getName()); } diff --git a/core/java/android/widget/TableLayout.java b/core/java/android/widget/TableLayout.java index f4b2ce0..a6b78a4 100644 --- a/core/java/android/widget/TableLayout.java +++ b/core/java/android/widget/TableLayout.java @@ -666,15 +666,17 @@ public class TableLayout extends LinearLayout { return new LayoutParams(p); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(TableLayout.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(TableLayout.class.getName()); } diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java index fe3631a..c29296a 100644 --- a/core/java/android/widget/TableRow.java +++ b/core/java/android/widget/TableRow.java @@ -379,15 +379,17 @@ public class TableRow extends LinearLayout { return new LayoutParams(p); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(TableRow.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(TableRow.class.getName()); } diff --git a/core/java/android/widget/TextSwitcher.java b/core/java/android/widget/TextSwitcher.java index 1aefd2b..7c883ba 100644 --- a/core/java/android/widget/TextSwitcher.java +++ b/core/java/android/widget/TextSwitcher.java @@ -91,15 +91,17 @@ public class TextSwitcher extends ViewSwitcher { ((TextView)getCurrentView()).setText(text); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(TextSwitcher.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(TextSwitcher.class.getName()); } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index dd8280b..94fc9e9 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.UndoManager; import android.content.res.ColorStateList; import android.content.res.CompatibilityInfo; +import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; @@ -32,6 +33,7 @@ import android.graphics.Canvas; import android.graphics.Insets; import android.graphics.Paint; import android.graphics.Path; +import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; @@ -214,6 +216,8 @@ import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; * @attr ref android.R.styleable#TextView_drawableStart * @attr ref android.R.styleable#TextView_drawableEnd * @attr ref android.R.styleable#TextView_drawablePadding + * @attr ref android.R.styleable#TextView_drawableTint + * @attr ref android.R.styleable#TextView_drawableTintMode * @attr ref android.R.styleable#TextView_lineSpacingExtra * @attr ref android.R.styleable#TextView_lineSpacingMultiplier * @attr ref android.R.styleable#TextView_marqueeRepeatLimit @@ -298,7 +302,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private float mShadowRadius, mShadowDx, mShadowDy; private int mShadowColor; - private boolean mPreDrawRegistered; private boolean mPreDrawListenerDetached; @@ -312,16 +315,27 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private TextUtils.TruncateAt mEllipsize; static class Drawables { - final static int DRAWABLE_NONE = -1; - final static int DRAWABLE_RIGHT = 0; - final static int DRAWABLE_LEFT = 1; + static final int LEFT = 0; + static final int TOP = 1; + static final int RIGHT = 2; + static final int BOTTOM = 3; + + static final int DRAWABLE_NONE = -1; + static final int DRAWABLE_RIGHT = 0; + static final int DRAWABLE_LEFT = 1; final Rect mCompoundRect = new Rect(); - Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight, - mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp; + final Drawable[] mShowing = new Drawable[4]; + ColorStateList mTintList; + PorterDuff.Mode mTintMode; + boolean mHasTint; + boolean mHasTintMode; + + Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp; Drawable mDrawableLeftInitial, mDrawableRightInitial; + boolean mIsRtlCompatibilityMode; boolean mOverride; @@ -344,19 +358,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void resolveWithLayoutDirection(int layoutDirection) { // First reset "left" and "right" drawables to their initial values - mDrawableLeft = mDrawableLeftInitial; - mDrawableRight = mDrawableRightInitial; + mShowing[Drawables.LEFT] = mDrawableLeftInitial; + mShowing[Drawables.RIGHT] = mDrawableRightInitial; if (mIsRtlCompatibilityMode) { // Use "start" drawable as "left" drawable if the "left" drawable was not defined - if (mDrawableStart != null && mDrawableLeft == null) { - mDrawableLeft = mDrawableStart; + if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) { + mShowing[Drawables.LEFT] = mDrawableStart; mDrawableSizeLeft = mDrawableSizeStart; mDrawableHeightLeft = mDrawableHeightStart; } // Use "end" drawable as "right" drawable if the "right" drawable was not defined - if (mDrawableEnd != null && mDrawableRight == null) { - mDrawableRight = mDrawableEnd; + if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) { + mShowing[Drawables.RIGHT] = mDrawableEnd; mDrawableSizeRight = mDrawableSizeEnd; mDrawableHeightRight = mDrawableHeightEnd; } @@ -366,11 +380,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener switch(layoutDirection) { case LAYOUT_DIRECTION_RTL: if (mOverride) { - mDrawableRight = mDrawableStart; + mShowing[Drawables.RIGHT] = mDrawableStart; mDrawableSizeRight = mDrawableSizeStart; mDrawableHeightRight = mDrawableHeightStart; - mDrawableLeft = mDrawableEnd; + mShowing[Drawables.LEFT] = mDrawableEnd; mDrawableSizeLeft = mDrawableSizeEnd; mDrawableHeightLeft = mDrawableHeightEnd; } @@ -379,11 +393,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case LAYOUT_DIRECTION_LTR: default: if (mOverride) { - mDrawableLeft = mDrawableStart; + mShowing[Drawables.LEFT] = mDrawableStart; mDrawableSizeLeft = mDrawableSizeStart; mDrawableHeightLeft = mDrawableHeightStart; - mDrawableRight = mDrawableEnd; + mShowing[Drawables.RIGHT] = mDrawableEnd; mDrawableSizeRight = mDrawableSizeEnd; mDrawableHeightRight = mDrawableHeightEnd; } @@ -395,17 +409,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private void updateDrawablesLayoutDirection(int layoutDirection) { - if (mDrawableLeft != null) { - mDrawableLeft.setLayoutDirection(layoutDirection); - } - if (mDrawableRight != null) { - mDrawableRight.setLayoutDirection(layoutDirection); - } - if (mDrawableTop != null) { - mDrawableTop.setLayoutDirection(layoutDirection); - } - if (mDrawableBottom != null) { - mDrawableBottom.setLayoutDirection(layoutDirection); + for (Drawable dr : mShowing) { + if (dr != null) { + dr.setLayoutDirection(layoutDirection); + } } } @@ -415,10 +422,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } mDrawableError = dr; - final Rect compoundRect = mCompoundRect; - int[] state = tv.getDrawableState(); - if (mDrawableError != null) { + final Rect compoundRect = mCompoundRect; + final int[] state = tv.getDrawableState(); + mDrawableError.setState(state); mDrawableError.copyBounds(compoundRect); mDrawableError.setCallback(tv); @@ -433,12 +440,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // first restore the initial state if needed switch (mDrawableSaved) { case DRAWABLE_LEFT: - mDrawableLeft = mDrawableTemp; + mShowing[Drawables.LEFT] = mDrawableTemp; mDrawableSizeLeft = mDrawableSizeTemp; mDrawableHeightLeft = mDrawableHeightTemp; break; case DRAWABLE_RIGHT: - mDrawableRight = mDrawableTemp; + mShowing[Drawables.RIGHT] = mDrawableTemp; mDrawableSizeRight = mDrawableSizeTemp; mDrawableHeightRight = mDrawableHeightTemp; break; @@ -451,11 +458,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case LAYOUT_DIRECTION_RTL: mDrawableSaved = DRAWABLE_LEFT; - mDrawableTemp = mDrawableLeft; + mDrawableTemp = mShowing[Drawables.LEFT]; mDrawableSizeTemp = mDrawableSizeLeft; mDrawableHeightTemp = mDrawableHeightLeft; - mDrawableLeft = mDrawableError; + mShowing[Drawables.LEFT] = mDrawableError; mDrawableSizeLeft = mDrawableSizeError; mDrawableHeightLeft = mDrawableHeightError; break; @@ -463,11 +470,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener default: mDrawableSaved = DRAWABLE_RIGHT; - mDrawableTemp = mDrawableRight; + mDrawableTemp = mShowing[Drawables.RIGHT]; mDrawableSizeTemp = mDrawableSizeRight; mDrawableHeightTemp = mDrawableHeightRight; - mDrawableRight = mDrawableError; + mShowing[Drawables.RIGHT] = mDrawableError; mDrawableSizeRight = mDrawableSizeError; mDrawableHeightRight = mDrawableHeightError; break; @@ -520,6 +527,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private final TextPaint mTextPaint; private boolean mUserSetTextScaleX; private Layout mLayout; + private boolean mLocaleChanged = false; private int mGravity = Gravity.TOP | Gravity.START; private boolean mHorizontallyScrolling; @@ -771,6 +779,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean selectallonfocus = false; Drawable drawableLeft = null, drawableTop = null, drawableRight = null, drawableBottom = null, drawableStart = null, drawableEnd = null; + ColorStateList drawableTint = null; + PorterDuff.Mode drawableTintMode = null; int drawablePadding = 0; int ellipsize = -1; boolean singleLine = false; @@ -856,6 +866,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener drawableEnd = a.getDrawable(attr); break; + case com.android.internal.R.styleable.TextView_drawableTint: + drawableTint = a.getColorStateList(attr); + break; + + case com.android.internal.R.styleable.TextView_drawableTintMode: + drawableTintMode = Drawable.parseTintMode(a.getInt(attr, -1), drawableTintMode); + break; + case com.android.internal.R.styleable.TextView_drawablePadding: drawablePadding = a.getDimensionPixelSize(attr, drawablePadding); break; @@ -1240,6 +1258,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener bufferType = BufferType.SPANNABLE; } + // Set up the tint (if needed) before setting the drawables so that it + // gets applied correctly. + if (drawableTint != null || drawableTintMode != null) { + if (mDrawables == null) { + mDrawables = new Drawables(context); + } + if (drawableTint != null) { + mDrawables.mTintList = drawableTint; + mDrawables.mHasTint = true; + } + if (drawableTintMode != null) { + mDrawables.mTintMode = drawableTintMode; + mDrawables.mHasTintMode = true; + } + } + // This call will save the initial left/right drawables setCompoundDrawablesWithIntrinsicBounds( drawableLeft, drawableTop, drawableRight, drawableBottom); @@ -1425,6 +1459,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } resetResolvedDrawables(); resolveDrawables(); + applyCompoundDrawableTint(); } } @@ -1783,7 +1818,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ public int getCompoundPaddingTop() { final Drawables dr = mDrawables; - if (dr == null || dr.mDrawableTop == null) { + if (dr == null || dr.mShowing[Drawables.TOP] == null) { return mPaddingTop; } else { return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop; @@ -1796,7 +1831,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ public int getCompoundPaddingBottom() { final Drawables dr = mDrawables; - if (dr == null || dr.mDrawableBottom == null) { + if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) { return mPaddingBottom; } else { return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom; @@ -1809,7 +1844,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ public int getCompoundPaddingLeft() { final Drawables dr = mDrawables; - if (dr == null || dr.mDrawableLeft == null) { + if (dr == null || dr.mShowing[Drawables.LEFT] == null) { return mPaddingLeft; } else { return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft; @@ -1822,7 +1857,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ public int getCompoundPaddingRight() { final Drawables dr = mDrawables; - if (dr == null || dr.mDrawableRight == null) { + if (dr == null || dr.mShowing[Drawables.RIGHT] == null) { return mPaddingRight; } else { return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight; @@ -2020,14 +2055,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } else { // We need to retain the last set padding, so just clear // out all of the fields in the existing structure. - if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null); - dr.mDrawableLeft = null; - if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null); - dr.mDrawableTop = null; - if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null); - dr.mDrawableRight = null; - if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null); - dr.mDrawableBottom = null; + for (int i = dr.mShowing.length - 1; i >= 0; i--) { + if (dr.mShowing[i] != null) { + dr.mShowing[i].setCallback(null); + } + dr.mShowing[i] = null; + } dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; @@ -2041,25 +2074,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mDrawables.mOverride = false; - if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) { - dr.mDrawableLeft.setCallback(null); + if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) { + dr.mShowing[Drawables.LEFT].setCallback(null); } - dr.mDrawableLeft = left; + dr.mShowing[Drawables.LEFT] = left; - if (dr.mDrawableTop != top && dr.mDrawableTop != null) { - dr.mDrawableTop.setCallback(null); + if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { + dr.mShowing[Drawables.TOP].setCallback(null); } - dr.mDrawableTop = top; + dr.mShowing[Drawables.TOP] = top; - if (dr.mDrawableRight != right && dr.mDrawableRight != null) { - dr.mDrawableRight.setCallback(null); + if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) { + dr.mShowing[Drawables.RIGHT].setCallback(null); } - dr.mDrawableRight = right; + dr.mShowing[Drawables.RIGHT] = right; - if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) { - dr.mDrawableBottom.setCallback(null); + if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { + dr.mShowing[Drawables.BOTTOM].setCallback(null); } - dr.mDrawableBottom = bottom; + dr.mShowing[Drawables.BOTTOM] = bottom; final Rect compoundRect = dr.mCompoundRect; int[] state; @@ -2115,6 +2148,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener resetResolvedDrawables(); resolveDrawables(); + applyCompoundDrawableTint(); invalidate(); requestLayout(); } @@ -2198,10 +2232,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // We're switching to relative, discard absolute. if (dr != null) { - if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null); - dr.mDrawableLeft = dr.mDrawableLeftInitial = null; - if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null); - dr.mDrawableRight = dr.mDrawableRightInitial = null; + if (dr.mShowing[Drawables.LEFT] != null) { + dr.mShowing[Drawables.LEFT].setCallback(null); + } + dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null; + if (dr.mShowing[Drawables.RIGHT] != null) { + dr.mShowing[Drawables.RIGHT].setCallback(null); + } + dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null; dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; } @@ -2219,12 +2257,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // out all of the fields in the existing structure. if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); dr.mDrawableStart = null; - if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null); - dr.mDrawableTop = null; - if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null); + if (dr.mShowing[Drawables.TOP] != null) { + dr.mShowing[Drawables.TOP].setCallback(null); + } + dr.mShowing[Drawables.TOP] = null; + if (dr.mDrawableEnd != null) { + dr.mDrawableEnd.setCallback(null); + } dr.mDrawableEnd = null; - if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null); - dr.mDrawableBottom = null; + if (dr.mShowing[Drawables.BOTTOM] != null) { + dr.mShowing[Drawables.BOTTOM].setCallback(null); + } + dr.mShowing[Drawables.BOTTOM] = null; dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; @@ -2243,20 +2287,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } dr.mDrawableStart = start; - if (dr.mDrawableTop != top && dr.mDrawableTop != null) { - dr.mDrawableTop.setCallback(null); + if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { + dr.mShowing[Drawables.TOP].setCallback(null); } - dr.mDrawableTop = top; + dr.mShowing[Drawables.TOP] = top; if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) { dr.mDrawableEnd.setCallback(null); } dr.mDrawableEnd = end; - if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) { - dr.mDrawableBottom.setCallback(null); + if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { + dr.mShowing[Drawables.BOTTOM].setCallback(null); } - dr.mDrawableBottom = bottom; + dr.mShowing[Drawables.BOTTOM] = bottom; final Rect compoundRect = dr.mCompoundRect; int[] state; @@ -2382,9 +2426,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public Drawable[] getCompoundDrawables() { final Drawables dr = mDrawables; if (dr != null) { - return new Drawable[] { - dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom - }; + return dr.mShowing.clone(); } else { return new Drawable[] { null, null, null, null }; } @@ -2403,7 +2445,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final Drawables dr = mDrawables; if (dr != null) { return new Drawable[] { - dr.mDrawableStart, dr.mDrawableTop, dr.mDrawableEnd, dr.mDrawableBottom + dr.mDrawableStart, dr.mShowing[Drawables.TOP], + dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM] }; } else { return new Drawable[] { null, null, null, null }; @@ -2444,6 +2487,118 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return dr != null ? dr.mDrawablePadding : 0; } + /** + * Applies a tint to the compound drawables. Does not modify the + * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. + * <p> + * Subsequent calls to + * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)} + * and related methods will automatically mutate the drawables and apply + * the specified tint and tint mode using + * {@link Drawable#setTintList(ColorStateList)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * + * @attr ref android.R.styleable#TextView_drawableTint + * @see #getCompoundDrawableTintList() + * @see Drawable#setTintList(ColorStateList) + */ + public void setCompoundDrawableTintList(@Nullable ColorStateList tint) { + if (mDrawables == null) { + mDrawables = new Drawables(getContext()); + } + mDrawables.mTintList = tint; + mDrawables.mHasTint = true; + + applyCompoundDrawableTint(); + } + + /** + * @return the tint applied to the compound drawables + * @attr ref android.R.styleable#TextView_drawableTint + * @see #setCompoundDrawableTintList(ColorStateList) + */ + public ColorStateList getCompoundDrawableTintList() { + return mDrawables != null ? mDrawables.mTintList : null; + } + + /** + * Specifies the blending mode used to apply the tint specified by + * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound + * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}. + * + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * @attr ref android.R.styleable#TextView_drawableTintMode + * @see #setCompoundDrawableTintList(ColorStateList) + * @see Drawable#setTintMode(PorterDuff.Mode) + */ + public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) { + if (mDrawables == null) { + mDrawables = new Drawables(getContext()); + } + mDrawables.mTintMode = tintMode; + mDrawables.mHasTintMode = true; + + applyCompoundDrawableTint(); + } + + /** + * Returns the blending mode used to apply the tint to the compound + * drawables, if specified. + * + * @return the blending mode used to apply the tint to the compound + * drawables + * @attr ref android.R.styleable#TextView_drawableTintMode + * @see #setCompoundDrawableTintMode(PorterDuff.Mode) + */ + public PorterDuff.Mode getCompoundDrawableTintMode() { + return mDrawables != null ? mDrawables.mTintMode : null; + } + + private void applyCompoundDrawableTint() { + if (mDrawables == null) { + return; + } + + if (mDrawables.mHasTint || mDrawables.mHasTintMode) { + final ColorStateList tintList = mDrawables.mTintList; + final PorterDuff.Mode tintMode = mDrawables.mTintMode; + final boolean hasTint = mDrawables.mHasTint; + final boolean hasTintMode = mDrawables.mHasTintMode; + final int[] state = getDrawableState(); + + for (Drawable dr : mDrawables.mShowing) { + if (dr == null) { + continue; + } + + if (dr == mDrawables.mDrawableError) { + // From a developer's perspective, the error drawable isn't + // a compound drawable. Don't apply the generic compound + // drawable tint to it. + continue; + } + + dr.mutate(); + + if (hasTint) { + dr.setTintList(tintList); + } + + if (hasTintMode) { + dr.setTintMode(tintMode); + } + + // The drawable (or one of its children) may not have been + // stateful before applying the tint, so let's try again. + if (dr.isStateful()) { + dr.setState(state); + } + } + } + } + @Override public void setPadding(int left, int top, int right, int bottom) { if (left != mPaddingLeft || @@ -2592,9 +2747,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @see Paint#setTextLocale */ public void setTextLocale(Locale locale) { + mLocaleChanged = true; mTextPaint.setTextLocale(locale); } + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (!mLocaleChanged) { + mTextPaint.setTextLocale(Locale.getDefault()); + } + } + /** * @return the size (in pixels) of the default text size in this TextView. */ @@ -3662,26 +3826,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener updateTextColors(); } - final Drawables dr = mDrawables; - if (dr != null) { - int[] state = getDrawableState(); - if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) { - dr.mDrawableTop.setState(state); - } - if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) { - dr.mDrawableBottom.setState(state); - } - if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) { - dr.mDrawableLeft.setState(state); - } - if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) { - dr.mDrawableRight.setState(state); - } - if (dr.mDrawableStart != null && dr.mDrawableStart.isStateful()) { - dr.mDrawableStart.setState(state); - } - if (dr.mDrawableEnd != null && dr.mDrawableEnd.isStateful()) { - dr.mDrawableEnd.setState(state); + if (mDrawables != null) { + final int[] state = getDrawableState(); + for (Drawable dr : mDrawables.mShowing) { + if (dr != null && dr.isStateful()) { + dr.setState(state); + } } } } @@ -3690,25 +3840,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void drawableHotspotChanged(float x, float y) { super.drawableHotspotChanged(x, y); - final Drawables dr = mDrawables; - if (dr != null) { - if (dr.mDrawableTop != null) { - dr.mDrawableTop.setHotspot(x, y); - } - if (dr.mDrawableBottom != null) { - dr.mDrawableBottom.setHotspot(x, y); - } - if (dr.mDrawableLeft != null) { - dr.mDrawableLeft.setHotspot(x, y); - } - if (dr.mDrawableRight != null) { - dr.mDrawableRight.setHotspot(x, y); - } - if (dr.mDrawableStart != null) { - dr.mDrawableStart.setHotspot(x, y); - } - if (dr.mDrawableEnd != null) { - dr.mDrawableEnd.setHotspot(x, y); + if (mDrawables != null) { + final int[] state = getDrawableState(); + for (Drawable dr : mDrawables.mShowing) { + if (dr != null && dr.isStateful()) { + dr.setHotspot(x, y); + } } } } @@ -5039,9 +5176,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener protected boolean verifyDrawable(Drawable who) { final boolean verified = super.verifyDrawable(who); if (!verified && mDrawables != null) { - return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop || - who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom || - who == mDrawables.mDrawableStart || who == mDrawables.mDrawableEnd; + for (Drawable dr : mDrawables.mShowing) { + if (who == dr) { + return true; + } + } } return verified; } @@ -5050,23 +5189,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void jumpDrawablesToCurrentState() { super.jumpDrawablesToCurrentState(); if (mDrawables != null) { - if (mDrawables.mDrawableLeft != null) { - mDrawables.mDrawableLeft.jumpToCurrentState(); - } - if (mDrawables.mDrawableTop != null) { - mDrawables.mDrawableTop.jumpToCurrentState(); - } - if (mDrawables.mDrawableRight != null) { - mDrawables.mDrawableRight.jumpToCurrentState(); - } - if (mDrawables.mDrawableBottom != null) { - mDrawables.mDrawableBottom.jumpToCurrentState(); - } - if (mDrawables.mDrawableStart != null) { - mDrawables.mDrawableStart.jumpToCurrentState(); - } - if (mDrawables.mDrawableEnd != null) { - mDrawables.mDrawableEnd.jumpToCurrentState(); + for (Drawable dr : mDrawables.mShowing) { + if (dr != null) { + dr.jumpToCurrentState(); + } } } } @@ -5085,7 +5211,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // accordingly. final TextView.Drawables drawables = mDrawables; if (drawables != null) { - if (drawable == drawables.mDrawableLeft) { + if (drawable == drawables.mShowing[Drawables.LEFT]) { final int compoundPaddingTop = getCompoundPaddingTop(); final int compoundPaddingBottom = getCompoundPaddingBottom(); final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; @@ -5093,7 +5219,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener scrollX += mPaddingLeft; scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2; handled = true; - } else if (drawable == drawables.mDrawableRight) { + } else if (drawable == drawables.mShowing[Drawables.RIGHT]) { final int compoundPaddingTop = getCompoundPaddingTop(); final int compoundPaddingBottom = getCompoundPaddingBottom(); final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; @@ -5101,7 +5227,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight); scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2; handled = true; - } else if (drawable == drawables.mDrawableTop) { + } else if (drawable == drawables.mShowing[Drawables.TOP]) { final int compoundPaddingLeft = getCompoundPaddingLeft(); final int compoundPaddingRight = getCompoundPaddingRight(); final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; @@ -5109,7 +5235,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2; scrollY += mPaddingTop; handled = true; - } else if (drawable == drawables.mDrawableBottom) { + } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) { final int compoundPaddingLeft = getCompoundPaddingLeft(); final int compoundPaddingRight = getCompoundPaddingRight(); final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; @@ -5313,44 +5439,44 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // IMPORTANT: The coordinates computed are also used in invalidateDrawable() // Make sure to update invalidateDrawable() when changing this code. - if (dr.mDrawableLeft != null) { + if (dr.mShowing[Drawables.LEFT] != null) { canvas.save(); canvas.translate(scrollX + mPaddingLeft + leftOffset, scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2); - dr.mDrawableLeft.draw(canvas); + dr.mShowing[Drawables.LEFT].draw(canvas); canvas.restore(); } // IMPORTANT: The coordinates computed are also used in invalidateDrawable() // Make sure to update invalidateDrawable() when changing this code. - if (dr.mDrawableRight != null) { + if (dr.mShowing[Drawables.RIGHT] != null) { canvas.save(); canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight - rightOffset, scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); - dr.mDrawableRight.draw(canvas); + dr.mShowing[Drawables.RIGHT].draw(canvas); canvas.restore(); } // IMPORTANT: The coordinates computed are also used in invalidateDrawable() // Make sure to update invalidateDrawable() when changing this code. - if (dr.mDrawableTop != null) { + if (dr.mShowing[Drawables.TOP] != null) { canvas.save(); canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); - dr.mDrawableTop.draw(canvas); + dr.mShowing[Drawables.TOP].draw(canvas); canvas.restore(); } // IMPORTANT: The coordinates computed are also used in invalidateDrawable() // Make sure to update invalidateDrawable() when changing this code. - if (dr.mDrawableBottom != null) { + if (dr.mShowing[Drawables.BOTTOM] != null) { canvas.save(); canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthBottom) / 2, scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); - dr.mDrawableBottom.draw(canvas); + dr.mShowing[Drawables.BOTTOM].draw(canvas); canvas.restore(); } } @@ -8381,9 +8507,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + /** @hide */ @Override - public void onPopulateAccessibilityEvent(AccessibilityEvent event) { - super.onPopulateAccessibilityEvent(event); + public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { + super.onPopulateAccessibilityEventInternal(event); final boolean isPassword = hasPasswordTransformationMethod(); if (!isPassword || shouldSpeakPasswordsForAccessibility()) { @@ -8404,9 +8531,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener UserHandle.USER_CURRENT_OR_SELF) == 1); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(TextView.class.getName()); final boolean isPassword = hasPasswordTransformationMethod(); @@ -8419,9 +8547,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(TextView.class.getName()); final boolean isPassword = hasPasswordTransformationMethod(); @@ -8579,15 +8708,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + /** @hide */ @Override - public void sendAccessibilityEvent(int eventType) { + public void sendAccessibilityEventInternal(int eventType) { // Do not send scroll events since first they are not interesting for // accessibility and second such events a generated too frequently. // For details see the implementation of bringTextIntoView(). if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) { return; } - super.sendAccessibilityEvent(eventType); + super.sendAccessibilityEventInternal(eventType); } /** diff --git a/core/java/android/widget/TextViewWithCircularIndicator.java b/core/java/android/widget/TextViewWithCircularIndicator.java index 43c0843..d3c786c 100644 --- a/core/java/android/widget/TextViewWithCircularIndicator.java +++ b/core/java/android/widget/TextViewWithCircularIndicator.java @@ -18,7 +18,6 @@ package android.widget; import android.content.Context; import android.content.res.Resources; -import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Typeface; @@ -27,14 +26,8 @@ import android.util.AttributeSet; import com.android.internal.R; class TextViewWithCircularIndicator extends TextView { - - private static final int SELECTED_CIRCLE_ALPHA = 60; - private final Paint mCirclePaint = new Paint(); - private final String mItemIsSelectedText; - private int mCircleColor; - private boolean mDrawIndicator; public TextViewWithCircularIndicator(Context context) { this(context, null); @@ -50,22 +43,11 @@ class TextViewWithCircularIndicator extends TextView { public TextViewWithCircularIndicator(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs); - - - // Use Theme attributes if possible - final TypedArray a = mContext.obtainStyledAttributes(attrs, - R.styleable.DatePicker, defStyleAttr, defStyleRes); - final int resId = a.getResourceId(R.styleable.DatePicker_yearListItemTextAppearance, -1); - if (resId != -1) { - setTextAppearance(context, resId); - } + super(context, attrs, defStyleAttr, defStyleRes); final Resources res = context.getResources(); mItemIsSelectedText = res.getString(R.string.item_is_selected); - a.recycle(); - init(); } @@ -77,33 +59,26 @@ class TextViewWithCircularIndicator extends TextView { } public void setCircleColor(int color) { - if (color != mCircleColor) { - mCircleColor = color; - mCirclePaint.setColor(mCircleColor); - mCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA); - requestLayout(); - } - } - - public void setDrawIndicator(boolean drawIndicator) { - mDrawIndicator = drawIndicator; + mCirclePaint.setColor(color); + invalidate(); } @Override public void onDraw(Canvas canvas) { - super.onDraw(canvas); - if (mDrawIndicator) { + if (isActivated()) { final int width = getWidth(); final int height = getHeight(); - int radius = Math.min(width, height) / 2; + final int radius = Math.min(width, height) / 2; canvas.drawCircle(width / 2, height / 2, radius, mCirclePaint); } + + super.onDraw(canvas); } @Override public CharSequence getContentDescription() { - CharSequence itemText = getText(); - if (mDrawIndicator) { + final CharSequence itemText = getText(); + if (isActivated()) { return String.format(mItemIsSelectedText, itemText); } else { return itemText; diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java index 26e02f8..bbf5f53 100644 --- a/core/java/android/widget/TimePicker.java +++ b/core/java/android/widget/TimePicker.java @@ -195,26 +195,30 @@ public class TimePicker extends FrameLayout { mDelegate.onRestoreInstanceState(ss); } + /** @hide */ @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { return mDelegate.dispatchPopulateAccessibilityEvent(event); } + /** @hide */ @Override - public void onPopulateAccessibilityEvent(AccessibilityEvent event) { - super.onPopulateAccessibilityEvent(event); + public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { + super.onPopulateAccessibilityEventInternal(event); mDelegate.onPopulateAccessibilityEvent(event); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); mDelegate.onInitializeAccessibilityEvent(event); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); mDelegate.onInitializeAccessibilityNodeInfo(info); } diff --git a/core/java/android/widget/ToggleButton.java b/core/java/android/widget/ToggleButton.java index 28519d1..1b23778 100644 --- a/core/java/android/widget/ToggleButton.java +++ b/core/java/android/widget/ToggleButton.java @@ -153,15 +153,17 @@ public class ToggleButton extends CompoundButton { } } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(ToggleButton.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(ToggleButton.class.getName()); } } diff --git a/core/java/android/widget/TwoLineListItem.java b/core/java/android/widget/TwoLineListItem.java index 5606c60..9035dbe 100644 --- a/core/java/android/widget/TwoLineListItem.java +++ b/core/java/android/widget/TwoLineListItem.java @@ -93,15 +93,17 @@ public class TwoLineListItem extends RelativeLayout { return mText2; } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(TwoLineListItem.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(TwoLineListItem.class.getName()); } } diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java index 572cca2..a240dc2 100644 --- a/core/java/android/widget/VideoView.java +++ b/core/java/android/widget/VideoView.java @@ -201,15 +201,17 @@ public class VideoView extends SurfaceView setMeasuredDimension(width, height); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(VideoView.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(VideoView.class.getName()); } diff --git a/core/java/android/widget/ViewAnimator.java b/core/java/android/widget/ViewAnimator.java index eee914e..d31754b 100644 --- a/core/java/android/widget/ViewAnimator.java +++ b/core/java/android/widget/ViewAnimator.java @@ -357,15 +357,17 @@ public class ViewAnimator extends FrameLayout { return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline(); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(ViewAnimator.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(ViewAnimator.class.getName()); } } diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java index cf1f554..587f469 100644 --- a/core/java/android/widget/ViewFlipper.java +++ b/core/java/android/widget/ViewFlipper.java @@ -149,15 +149,17 @@ public class ViewFlipper extends ViewAnimator { updateRunning(); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(ViewFlipper.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(ViewFlipper.class.getName()); } diff --git a/core/java/android/widget/ViewSwitcher.java b/core/java/android/widget/ViewSwitcher.java index 0376918..c97770f 100644 --- a/core/java/android/widget/ViewSwitcher.java +++ b/core/java/android/widget/ViewSwitcher.java @@ -68,15 +68,17 @@ public class ViewSwitcher extends ViewAnimator { super.addView(child, index, params); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(ViewSwitcher.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(ViewSwitcher.class.getName()); } diff --git a/core/java/android/widget/YearPickerView.java b/core/java/android/widget/YearPickerView.java index 24ed7ce..6f0465f 100644 --- a/core/java/android/widget/YearPickerView.java +++ b/core/java/android/widget/YearPickerView.java @@ -17,8 +17,10 @@ package android.widget; import android.content.Context; +import android.content.res.ColorStateList; import android.content.res.Resources; import android.util.AttributeSet; +import android.util.StateSet; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; @@ -42,7 +44,7 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener private DatePickerController mController; private int mSelectedPosition = -1; - private int mYearSelectedCircleColor; + private int mYearActivatedColor; public YearPickerView(Context context) { this(context, null); @@ -97,15 +99,14 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener onDateChanged(); } - public void setYearSelectedCircleColor(int color) { - if (color != mYearSelectedCircleColor) { - mYearSelectedCircleColor = color; - } - requestLayout(); + public void setYearBackgroundColor(ColorStateList yearBackgroundColor) { + mYearActivatedColor = yearBackgroundColor.getColorForState( + StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0); + invalidate(); } - public int getYearSelectedCircleColor() { - return mYearSelectedCircleColor; + public void setYearTextAppearance(int resId) { + mAdapter.setItemTextAppearance(resId); } private void updateAdapterData() { @@ -127,12 +128,8 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener mController.onYearSelected(mAdapter.getItem(position)); } - void setItemTextAppearance(int resId) { - mAdapter.setItemTextAppearance(resId); - } - private class YearAdapter extends ArrayAdapter<Integer> { - int mItemTextAppearanceResId; + private int mItemTextAppearanceResId; public YearAdapter(Context context, int resource) { super(context, resource); @@ -140,16 +137,15 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener @Override public View getView(int position, View convertView, ViewGroup parent) { - TextViewWithCircularIndicator v = (TextViewWithCircularIndicator) + final TextViewWithCircularIndicator v = (TextViewWithCircularIndicator) super.getView(position, convertView, parent); - v.setTextAppearance(getContext(), mItemTextAppearanceResId); - v.requestLayout(); - int year = getItem(position); - boolean selected = mController.getSelectedDay().get(Calendar.YEAR) == year; - v.setDrawIndicator(selected); - if (selected) { - v.setCircleColor(mYearSelectedCircleColor); - } + v.setTextAppearance(v.getContext(), mItemTextAppearanceResId); + v.setCircleColor(mYearActivatedColor); + + final int year = getItem(position); + final boolean selected = mController.getSelectedDay().get(Calendar.YEAR) == year; + v.setActivated(selected); + return v; } @@ -189,9 +185,10 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener mController.getSelectedDay().get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR)); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { event.setFromIndex(0); diff --git a/core/java/android/widget/ZoomButton.java b/core/java/android/widget/ZoomButton.java index 715e868..e0be0ab 100644 --- a/core/java/android/widget/ZoomButton.java +++ b/core/java/android/widget/ZoomButton.java @@ -103,15 +103,17 @@ public class ZoomButton extends ImageButton implements OnLongClickListener { return super.dispatchUnhandledMove(focused, direction); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(ZoomButton.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(ZoomButton.class.getName()); } } diff --git a/core/java/android/widget/ZoomControls.java b/core/java/android/widget/ZoomControls.java index 8897875..a0aacea 100644 --- a/core/java/android/widget/ZoomControls.java +++ b/core/java/android/widget/ZoomControls.java @@ -109,15 +109,17 @@ public class ZoomControls extends LinearLayout { return mZoomIn.hasFocus() || mZoomOut.hasFocus(); } + /** @hide */ @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); event.setClassName(ZoomControls.class.getName()); } + /** @hide */ @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); info.setClassName(ZoomControls.class.getName()); } } |