summaryrefslogtreecommitdiffstats
path: root/graphics
diff options
context:
space:
mode:
Diffstat (limited to 'graphics')
-rw-r--r--graphics/java/android/graphics/CanvasProperty.java20
-rw-r--r--graphics/java/android/graphics/FontFamily.java56
-rw-r--r--graphics/java/android/graphics/FontListParser.java117
-rw-r--r--graphics/java/android/graphics/ImageFormat.java1
-rw-r--r--graphics/java/android/graphics/Matrix.java11
-rw-r--r--graphics/java/android/graphics/Paint.java24
-rw-r--r--graphics/java/android/graphics/Rect.java16
-rw-r--r--graphics/java/android/graphics/Typeface.java134
-rw-r--r--graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java524
-rw-r--r--graphics/java/android/graphics/drawable/AnimationDrawable.java2
-rw-r--r--graphics/java/android/graphics/drawable/Drawable.java2
-rw-r--r--graphics/java/android/graphics/drawable/DrawableContainer.java10
-rw-r--r--graphics/java/android/graphics/drawable/StateListDrawable.java31
-rw-r--r--graphics/java/android/graphics/drawable/VectorDrawable.java64
-rw-r--r--graphics/java/android/graphics/pdf/PdfDocument.java4
-rw-r--r--graphics/java/android/graphics/pdf/PdfRenderer.java391
16 files changed, 1321 insertions, 86 deletions
diff --git a/graphics/java/android/graphics/CanvasProperty.java b/graphics/java/android/graphics/CanvasProperty.java
index 99ea9b1..be86060 100644
--- a/graphics/java/android/graphics/CanvasProperty.java
+++ b/graphics/java/android/graphics/CanvasProperty.java
@@ -16,12 +16,15 @@
package android.graphics;
+import com.android.internal.util.VirtualRefBasePtr;
+
/**
* TODO: Make public?
* @hide
*/
public final class CanvasProperty<T> {
- private long mNativeContainer;
+
+ private VirtualRefBasePtr mProperty;
public static CanvasProperty<Float> createFloat(float initialValue) {
return new CanvasProperty<Float>(nCreateFloat(initialValue));
@@ -32,25 +35,14 @@ public final class CanvasProperty<T> {
}
private CanvasProperty(long nativeContainer) {
- mNativeContainer = nativeContainer;
+ mProperty = new VirtualRefBasePtr(nativeContainer);
}
/** @hide */
public long getNativeContainer() {
- return mNativeContainer;
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- nUnref(mNativeContainer);
- mNativeContainer = 0;
- } finally {
- super.finalize();
- }
+ return mProperty.get();
}
private static native long nCreateFloat(float initialValue);
private static native long nCreatePaint(long initialValuePaintPtr);
- private static native void nUnref(long ptr);
}
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
new file mode 100644
index 0000000..a759a79
--- /dev/null
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -0,0 +1,56 @@
+/*
+ * 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.graphics;
+
+import java.io.File;
+
+/**
+ * A family of typefaces with different styles.
+ *
+ * @hide
+ */
+public class FontFamily {
+ /**
+ * @hide
+ */
+ public long mNativePtr;
+
+ public FontFamily() {
+ mNativePtr = nCreateFamily();
+ mNativePtr = nCreateFamily();
+ if (mNativePtr == 0) {
+ throw new RuntimeException();
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ nUnrefFamily(mNativePtr);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ public boolean addFont(File path) {
+ return nAddFont(mNativePtr, path.getAbsolutePath());
+ }
+
+ static native long nCreateFamily();
+ static native void nUnrefFamily(long nativePtr);
+ static native boolean nAddFont(long nativeFamily, String path);
+}
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
new file mode 100644
index 0000000..f304f4e
--- /dev/null
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -0,0 +1,117 @@
+/*
+ * 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.graphics;
+
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Parser for font config files.
+ *
+ * @hide
+ */
+public class FontListParser {
+
+ public static class Family {
+ public Family(List<String> names, List<String> fontFiles) {
+ this.names = names;
+ this.fontFiles = fontFiles;
+ }
+
+ public List<String> names;
+ // todo: need attributes for font files
+ public List<String> fontFiles;
+ }
+
+ /* Parse fallback list (no names) */
+ public static List<Family> parse(InputStream in) throws XmlPullParserException, IOException {
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, null);
+ parser.nextTag();
+ return readFamilies(parser);
+ } finally {
+ in.close();
+ }
+ }
+
+ private static List<Family> readFamilies(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ List<Family> families = new ArrayList<Family>();
+ parser.require(XmlPullParser.START_TAG, null, "familyset");
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+ if (parser.getName().equals("family")) {
+ families.add(readFamily(parser));
+ } else {
+ skip(parser);
+ }
+ }
+ return families;
+ }
+
+ private static Family readFamily(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ List<String> names = null;
+ List<String> fontFiles = new ArrayList<String>();
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+ String tag = parser.getName();
+ if (tag.equals("fileset")) {
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+ if (parser.getName().equals("file")) {
+ String filename = parser.nextText();
+ String fullFilename = "/system/fonts/" + filename;
+ fontFiles.add(fullFilename);
+ }
+ }
+ } else if (tag.equals("nameset")) {
+ names = new ArrayList<String>();
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+ if (parser.getName().equals("name")) {
+ String name = parser.nextText();
+ names.add(name);
+ }
+ }
+ }
+ }
+ return new Family(names, fontFiles);
+ }
+
+ private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
+ int depth = 1;
+ while (depth > 0) {
+ switch (parser.next()) {
+ case XmlPullParser.START_TAG:
+ depth++;
+ break;
+ case XmlPullParser.END_TAG:
+ depth--;
+ break;
+ }
+ }
+ }
+}
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index 062acaf..fe53a17 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -272,6 +272,7 @@ public class ImageFormat {
case NV16:
case YUY2:
case YV12:
+ case JPEG:
case NV21:
case YUV_420_888:
case RAW_SENSOR:
diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java
index 66bf75c..b4e6bab 100644
--- a/graphics/java/android/graphics/Matrix.java
+++ b/graphics/java/android/graphics/Matrix.java
@@ -245,6 +245,16 @@ public class Matrix {
}
/**
+ * Gets whether this matrix is affine. An affine matrix preserves
+ * straight lines and has no perspective.
+ *
+ * @return Whether the matrix is affine.
+ */
+ public boolean isAffine() {
+ return native_isAffine(native_instance);
+ }
+
+ /**
* Returns true if will map a rectangle to another rectangle. This can be
* true if the matrix is identity, scale-only, or rotates a multiple of 90
* degrees.
@@ -828,6 +838,7 @@ public class Matrix {
private static native long native_create(long native_src_or_zero);
private static native boolean native_isIdentity(long native_object);
+ private static native boolean native_isAffine(long native_object);
private static native boolean native_rectStaysRect(long native_object);
private static native void native_reset(long native_object);
private static native void native_set(long native_object,
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index d96484e..92cfd6b 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1661,12 +1661,12 @@ public class Paint {
return 0;
}
if (!mHasCompatScaling) {
- return native_getTextWidths(mNativePaint, text, index, count, mBidiFlags, widths);
+ return native_getTextWidths(mNativePaint, mNativeTypeface, text, index, count, mBidiFlags, widths);
}
final float oldSize = getTextSize();
setTextSize(oldSize*mCompatScaling);
- int res = native_getTextWidths(mNativePaint, text, index, count, mBidiFlags, widths);
+ int res = native_getTextWidths(mNativePaint, mNativeTypeface, text, index, count, mBidiFlags, widths);
setTextSize(oldSize);
for (int i=0; i<res; i++) {
widths[i] *= mInvCompatScaling;
@@ -1743,12 +1743,12 @@ public class Paint {
return 0;
}
if (!mHasCompatScaling) {
- return native_getTextWidths(mNativePaint, text, start, end, mBidiFlags, widths);
+ return native_getTextWidths(mNativePaint, mNativeTypeface, text, start, end, mBidiFlags, widths);
}
final float oldSize = getTextSize();
setTextSize(oldSize*mCompatScaling);
- int res = native_getTextWidths(mNativePaint, text, start, end, mBidiFlags, widths);
+ int res = native_getTextWidths(mNativePaint, mNativeTypeface, text, start, end, mBidiFlags, widths);
setTextSize(oldSize);
for (int i=0; i<res; i++) {
widths[i] *= mInvCompatScaling;
@@ -1838,13 +1838,13 @@ public class Paint {
return 0f;
}
if (!mHasCompatScaling) {
- return native_getTextRunAdvances(mNativePaint, chars, index, count,
+ return native_getTextRunAdvances(mNativePaint, mNativeTypeface, chars, index, count,
contextIndex, contextCount, flags, advances, advancesIndex);
}
final float oldSize = getTextSize();
setTextSize(oldSize * mCompatScaling);
- float res = native_getTextRunAdvances(mNativePaint, chars, index, count,
+ float res = native_getTextRunAdvances(mNativePaint, mNativeTypeface, chars, index, count,
contextIndex, contextCount, flags, advances, advancesIndex);
setTextSize(oldSize);
@@ -1969,13 +1969,13 @@ public class Paint {
}
if (!mHasCompatScaling) {
- return native_getTextRunAdvances(mNativePaint, text, start, end,
+ return native_getTextRunAdvances(mNativePaint, mNativeTypeface, text, start, end,
contextStart, contextEnd, flags, advances, advancesIndex);
}
final float oldSize = getTextSize();
setTextSize(oldSize * mCompatScaling);
- float totalAdvance = native_getTextRunAdvances(mNativePaint, text, start, end,
+ float totalAdvance = native_getTextRunAdvances(mNativePaint, mNativeTypeface, text, start, end,
contextStart, contextEnd, flags, advances, advancesIndex);
setTextSize(oldSize);
@@ -2240,19 +2240,19 @@ public class Paint {
private static native void native_setTextLocale(long native_object,
String locale);
- private static native int native_getTextWidths(long native_object,
+ private static native int native_getTextWidths(long native_object, long native_typeface,
char[] text, int index, int count, int bidiFlags, float[] widths);
- private static native int native_getTextWidths(long native_object,
+ private static native int native_getTextWidths(long native_object, long native_typeface,
String text, int start, int end, int bidiFlags, float[] widths);
private static native int native_getTextGlyphs(long native_object,
String text, int start, int end, int contextStart, int contextEnd,
int flags, char[] glyphs);
- private static native float native_getTextRunAdvances(long native_object,
+ private static native float native_getTextRunAdvances(long native_object, long native_typeface,
char[] text, int index, int count, int contextIndex, int contextCount,
int flags, float[] advances, int advancesIndex);
- private static native float native_getTextRunAdvances(long native_object,
+ private static native float native_getTextRunAdvances(long native_object, long native_typeface,
String text, int start, int end, int contextStart, int contextEnd,
int flags, float[] advances, int advancesIndex);
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index 8b5609f..437d2f4 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -36,9 +36,21 @@ public final class Rect implements Parcelable {
public int right;
public int bottom;
- private static final Pattern FLATTENED_PATTERN = Pattern.compile(
+ /**
+ * A helper class for flattened rectange pattern recognition. A separate
+ * class to avoid an initialization dependency on a regular expression
+ * causing Rect to not be initializable with an ahead-of-time compilation
+ * scheme.
+ */
+ private static final class UnflattenHelper {
+ private static final Pattern FLATTENED_PATTERN = Pattern.compile(
"(-?\\d+) (-?\\d+) (-?\\d+) (-?\\d+)");
+ static Matcher getMatcher(String str) {
+ return FLATTENED_PATTERN.matcher(str);
+ }
+ }
+
/**
* Create a new empty Rect. All coordinates are initialized to 0.
*/
@@ -152,7 +164,7 @@ public final class Rect implements Parcelable {
* or null if the string is not of that form.
*/
public static Rect unflattenFromString(String str) {
- Matcher matcher = FLATTENED_PATTERN.matcher(str);
+ Matcher matcher = UnflattenHelper.getMatcher(str);
if (!matcher.matches()) {
return null;
}
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 73e0e8d..64451c4 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -17,10 +17,21 @@
package android.graphics;
import android.content.res.AssetManager;
-import android.util.SparseArray;
+import android.graphics.FontListParser.Family;
+import android.util.Log;
import android.util.LongSparseArray;
+import android.util.SparseArray;
+
+import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
/**
* The Typeface class specifies the typeface and intrinsic style of a font.
@@ -30,6 +41,8 @@ import java.io.File;
*/
public class Typeface {
+ private static String TAG = "Typeface";
+
/** The default NORMAL typeface object */
public static final Typeface DEFAULT;
/**
@@ -49,6 +62,10 @@ public class Typeface {
private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache =
new LongSparseArray<SparseArray<Typeface>>(3);
+ static Typeface sDefaultTypeface;
+ static Map<String, Typeface> sSystemFontMap;
+ static FontFamily[] sFallbackFonts;
+
/**
* @hide
*/
@@ -62,6 +79,11 @@ public class Typeface {
private int mStyle = 0;
+ private static void setDefault(Typeface t) {
+ sDefaultTypeface = t;
+ nativeSetDefault(t.native_instance);
+ }
+
/** Returns the typeface's intrinsic style attributes */
public int getStyle() {
return mStyle;
@@ -89,6 +111,9 @@ public class Typeface {
* @return The best matching typeface.
*/
public static Typeface create(String familyName, int style) {
+ if (sSystemFontMap != null) {
+ return create(sSystemFontMap.get(familyName), style);
+ }
return new Typeface(nativeCreate(familyName, style));
}
@@ -142,7 +167,7 @@ public class Typeface {
public static Typeface defaultFromStyle(int style) {
return sDefaults[style];
}
-
+
/**
* Create a new typeface from the specified font data.
* @param mgr The application's asset manager
@@ -156,7 +181,7 @@ public class Typeface {
/**
* Create a new typeface from the specified font file.
*
- * @param path The path to the font data.
+ * @param path The path to the font data.
* @return The new typeface.
*/
public static Typeface createFromFile(File path) {
@@ -166,13 +191,45 @@ public class Typeface {
/**
* Create a new typeface from the specified font file.
*
- * @param path The full path to the font data.
+ * @param path The full path to the font data.
* @return The new typeface.
*/
public static Typeface createFromFile(String path) {
return new Typeface(nativeCreateFromFile(path));
}
+ /**
+ * Create a new typeface from an array of font families.
+ *
+ * @param families array of font families
+ * @hide
+ */
+ public static Typeface createFromFamilies(FontFamily[] families) {
+ long[] ptrArray = new long[families.length];
+ for (int i = 0; i < families.length; i++) {
+ ptrArray[i] = families[i].mNativePtr;
+ }
+ return new Typeface(nativeCreateFromArray(ptrArray));
+ }
+
+ /**
+ * Create a new typeface from an array of font families, including
+ * also the font families in the fallback list.
+ *
+ * @param families array of font families
+ * @hide
+ */
+ public static Typeface createFromFamiliesWithDefault(FontFamily[] families) {
+ long[] ptrArray = new long[families.length + sFallbackFonts.length];
+ for (int i = 0; i < families.length; i++) {
+ ptrArray[i] = families[i].mNativePtr;
+ }
+ for (int i = 0; i < sFallbackFonts.length; i++) {
+ ptrArray[i + families.length] = sFallbackFonts[i].mNativePtr;
+ }
+ return new Typeface(nativeCreateFromArray(ptrArray));
+ }
+
// don't allow clients to call this directly
private Typeface(long ni) {
if (ni == 0) {
@@ -182,14 +239,76 @@ public class Typeface {
native_instance = ni;
mStyle = nativeGetStyle(ni);
}
-
+
+ private static FontFamily makeFamilyFromParsed(FontListParser.Family family) {
+ // TODO: expand to handle attributes like lang and variant
+ FontFamily fontFamily = new FontFamily();
+ for (String fontFile : family.fontFiles) {
+ fontFamily.addFont(new File(fontFile));
+ }
+ return fontFamily;
+ }
+
static {
+ // Load font config and initialize Minikin state
+ String systemConfigFilename = "/system/etc/system_fonts.xml";
+ String configFilename = "/system/etc/fallback_fonts.xml";
+ try {
+ // TODO: throws an exception non-Minikin builds, to fail early;
+ // remove when Minikin-only
+ new FontFamily();
+
+ FileInputStream systemIn = new FileInputStream(systemConfigFilename);
+ List<FontListParser.Family> systemFontConfig = FontListParser.parse(systemIn);
+
+ FileInputStream fallbackIn = new FileInputStream(configFilename);
+ List<FontFamily> familyList = new ArrayList<FontFamily>();
+ // Note that the default typeface is always present in the fallback list;
+ // this is an enhancement from pre-Minikin behavior.
+ familyList.add(makeFamilyFromParsed(systemFontConfig.get(0)));
+ for (Family f : FontListParser.parse(fallbackIn)) {
+ familyList.add(makeFamilyFromParsed(f));
+ }
+ sFallbackFonts = familyList.toArray(new FontFamily[familyList.size()]);
+ setDefault(Typeface.createFromFamilies(sFallbackFonts));
+
+ Map<String, Typeface> systemFonts = new HashMap<String, Typeface>();
+ for (int i = 0; i < systemFontConfig.size(); i++) {
+ Typeface typeface;
+ Family f = systemFontConfig.get(i);
+ if (i == 0) {
+ // The first entry is the default typeface; no sense in duplicating
+ // the corresponding FontFamily.
+ typeface = sDefaultTypeface;
+ } else {
+ FontFamily fontFamily = makeFamilyFromParsed(f);
+ FontFamily[] families = { fontFamily };
+ typeface = Typeface.createFromFamiliesWithDefault(families);
+ }
+ for (String name : f.names) {
+ systemFonts.put(name, typeface);
+ }
+ }
+ sSystemFontMap = systemFonts;
+
+ } catch (RuntimeException e) {
+ Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)");
+ // TODO: normal in non-Minikin case, remove or make error when Minikin-only
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Error opening " + configFilename);
+ } catch (IOException e) {
+ Log.e(TAG, "Error reading " + configFilename);
+ } catch (XmlPullParserException e) {
+ Log.e(TAG, "XML parse exception for " + configFilename);
+ }
+
+ // Set up defaults and typefaces exposed in public API
DEFAULT = create((String) null, 0);
DEFAULT_BOLD = create((String) null, Typeface.BOLD);
SANS_SERIF = create("sans-serif", 0);
SERIF = create("serif", 0);
MONOSPACE = create("monospace", 0);
-
+
sDefaults = new Typeface[] {
DEFAULT,
DEFAULT_BOLD,
@@ -198,6 +317,7 @@ public class Typeface {
};
}
+ @Override
protected void finalize() throws Throwable {
try {
nativeUnref(native_instance);
@@ -234,4 +354,6 @@ public class Typeface {
private static native int nativeGetStyle(long native_instance);
private static native long nativeCreateFromAsset(AssetManager mgr, String path);
private static native long nativeCreateFromFile(String path);
+ private static native long nativeCreateFromArray(long[] familyArray);
+ private static native void nativeSetDefault(long native_instance);
}
diff --git a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
new file mode 100644
index 0000000..46e3401
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
@@ -0,0 +1,524 @@
+/*
+ * 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.graphics.drawable;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.LongSparseLongArray;
+import android.util.SparseIntArray;
+import android.util.StateSet;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * Drawable containing a set of Drawable keyframes where the currently displayed
+ * keyframe is chosen based on the current state set. Animations between
+ * keyframes may optionally be defined using transition elements.
+ * <p>
+ * This drawable can be defined in an XML file with the <code>
+ * &lt;animated-selector></code> element. Each keyframe Drawable is defined in a
+ * nested <code>&lt;item></code> element. Transitions are defined in a nested
+ * <code>&lt;transition></code> element.
+ *
+ * @attr ref android.R.styleable#DrawableStates_state_focused
+ * @attr ref android.R.styleable#DrawableStates_state_window_focused
+ * @attr ref android.R.styleable#DrawableStates_state_enabled
+ * @attr ref android.R.styleable#DrawableStates_state_checkable
+ * @attr ref android.R.styleable#DrawableStates_state_checked
+ * @attr ref android.R.styleable#DrawableStates_state_selected
+ * @attr ref android.R.styleable#DrawableStates_state_activated
+ * @attr ref android.R.styleable#DrawableStates_state_active
+ * @attr ref android.R.styleable#DrawableStates_state_single
+ * @attr ref android.R.styleable#DrawableStates_state_first
+ * @attr ref android.R.styleable#DrawableStates_state_middle
+ * @attr ref android.R.styleable#DrawableStates_state_last
+ * @attr ref android.R.styleable#DrawableStates_state_pressed
+ */
+public class AnimatedStateListDrawable extends StateListDrawable {
+ private static final String ELEMENT_TRANSITION = "transition";
+ private static final String ELEMENT_ITEM = "item";
+
+ private AnimatedStateListState mState;
+
+ /** The currently running animation, if any. */
+ private ObjectAnimator mAnim;
+
+ /** Index to be set after the animation ends. */
+ private int mAnimToIndex = -1;
+
+ /** Index away from which we are animating. */
+ private int mAnimFromIndex = -1;
+
+ private boolean mMutated;
+
+ public AnimatedStateListDrawable() {
+ this(null, null);
+ }
+
+ /**
+ * Add a new drawable to the set of keyframes.
+ *
+ * @param stateSet An array of resource IDs to associate with the keyframe
+ * @param drawable The drawable to show when in the specified state
+ * @param id The unique identifier for the keyframe
+ */
+ public void addState(int[] stateSet, Drawable drawable, int id) {
+ if (drawable != null) {
+ mState.addStateSet(stateSet, drawable, id);
+ onStateChange(getState());
+ }
+ }
+
+ /**
+ * Adds a new transition between keyframes.
+ *
+ * @param fromId Unique identifier of the starting keyframe
+ * @param toId Unique identifier of the ending keyframe
+ * @param anim An AnimationDrawable to use as a transition
+ * @param reversible Whether the transition can be reversed
+ */
+ public void addTransition(int fromId, int toId, AnimationDrawable anim, boolean reversible) {
+ mState.addTransition(fromId, toId, anim, reversible);
+ }
+
+ @Override
+ public boolean isStateful() {
+ return true;
+ }
+
+ @Override
+ protected boolean onStateChange(int[] stateSet) {
+ final int keyframeIndex = mState.indexOfKeyframe(stateSet);
+ if (keyframeIndex == getCurrentIndex()) {
+ return false;
+ }
+
+ if (selectTransition(keyframeIndex)) {
+ return true;
+ }
+
+ if (selectDrawable(keyframeIndex)) {
+ return true;
+ }
+
+ return super.onStateChange(stateSet);
+ }
+
+ private boolean selectTransition(int toIndex) {
+ if (mAnim != null) {
+ if (toIndex == mAnimToIndex) {
+ // Already animating to that keyframe.
+ return true;
+ } else if (toIndex == mAnimFromIndex) {
+ // Reverse the current animation.
+ mAnim.reverse();
+ mAnimFromIndex = mAnimToIndex;
+ mAnimToIndex = toIndex;
+ return true;
+ }
+
+ // Changing animation, end the current animation.
+ mAnim.end();
+ }
+
+ final AnimatedStateListState state = mState;
+ final int fromIndex = getCurrentIndex();
+ final int fromId = state.getKeyframeIdAt(fromIndex);
+ final int toId = state.getKeyframeIdAt(toIndex);
+
+ if (toId == 0 || fromId == 0) {
+ // Missing a keyframe ID.
+ return false;
+ }
+
+ final int transitionIndex = state.indexOfTransition(fromId, toId);
+ if (transitionIndex < 0 || !selectDrawable(transitionIndex)) {
+ // Couldn't select a transition.
+ return false;
+ }
+
+ final Drawable d = getCurrent();
+ if (!(d instanceof AnimationDrawable)) {
+ // Transition isn't an animation.
+ return false;
+ }
+
+ final AnimationDrawable ad = (AnimationDrawable) d;
+ final boolean reversed = mState.isTransitionReversed(fromId, toId);
+ final int frameCount = ad.getNumberOfFrames();
+ final int fromFrame = reversed ? frameCount - 1 : 0;
+ final int toFrame = reversed ? 0 : frameCount - 1;
+
+ final FrameInterpolator interp = new FrameInterpolator(ad, reversed);
+ final ObjectAnimator anim = ObjectAnimator.ofInt(ad, "currentIndex", fromFrame, toFrame);
+ anim.setAutoCancel(true);
+ anim.setDuration(interp.getTotalDuration());
+ anim.addListener(mAnimListener);
+ anim.setInterpolator(interp);
+ anim.start();
+
+ mAnim = anim;
+ mAnimFromIndex = fromIndex;
+ mAnimToIndex = toIndex;
+ return true;
+ }
+
+ @Override
+ public void jumpToCurrentState() {
+ super.jumpToCurrentState();
+
+ if (mAnim != null) {
+ mAnim.end();
+ }
+ }
+
+ @Override
+ public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+ throws XmlPullParserException, IOException {
+ final TypedArray a = r.obtainAttributes(attrs, R.styleable.AnimatedStateListDrawable);
+
+ super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedStateListDrawable_visible);
+
+ final StateListState stateListState = getStateListState();
+ stateListState.setVariablePadding(a.getBoolean(
+ R.styleable.AnimatedStateListDrawable_variablePadding, false));
+ stateListState.setConstantSize(a.getBoolean(
+ R.styleable.AnimatedStateListDrawable_constantSize, false));
+ stateListState.setEnterFadeDuration(a.getInt(
+ R.styleable.AnimatedStateListDrawable_enterFadeDuration, 0));
+ stateListState.setExitFadeDuration(a.getInt(
+ R.styleable.AnimatedStateListDrawable_exitFadeDuration, 0));
+
+ setDither(a.getBoolean(R.styleable.AnimatedStateListDrawable_dither, true));
+ setAutoMirrored(a.getBoolean(R.styleable.AnimatedStateListDrawable_autoMirrored, false));
+
+ a.recycle();
+
+ int type;
+
+ final int innerDepth = parser.getDepth() + 1;
+ int depth;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && ((depth = parser.getDepth()) >= innerDepth
+ || type != XmlPullParser.END_TAG)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ if (depth > innerDepth) {
+ continue;
+ }
+
+ if (parser.getName().equals(ELEMENT_ITEM)) {
+ parseItem(r, parser, attrs, theme);
+ } else if (parser.getName().equals(ELEMENT_TRANSITION)) {
+ parseTransition(r, parser, attrs, theme);
+ }
+ }
+
+ onStateChange(getState());
+ }
+
+ private int parseTransition(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+ throws XmlPullParserException, IOException {
+ int drawableRes = 0;
+ int fromId = 0;
+ int toId = 0;
+ boolean reversible = false;
+
+ final int numAttrs = attrs.getAttributeCount();
+ for (int i = 0; i < numAttrs; i++) {
+ final int stateResId = attrs.getAttributeNameResource(i);
+ switch (stateResId) {
+ case 0:
+ break;
+ case R.attr.fromId:
+ fromId = attrs.getAttributeResourceValue(i, 0);
+ break;
+ case R.attr.toId:
+ toId = attrs.getAttributeResourceValue(i, 0);
+ break;
+ case R.attr.drawable:
+ drawableRes = attrs.getAttributeResourceValue(i, 0);
+ break;
+ case R.attr.reversible:
+ reversible = attrs.getAttributeBooleanValue(i, false);
+ break;
+ }
+ }
+
+ final Drawable dr;
+ if (drawableRes != 0) {
+ dr = r.getDrawable(drawableRes);
+ } else {
+ int type;
+ while ((type = parser.next()) == XmlPullParser.TEXT) {
+ }
+ if (type != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException(
+ parser.getPositionDescription()
+ + ": <item> tag requires a 'drawable' attribute or "
+ + "child tag defining a drawable");
+ }
+ dr = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme);
+ }
+
+ final AnimationDrawable anim;
+ if (dr instanceof AnimationDrawable) {
+ anim = (AnimationDrawable) dr;
+ } else {
+ throw new XmlPullParserException(parser.getPositionDescription()
+ + ": <transition> tag requires a 'drawable' attribute or "
+ + "child tag defining a drawable of type <animation>");
+ }
+
+ return mState.addTransition(fromId, toId, anim, reversible);
+ }
+
+ private int parseItem(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+ throws XmlPullParserException, IOException {
+ int drawableRes = 0;
+ int keyframeId = 0;
+
+ int j = 0;
+ final int numAttrs = attrs.getAttributeCount();
+ int[] states = new int[numAttrs];
+ for (int i = 0; i < numAttrs; i++) {
+ final int stateResId = attrs.getAttributeNameResource(i);
+ switch (stateResId) {
+ case 0:
+ break;
+ case R.attr.id:
+ keyframeId = attrs.getAttributeResourceValue(i, 0);
+ break;
+ case R.attr.drawable:
+ drawableRes = attrs.getAttributeResourceValue(i, 0);
+ break;
+ default:
+ final boolean hasState = attrs.getAttributeBooleanValue(i, false);
+ states[j++] = hasState ? stateResId : -stateResId;
+ }
+ }
+ states = StateSet.trimStateSet(states, j);
+
+ final Drawable dr;
+ if (drawableRes != 0) {
+ dr = r.getDrawable(drawableRes);
+ } else {
+ int type;
+ while ((type = parser.next()) == XmlPullParser.TEXT) {
+ }
+ if (type != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException(
+ parser.getPositionDescription()
+ + ": <item> tag requires a 'drawable' attribute or "
+ + "child tag defining a drawable");
+ }
+ dr = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme);
+ }
+
+ return mState.addStateSet(states, dr, keyframeId);
+ }
+
+ @Override
+ public Drawable mutate() {
+ if (!mMutated) {
+ final AnimatedStateListState newState = new AnimatedStateListState(mState, this, null);
+ setConstantState(newState);
+ mMutated = true;
+ }
+
+ return this;
+ }
+
+ private final AnimatorListenerAdapter mAnimListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator anim) {
+ selectDrawable(mAnimToIndex);
+
+ mAnimToIndex = -1;
+ mAnimFromIndex = -1;
+ mAnim = null;
+ }
+ };
+
+ static class AnimatedStateListState extends StateListState {
+ private static final int REVERSE_SHIFT = 32;
+ private static final int REVERSE_MASK = 0x1;
+
+ final LongSparseLongArray mTransitions;
+ final SparseIntArray mStateIds;
+
+ AnimatedStateListState(AnimatedStateListState orig, AnimatedStateListDrawable owner,
+ Resources res) {
+ super(orig, owner, res);
+
+ if (orig != null) {
+ mTransitions = orig.mTransitions.clone();
+ mStateIds = orig.mStateIds.clone();
+ } else {
+ mTransitions = new LongSparseLongArray();
+ mStateIds = new SparseIntArray();
+ }
+ }
+
+ int addTransition(int fromId, int toId, AnimationDrawable anim, boolean reversible) {
+ final int pos = super.addChild(anim);
+ final long keyFromTo = generateTransitionKey(fromId, toId);
+ mTransitions.append(keyFromTo, pos);
+
+ if (reversible) {
+ final long keyToFrom = generateTransitionKey(toId, fromId);
+ mTransitions.append(keyToFrom, pos | (1L << REVERSE_SHIFT));
+ }
+
+ return addChild(anim);
+ }
+
+ int addStateSet(int[] stateSet, Drawable drawable, int id) {
+ final int index = super.addStateSet(stateSet, drawable);
+ mStateIds.put(index, id);
+ return index;
+ }
+
+ int indexOfKeyframe(int[] stateSet) {
+ final int index = super.indexOfStateSet(stateSet);
+ if (index >= 0) {
+ return index;
+ }
+
+ return super.indexOfStateSet(StateSet.WILD_CARD);
+ }
+
+ int getKeyframeIdAt(int index) {
+ return index < 0 ? 0 : mStateIds.get(index, 0);
+ }
+
+ int indexOfTransition(int fromId, int toId) {
+ final long keyFromTo = generateTransitionKey(fromId, toId);
+ return (int) mTransitions.get(keyFromTo, -1);
+ }
+
+ boolean isTransitionReversed(int fromId, int toId) {
+ final long keyFromTo = generateTransitionKey(fromId, toId);
+ return (mTransitions.get(keyFromTo, -1) >> REVERSE_SHIFT & REVERSE_MASK) == 1;
+ }
+
+ @Override
+ public Drawable newDrawable() {
+ return new AnimatedStateListDrawable(this, null);
+ }
+
+ @Override
+ public Drawable newDrawable(Resources res) {
+ return new AnimatedStateListDrawable(this, res);
+ }
+
+ private static long generateTransitionKey(int fromId, int toId) {
+ return (long) fromId << 32 | toId;
+ }
+ }
+
+ void setConstantState(AnimatedStateListState state) {
+ super.setConstantState(state);
+
+ mState = state;
+ }
+
+ private AnimatedStateListDrawable(AnimatedStateListState state, Resources res) {
+ super(null);
+
+ final AnimatedStateListState newState = new AnimatedStateListState(state, this, res);
+ setConstantState(newState);
+ onStateChange(getState());
+ jumpToCurrentState();
+ }
+
+ /**
+ * Interpolates between frames with respect to their individual durations.
+ */
+ private static class FrameInterpolator implements TimeInterpolator {
+ private int[] mFrameTimes;
+ private int mFrames;
+ private int mTotalDuration;
+
+ public FrameInterpolator(AnimationDrawable d, boolean reversed) {
+ updateFrames(d, reversed);
+ }
+
+ public int updateFrames(AnimationDrawable d, boolean reversed) {
+ final int N = d.getNumberOfFrames();
+ mFrames = N;
+
+ if (mFrameTimes == null || mFrameTimes.length < N) {
+ mFrameTimes = new int[N];
+ }
+
+ final int[] frameTimes = mFrameTimes;
+ int totalDuration = 0;
+ for (int i = 0; i < N; i++) {
+ final int duration = d.getDuration(reversed ? N - i - 1 : i);
+ frameTimes[i] = duration;
+ totalDuration += duration;
+ }
+
+ mTotalDuration = totalDuration;
+ return totalDuration;
+ }
+
+ public int getTotalDuration() {
+ return mTotalDuration;
+ }
+
+ @Override
+ public float getInterpolation(float input) {
+ final int elapsed = (int) (input * mTotalDuration + 0.5f);
+ final int N = mFrames;
+ final int[] frameTimes = mFrameTimes;
+
+ // Find the current frame and remaining time within that frame.
+ int remaining = elapsed;
+ int i = 0;
+ while (i < N && remaining >= frameTimes[i]) {
+ remaining -= frameTimes[i];
+ i++;
+ }
+
+ // Remaining time is relative of total duration.
+ final float frameElapsed;
+ if (i < N) {
+ frameElapsed = remaining / (float) mTotalDuration;
+ } else {
+ frameElapsed = 0;
+ }
+
+ return i / (float) N + frameElapsed;
+ }
+ }
+}
+
diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java
index 3f94e26..da4bc10 100644
--- a/graphics/java/android/graphics/drawable/AnimationDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java
@@ -94,7 +94,7 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An
boolean changed = super.setVisible(visible, restart);
if (visible) {
if (changed || restart) {
- setFrame(0, true, true);
+ setFrame(0, true, mCurFrame >= 0);
}
} else {
unscheduleSelf(this);
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index b9d5e19..b939636 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -1039,6 +1039,8 @@ public abstract class Drawable {
final String name = parser.getName();
if (name.equals("selector")) {
drawable = new StateListDrawable();
+ } else if (name.equals("animated-selector")) {
+ drawable = new AnimatedStateListDrawable();
} else if (name.equals("level-list")) {
drawable = new LevelListDrawable();
} else if (name.equals("layer-list")) {
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index 1f8b51d..08fc99d 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -359,6 +359,16 @@ public class DrawableContainer extends Drawable implements Drawable.Callback {
mDrawableContainerState.getOpacity();
}
+ /** @hide */
+ public void setCurrentIndex(int index) {
+ selectDrawable(index);
+ }
+
+ /** @hide */
+ public int getCurrentIndex() {
+ return mCurIndex;
+ }
+
public boolean selectDrawable(int idx) {
if (idx == mCurIndex) {
return false;
diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java
index 271af2b..f22a063 100644
--- a/graphics/java/android/graphics/drawable/StateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/StateListDrawable.java
@@ -55,8 +55,9 @@ import android.util.StateSet;
* @attr ref android.R.styleable#DrawableStates_state_pressed
*/
public class StateListDrawable extends DrawableContainer {
+ private static final String TAG = StateListDrawable.class.getSimpleName();
+
private static final boolean DEBUG = false;
- private static final String TAG = "StateListDrawable";
/**
* To be proper, we should have a getter for dither (and alpha, etc.)
@@ -69,7 +70,8 @@ public class StateListDrawable extends DrawableContainer {
* to improve the quality at negligible cost.
*/
private static final boolean DEFAULT_DITHER = true;
- private final StateListState mStateListState;
+
+ private StateListState mStateListState;
private boolean mMutated;
public StateListDrawable() {
@@ -274,7 +276,7 @@ public class StateListDrawable extends DrawableContainer {
mStateListState.setLayoutDirection(layoutDirection);
}
- static final class StateListState extends DrawableContainerState {
+ static class StateListState extends DrawableContainerState {
int[][] mStateSets;
StateListState(StateListState orig, StateListDrawable owner, Resources res) {
@@ -293,7 +295,7 @@ public class StateListDrawable extends DrawableContainer {
return pos;
}
- private int indexOfStateSet(int[] stateSet) {
+ int indexOfStateSet(int[] stateSet) {
final int[][] stateSets = mStateSets;
final int N = getChildCount();
for (int i = 0; i < N; i++) {
@@ -323,11 +325,26 @@ public class StateListDrawable extends DrawableContainer {
}
}
+ void setConstantState(StateListState state) {
+ super.setConstantState(state);
+
+ mStateListState = state;
+ }
+
private StateListDrawable(StateListState state, Resources res) {
- StateListState as = new StateListState(state, this, res);
- mStateListState = as;
- setConstantState(as);
+ final StateListState newState = new StateListState(state, this, res);
+ setConstantState(newState);
onStateChange(getState());
}
+
+ /**
+ * This constructor exists so subclasses can avoid calling the default
+ * constructor and setting up a StateListDrawable-specific constant state.
+ */
+ StateListDrawable(StateListState state) {
+ if (state != null) {
+ setConstantState(state);
+ }
+ }
}
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index ff4ab98..2da8615 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -46,7 +46,7 @@ import java.util.HashMap;
* This lets you create a drawable based on an XML vector graphic It can be
* defined in an XML file with the <code>&lt;vector></code> element.
* <p/>
- * The vector drawable has 6 elements:
+ * The vector drawable has the following elements:
* <p/>
* <dl>
* <dt><code>&lt;vector></code></dt>
@@ -59,15 +59,15 @@ import java.util.HashMap;
* <dd>Used to defined the size of the virtual canvas the paths are drawn on.
* The size is defined using the attributes <code>android:viewportHeight</code>
* <code>android:viewportWidth</code></dd>
- * <dt><code>&lt;group></code></dt>
- * <dd>Defines the static 2D image.</dd>
* <dt><code>&lt;path></code></dt>
- * <dd>Defines paths to be drawn. The path elements must be within a group
+ * <dd>Defines paths to be drawn. Multiple paths can be defined in one xml file.
+ * The paths are drawn in the order of their definition order.
* <dl>
* <dt><code>android:name</code>
* <dd>Defines the name of the path.</dd></dt>
* <dt><code>android:pathData</code>
- * <dd>Defines path string.</dd></dt>
+ * <dd>Defines path string. This is using exactly same format as "d" attribute
+ * in the SVG's path data</dd></dt>
* <dt><code>android:fill</code>
* <dd>Defines the color to fill the path (none if not present).</dd></dt>
* <dt><code>android:stroke</code>
@@ -108,7 +108,6 @@ public class VectorDrawable extends Drawable {
private static final String SHAPE_SIZE = "size";
private static final String SHAPE_VIEWPORT = "viewport";
- private static final String SHAPE_GROUP = "group";
private static final String SHAPE_PATH = "path";
private static final String SHAPE_VECTOR = "vector";
@@ -266,10 +265,9 @@ public class VectorDrawable extends Drawable {
boolean noSizeTag = true;
boolean noViewportTag = true;
- boolean noGroupTag = true;
boolean noPathTag = true;
- VGroup currentGroup = null;
+ VGroup currentGroup = new VGroup();
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
@@ -286,10 +284,6 @@ public class VectorDrawable extends Drawable {
} else if (SHAPE_VIEWPORT.equals(tagName)) {
pathRenderer.parseViewport(res, attrs);
noViewportTag = false;
- } else if (SHAPE_GROUP.equals(tagName)) {
- currentGroup = new VGroup();
- pathRenderer.mGroupList.add(currentGroup);
- noGroupTag = false;
} else if (SHAPE_VECTOR.equals(tagName)) {
final TypedArray a = res.obtainAttributes(attrs, R.styleable.VectorDrawable);
@@ -310,7 +304,7 @@ public class VectorDrawable extends Drawable {
eventType = parser.next();
}
- if (noSizeTag || noViewportTag || noGroupTag || noPathTag) {
+ if (noSizeTag || noViewportTag || noPathTag) {
final StringBuffer tag = new StringBuffer();
if (noSizeTag) {
@@ -324,13 +318,6 @@ public class VectorDrawable extends Drawable {
tag.append(SHAPE_SIZE);
}
- if (noGroupTag) {
- if (tag.length() > 0) {
- tag.append(" & ");
- }
- tag.append(SHAPE_GROUP);
- }
-
if (noPathTag) {
if (tag.length() > 0) {
tag.append(" or ");
@@ -341,6 +328,7 @@ public class VectorDrawable extends Drawable {
throw new XmlPullParserException("no " + tag + " defined");
}
+ pathRenderer.mCurrentGroup = currentGroup;
// post parse cleanup
pathRenderer.parseFinish();
return pathRenderer;
@@ -394,7 +382,7 @@ public class VectorDrawable extends Drawable {
private Paint mFillPaint;
private PathMeasure mPathMeasure;
- final ArrayList<VGroup> mGroupList = new ArrayList<VGroup>();
+ private VGroup mCurrentGroup = new VGroup();
float mBaseWidth = 1;
float mBaseHeight = 1;
@@ -405,7 +393,7 @@ public class VectorDrawable extends Drawable {
}
public VPathRenderer(VPathRenderer copy) {
- mGroupList.addAll(copy.mGroupList);
+ mCurrentGroup = copy.mCurrentGroup;
if (copy.mCurrentPaths != null) {
mCurrentPaths = new VPath[copy.mCurrentPaths.length];
for (int i = 0; i < mCurrentPaths.length; i++) {
@@ -420,32 +408,24 @@ public class VectorDrawable extends Drawable {
}
public boolean canApplyTheme() {
- final ArrayList<VGroup> groups = mGroupList;
- for (int i = groups.size() - 1; i >= 0; i--) {
- final ArrayList<VPath> paths = groups.get(i).mVGList;
- for (int j = paths.size() - 1; j >= 0; j--) {
- final VPath path = paths.get(j);
- if (path.canApplyTheme()) {
- return true;
- }
+ final ArrayList<VPath> paths = mCurrentGroup.mVGList;
+ for (int j = paths.size() - 1; j >= 0; j--) {
+ final VPath path = paths.get(j);
+ if (path.canApplyTheme()) {
+ return true;
}
}
-
return false;
}
public void applyTheme(Theme t) {
- final ArrayList<VGroup> groups = mGroupList;
- for (int i = groups.size() - 1; i >= 0; i--) {
- final ArrayList<VPath> paths = groups.get(i).mVGList;
- for (int j = paths.size() - 1; j >= 0; j--) {
- final VPath path = paths.get(j);
- if (path.canApplyTheme()) {
- path.applyTheme(t);
- }
+ final ArrayList<VPath> paths = mCurrentGroup.mVGList;
+ for (int j = paths.size() - 1; j >= 0; j--) {
+ final VPath path = paths.get(j);
+ if (path.canApplyTheme()) {
+ path.applyTheme(t);
}
}
-
}
public void draw(Canvas canvas, int w, int h) {
@@ -537,11 +517,11 @@ public class VectorDrawable extends Drawable {
}
/**
- * Build the "current" path based on the first group
+ * Build the "current" path based on the current group
* TODO: improve memory use & performance or move to C++
*/
public void parseFinish() {
- final Collection<VPath> paths = mGroupList.get(0).getPaths();
+ final Collection<VPath> paths = mCurrentGroup.getPaths();
mCurrentPaths = paths.toArray(new VPath[paths.size()]);
for (int i = 0; i < mCurrentPaths.length; i++) {
mCurrentPaths[i] = new VPath(mCurrentPaths[i]);
diff --git a/graphics/java/android/graphics/pdf/PdfDocument.java b/graphics/java/android/graphics/pdf/PdfDocument.java
index f5b07c1..d603436 100644
--- a/graphics/java/android/graphics/pdf/PdfDocument.java
+++ b/graphics/java/android/graphics/pdf/PdfDocument.java
@@ -32,7 +32,7 @@ import java.util.List;
/**
* <p>
* This class enables generating a PDF document from native Android content. You
- * open a new document and then for every page you want to add you start a page,
+ * create a new document and then for every page you want to add you start a page,
* write content to the page, and finish the page. After you are done with all
* pages, you write the document to an output stream and close the document.
* After a document is closed you should not use it anymore. Note that pages are
@@ -64,7 +64,7 @@ import java.util.List;
* // write the document content
* document.writeTo(getOutputStream());
*
- * //close the document
+ * // close the document
* document.close();
* </pre>
*/
diff --git a/graphics/java/android/graphics/pdf/PdfRenderer.java b/graphics/java/android/graphics/pdf/PdfRenderer.java
new file mode 100644
index 0000000..3fa3b9f
--- /dev/null
+++ b/graphics/java/android/graphics/pdf/PdfRenderer.java
@@ -0,0 +1,391 @@
+/*
+ * 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.graphics.pdf;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+import dalvik.system.CloseGuard;
+import libcore.io.Libcore;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * <p>
+ * This class enables rendering a PDF document. This class is not thread safe.
+ * </p>
+ * <p>
+ * If you want to render a PDF, you create a renderer and for every page you want
+ * to render, you open the page, render it, and close the page. After you are done
+ * with rendering, you close the renderer. After the renderer is closed it should not
+ * be used anymore. Note that the pages are rendered one by one, i.e. you can have
+ * only a single page opened at any given time.
+ * </p>
+ * <p>
+ * A typical use of the APIs to render a PDF looks like this:
+ * </p>
+ * <pre>
+ * // create a new renderer
+ * PdfRenderer renderer = new PdfRenderer(getSeekableFileDescriptor());
+ *
+ * // let us just render all pages
+ * final int pageCount = renderer.getPageCount();
+ * for (int i = 0; i < pageCount; i++) {
+ * Page page = renderer.openPage(i);
+ * Bitmap bitmap = getBitmapReuseIfPossible(page);
+ *
+ * // say we render for showing on the screen
+ * page.render(bitmap, getContentBoundsInBitmap(),
+ * getDesiredTransformation(), Page.RENDER_MODE_FOR_DISPLAY);
+ *
+ * // do stuff with the bitmap
+ *
+ * renderer.closePage(page);
+ * }
+ *
+ * // close the renderer
+ * renderer.close();
+ * </pre>
+ *
+ * @see #close()
+ */
+public final class PdfRenderer implements AutoCloseable {
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ private final Point mTempPoint = new Point();
+
+ private final long mNativeDocument;
+
+ private final int mPageCount;
+
+ private ParcelFileDescriptor mInput;
+
+ private Page mCurrentPage;
+
+ /** @hide */
+ @IntDef({
+ Page.RENDER_MODE_FOR_DISPLAY,
+ Page.RENDER_MODE_FOR_PRINT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RenderMode {}
+
+ /**
+ * Creates a new instance.
+ * <p>
+ * <strong>Note:</strong> The provided file descriptor must be <strong>seekable</strong>,
+ * i.e. its data being randomly accessed, e.g. pointing to a file.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This class takes ownership of the passed in file descriptor
+ * and is responsible for closing it when the renderer is closed.
+ * </p>
+ *
+ * @param input Seekable file descriptor to read from.
+ */
+ public PdfRenderer(@NonNull ParcelFileDescriptor input) throws IOException {
+ if (input == null) {
+ throw new NullPointerException("input cannot be null");
+ }
+
+ final long size;
+ try {
+ Libcore.os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
+ size = Libcore.os.fstat(input.getFileDescriptor()).st_size;
+ } catch (ErrnoException ee) {
+ throw new IllegalArgumentException("file descriptor not seekable");
+ }
+
+ mInput = input;
+ mNativeDocument = nativeCreate(mInput.getFd(), size);
+ mPageCount = nativeGetPageCount(mNativeDocument);
+ mCloseGuard.open("close");
+ }
+
+ /**
+ * Closes this renderer. You should not use this instance
+ * after this method is called.
+ */
+ public void close() {
+ throwIfClosed();
+ throwIfPageOpened();
+ doClose();
+ }
+
+ /**
+ * Gets the number of pages in the document.
+ *
+ * @return The page count.
+ */
+ public int getPageCount() {
+ throwIfClosed();
+ return mPageCount;
+ }
+
+ /**
+ * Gets whether the document prefers to be scaled for printing.
+ * You should take this info account if the document is rendered
+ * for printing and the target media size differs from the page
+ * size.
+ *
+ * @return If to scale the document.
+ */
+ public boolean shouldScaleForPrinting() {
+ throwIfClosed();
+ return nativeScaleForPrinting(mNativeDocument);
+ }
+
+ /**
+ * Opens a page for rendering.
+ *
+ * @param index The page index.
+ * @return A page that can be rendered.
+ *
+ * @see #closePage(PdfRenderer.Page)
+ */
+ public Page openPage(int index) {
+ throwIfClosed();
+ throwIfPageOpened();
+ mCurrentPage = new Page(index);
+ return mCurrentPage;
+ }
+
+ /**
+ * Closes a page opened for rendering.
+ *
+ * @param page The page to close.
+ *
+ * @see #openPage(int)
+ */
+ public void closePage(@NonNull Page page) {
+ throwIfClosed();
+ throwIfNotCurrentPage(page);
+ throwIfCurrentPageClosed();
+ mCurrentPage.close();
+ mCurrentPage = null;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ mCloseGuard.warnIfOpen();
+ if (mInput != null) {
+ doClose();
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private void doClose() {
+ if (mCurrentPage != null) {
+ mCurrentPage.close();
+ mCurrentPage = null;
+ }
+ nativeClose(mNativeDocument);
+ try {
+ mInput.close();
+ } catch (IOException ioe) {
+ /* ignore - best effort */
+ }
+ mInput = null;
+ mCloseGuard.close();
+ }
+
+ private void throwIfClosed() {
+ if (mInput == null) {
+ throw new IllegalStateException("Already closed");
+ }
+ }
+
+ private void throwIfPageOpened() {
+ if (mCurrentPage != null) {
+ throw new IllegalStateException("Current page not closed");
+ }
+ }
+
+ private void throwIfCurrentPageClosed() {
+ if (mCurrentPage == null) {
+ throw new IllegalStateException("Already closed");
+ }
+ }
+
+ private void throwIfNotCurrentPage(Page page) {
+ if (page != mCurrentPage) {
+ throw new IllegalArgumentException("Page not from document");
+ }
+ }
+
+ /**
+ * This class represents a PDF document page for rendering.
+ */
+ public final class Page {
+
+ /**
+ * Mode to render the content for display on a screen.
+ */
+ public static final int RENDER_MODE_FOR_DISPLAY = 1;
+
+ /**
+ * Mode to render the content for printing.
+ */
+ public static final int RENDER_MODE_FOR_PRINT = 2;
+
+ private final int mIndex;
+ private final int mWidth;
+ private final int mHeight;
+
+ private long mNativePage;
+
+ private Page(int index) {
+ Point size = mTempPoint;
+ mNativePage = nativeOpenPageAndGetSize(mNativeDocument, index, size);
+ mIndex = index;
+ mWidth = size.x;
+ mHeight = size.y;
+ }
+
+ /**
+ * Gets the page index.
+ *
+ * @return The index.
+ */
+ public int getIndex() {
+ return mIndex;
+ }
+
+ /**
+ * Gets the page width in points (1/72").
+ *
+ * @return The width in points.
+ */
+ public int getWidth() {
+ return mWidth;
+ }
+
+ /**
+ * Gets the page height in points (1/72").
+ *
+ * @return The height in points.
+ */
+ public int getHeight() {
+ return mHeight;
+ }
+
+ /**
+ * Renders a page to a bitmap.
+ * <p>
+ * You may optionally specify a rectangular clip in the bitmap bounds. No rendering
+ * outside the clip will be performed, hence it is your responsibility to initialize
+ * the bitmap outside the clip.
+ * </p>
+ * <p>
+ * You may optionally specify a matrix to transform the content from page coordinates
+ * which are in points (1/72") to bitmap coordintates which are in pixels. If this
+ * matrix is not provided this method will apply a transformation that will fit the
+ * whole page to the destination clip if profided or the destination bitmap if no
+ * clip is provided.
+ * </p>
+ * <p>
+ * The clip and transformation are useful for implementing tile rendering where the
+ * destination bitmap contains a portion of the image, for example when zooming.
+ * Another useful application is for printing where the size of the bitmap holding
+ * the page is too large and a client can render the page in stripes.
+ * </p>
+ * <p>
+ * <strong>Note: </strong> The destination bitmap format must be
+ * {@link Config#ARGB_8888 ARGB}.
+ * </p>
+ * <p>
+ * <strong>Note: </strong> The optional transformation matrix must be affine as per
+ * {@link android.graphics.Matrix#isAffine()}. Hence, you can specify rotation, scaling,
+ * translation but not a perspective transformation.
+ * </p>
+ *
+ * @param destination Destination bitmap to which to render.
+ * @param destClip Optional clip in the bitmap bounds.
+ * @param transform Optional transformation to apply when rendering.
+ * @param renderMode The render mode.
+ *
+ * @see #RENDER_MODE_FOR_DISPLAY
+ * @see #RENDER_MODE_FOR_PRINT
+ */
+ public void render(@NonNull Bitmap destination, @Nullable Rect destClip,
+ @Nullable Matrix transform, @RenderMode int renderMode) {
+ if (destination.getConfig() != Config.ARGB_8888) {
+ throw new IllegalArgumentException("Unsupported pixel format");
+ }
+
+ if (destClip != null) {
+ if (destClip.left < 0 || destClip.top < 0
+ || destClip.right > destination.getWidth()
+ || destClip.bottom > destination.getHeight()) {
+ throw new IllegalArgumentException("destBounds not in destination");
+ }
+ }
+
+ if (transform != null && !transform.isAffine()) {
+ throw new IllegalArgumentException("transform not affine");
+ }
+
+ if (renderMode != RENDER_MODE_FOR_PRINT && renderMode != RENDER_MODE_FOR_DISPLAY) {
+ throw new IllegalArgumentException("Unsupported render mode");
+ }
+
+ if (renderMode == RENDER_MODE_FOR_PRINT && renderMode == RENDER_MODE_FOR_DISPLAY) {
+ throw new IllegalArgumentException("Only single render mode supported");
+ }
+
+ final int contentLeft = (destClip != null) ? destClip.left : 0;
+ final int contentTop = (destClip != null) ? destClip.top : 0;
+ final int contentRight = (destClip != null) ? destClip.right
+ : destination.getWidth();
+ final int contentBottom = (destClip != null) ? destClip.bottom
+ : destination.getHeight();
+
+ final long transformPtr = (transform != null) ? transform.native_instance : 0;
+
+ nativeRenderPage(mNativeDocument, mNativePage, destination.mNativeBitmap, contentLeft,
+ contentTop, contentRight, contentBottom, transformPtr, renderMode);
+ }
+
+ void close() {
+ nativeClosePage(mNativePage);
+ mNativePage = 0;
+ }
+ }
+
+ private static native long nativeCreate(int fd, long size);
+ private static native void nativeClose(long documentPtr);
+ private static native int nativeGetPageCount(long documentPtr);
+ private static native boolean nativeScaleForPrinting(long documentPtr);
+ private static native void nativeRenderPage(long documentPtr, long pagePtr, long destPtr,
+ int destLeft, int destTop, int destRight, int destBottom, long matrixPtr, int renderMode);
+ private static native long nativeOpenPageAndGetSize(long documentPtr, int pageIndex,
+ Point outSize);
+ private static native void nativeClosePage(long pagePtr);
+}