summaryrefslogtreecommitdiffstats
path: root/graphics
diff options
context:
space:
mode:
Diffstat (limited to 'graphics')
-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/LayerRasterizer.java3
-rw-r--r--graphics/java/android/graphics/Matrix.java11
-rw-r--r--graphics/java/android/graphics/Outline.java50
-rw-r--r--graphics/java/android/graphics/Paint.java34
-rw-r--r--graphics/java/android/graphics/Rasterizer.java1
-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/BitmapDrawable.java348
-rw-r--r--graphics/java/android/graphics/drawable/ColorDrawable.java2
-rw-r--r--graphics/java/android/graphics/drawable/GradientDrawable.java14
-rw-r--r--graphics/java/android/graphics/drawable/LayerDrawable.java2
-rw-r--r--graphics/java/android/graphics/drawable/NinePatchDrawable.java225
-rw-r--r--graphics/java/android/graphics/drawable/Ripple.java612
-rw-r--r--graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java295
-rw-r--r--graphics/java/android/graphics/drawable/VectorDrawable.java2
-rw-r--r--graphics/java/android/graphics/pdf/PdfDocument.java4
-rw-r--r--graphics/java/android/graphics/pdf/PdfRenderer.java391
20 files changed, 1513 insertions, 805 deletions
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/LayerRasterizer.java b/graphics/java/android/graphics/LayerRasterizer.java
index 5b35608..e7a24a4 100644
--- a/graphics/java/android/graphics/LayerRasterizer.java
+++ b/graphics/java/android/graphics/LayerRasterizer.java
@@ -16,11 +16,12 @@
package android.graphics;
+@Deprecated
public class LayerRasterizer extends Rasterizer {
public LayerRasterizer() {
native_instance = nativeConstructor();
}
-
+
/** Add a new layer (above any previous layers) to the rasterizer.
The layer will extract those fields that affect the mask from
the specified paint, but will not retain a reference to the paint
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/Outline.java b/graphics/java/android/graphics/Outline.java
index b5c0801..c6ba75c 100644
--- a/graphics/java/android/graphics/Outline.java
+++ b/graphics/java/android/graphics/Outline.java
@@ -53,8 +53,7 @@ public final class Outline {
set(src);
}
- /** @hide */
- public void markInvalid() {
+ public void reset() {
mRadius = 0;
mRect = null;
mPath = null;
@@ -74,27 +73,21 @@ public final class Outline {
*
* @param src Source outline to copy from.
*/
- public void set(@Nullable Outline src) {
- if (src == null) {
- mRadius = 0;
- mRect = null;
- mPath = null;
- } else {
- if (src.mPath != null) {
- if (mPath == null) {
- mPath = new Path();
- }
- mPath.set(src.mPath);
- mRect = null;
+ public void set(@NonNull Outline src) {
+ if (src.mPath != null) {
+ if (mPath == null) {
+ mPath = new Path();
}
- if (src.mRect != null) {
- if (mRect == null) {
- mRect = new Rect();
- }
- mRect.set(src.mRect);
+ mPath.set(src.mPath);
+ mRect = null;
+ }
+ if (src.mRect != null) {
+ if (mRect == null) {
+ mRect = new Rect();
}
- mRadius = src.mRadius;
+ mRect.set(src.mRect);
}
+ mRadius = src.mRadius;
}
/**
@@ -134,6 +127,11 @@ public final class Outline {
* Sets the outline to the oval defined by input rect.
*/
public void setOval(int left, int top, int right, int bottom) {
+ if ((bottom - top) == (right - left)) {
+ // represent circle as round rect, for efficiency, and to enable clipping
+ setRoundRect(left, top, right, bottom, (bottom - top) / 2.0f);
+ return;
+ }
mRect = null;
if (mPath == null) mPath = new Path();
mPath.reset();
@@ -160,4 +158,16 @@ public final class Outline {
mRadius = -1.0f;
mPath.set(convexPath);
}
+
+ /**
+ * Returns whether the outline can be used to clip a View.
+ *
+ * Currently, only outlines that can be represented as a rectangle, circle, or round rect
+ * support clipping.
+ *
+ * @see {@link View#setClipToOutline(boolean)}
+ */
+ public boolean canClip() {
+ return mRect != null;
+ }
}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 457b3ea..92cfd6b 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1064,14 +1064,17 @@ public class Paint {
mNativeTypeface = typefaceNative;
return typeface;
}
-
+
/**
* Get the paint's rasterizer (or null).
* <p />
* The raster controls/modifies how paths/text are turned into alpha masks.
*
* @return the paint's rasterizer (or null)
+ *
+ * @deprecated Rasterizer is not supported by either the HW or PDF backends.
*/
+ @Deprecated
public Rasterizer getRasterizer() {
return mRasterizer;
}
@@ -1085,7 +1088,10 @@ public class Paint {
* @param rasterizer May be null. The new rasterizer to be installed in
* the paint.
* @return rasterizer
+ *
+ * @deprecated Rasterizer is not supported by either the HW or PDF backends.
*/
+ @Deprecated
public Rasterizer setRasterizer(Rasterizer rasterizer) {
long rasterizerNative = 0;
if (rasterizer != null) {
@@ -1095,7 +1101,7 @@ public class Paint {
mRasterizer = rasterizer;
return rasterizer;
}
-
+
/**
* This draws a shadow layer below the main layer, with the specified
* offset and color, and blur radius. If radius is 0, then the shadow
@@ -1655,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;
@@ -1737,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;
@@ -1832,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);
@@ -1963,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);
@@ -2234,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/Rasterizer.java b/graphics/java/android/graphics/Rasterizer.java
index 817814c..c351d94e 100644
--- a/graphics/java/android/graphics/Rasterizer.java
+++ b/graphics/java/android/graphics/Rasterizer.java
@@ -21,6 +21,7 @@
package android.graphics;
+@Deprecated
public class Rasterizer {
protected void finalize() throws Throwable {
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/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index 60b4615..6755f3e 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -67,15 +67,22 @@ import java.io.IOException;
* @attr ref android.R.styleable#BitmapDrawable_tileMode
*/
public class BitmapDrawable extends Drawable {
-
private static final int DEFAULT_PAINT_FLAGS =
Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG;
+
+ // Constants for {@link android.R.styleable#BitmapDrawable_tileMode}.
+ private static final int TILE_MODE_UNDEFINED = -2;
+ private static final int TILE_MODE_DISABLED = -1;
+ private static final int TILE_MODE_CLAMP = 0;
+ private static final int TILE_MODE_REPEAT = 1;
+ private static final int TILE_MODE_MIRROR = 2;
+
+ private final Rect mDstRect = new Rect(); // Gravity.apply() sets this
+
private BitmapState mBitmapState;
- private Bitmap mBitmap;
private PorterDuffColorFilter mTintFilter;
- private int mTargetDensity;
- private final Rect mDstRect = new Rect(); // Gravity.apply() sets this
+ private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
private boolean mApplyGravity;
private boolean mMutated;
@@ -100,11 +107,12 @@ public class BitmapDrawable extends Drawable {
/**
* Create an empty drawable, setting initial target density based on
* the display metrics of the resources.
+ *
* @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)}
* instead to specify a bitmap to draw with.
*/
+ @SuppressWarnings("unused")
@Deprecated
- @SuppressWarnings({"UnusedParameters"})
public BitmapDrawable(Resources res) {
mBitmapState = new BitmapState((Bitmap) null);
mBitmapState.mTargetDensity = mTargetDensity;
@@ -137,7 +145,7 @@ public class BitmapDrawable extends Drawable {
@Deprecated
public BitmapDrawable(String filepath) {
this(new BitmapState(BitmapFactory.decodeFile(filepath)), null, null);
- if (mBitmap == null) {
+ if (mBitmapState.mBitmap == null) {
android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
}
}
@@ -145,11 +153,11 @@ public class BitmapDrawable extends Drawable {
/**
* Create a drawable by opening a given file path and decoding the bitmap.
*/
- @SuppressWarnings({"UnusedParameters"})
+ @SuppressWarnings("unused")
public BitmapDrawable(Resources res, String filepath) {
this(new BitmapState(BitmapFactory.decodeFile(filepath)), null, null);
mBitmapState.mTargetDensity = mTargetDensity;
- if (mBitmap == null) {
+ if (mBitmapState.mBitmap == null) {
android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
}
}
@@ -162,7 +170,7 @@ public class BitmapDrawable extends Drawable {
@Deprecated
public BitmapDrawable(java.io.InputStream is) {
this(new BitmapState(BitmapFactory.decodeStream(is)), null, null);
- if (mBitmap == null) {
+ if (mBitmapState.mBitmap == null) {
android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
}
}
@@ -170,11 +178,11 @@ public class BitmapDrawable extends Drawable {
/**
* Create a drawable by decoding a bitmap from the given input stream.
*/
- @SuppressWarnings({"UnusedParameters"})
+ @SuppressWarnings("unused")
public BitmapDrawable(Resources res, java.io.InputStream is) {
this(new BitmapState(BitmapFactory.decodeStream(is)), null, null);
mBitmapState.mTargetDensity = mTargetDensity;
- if (mBitmap == null) {
+ if (mBitmapState.mBitmap == null) {
android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
}
}
@@ -190,22 +198,23 @@ public class BitmapDrawable extends Drawable {
* Returns the bitmap used by this drawable to render. May be null.
*/
public final Bitmap getBitmap() {
- return mBitmap;
+ return mBitmapState.mBitmap;
}
private void computeBitmapSize() {
- mBitmapWidth = mBitmap.getScaledWidth(mTargetDensity);
- mBitmapHeight = mBitmap.getScaledHeight(mTargetDensity);
+ final Bitmap bitmap = mBitmapState.mBitmap;
+ if (bitmap != null) {
+ mBitmapWidth = bitmap.getScaledWidth(mTargetDensity);
+ mBitmapHeight = bitmap.getScaledHeight(mTargetDensity);
+ } else {
+ mBitmapWidth = mBitmapHeight = -1;
+ }
}
private void setBitmap(Bitmap bitmap) {
- if (bitmap != mBitmap) {
- mBitmap = bitmap;
- if (bitmap != null) {
- computeBitmapSize();
- } else {
- mBitmapWidth = mBitmapHeight = -1;
- }
+ if (mBitmapState.mBitmap != bitmap) {
+ mBitmapState.mBitmap = bitmap;
+ computeBitmapSize();
invalidateSelf();
}
}
@@ -247,7 +256,7 @@ public class BitmapDrawable extends Drawable {
public void setTargetDensity(int density) {
if (mTargetDensity != density) {
mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
- if (mBitmap != null) {
+ if (mBitmapState.mBitmap != null) {
computeBitmapSize();
}
invalidateSelf();
@@ -343,8 +352,9 @@ public class BitmapDrawable extends Drawable {
/**
* Indicates the repeat behavior of this drawable on the X axis.
*
- * @return {@link Shader.TileMode#CLAMP} if the bitmap does not repeat,
- * {@link Shader.TileMode#REPEAT} or {@link Shader.TileMode#MIRROR} otherwise.
+ * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat,
+ * {@link android.graphics.Shader.TileMode#REPEAT} or
+ * {@link android.graphics.Shader.TileMode#MIRROR} otherwise.
*/
public Shader.TileMode getTileModeX() {
return mBitmapState.mTileModeX;
@@ -353,8 +363,9 @@ public class BitmapDrawable extends Drawable {
/**
* Indicates the repeat behavior of this drawable on the Y axis.
*
- * @return {@link Shader.TileMode#CLAMP} if the bitmap does not repeat,
- * {@link Shader.TileMode#REPEAT} or {@link Shader.TileMode#MIRROR} otherwise.
+ * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat,
+ * {@link android.graphics.Shader.TileMode#REPEAT} or
+ * {@link android.graphics.Shader.TileMode#MIRROR} otherwise.
*/
public Shader.TileMode getTileModeY() {
return mBitmapState.mTileModeY;
@@ -362,9 +373,9 @@ public class BitmapDrawable extends Drawable {
/**
* Sets the repeat behavior of this drawable on the X axis. By default, the drawable
- * does not repeat its bitmap. Using {@link Shader.TileMode#REPEAT} or
- * {@link Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) if the bitmap
- * is smaller than this drawable.
+ * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
+ * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
+ * if the bitmap is smaller than this drawable.
*
* @param mode The repeat mode for this drawable.
*
@@ -377,9 +388,9 @@ public class BitmapDrawable extends Drawable {
/**
* Sets the repeat behavior of this drawable on the Y axis. By default, the drawable
- * does not repeat its bitmap. Using {@link Shader.TileMode#REPEAT} or
- * {@link Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) if the bitmap
- * is smaller than this drawable.
+ * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
+ * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
+ * if the bitmap is smaller than this drawable.
*
* @param mode The repeat mode for this drawable.
*
@@ -392,9 +403,9 @@ public class BitmapDrawable extends Drawable {
/**
* Sets the repeat behavior of this drawable on both axis. By default, the drawable
- * does not repeat its bitmap. Using {@link Shader.TileMode#REPEAT} or
- * {@link Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) if the bitmap
- * is smaller than this drawable.
+ * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
+ * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
+ * if the bitmap is smaller than this drawable.
*
* @param xmode The X repeat mode for this drawable.
* @param ymode The Y repeat mode for this drawable.
@@ -462,7 +473,7 @@ public class BitmapDrawable extends Drawable {
@Override
public void draw(Canvas canvas) {
- final Bitmap bitmap = mBitmap;
+ final Bitmap bitmap = mBitmapState.mBitmap;
if (bitmap == null) {
return;
}
@@ -589,7 +600,7 @@ public class BitmapDrawable extends Drawable {
public void setTint(ColorStateList tint) {
if (mBitmapState.mTint != tint) {
mBitmapState.mTint = tint;
- updateTintFilter();
+ computeTintFilter();
invalidateSelf();
}
}
@@ -612,7 +623,7 @@ public class BitmapDrawable extends Drawable {
public void setTintMode(Mode tintMode) {
if (mBitmapState.mTintMode != tintMode) {
mBitmapState.mTintMode = tintMode;
- updateTintFilter();
+ computeTintFilter();
invalidateSelf();
}
}
@@ -624,18 +635,15 @@ public class BitmapDrawable extends Drawable {
return mBitmapState.mTintMode;
}
- /**
- * Ensures the tint filter is consistent with the current tint color and
- * mode.
- */
- private void updateTintFilter() {
- final ColorStateList tint = mBitmapState.mTint;
- final Mode tintMode = mBitmapState.mTintMode;
- if (tint != null && tintMode != null) {
- if (mTintFilter == null) {
- mTintFilter = new PorterDuffColorFilter(0, tintMode);
+ private void computeTintFilter() {
+ final BitmapState state = mBitmapState;
+ if (state.mTint != null && state.mTintMode != null) {
+ final int color = state.mTint.getColorForState(getState(), 0);
+ if (mTintFilter != null) {
+ mTintFilter.setColor(color);
+ mTintFilter.setMode(state.mTintMode);
} else {
- mTintFilter.setMode(tintMode);
+ mTintFilter = new PorterDuffColorFilter(color, state.mTintMode);
}
} else {
mTintFilter = null;
@@ -693,16 +701,15 @@ public class BitmapDrawable extends Drawable {
throws XmlPullParserException, IOException {
super.inflate(r, parser, attrs, theme);
- final TypedArray a = obtainAttributes(
- r, theme, attrs, R.styleable.BitmapDrawable);
- inflateStateFromTypedArray(a);
+ final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.BitmapDrawable);
+ updateStateFromTypedArray(a);
a.recycle();
}
/**
- * Initializes the constant state from the values in the typed array.
+ * Updates the constant state from the values in the typed array.
*/
- private void inflateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
+ private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
final Resources r = a.getResources();
final BitmapState state = mBitmapState;
@@ -710,86 +717,52 @@ public class BitmapDrawable extends Drawable {
final int[] themeAttrs = a.extractThemeAttrs();
state.mThemeAttrs = themeAttrs;
- if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_src] == 0) {
- final int id = a.getResourceId(R.styleable.BitmapDrawable_src, 0);
- if (id == 0) {
- throw new XmlPullParserException(a.getPositionDescription() +
- ": <bitmap> requires a valid src attribute");
- }
-
- final Bitmap bitmap = BitmapFactory.decodeResource(r, id);
+ final int srcResId = a.getResourceId(R.styleable.BitmapDrawable_src, 0);
+ if (srcResId != 0) {
+ final Bitmap bitmap = BitmapFactory.decodeResource(r, srcResId);
if (bitmap == null) {
throw new XmlPullParserException(a.getPositionDescription() +
": <bitmap> requires a valid src attribute");
}
+
state.mBitmap = bitmap;
- setBitmap(bitmap);
}
- setTargetDensity(r.getDisplayMetrics());
+ state.mTargetDensity = r.getDisplayMetrics().densityDpi;
- if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_mipMap] == 0) {
- final boolean defMipMap = state.mBitmap != null ? state.mBitmap.hasMipMap() : false;
- final boolean mipMap = a.getBoolean(
- R.styleable.BitmapDrawable_mipMap, defMipMap);
- setMipMap(mipMap);
- }
+ final boolean defMipMap = state.mBitmap != null ? state.mBitmap.hasMipMap() : false;
+ setMipMap(a.getBoolean(R.styleable.BitmapDrawable_mipMap, defMipMap));
- if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_autoMirrored] == 0) {
- final boolean autoMirrored = a.getBoolean(
- R.styleable.BitmapDrawable_autoMirrored, false);
- setAutoMirrored(autoMirrored);
- }
+ state.mAutoMirrored = a.getBoolean(
+ R.styleable.BitmapDrawable_autoMirrored, state.mAutoMirrored);
+ state.mBaseAlpha = a.getFloat(R.styleable.BitmapDrawable_alpha, state.mBaseAlpha);
- if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_tintMode] == 0) {
- final int tintModeValue = a.getInt(
- R.styleable.BitmapDrawable_tintMode, -1);
- state.mTintMode = Drawable.parseTintMode(tintModeValue, Mode.SRC_IN);
+ final int tintMode = a.getInt(R.styleable.BitmapDrawable_tintMode, -1);
+ if (tintMode != -1) {
+ state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
}
- if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_tint] == 0) {
- state.mTint = a.getColorStateList(R.styleable.BitmapDrawable_tint);
- if (state.mTint != null) {
- final int color = state.mTint.getColorForState(getState(), 0);
- mTintFilter = new PorterDuffColorFilter(color, mBitmapState.mTintMode);
- }
+ final ColorStateList tint = a.getColorStateList(R.styleable.BitmapDrawable_tint);
+ if (tint != null) {
+ state.mTint = tint;
}
final Paint paint = mBitmapState.mPaint;
+ paint.setAntiAlias(a.getBoolean(
+ R.styleable.BitmapDrawable_antialias, paint.isAntiAlias()));
+ paint.setFilterBitmap(a.getBoolean(
+ R.styleable.BitmapDrawable_filter, paint.isFilterBitmap()));
+ paint.setDither(a.getBoolean(R.styleable.BitmapDrawable_dither, paint.isDither()));
- if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_antialias] == 0) {
- final boolean antiAlias = a.getBoolean(
- R.styleable.BitmapDrawable_antialias, paint.isAntiAlias());
- paint.setAntiAlias(antiAlias);
- }
-
- if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_filter] == 0) {
- final boolean filter = a.getBoolean(
- R.styleable.BitmapDrawable_filter, paint.isFilterBitmap());
- paint.setFilterBitmap(filter);
- }
+ setGravity(a.getInt(R.styleable.BitmapDrawable_gravity, state.mGravity));
- if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_dither] == 0) {
- final boolean dither = a.getBoolean(
- R.styleable.BitmapDrawable_dither, paint.isDither());
- paint.setDither(dither);
- }
-
- if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_alpha] == 0) {
- state.mBaseAlpha = a.getFloat(R.styleable.BitmapDrawable_alpha, 1.0f);
- }
-
- if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_gravity] == 0) {
- final int gravity = a.getInt(
- R.styleable.BitmapDrawable_gravity, Gravity.FILL);
- setGravity(gravity);
- }
-
- if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_tileMode] == 0) {
- final int tileMode = a.getInt(
- R.styleable.BitmapDrawable_tileMode, -1);
+ final int tileMode = a.getInt(R.styleable.BitmapDrawable_tileMode, TILE_MODE_UNDEFINED);
+ if (tileMode != TILE_MODE_UNDEFINED) {
setTileModeInternal(tileMode);
}
+
+ // Update local properties.
+ initializeWithState(state, r);
}
@Override
@@ -797,120 +770,32 @@ public class BitmapDrawable extends Drawable {
super.applyTheme(t);
final BitmapState state = mBitmapState;
- if (state == null) {
- throw new RuntimeException("Can't apply theme to <bitmap> with no constant state");
+ if (state == null || state.mThemeAttrs == null) {
+ return;
}
- final int[] themeAttrs = state.mThemeAttrs;
- if (themeAttrs != null) {
- final TypedArray a = t.resolveAttributes(themeAttrs, R.styleable.BitmapDrawable, 0, 0);
+ final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.BitmapDrawable);
+ try {
updateStateFromTypedArray(a);
+ } catch (XmlPullParserException e) {
+ throw new RuntimeException(e);
+ } finally {
a.recycle();
}
}
- /**
- * Updates the constant state from the values in the typed array.
- */
- private void updateStateFromTypedArray(TypedArray a) {
- final Resources r = a.getResources();
- final BitmapState state = mBitmapState;
- final Paint paint = mBitmapState.mPaint;
-
- if (a.hasValue(R.styleable.BitmapDrawable_antialias)) {
- final boolean antiAlias = a.getBoolean(
- R.styleable.BitmapDrawable_antialias, paint.isAntiAlias());
- paint.setAntiAlias(antiAlias);
- }
-
- if (a.hasValue(R.styleable.BitmapDrawable_filter)) {
- final boolean filter = a.getBoolean(
- R.styleable.BitmapDrawable_filter, paint.isFilterBitmap());
- paint.setFilterBitmap(filter);
- }
-
- if (a.hasValue(R.styleable.BitmapDrawable_dither)) {
- final boolean dither = a.getBoolean(
- R.styleable.BitmapDrawable_dither, paint.isDither());
- paint.setDither(dither);
- }
-
- if (a.hasValue(R.styleable.BitmapDrawable_alpha)) {
- state.mBaseAlpha = a.getFloat(R.styleable.BitmapDrawable_alpha, state.mBaseAlpha);
- }
-
- if (a.hasValue(R.styleable.BitmapDrawable_gravity)) {
- final int gravity = a.getInt(
- R.styleable.BitmapDrawable_gravity, Gravity.FILL);
- setGravity(gravity);
- }
-
- if (a.hasValue(R.styleable.BitmapDrawable_tileMode)) {
- final int tileMode = a.getInt(
- R.styleable.BitmapDrawable_tileMode, -1);
- setTileModeInternal(tileMode);
- }
-
- if (a.hasValue(R.styleable.BitmapDrawable_src)) {
- final int id = a.getResourceId(R.styleable.BitmapDrawable_src, 0);
- if (id == 0) {
- throw new RuntimeException(a.getPositionDescription() +
- ": <bitmap> requires a valid src attribute");
- }
-
- final Bitmap bitmap = BitmapFactory.decodeResource(r, id);
- if (bitmap == null) {
- throw new RuntimeException(a.getPositionDescription() +
- ": <bitmap> requires a valid src attribute");
- }
-
- setBitmap(bitmap);
- }
-
- setTargetDensity(r.getDisplayMetrics());
-
- if (a.hasValue(R.styleable.BitmapDrawable_mipMap)) {
- final boolean mipMap = a.getBoolean(
- R.styleable.BitmapDrawable_mipMap,
- state.mBitmap.hasMipMap());
- setMipMap(mipMap);
- }
-
- if (a.hasValue(R.styleable.BitmapDrawable_autoMirrored)) {
- final boolean autoMirrored = a.getBoolean(
- R.styleable.BitmapDrawable_autoMirrored, false);
- setAutoMirrored(autoMirrored);
- }
-
- if (a.hasValue(R.styleable.BitmapDrawable_tintMode)) {
- final int modeValue = a.getInt(
- R.styleable.BitmapDrawable_tintMode, -1);
- state.mTintMode = Drawable.parseTintMode(modeValue, Mode.SRC_IN);
- }
-
- if (a.hasValue(R.styleable.BitmapDrawable_tint)) {
- final ColorStateList tint = a.getColorStateList(
- R.styleable.BitmapDrawable_tint);
- if (tint != null) {
- state.mTint = tint;
- final int color = tint.getColorForState(getState(), 0);
- mTintFilter = new PorterDuffColorFilter(color, state.mTintMode);
- }
- }
- }
-
private void setTileModeInternal(final int tileMode) {
switch (tileMode) {
- case -1:
- // Do nothing.
+ case TILE_MODE_DISABLED:
+ setTileModeXY(null, null);
break;
- case 0:
+ case TILE_MODE_CLAMP:
setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
break;
- case 1:
+ case TILE_MODE_REPEAT:
setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
break;
- case 2:
+ case TILE_MODE_MIRROR:
setTileModeXY(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);
break;
}
@@ -936,8 +821,9 @@ public class BitmapDrawable extends Drawable {
if (mBitmapState.mGravity != Gravity.FILL) {
return PixelFormat.TRANSLUCENT;
}
- Bitmap bm = mBitmap;
- return (bm == null || bm.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ?
+
+ final Bitmap bitmap = mBitmapState.mBitmap;
+ return (bitmap == null || bitmap.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ?
PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
}
@@ -948,22 +834,26 @@ public class BitmapDrawable extends Drawable {
}
final static class BitmapState extends ConstantState {
- Bitmap mBitmap;
- ColorStateList mTint;
+ final Paint mPaint;
+
+ // Values loaded during inflation.
+ int[] mThemeAttrs = null;
+ Bitmap mBitmap = null;
+ ColorStateList mTint = null;
Mode mTintMode = Mode.SRC_IN;
- int[] mThemeAttrs;
- int mChangingConfigurations;
int mGravity = Gravity.FILL;
float mBaseAlpha = 1.0f;
- Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS);
Shader.TileMode mTileModeX = null;
Shader.TileMode mTileModeY = null;
int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
+ boolean mAutoMirrored = false;
+
+ int mChangingConfigurations;
boolean mRebuildShader;
- boolean mAutoMirrored;
BitmapState(Bitmap bitmap) {
mBitmap = bitmap;
+ mPaint = new Paint(DEFAULT_PAINT_FLAGS);
}
BitmapState(BitmapState bitmapState) {
@@ -1013,6 +903,10 @@ public class BitmapDrawable extends Drawable {
}
}
+ /**
+ * The one constructor to rule them all. This is called by all public
+ * constructors to set the state and initialize local properties.
+ */
private BitmapDrawable(BitmapState state, Resources res, Theme theme) {
if (theme != null && state.canApplyTheme()) {
mBitmapState = new BitmapState(state);
@@ -1034,11 +928,7 @@ public class BitmapDrawable extends Drawable {
mTargetDensity = state.mTargetDensity;
}
- if (state.mTint != null) {
- final int color = state.mTint.getColorForState(getState(), 0);
- mTintFilter = new PorterDuffColorFilter(color, state.mTintMode);
- }
-
- setBitmap(state.mBitmap);
+ computeTintFilter();
+ computeBitmapSize();
}
}
diff --git a/graphics/java/android/graphics/drawable/ColorDrawable.java b/graphics/java/android/graphics/drawable/ColorDrawable.java
index 8243b7c..df5ca33 100644
--- a/graphics/java/android/graphics/drawable/ColorDrawable.java
+++ b/graphics/java/android/graphics/drawable/ColorDrawable.java
@@ -199,7 +199,7 @@ public class ColorDrawable extends Drawable {
final int[] themeAttrs = state.mThemeAttrs;
if (themeAttrs != null) {
- final TypedArray a = t.resolveAttributes(themeAttrs, R.styleable.ColorDrawable, 0, 0);
+ final TypedArray a = t.resolveAttributes(themeAttrs, R.styleable.ColorDrawable);
updateStateFromTypedArray(a);
a.recycle();
}
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index dc06350..8fe1544 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -1053,7 +1053,7 @@ public class GradientDrawable extends Drawable {
final int[] themeAttrs = state.mThemeAttrs;
if (themeAttrs != null) {
final TypedArray a = t.resolveAttributes(
- themeAttrs, R.styleable.GradientDrawable, 0, 0);
+ themeAttrs, R.styleable.GradientDrawable);
updateStateFromTypedArray(a);
a.recycle();
@@ -1123,37 +1123,37 @@ public class GradientDrawable extends Drawable {
TypedArray a;
if (state.mAttrSize != null) {
- a = t.resolveAttributes(state.mAttrSize, R.styleable.GradientDrawableSize, 0, 0);
+ a = t.resolveAttributes(state.mAttrSize, R.styleable.GradientDrawableSize);
// TODO: updateGradientDrawableSize(a);
a.recycle();
}
if (state.mAttrGradient != null) {
- a = t.resolveAttributes(state.mAttrGradient, R.styleable.GradientDrawableGradient, 0, 0);
+ a = t.resolveAttributes(state.mAttrGradient, R.styleable.GradientDrawableGradient);
// TODO: updateGradientDrawableGradient(a);
a.recycle();
}
if (state.mAttrSolid != null) {
- a = t.resolveAttributes(state.mAttrSolid, R.styleable.GradientDrawableSolid, 0, 0);
+ a = t.resolveAttributes(state.mAttrSolid, R.styleable.GradientDrawableSolid);
// TODO: updateGradientDrawableSolid(a);
a.recycle();
}
if (state.mAttrStroke != null) {
- a = t.resolveAttributes(state.mAttrStroke, R.styleable.GradientDrawableStroke, 0, 0);
+ a = t.resolveAttributes(state.mAttrStroke, R.styleable.GradientDrawableStroke);
// TODO: updateGradientDrawableStroke(a);
a.recycle();
}
if (state.mAttrCorners != null) {
- a = t.resolveAttributes(state.mAttrCorners, R.styleable.DrawableCorners, 0, 0);
+ a = t.resolveAttributes(state.mAttrCorners, R.styleable.DrawableCorners);
// TODO: updateDrawableCorners(a);
a.recycle();
}
if (state.mAttrPadding != null) {
- a = t.resolveAttributes(state.mAttrPadding, R.styleable.GradientDrawablePadding, 0, 0);
+ a = t.resolveAttributes(state.mAttrPadding, R.styleable.GradientDrawablePadding);
// TODO: updateGradientDrawablePadding(a);
a.recycle();
}
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index 639d719..7847aad 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -229,7 +229,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback {
final int[] themeAttrs = state.mThemeAttrs;
if (themeAttrs != null) {
- final TypedArray a = t.resolveAttributes(themeAttrs, R.styleable.LayerDrawable, 0, 0);
+ final TypedArray a = t.resolveAttributes(themeAttrs, R.styleable.LayerDrawable);
updateStateFromTypedArray(a);
a.recycle();
}
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index 21992ce..3e09707 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -72,8 +72,8 @@ public class NinePatchDrawable extends Drawable {
private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
// These are scaled to match the target density.
- private int mBitmapWidth;
- private int mBitmapHeight;
+ private int mBitmapWidth = -1;
+ private int mBitmapHeight = -1;
NinePatchDrawable() {
mNinePatchState = new NinePatchState();
@@ -209,7 +209,7 @@ public class NinePatchDrawable extends Drawable {
}
private void setNinePatch(NinePatch ninePatch) {
- if (ninePatch != mNinePatch) {
+ if (mNinePatch != ninePatch) {
mNinePatch = ninePatch;
if (ninePatch != null) {
computeBitmapSize();
@@ -339,7 +339,7 @@ public class NinePatchDrawable extends Drawable {
public void setTint(ColorStateList tint) {
if (mNinePatchState.mTint != tint) {
mNinePatchState.mTint = tint;
- updateTintFilter();
+ computeTintFilter();
invalidateSelf();
}
}
@@ -362,23 +362,20 @@ public class NinePatchDrawable extends Drawable {
public void setTintMode(Mode tintMode) {
if (mNinePatchState.mTintMode != tintMode) {
mNinePatchState.mTintMode = tintMode;
- updateTintFilter();
+ computeTintFilter();
invalidateSelf();
}
}
- /**
- * Ensures the tint filter is consistent with the current tint color and
- * mode.
- */
- private void updateTintFilter() {
- final ColorStateList tint = mNinePatchState.mTint;
- final Mode tintMode = mNinePatchState.mTintMode;
- if (tint != null && tintMode != null) {
- if (mTintFilter == null) {
- mTintFilter = new PorterDuffColorFilter(0, tintMode);
+ private void computeTintFilter() {
+ final NinePatchState state = mNinePatchState;
+ if (state.mTint != null && state.mTintMode != null) {
+ final int color = state.mTint.getColorForState(getState(), 0);
+ if (mTintFilter != null) {
+ mTintFilter.setColor(color);
+ mTintFilter.setMode(state.mTintMode);
} else {
- mTintFilter.setMode(tintMode);
+ mTintFilter = new PorterDuffColorFilter(color, state.mTintMode);
}
} else {
mTintFilter = null;
@@ -422,38 +419,28 @@ public class NinePatchDrawable extends Drawable {
throws XmlPullParserException, IOException {
super.inflate(r, parser, attrs, theme);
- final TypedArray a = obtainAttributes(
- r, theme, attrs, R.styleable.NinePatchDrawable);
- inflateStateFromTypedArray(a);
+ final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.NinePatchDrawable);
+ updateStateFromTypedArray(a);
a.recycle();
}
/**
- * Initializes the constant state from the values in the typed array.
+ * Updates the constant state from the values in the typed array.
*/
- private void inflateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
+ private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
final Resources r = a.getResources();
- final NinePatchState ninePatchState = mNinePatchState;
+ final NinePatchState state = mNinePatchState;
// Extract the theme attributes, if any.
final int[] themeAttrs = a.extractThemeAttrs();
- ninePatchState.mThemeAttrs = themeAttrs;
+ state.mThemeAttrs = themeAttrs;
- if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_dither] == 0) {
- final boolean dither = a.getBoolean(
- R.styleable.NinePatchDrawable_dither, DEFAULT_DITHER);
- ninePatchState.mDither = dither;
- }
-
- if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_src] == 0) {
- final int id = a.getResourceId(R.styleable.NinePatchDrawable_src, 0);
- if (id == 0) {
- throw new XmlPullParserException(a.getPositionDescription() +
- ": <nine-patch> requires a valid src attribute");
- }
+ state.mDither = a.getBoolean(R.styleable.NinePatchDrawable_dither, state.mDither);
+ final int srcResId = a.getResourceId(R.styleable.NinePatchDrawable_src, 0);
+ if (srcResId != 0) {
final BitmapFactory.Options options = new BitmapFactory.Options();
- options.inDither = !ninePatchState.mDither;
+ options.inDither = !state.mDither;
options.inScreenDensity = r.getDisplayMetrics().noncompatDensityDpi;
final Rect padding = new Rect();
@@ -462,7 +449,7 @@ public class NinePatchDrawable extends Drawable {
try {
final TypedValue value = new TypedValue();
- final InputStream is = r.openRawResource(id, value);
+ final InputStream is = r.openRawResource(srcResId, value);
bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options);
@@ -479,40 +466,30 @@ public class NinePatchDrawable extends Drawable {
": <nine-patch> requires a valid 9-patch source image");
}
- final NinePatch ninePatch = new NinePatch(bitmap, bitmap.getNinePatchChunk());
- ninePatchState.mNinePatch = ninePatch;
- ninePatchState.mPadding = padding;
- ninePatchState.mOpticalInsets = Insets.of(opticalInsets);
- }
-
- if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_autoMirrored] == 0) {
- final boolean autoMirrored = a.getBoolean(
- R.styleable.NinePatchDrawable_autoMirrored, false);
- ninePatchState.mAutoMirrored = autoMirrored;
+ state.mNinePatch = new NinePatch(bitmap, bitmap.getNinePatchChunk());
+ state.mPadding = padding;
+ state.mOpticalInsets = Insets.of(opticalInsets);
}
- if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_tintMode] == 0) {
- final int tintModeValue = a.getInt(R.styleable.NinePatchDrawable_tintMode, -1);
- ninePatchState.mTintMode = Drawable.parseTintMode(tintModeValue, Mode.SRC_IN);
- }
+ state.mAutoMirrored = a.getBoolean(
+ R.styleable.NinePatchDrawable_autoMirrored, state.mAutoMirrored);
+ state.mBaseAlpha = a.getFloat(R.styleable.NinePatchDrawable_alpha, state.mBaseAlpha);
- if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_tint] == 0) {
- ninePatchState.mTint = a.getColorStateList(R.styleable.NinePatchDrawable_tint);
- if (ninePatchState.mTint != null) {
- final int color = ninePatchState.mTint.getColorForState(getState(), 0);
- mTintFilter = new PorterDuffColorFilter(color, ninePatchState.mTintMode);
- }
+ final int tintMode = a.getInt(R.styleable.NinePatchDrawable_tintMode, -1);
+ if (tintMode != -1) {
+ state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
}
- if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_alpha] == 0) {
- ninePatchState.mBaseAlpha = a.getFloat(R.styleable.NinePatchDrawable_alpha, 1.0f);
+ final ColorStateList tint = a.getColorStateList(R.styleable.NinePatchDrawable_tint);
+ if (tint != null) {
+ state.mTint = tint;
}
- // Apply the constant state to the paint.
- initializeWithState(ninePatchState, r);
+ // Update local properties.
+ initializeWithState(state, r);
// Push density applied by setNinePatchState into state.
- ninePatchState.mTargetDensity = mTargetDensity;
+ state.mTargetDensity = mTargetDensity;
}
@Override
@@ -520,98 +497,20 @@ public class NinePatchDrawable extends Drawable {
super.applyTheme(t);
final NinePatchState state = mNinePatchState;
- if (state == null) {
- throw new RuntimeException("Can't apply theme to <nine-patch> with no constant state");
+ if (state == null || state.mThemeAttrs == null) {
+ return;
}
- final int[] themeAttrs = state.mThemeAttrs;
- if (themeAttrs != null) {
- final TypedArray a = t.resolveAttributes(
- themeAttrs, R.styleable.NinePatchDrawable, 0, 0);
+ final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.NinePatchDrawable);
+ try {
updateStateFromTypedArray(a);
+ } catch (XmlPullParserException e) {
+ throw new RuntimeException(e);
+ } finally {
a.recycle();
}
}
- /**
- * Updates the constant state from the values in the typed array.
- */
- private void updateStateFromTypedArray(TypedArray a) {
- final Resources r = a.getResources();
- final NinePatchState state = mNinePatchState;
-
- if (a.hasValue(R.styleable.NinePatchDrawable_dither)) {
- state.mDither = a.getBoolean(R.styleable.NinePatchDrawable_dither, DEFAULT_DITHER);
- }
-
- if (a.hasValue(R.styleable.NinePatchDrawable_autoMirrored)) {
- state.mAutoMirrored = a.getBoolean(R.styleable.NinePatchDrawable_autoMirrored, false);
- }
-
- if (a.hasValue(R.styleable.NinePatchDrawable_src)) {
- final int id = a.getResourceId(R.styleable.NinePatchDrawable_src, 0);
- if (id == 0) {
- throw new RuntimeException(a.getPositionDescription() +
- ": <nine-patch> requires a valid src attribute");
- }
-
- final BitmapFactory.Options options = new BitmapFactory.Options();
- options.inDither = !state.mDither;
- options.inScreenDensity = r.getDisplayMetrics().noncompatDensityDpi;
-
- final Rect padding = new Rect();
- final Rect opticalInsets = new Rect();
- Bitmap bitmap = null;
-
- try {
- final TypedValue value = new TypedValue();
- final InputStream is = r.openRawResource(id, value);
-
- bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options);
-
- is.close();
- } catch (IOException e) {
- // Ignore
- }
-
- if (bitmap == null) {
- throw new RuntimeException(a.getPositionDescription() +
- ": <nine-patch> requires a valid src attribute");
- } else if (bitmap.getNinePatchChunk() == null) {
- throw new RuntimeException(a.getPositionDescription() +
- ": <nine-patch> requires a valid 9-patch source image");
- }
-
- state.mNinePatch = new NinePatch(bitmap, bitmap.getNinePatchChunk());
- state.mPadding = padding;
- state.mOpticalInsets = Insets.of(opticalInsets);
- }
-
- if (a.hasValue(R.styleable.NinePatchDrawable_tintMode)) {
- final int modeValue = a.getInt(R.styleable.NinePatchDrawable_tintMode, -1);
- state.mTintMode = Drawable.parseTintMode(modeValue, Mode.SRC_IN);
- }
-
- if (a.hasValue(R.styleable.NinePatchDrawable_tint)) {
- final ColorStateList tint = a.getColorStateList(R.styleable.NinePatchDrawable_tint);
- if (tint != null) {
- state.mTint = tint;
- final int color = tint.getColorForState(getState(), 0);
- mTintFilter = new PorterDuffColorFilter(color, state.mTintMode);
- }
- }
-
- if (a.hasValue(R.styleable.NinePatchDrawable_alpha)) {
- state.mBaseAlpha = a.getFloat(R.styleable.NinePatchDrawable_alpha, 1.0f);
- }
-
- // Apply the constant state to the paint.
- initializeWithState(state, r);
-
- // Push density applied by setNinePatchState into state.
- state.mTargetDensity = mTargetDensity;
- }
-
@Override
public boolean canApplyTheme() {
return mNinePatchState != null && mNinePatchState.mThemeAttrs != null;
@@ -705,17 +604,19 @@ public class NinePatchDrawable extends Drawable {
}
final static class NinePatchState extends ConstantState {
- NinePatch mNinePatch;
- ColorStateList mTint;
+ // Values loaded during inflation.
+ int[] mThemeAttrs = null;
+ NinePatch mNinePatch = null;
+ ColorStateList mTint = null;
Mode mTintMode = Mode.SRC_IN;
- Rect mPadding;
- Insets mOpticalInsets;
+ Rect mPadding = null;
+ Insets mOpticalInsets = Insets.NONE;
float mBaseAlpha = 1.0f;
- boolean mDither;
- int[] mThemeAttrs;
- int mChangingConfigurations;
+ boolean mDither = DEFAULT_DITHER;
int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
- boolean mAutoMirrored;
+ boolean mAutoMirrored = false;
+
+ int mChangingConfigurations;
NinePatchState() {
// Empty constructor.
@@ -786,6 +687,10 @@ public class NinePatchDrawable extends Drawable {
}
}
+ /**
+ * The one constructor to rule them all. This is called by all public
+ * constructors to set the state and initialize local properties.
+ */
private NinePatchDrawable(NinePatchState state, Resources res, Theme theme) {
if (theme != null && state.canApplyTheme()) {
mNinePatchState = new NinePatchState(state);
@@ -812,14 +717,12 @@ public class NinePatchDrawable extends Drawable {
setDither(state.mDither);
}
- if (state.mTint != null) {
- final int color = state.mTint.getColorForState(getState(), 0);
- mTintFilter = new PorterDuffColorFilter(color, state.mTintMode);
+ // Make a local copy of the padding.
+ if (state.mPadding != null) {
+ mPadding = new Rect(state.mPadding);
}
- final Rect statePadding = state.mPadding;
- mPadding = statePadding != null ? new Rect(statePadding) : null;
-
+ computeTintFilter();
setNinePatch(state.mNinePatch);
}
}
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index 218a057..24e8de6 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -17,227 +17,220 @@
package android.graphics.drawable;
import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.graphics.Canvas;
+import android.graphics.CanvasProperty;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
-import android.view.animation.DecelerateInterpolator;
+import android.view.HardwareCanvas;
+import android.view.RenderNodeAnimator;
+import android.view.animation.AccelerateInterpolator;
+
+import java.util.ArrayList;
/**
* Draws a Quantum Paper ripple.
*/
class Ripple {
- private static final TimeInterpolator INTERPOLATOR = new DecelerateInterpolator();
-
- /** Starting radius for a ripple. */
- private static final int STARTING_RADIUS_DP = 16;
-
- /** Radius when finger is outside view bounds. */
- private static final int OUTSIDE_RADIUS_DP = 16;
-
- /** Radius when finger is inside view bounds. */
- private static final int INSIDE_RADIUS_DP = 96;
-
- /** Margin when constraining outside touches (fraction of outer radius). */
- private static final float OUTSIDE_MARGIN = 0.8f;
-
- /** Resistance factor when constraining outside touches. */
- private static final float OUTSIDE_RESISTANCE = 0.7f;
-
- /** Minimum alpha value during a pulse animation. */
- private static final float PULSE_MIN_ALPHA = 0.5f;
-
- /** Duration for animating the trailing edge of the ripple. */
- private static final int EXIT_DURATION = 600;
+ private static final TimeInterpolator INTERPOLATOR = new AccelerateInterpolator();
- /** Duration for animating the leading edge of the ripple. */
- private static final int ENTER_DURATION = 400;
+ private static final float GLOBAL_SPEED = 1.0f;
+ private static final float WAVE_TOUCH_DOWN_ACCELERATION = 512.0f * GLOBAL_SPEED;
+ private static final float WAVE_TOUCH_UP_ACCELERATION = 1024.0f * GLOBAL_SPEED;
+ private static final float WAVE_OPACITY_DECAY_VELOCITY = 1.6f / GLOBAL_SPEED;
+ private static final float WAVE_OUTER_OPACITY_VELOCITY = 1.2f * GLOBAL_SPEED;
- /** Duration for animating the ripple alpha in and out. */
- private static final int FADE_DURATION = 50;
-
- /** Minimum elapsed time between start of enter and exit animations. */
- private static final int EXIT_MIN_DELAY = 200;
-
- /** Duration for animating between inside and outside touch. */
- private static final int OUTSIDE_DURATION = 300;
-
- /** Duration for animating pulses. */
- private static final int PULSE_DURATION = 400;
-
- /** Interval between pulses while inside and fully entered. */
- private static final int PULSE_INTERVAL = 400;
-
- /** Delay before pulses start. */
- private static final int PULSE_DELAY = 500;
+ // Hardware animators.
+ private final ArrayList<RenderNodeAnimator> mRunningAnimations = new ArrayList<>();
+ private final ArrayList<RenderNodeAnimator> mPendingAnimations = new ArrayList<>();
private final Drawable mOwner;
- /** Bounds used for computing max radius and containment. */
+ /** Bounds used for computing max radius. */
private final Rect mBounds;
- /** Configured maximum ripple radius when the center is outside the bounds. */
- private final int mMaxOutsideRadius;
-
- /** Configured maximum ripple radius. */
- private final int mMaxInsideRadius;
-
- private ObjectAnimator mOuter;
- private ObjectAnimator mInner;
- private ObjectAnimator mAlpha;
+ /** Full-opacity color for drawing this ripple. */
+ private final int mColor;
/** Maximum ripple radius. */
- private int mMaxRadius;
-
private float mOuterRadius;
- private float mInnerRadius;
- private float mAlphaMultiplier;
- /** Center x-coordinate. */
+ // Hardware rendering properties.
+ private CanvasProperty<Paint> mPropPaint;
+ private CanvasProperty<Float> mPropRadius;
+ private CanvasProperty<Float> mPropX;
+ private CanvasProperty<Float> mPropY;
+ private CanvasProperty<Paint> mPropOuterPaint;
+ private CanvasProperty<Float> mPropOuterRadius;
+ private CanvasProperty<Float> mPropOuterX;
+ private CanvasProperty<Float> mPropOuterY;
+
+ // Software animators.
+ private ObjectAnimator mAnimRadius;
+ private ObjectAnimator mAnimOpacity;
+ private ObjectAnimator mAnimOuterOpacity;
+ private ObjectAnimator mAnimX;
+ private ObjectAnimator mAnimY;
+
+ // Software rendering properties.
+ private float mOuterOpacity = 0;
+ private float mOpacity = 1;
+ private float mRadius = 0;
+ private float mOuterX;
+ private float mOuterY;
private float mX;
-
- /** Center y-coordinate. */
private float mY;
- /** Whether the center is within the parent bounds. */
- private boolean mInsideBounds;
+ private boolean mFinished;
- /** Whether to pulse this ripple. */
- private boolean mPulseEnabled;
+ /** Whether we should be drawing hardware animations. */
+ private boolean mHardwareAnimating;
- /** Temporary hack since we can't check finished state of animator. */
- private boolean mExitFinished;
-
- /** Whether this ripple has ever moved. */
- private boolean mHasMoved;
+ /** Whether we can use hardware acceleration for the exit animation. */
+ private boolean mCanUseHardware;
/**
* Creates a new ripple.
*/
- public Ripple(Drawable owner, Rect bounds, float density, boolean pulseEnabled) {
+ public Ripple(Drawable owner, Rect bounds, int color) {
mOwner = owner;
mBounds = bounds;
- mPulseEnabled = pulseEnabled;
+ mColor = color | 0xFF000000;
+
+ final float halfWidth = bounds.width() / 2.0f;
+ final float halfHeight = bounds.height() / 2.0f;
+ mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
+ mOuterX = 0;
+ mOuterY = 0;
+ }
+
+ public void setRadius(float r) {
+ mRadius = r;
+ invalidateSelf();
+ }
- mOuterRadius = (int) (density * STARTING_RADIUS_DP + 0.5f);
- mMaxOutsideRadius = (int) (density * OUTSIDE_RADIUS_DP + 0.5f);
- mMaxInsideRadius = (int) (density * INSIDE_RADIUS_DP + 0.5f);
- mMaxRadius = Math.min(mMaxInsideRadius, Math.max(bounds.width(), bounds.height()));
+ public float getRadius() {
+ return mRadius;
}
- public void setOuterRadius(float r) {
- mOuterRadius = r;
+ public void setOpacity(float a) {
+ mOpacity = a;
invalidateSelf();
}
- public float getOuterRadius() {
- return mOuterRadius;
+ public float getOpacity() {
+ return mOpacity;
+ }
+
+ public void setOuterOpacity(float a) {
+ mOuterOpacity = a;
+ invalidateSelf();
}
- public void setInnerRadius(float r) {
- mInnerRadius = r;
+ public float getOuterOpacity() {
+ return mOuterOpacity;
+ }
+
+ public void setX(float x) {
+ mX = x;
invalidateSelf();
}
- public float getInnerRadius() {
- return mInnerRadius;
+ public float getX() {
+ return mX;
}
- public void setAlphaMultiplier(float a) {
- mAlphaMultiplier = a;
+ public void setY(float y) {
+ mY = y;
invalidateSelf();
}
- public float getAlphaMultiplier() {
- return mAlphaMultiplier;
+ public float getY() {
+ return mY;
}
/**
* Returns whether this ripple has finished exiting.
*/
public boolean isFinished() {
- return mExitFinished;
+ return mFinished;
}
/**
- * Called when the bounds change.
+ * Draws the ripple centered at (0,0) using the specified paint.
*/
- public void onBoundsChanged() {
- mMaxRadius = Math.min(mMaxInsideRadius, Math.max(mBounds.width(), mBounds.height()));
+ public boolean draw(Canvas c, Paint p) {
+ final boolean canUseHardware = c.isHardwareAccelerated();
+ if (mCanUseHardware != canUseHardware && mCanUseHardware) {
+ // We've switched from hardware to non-hardware mode. Panic.
+ cancelHardwareAnimations();
+ }
+ mCanUseHardware = canUseHardware;
- updateInsideBounds();
- }
+ final boolean hasContent;
+ if (canUseHardware && mHardwareAnimating) {
+ hasContent = drawHardware((HardwareCanvas) c);
+ } else {
+ hasContent = drawSoftware(c, p);
+ }
- private void updateInsideBounds() {
- final boolean insideBounds = mBounds.contains((int) (mX + 0.5f), (int) (mY + 0.5f));
- if (mInsideBounds != insideBounds || !mHasMoved) {
- mInsideBounds = insideBounds;
- mHasMoved = true;
+ return hasContent;
+ }
- if (insideBounds) {
- enter();
- } else {
- outside();
+ private boolean drawHardware(HardwareCanvas c) {
+ // If we have any pending hardware animations, cancel any running
+ // animations and start those now.
+ final ArrayList<RenderNodeAnimator> pendingAnimations = mPendingAnimations;
+ final int N = pendingAnimations == null ? 0 : pendingAnimations.size();
+ if (N > 0) {
+ cancelHardwareAnimations();
+
+ for (int i = 0; i < N; i++) {
+ pendingAnimations.get(i).setTarget(c);
+ pendingAnimations.get(i).start();
}
+
+ mRunningAnimations.addAll(pendingAnimations);
+ pendingAnimations.clear();
}
+
+ c.drawCircle(mPropOuterX, mPropOuterY, mPropOuterRadius, mPropOuterPaint);
+ c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
+
+ return true;
}
- /**
- * Draws the ripple using the specified paint.
- */
- public boolean draw(Canvas c, Paint p) {
- final Rect bounds = mBounds;
- final float outerRadius = mOuterRadius;
- final float innerRadius = mInnerRadius;
- final float alphaMultiplier = mAlphaMultiplier;
+ private boolean drawSoftware(Canvas c, Paint p) {
+ final float radius = mRadius;
+ final float opacity = mOpacity;
+ final float outerOpacity = mOuterOpacity;
// Cache the paint alpha so we can restore it later.
final int paintAlpha = p.getAlpha();
- final int alpha = (int) (paintAlpha * alphaMultiplier + 0.5f);
-
- // Apply resistance effect when outside bounds.
- final float x;
- final float y;
- if (mInsideBounds) {
- x = mX;
- y = mY;
- } else {
- // TODO: We need to do this outside of draw() so that our dirty
- // bounds accurately reflect resistance.
- x = looseConstrain(mX, bounds.left, bounds.right,
- mOuterRadius * OUTSIDE_MARGIN, OUTSIDE_RESISTANCE);
- y = looseConstrain(mY, bounds.top, bounds.bottom,
- mOuterRadius * OUTSIDE_MARGIN, OUTSIDE_RESISTANCE);
- }
+ final int alpha = (int) (255 * opacity + 0.5f);
+ final int outerAlpha = (int) (255 * outerOpacity + 0.5f);
- final boolean hasContent;
- if (alphaMultiplier <= 0 || innerRadius >= outerRadius) {
- // Nothing to draw.
- hasContent = false;
- } else if (innerRadius > 0) {
- // Draw a ring.
- final float strokeWidth = outerRadius - innerRadius;
- final float strokeRadius = innerRadius + strokeWidth / 2.0f;
- p.setAlpha(alpha);
- p.setStyle(Style.STROKE);
- p.setStrokeWidth(strokeWidth);
- c.drawCircle(x, y, strokeRadius, p);
+ boolean hasContent = false;
+
+ if (outerAlpha > 0 && alpha > 0) {
+ p.setAlpha(Math.min(alpha, outerAlpha));
+ p.setStyle(Style.FILL);
+ c.drawCircle(mOuterX, mOuterY, mOuterRadius, p);
hasContent = true;
- } else if (outerRadius > 0) {
- // Draw a circle.
+ }
+
+ if (opacity > 0 && radius > 0) {
p.setAlpha(alpha);
p.setStyle(Style.FILL);
- c.drawCircle(x, y, outerRadius, p);
+ c.drawCircle(mX, mY, radius, p);
hasContent = true;
- } else {
- hasContent = false;
}
p.setAlpha(paintAlpha);
+
return hasContent;
}
@@ -245,156 +238,279 @@ class Ripple {
* Returns the maximum bounds for this ripple.
*/
public void getBounds(Rect bounds) {
+ final int outerX = (int) mOuterX;
+ final int outerY = (int) mOuterY;
+ final int r = (int) mOuterRadius;
+ bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
+
final int x = (int) mX;
final int y = (int) mY;
- final int maxRadius = mMaxRadius;
- bounds.set(x - maxRadius, y - maxRadius, x + maxRadius, y + maxRadius);
+ bounds.union(x - r, y - r, x + r, y + r);
}
/**
- * Updates the center coordinates.
+ * Starts the enter animation at the specified absolute coordinates.
*/
- public void move(float x, float y) {
- mX = x;
- mY = y;
+ public void enter(float x, float y) {
+ mX = x - mBounds.exactCenterX();
+ mY = y - mBounds.exactCenterY();
- updateInsideBounds();
- invalidateSelf();
+ final int radiusDuration = (int)
+ (1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION) + 0.5);
+ final int outerDuration = (int) (1000 * 1.0f / WAVE_OUTER_OPACITY_VELOCITY);
+
+ final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radius", 0, mOuterRadius);
+ radius.setAutoCancel(true);
+ radius.setDuration(radiusDuration);
+
+ final ObjectAnimator cX = ObjectAnimator.ofFloat(this, "x", mOuterX);
+ cX.setAutoCancel(true);
+ cX.setDuration(radiusDuration);
+
+ final ObjectAnimator cY = ObjectAnimator.ofFloat(this, "y", mOuterY);
+ cY.setAutoCancel(true);
+ cY.setDuration(radiusDuration);
+
+ final ObjectAnimator outer = ObjectAnimator.ofFloat(this, "outerOpacity", 0, 1);
+ outer.setAutoCancel(true);
+ outer.setDuration(outerDuration);
+
+ mAnimRadius = radius;
+ mAnimOuterOpacity = outer;
+ mAnimX = cX;
+ mAnimY = cY;
+
+ // Enter animations always run on the UI thread, since it's unlikely
+ // that anything interesting is happening until the user lifts their
+ // finger.
+ radius.start();
+ outer.start();
+ cX.start();
+ cY.start();
}
/**
- * Starts the exit animation. If {@link #enter()} was called recently, the
- * animation may be postponed.
+ * Starts the exit animation.
*/
public void exit() {
- mExitFinished = false;
-
- final ObjectAnimator inner = ObjectAnimator.ofFloat(this, "innerRadius", 0, mMaxRadius);
- inner.setAutoCancel(true);
- inner.setDuration(EXIT_DURATION);
- inner.setInterpolator(INTERPOLATOR);
- inner.addListener(mAnimationListener);
-
- if (mOuter != null && mOuter.isStarted()) {
- // If we haven't been running the enter animation for long enough,
- // delay the exit animator.
- final int elapsed = (int) (mOuter.getAnimatedFraction() * mOuter.getDuration());
- final int delay = Math.max(0, EXIT_MIN_DELAY - elapsed);
- inner.setStartDelay(delay);
+ cancelSoftwareAnimations();
+
+ final float remaining;
+ if (mAnimRadius != null && mAnimRadius.isRunning()) {
+ remaining = mOuterRadius - mRadius;
+ } else {
+ remaining = mOuterRadius;
}
- inner.start();
+ final int radiusDuration = (int) (1000 * Math.sqrt(remaining / (WAVE_TOUCH_UP_ACCELERATION
+ + WAVE_TOUCH_DOWN_ACCELERATION)) + 0.5);
+ final int opacityDuration = (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
- final ObjectAnimator alpha = ObjectAnimator.ofFloat(this, "alphaMultiplier", 0);
- alpha.setAutoCancel(true);
- alpha.setDuration(EXIT_DURATION);
- alpha.start();
+ // Determine at what time the inner and outer opacity intersect.
+ // inner(t) = mOpacity - t * WAVE_OPACITY_DECAY_VELOCITY / 1000
+ // outer(t) = mOuterOpacity + t * WAVE_OUTER_OPACITY_VELOCITY / 1000
+ final int outerInflection = Math.max(0, (int) (1000 * (mOpacity - mOuterOpacity)
+ / (WAVE_OPACITY_DECAY_VELOCITY + WAVE_OUTER_OPACITY_VELOCITY) + 0.5f));
+ final int inflectionOpacity = (int) (255 * (mOuterOpacity + outerInflection
+ * WAVE_OUTER_OPACITY_VELOCITY / 1000) + 0.5f);
- mInner = inner;
- mAlpha = alpha;
+ if (mCanUseHardware) {
+ exitHardware(radiusDuration, opacityDuration, outerInflection, inflectionOpacity);
+ } else {
+ exitSoftware(radiusDuration, opacityDuration, outerInflection, inflectionOpacity);
+ }
}
- /**
- * Cancel all animations.
- */
- public void cancel() {
- if (mInner != null) {
- mInner.cancel();
+ private void exitHardware(int radiusDuration, int opacityDuration, int outerInflection,
+ int inflectionOpacity) {
+ mPendingAnimations.clear();
+
+ final Paint outerPaint = new Paint();
+ outerPaint.setAntiAlias(true);
+ outerPaint.setColor(mColor);
+ outerPaint.setAlpha((int) (255 * mOuterOpacity + 0.5f));
+ outerPaint.setStyle(Style.FILL);
+ mPropOuterPaint = CanvasProperty.createPaint(outerPaint);
+ mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius);
+ mPropOuterX = CanvasProperty.createFloat(mOuterX);
+ mPropOuterY = CanvasProperty.createFloat(mOuterY);
+
+ final Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setColor(mColor);
+ paint.setAlpha((int) (255 * mOpacity + 0.5f));
+ paint.setStyle(Style.FILL);
+ mPropPaint = CanvasProperty.createPaint(paint);
+ mPropRadius = CanvasProperty.createFloat(mRadius);
+ mPropX = CanvasProperty.createFloat(mX);
+ mPropY = CanvasProperty.createFloat(mY);
+
+ final RenderNodeAnimator radius = new RenderNodeAnimator(mPropRadius, mOuterRadius);
+ radius.setDuration(radiusDuration);
+
+ final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mOuterX);
+ x.setDuration(radiusDuration);
+
+ final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mOuterY);
+ y.setDuration(radiusDuration);
+
+ final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint,
+ RenderNodeAnimator.PAINT_ALPHA, 0);
+ opacity.setDuration(opacityDuration);
+ opacity.addListener(mAnimationListener);
+
+ final RenderNodeAnimator outerOpacity;
+ if (outerInflection > 0) {
+ // Outer opacity continues to increase for a bit.
+ outerOpacity = new RenderNodeAnimator(
+ mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity);
+ outerOpacity.setDuration(outerInflection);
+
+ // Chain the outer opacity exit animation.
+ final int outerDuration = opacityDuration - outerInflection;
+ if (outerDuration > 0) {
+ final RenderNodeAnimator outerFadeOut = new RenderNodeAnimator(
+ mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
+ outerFadeOut.setDuration(outerDuration);
+ outerFadeOut.setStartDelay(outerInflection);
+
+ mPendingAnimations.add(outerFadeOut);
+ }
+ } else {
+ outerOpacity = new RenderNodeAnimator(
+ mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
+ outerOpacity.setDuration(opacityDuration);
}
- if (mOuter != null) {
- mOuter.cancel();
- }
+ mPendingAnimations.add(radius);
+ mPendingAnimations.add(opacity);
+ mPendingAnimations.add(outerOpacity);
+ mPendingAnimations.add(x);
+ mPendingAnimations.add(y);
- if (mAlpha != null) {
- mAlpha.cancel();
- }
- }
+ mHardwareAnimating = true;
- private void invalidateSelf() {
- mOwner.invalidateSelf();
+ invalidateSelf();
}
- /**
- * Starts the enter animation.
- */
- private void enter() {
- final ObjectAnimator outer = ObjectAnimator.ofFloat(this, "outerRadius", mMaxRadius);
- outer.setAutoCancel(true);
- outer.setDuration(ENTER_DURATION);
- outer.setInterpolator(INTERPOLATOR);
- outer.start();
-
- final ObjectAnimator alpha = ObjectAnimator.ofFloat(this, "alphaMultiplier", 1);
- if (mPulseEnabled) {
- alpha.addListener(new AnimatorListenerAdapter() {
+ private void exitSoftware(int radiusDuration, int opacityDuration, int outerInflection,
+ float inflectionOpacity) {
+ final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radius", mOuterRadius);
+ radius.setAutoCancel(true);
+ radius.setDuration(radiusDuration);
+
+ final ObjectAnimator x = ObjectAnimator.ofFloat(this, "x", mOuterX);
+ x.setAutoCancel(true);
+ x.setDuration(radiusDuration);
+
+ final ObjectAnimator y = ObjectAnimator.ofFloat(this, "y", mOuterY);
+ y.setAutoCancel(true);
+ y.setDuration(radiusDuration);
+
+ final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, "opacity", 0);
+ opacity.setAutoCancel(true);
+ opacity.setDuration(opacityDuration);
+ opacity.addListener(mAnimationListener);
+
+ final ObjectAnimator outerOpacity;
+ if (outerInflection > 0) {
+ // Outer opacity continues to increase for a bit.
+ outerOpacity = ObjectAnimator.ofFloat(this, "outerOpacity", inflectionOpacity);
+ outerOpacity.setDuration(outerInflection);
+
+ // Chain the outer opacity exit animation.
+ final int outerDuration = opacityDuration - outerInflection;
+ outerOpacity.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- final ObjectAnimator pulse = ObjectAnimator.ofFloat(
- this, "alphaMultiplier", 1, PULSE_MIN_ALPHA);
- pulse.setAutoCancel(true);
- pulse.setDuration(PULSE_DURATION + PULSE_INTERVAL);
- pulse.setRepeatCount(ObjectAnimator.INFINITE);
- pulse.setRepeatMode(ObjectAnimator.REVERSE);
- pulse.setStartDelay(PULSE_DELAY);
- pulse.start();
-
- mAlpha = pulse;
+ final ObjectAnimator outerFadeOut = ObjectAnimator.ofFloat(Ripple.this,
+ "outerOpacity", 0);
+ outerFadeOut.setDuration(outerDuration);
+
+ mAnimOuterOpacity = outerFadeOut;
+
+ outerFadeOut.start();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ animation.removeListener(this);
}
});
+ } else {
+ outerOpacity = ObjectAnimator.ofFloat(this, "outerOpacity", 0);
+ outerOpacity.setDuration(opacityDuration);
}
- alpha.setAutoCancel(true);
- alpha.setDuration(FADE_DURATION);
- alpha.start();
- mOuter = outer;
- mAlpha = alpha;
+ mAnimRadius = radius;
+ mAnimOpacity = opacity;
+ mAnimOuterOpacity = outerOpacity;
+ mAnimX = opacity;
+ mAnimY = opacity;
+
+ radius.start();
+ opacity.start();
+ outerOpacity.start();
+ x.start();
+ y.start();
}
/**
- * Starts the outside transition animation.
+ * Cancel all animations.
*/
- private void outside() {
- final ObjectAnimator outer = ObjectAnimator.ofFloat(this, "outerRadius", mMaxOutsideRadius);
- outer.setAutoCancel(true);
- outer.setDuration(OUTSIDE_DURATION);
- outer.setInterpolator(INTERPOLATOR);
- outer.start();
+ public void cancel() {
+ cancelSoftwareAnimations();
+ cancelHardwareAnimations();
+ }
- final ObjectAnimator alpha = ObjectAnimator.ofFloat(this, "alphaMultiplier", 1);
- alpha.setAutoCancel(true);
- alpha.setDuration(FADE_DURATION);
- alpha.start();
+ private void cancelSoftwareAnimations() {
+ if (mAnimRadius != null) {
+ mAnimRadius.cancel();
+ }
- mOuter = outer;
- mAlpha = alpha;
+ if (mAnimOpacity != null) {
+ mAnimOpacity.cancel();
+ }
+
+ if (mAnimOuterOpacity != null) {
+ mAnimOuterOpacity.cancel();
+ }
+
+ if (mAnimX != null) {
+ mAnimX.cancel();
+ }
+
+ if (mAnimY != null) {
+ mAnimY.cancel();
+ }
}
/**
- * Constrains a value within a specified asymptotic margin outside a minimum
- * and maximum.
+ * Cancels any running hardware animations.
*/
- private static float looseConstrain(float value, float min, float max, float margin,
- float factor) {
- // TODO: Can we use actual spring physics here?
- if (value < min) {
- return min - Math.min(margin, (float) Math.pow(min - value, factor));
- } else if (value > max) {
- return max + Math.min(margin, (float) Math.pow(value - max, factor));
- } else {
- return value;
+ private void cancelHardwareAnimations() {
+ final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations;
+ final int N = runningAnimations == null ? 0 : runningAnimations.size();
+ for (int i = 0; i < N; i++) {
+ runningAnimations.get(i).cancel();
}
+
+ runningAnimations.clear();
+ }
+
+ private void invalidateSelf() {
+ mOwner.invalidateSelf();
}
- private final AnimatorListener mAnimationListener = new AnimatorListenerAdapter() {
+ private final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- if (animation == mInner) {
- mExitFinished = true;
- mOuterRadius = 0;
- mInnerRadius = 0;
- mAlphaMultiplier = 1;
- }
+ mFinished = true;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mFinished = true;
}
};
}
diff --git a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
index 0097183..a55a4b2 100644
--- a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
+++ b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
@@ -24,6 +24,7 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
+import android.graphics.PointF;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
@@ -33,6 +34,7 @@ import android.util.Log;
import android.util.SparseArray;
import com.android.internal.R;
+import com.android.org.bouncycastle.util.Arrays;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -40,11 +42,36 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
/**
- * Documentation pending.
+ * Drawable that shows a ripple effect in response to state changes. The
+ * anchoring position of the ripple for a given state may be specified by
+ * calling {@link #setHotspot(int, float, float)} with the corresponding state
+ * attribute identifier.
+ * <p>
+ * A touch feedback drawable may contain multiple child layers, including a
+ * special mask layer that is not drawn to the screen. A single layer may be set
+ * as the mask by specifying its android:id value as {@link android.R.id#mask}.
+ * <p>
+ * If a mask layer is set, the ripple effect will be masked against that layer
+ * before it is blended onto the composite of the remaining child layers.
+ * <p>
+ * If no mask layer is set, the ripple effect is simply blended onto the
+ * composite of the child layers using the specified
+ * {@link android.R.styleable#TouchFeedbackDrawable_tintMode}.
+ * <p>
+ * If no child layers or mask is specified and the ripple is set as a View
+ * background, the ripple will be blended onto the first available parent
+ * background within the View's hierarchy using the specified
+ * {@link android.R.styleable#TouchFeedbackDrawable_tintMode}. In this case, the
+ * drawing region may extend outside of the Drawable bounds.
+ *
+ * @attr ref android.R.styleable#DrawableStates_state_focused
+ * @attr ref android.R.styleable#DrawableStates_state_pressed
*/
public class TouchFeedbackDrawable extends LayerDrawable {
private static final String LOG_TAG = TouchFeedbackDrawable.class.getSimpleName();
private static final PorterDuffXfermode DST_IN = new PorterDuffXfermode(Mode.DST_IN);
+ private static final PorterDuffXfermode DST_ATOP = new PorterDuffXfermode(Mode.DST_ATOP);
+ private static final PorterDuffXfermode SRC_ATOP = new PorterDuffXfermode(Mode.SRC_ATOP);
private static final PorterDuffXfermode SRC_OVER = new PorterDuffXfermode(Mode.SRC_OVER);
/** The maximum number of ripples supported. */
@@ -63,10 +90,22 @@ public class TouchFeedbackDrawable extends LayerDrawable {
private final TouchFeedbackState mState;
- /** Lazily-created map of touch hotspot IDs to ripples. */
- private SparseArray<Ripple> mRipples;
+ /**
+ * Lazily-created map of pending hotspot locations. These may be modified by
+ * calls to {@link #setHotspot(int, float, float)}.
+ */
+ private SparseArray<PointF> mPendingHotspots;
+
+ /**
+ * Lazily-created map of active hotspot locations. These may be modified by
+ * calls to {@link #setHotspot(int, float, float)}.
+ */
+ private SparseArray<Ripple> mActiveHotspots;
- /** Lazily-created array of actively animating ripples. */
+ /**
+ * Lazily-created array of actively animating ripples. Inactive ripples are
+ * pruned during draw(). The locations of these will not change.
+ */
private Ripple[] mAnimatingRipples;
private int mAnimatingRipplesCount = 0;
@@ -96,24 +135,18 @@ public class TouchFeedbackDrawable extends LayerDrawable {
protected boolean onStateChange(int[] stateSet) {
super.onStateChange(stateSet);
- // TODO: Implicitly tie states to ripple IDs. For now, just clear
- // focused and pressed if they aren't in the state set.
- boolean hasFocused = false;
- boolean hasPressed = false;
- for (int i = 0; i < stateSet.length; i++) {
- if (stateSet[i] == R.attr.state_pressed) {
- hasPressed = true;
- } else if (stateSet[i] == R.attr.state_focused) {
- hasFocused = true;
- }
- }
-
- if (!hasPressed) {
+ final boolean pressed = Arrays.contains(stateSet, R.attr.state_pressed);
+ if (!pressed) {
removeHotspot(R.attr.state_pressed);
+ } else {
+ activateHotspot(R.attr.state_pressed);
}
- if (!hasFocused) {
+ final boolean focused = Arrays.contains(stateSet, R.attr.state_focused);
+ if (!focused) {
removeHotspot(R.attr.state_focused);
+ } else {
+ activateHotspot(R.attr.state_focused);
}
if (mRipplePaint != null && mState.mTint != null) {
@@ -138,19 +171,7 @@ public class TouchFeedbackDrawable extends LayerDrawable {
mHotspotBounds.set(bounds);
}
- onHotspotBoundsChange();
- }
-
- private void onHotspotBoundsChange() {
- final int x = mHotspotBounds.centerX();
- final int y = mHotspotBounds.centerY();
- final int N = mAnimatingRipplesCount;
- for (int i = 0; i < N; i++) {
- if (mState.mPinned) {
- mAnimatingRipples[i].move(x, y);
- }
- mAnimatingRipples[i].onBoundsChanged();
- }
+ invalidateSelf();
}
@Override
@@ -172,7 +193,7 @@ public class TouchFeedbackDrawable extends LayerDrawable {
@Override
public boolean isStateful() {
- return super.isStateful() || mState.mTint != null && mState.mTint.isStateful();
+ return true;
}
/**
@@ -213,7 +234,7 @@ public class TouchFeedbackDrawable extends LayerDrawable {
throws XmlPullParserException, IOException {
final TypedArray a = obtainAttributes(
r, theme, attrs, R.styleable.TouchFeedbackDrawable);
- inflateStateFromTypedArray(a);
+ updateStateFromTypedArray(a);
a.recycle();
super.inflate(r, parser, attrs, theme);
@@ -245,25 +266,23 @@ public class TouchFeedbackDrawable extends LayerDrawable {
/**
* Initializes the constant state from the values in the typed array.
*/
- private void inflateStateFromTypedArray(TypedArray a) {
+ private void updateStateFromTypedArray(TypedArray a) {
final TouchFeedbackState state = mState;
// Extract the theme attributes, if any.
- final int[] themeAttrs = a.extractThemeAttrs();
- state.mTouchThemeAttrs = themeAttrs;
+ state.mTouchThemeAttrs = a.extractThemeAttrs();
- if (themeAttrs == null || themeAttrs[R.styleable.TouchFeedbackDrawable_tint] == 0) {
- mState.mTint = a.getColorStateList(R.styleable.TouchFeedbackDrawable_tint);
+ final ColorStateList tint = a.getColorStateList(R.styleable.TouchFeedbackDrawable_tint);
+ if (tint != null) {
+ mState.mTint = tint;
}
- if (themeAttrs == null || themeAttrs[R.styleable.TouchFeedbackDrawable_tintMode] == 0) {
- mState.setTintMode(Drawable.parseTintMode(
- a.getInt(R.styleable.TouchFeedbackDrawable_tintMode, -1), Mode.SRC_ATOP));
+ final int tintMode = a.getInt(R.styleable.TouchFeedbackDrawable_tintMode, -1);
+ if (tintMode != -1) {
+ mState.setTintMode(Drawable.parseTintMode(tintMode, Mode.SRC_ATOP));
}
- if (themeAttrs == null || themeAttrs[R.styleable.TouchFeedbackDrawable_pinned] == 0) {
- mState.mPinned = a.getBoolean(R.styleable.TouchFeedbackDrawable_pinned, false);
- }
+ mState.mPinned = a.getBoolean(R.styleable.TouchFeedbackDrawable_pinned, mState.mPinned);
}
/**
@@ -283,38 +302,14 @@ public class TouchFeedbackDrawable extends LayerDrawable {
super.applyTheme(t);
final TouchFeedbackState state = mState;
- if (state == null) {
- throw new RuntimeException(
- "Can't apply theme to <touch-feedback> with no constant state");
- }
-
- final int[] themeAttrs = state.mTouchThemeAttrs;
- if (themeAttrs != null) {
- final TypedArray a = t.resolveAttributes(
- themeAttrs, R.styleable.TouchFeedbackDrawable, 0, 0);
- updateStateFromTypedArray(a);
- a.recycle();
- }
- }
-
- /**
- * Updates the constant state from the values in the typed array.
- */
- private void updateStateFromTypedArray(TypedArray a) {
- final TouchFeedbackState state = mState;
-
- if (a.hasValue(R.styleable.TouchFeedbackDrawable_tint)) {
- state.mTint = a.getColorStateList(R.styleable.TouchFeedbackDrawable_tint);
- }
-
- if (a.hasValue(R.styleable.TouchFeedbackDrawable_tintMode)) {
- mState.setTintMode(Drawable.parseTintMode(
- a.getInt(R.styleable.TouchFeedbackDrawable_tintMode, -1), Mode.SRC_ATOP));
+ if (state == null || state.mTouchThemeAttrs == null) {
+ return;
}
- if (a.hasValue(R.styleable.TouchFeedbackDrawable_pinned)) {
- mState.mPinned = a.getBoolean(R.styleable.TouchFeedbackDrawable_pinned, false);
- }
+ final TypedArray a = t.resolveAttributes(state.mTouchThemeAttrs,
+ R.styleable.TouchFeedbackDrawable);
+ updateStateFromTypedArray(a);
+ a.recycle();
}
@Override
@@ -329,59 +324,123 @@ public class TouchFeedbackDrawable extends LayerDrawable {
@Override
public void setHotspot(int id, float x, float y) {
- if (mRipples == null) {
- mRipples = new SparseArray<Ripple>();
- mAnimatingRipples = new Ripple[MAX_RIPPLES];
+ if (mState.mPinned && !circleContains(mHotspotBounds, x, y)) {
+ x = mHotspotBounds.exactCenterX();
+ y = mHotspotBounds.exactCenterY();
+ }
+
+ final int[] stateSet = getState();
+ if (!Arrays.contains(stateSet, id)) {
+ // The hotspot is not active, so just modify the pending location.
+ getOrCreatePendingHotspot(id).set(x, y);
+ return;
}
if (mAnimatingRipplesCount >= MAX_RIPPLES) {
- Log.e(LOG_TAG, "Max ripple count exceeded", new RuntimeException());
+ // This should never happen unless the user is tapping like a maniac
+ // or there is a bug that's preventing ripples from being removed.
+ Log.d(LOG_TAG, "Max ripple count exceeded", new RuntimeException());
return;
}
- final Ripple ripple = mRipples.get(id);
- if (ripple == null) {
- final Rect bounds = mHotspotBounds;
- if (mState.mPinned) {
- x = bounds.exactCenterX();
- y = bounds.exactCenterY();
- }
+ if (mActiveHotspots == null) {
+ mActiveHotspots = new SparseArray<Ripple>();
+ mAnimatingRipples = new Ripple[MAX_RIPPLES];
+ }
+
+ final Ripple ripple = mActiveHotspots.get(id);
+ if (ripple != null) {
+ // The hotspot is active, but we can't move it because it's probably
+ // busy animating the center position.
+ return;
+ }
+
+ // The hotspot needs to be made active.
+ createActiveHotspot(id, x, y);
+ }
+
+ private boolean circleContains(Rect bounds, float x, float y) {
+ final float pX = bounds.exactCenterX() - x;
+ final float pY = bounds.exactCenterY() - y;
+ final double pointRadius = Math.sqrt(pX * pX + pY * pY);
+
+ final float bX = bounds.width() / 2.0f;
+ final float bY = bounds.height() / 2.0f;
+ final double boundsRadius = Math.sqrt(bX * bX + bY * bY);
+
+ return pointRadius < boundsRadius;
+ }
+
+ private PointF getOrCreatePendingHotspot(int id) {
+ final PointF p;
+ if (mPendingHotspots == null) {
+ mPendingHotspots = new SparseArray<>(2);
+ p = null;
+ } else {
+ p = mPendingHotspots.get(id);
+ }
- // TODO: Clean this up in the API.
- final boolean pulse = (id != R.attr.state_focused);
- final Ripple newRipple = new Ripple(this, bounds, mDensity, pulse);
- newRipple.move(x, y);
-
- mAnimatingRipples[mAnimatingRipplesCount++] = newRipple;
- mRipples.put(id, newRipple);
- } else if (mState.mPinned) {
- final Rect bounds = mHotspotBounds;
- x = bounds.exactCenterX();
- y = bounds.exactCenterY();
- ripple.move(x, y);
+ if (p == null) {
+ final PointF newPoint = new PointF();
+ mPendingHotspots.put(id, newPoint);
+ return newPoint;
} else {
- ripple.move(x, y);
+ return p;
}
}
+ /**
+ * Moves a hotspot from pending to active.
+ */
+ private void activateHotspot(int id) {
+ final SparseArray<PointF> pendingHotspots = mPendingHotspots;
+ if (pendingHotspots != null) {
+ final int index = pendingHotspots.indexOfKey(id);
+ if (index >= 0) {
+ final PointF hotspot = pendingHotspots.valueAt(index);
+ pendingHotspots.removeAt(index);
+ createActiveHotspot(id, hotspot.x, hotspot.y);
+ }
+ }
+ }
+
+ /**
+ * Creates an active hotspot at the specified location.
+ */
+ private void createActiveHotspot(int id, float x, float y) {
+ final int color = mState.mTint.getColorForState(getState(), Color.TRANSPARENT);
+ final Ripple newRipple = new Ripple(this, mHotspotBounds, color);
+ newRipple.enter(x, y);
+
+ if (mAnimatingRipples == null) {
+ mAnimatingRipples = new Ripple[MAX_RIPPLES];
+ }
+ mAnimatingRipples[mAnimatingRipplesCount++] = newRipple;
+
+ if (mActiveHotspots == null) {
+ mActiveHotspots = new SparseArray<Ripple>();
+ }
+ mActiveHotspots.put(id, newRipple);
+ }
+
@Override
public void removeHotspot(int id) {
- if (mRipples == null) {
+ if (mActiveHotspots == null) {
return;
}
- final Ripple ripple = mRipples.get(id);
+ final Ripple ripple = mActiveHotspots.get(id);
if (ripple != null) {
ripple.exit();
- mRipples.remove(id);
+ mActiveHotspots.remove(id);
}
}
@Override
public void clearHotspots() {
- if (mRipples != null) {
- mRipples.clear();
+ if (mActiveHotspots != null) {
+ mActiveHotspots.clear();
}
final int count = mAnimatingRipplesCount;
@@ -402,7 +461,6 @@ public class TouchFeedbackDrawable extends LayerDrawable {
public void setHotspotBounds(int left, int top, int right, int bottom) {
mOverrideBounds = true;
mHotspotBounds.set(left, top, right, bottom);
- onHotspotBoundsChange();
}
@Override
@@ -412,9 +470,9 @@ public class TouchFeedbackDrawable extends LayerDrawable {
final ChildDrawable[] array = mLayerState.mChildren;
final boolean maskOnly = mState.mMask != null && N == 1;
- int restoreToCount = drawRippleLayer(canvas, bounds, maskOnly);
+ int restoreToCount = drawRippleLayer(canvas, maskOnly);
- if (restoreToCount >= 0) {
+ if (restoreToCount >= 0) {
// We have a ripple layer that contains ripples. If we also have an
// explicit mask drawable, apply it now using DST_IN blending.
if (mState.mMask != null) {
@@ -450,7 +508,7 @@ public class TouchFeedbackDrawable extends LayerDrawable {
}
}
- private int drawRippleLayer(Canvas canvas, Rect bounds, boolean maskOnly) {
+ private int drawRippleLayer(Canvas canvas, boolean maskOnly) {
final int count = mAnimatingRipplesCount;
if (count == 0) {
return -1;
@@ -458,7 +516,7 @@ public class TouchFeedbackDrawable extends LayerDrawable {
final Ripple[] ripples = mAnimatingRipples;
final boolean projected = isProjected();
- final Rect layerBounds = projected ? getDirtyBounds() : bounds;
+ final Rect layerBounds = projected ? getDirtyBounds() : getBounds();
// Separate the ripple color and alpha channel. The alpha will be
// applied when we merge the ripples down to the canvas.
@@ -479,6 +537,7 @@ public class TouchFeedbackDrawable extends LayerDrawable {
boolean drewRipples = false;
int restoreToCount = -1;
+ int restoreTranslate = -1;
int animatingCount = 0;
// Draw ripples and update the animating ripples array.
@@ -509,6 +568,10 @@ public class TouchFeedbackDrawable extends LayerDrawable {
restoreToCount = canvas.saveLayer(layerBounds.left, layerBounds.top,
layerBounds.right, layerBounds.bottom, layerPaint);
layerPaint.setAlpha(255);
+
+ restoreTranslate = canvas.save();
+ // Translate the canvas to the current hotspot bounds.
+ canvas.translate(mHotspotBounds.exactCenterX(), mHotspotBounds.exactCenterY());
}
drewRipples |= ripple.draw(canvas, ripplePaint);
@@ -519,6 +582,11 @@ public class TouchFeedbackDrawable extends LayerDrawable {
mAnimatingRipplesCount = animatingCount;
+ // Always restore the translation.
+ if (restoreTranslate >= 0) {
+ canvas.restoreToCount(restoreTranslate);
+ }
+
// If we created a layer with no content, merge it immediately.
if (restoreToCount >= 0 && !drewRipples) {
canvas.restoreToCount(restoreToCount);
@@ -543,11 +611,14 @@ public class TouchFeedbackDrawable extends LayerDrawable {
dirtyBounds.set(drawingBounds);
drawingBounds.setEmpty();
+ final int cX = (int) mHotspotBounds.exactCenterX();
+ final int cY = (int) mHotspotBounds.exactCenterY();
final Rect rippleBounds = mTempRect;
final Ripple[] activeRipples = mAnimatingRipples;
final int N = mAnimatingRipplesCount;
for (int i = 0; i < N; i++) {
activeRipples[i].getBounds(rippleBounds);
+ rippleBounds.offset(cX, cY);
drawingBounds.union(rippleBounds);
}
@@ -563,11 +634,11 @@ public class TouchFeedbackDrawable extends LayerDrawable {
static class TouchFeedbackState extends LayerState {
int[] mTouchThemeAttrs;
- ColorStateList mTint;
- PorterDuffXfermode mTintXfermode;
- PorterDuffXfermode mTintXfermodeInverse;
+ ColorStateList mTint = null;
+ PorterDuffXfermode mTintXfermode = SRC_ATOP;
+ PorterDuffXfermode mTintXfermodeInverse = DST_ATOP;
Drawable mMask;
- boolean mPinned;
+ boolean mPinned = false;
public TouchFeedbackState(
TouchFeedbackState orig, TouchFeedbackDrawable owner, Resources res) {
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 65d4e48..e3ed75e 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -738,7 +738,7 @@ public class VectorDrawable extends Drawable {
}
final TypedArray a = t.resolveAttributes(
- mThemeAttrs, R.styleable.VectorDrawablePath, 0, 0);
+ mThemeAttrs, R.styleable.VectorDrawablePath);
mClip = a.getBoolean(R.styleable.VectorDrawablePath_clipToPath, mClip);
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);
+}