Merge "Added crash-detection mechanism."
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 1b7daa6..f73e4d5 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -68,6 +68,14 @@
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libreverbtest_intermediates)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/soundfx/)
$(call add-clean-step, find . -type f -name "*.rs" -print0 | xargs -0 touch)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libandroid_runtime_intermediates)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/lib/libandroid_runtime.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/symbols/system/lib/libandroid_runtime.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/libandroid_runtime.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libhwui_intermediates)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/lib/libhwui.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/symbols/system/lib/libhwui.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/libhwui.so)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/api/9.xml b/api/9.xml
index abb67f9..f151a16 100644
--- a/api/9.xml
+++ b/api/9.xml
@@ -58593,16 +58593,6 @@
<parameter name="bitmap" type="android.graphics.Bitmap">
</parameter>
</constructor>
-<constructor name="Canvas"
- type="android.graphics.Canvas"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="gl" type="javax.microedition.khronos.opengles.GL">
-</parameter>
-</constructor>
<method name="clipPath"
return="boolean"
abstract="false"
@@ -59519,17 +59509,6 @@
<parameter name="paint" type="android.graphics.Paint">
</parameter>
</method>
-<method name="freeGlCaches"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="true"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
<method name="getClipBounds"
return="boolean"
abstract="false"
@@ -59576,17 +59555,6 @@
visibility="public"
>
</method>
-<method name="getGL"
- return="javax.microedition.khronos.opengles.GL"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
<method name="getHeight"
return="int"
abstract="false"
@@ -59950,21 +59918,6 @@
<parameter name="matrix" type="android.graphics.Matrix">
</parameter>
</method>
-<method name="setViewport"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="width" type="int">
-</parameter>
-<parameter name="height" type="int">
-</parameter>
-</method>
<method name="skew"
return="void"
abstract="false"
diff --git a/api/current.xml b/api/current.xml
index 767f9af..61c1cac 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -38871,7 +38871,7 @@
synchronized="false"
static="false"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</method>
@@ -39106,6 +39106,19 @@
<parameter name="uri" type="android.net.Uri">
</parameter>
</constructor>
+<method name="coerceToText"
+ return="java.lang.CharSequence"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</method>
<method name="getIntent"
return="android.content.Intent"
abstract="false"
@@ -39463,6 +39476,21 @@
<parameter name="values" type="android.content.ContentValues[]">
</parameter>
</method>
+<method name="compareMimeTypes"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="concreteType" type="java.lang.String">
+</parameter>
+<parameter name="desiredType" type="java.lang.String">
+</parameter>
+</method>
<method name="delete"
return="int"
abstract="true"
@@ -39513,6 +39541,21 @@
visibility="public"
>
</method>
+<method name="getStreamTypes"
+ return="java.lang.String[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="mimeTypeFilter" type="java.lang.String">
+</parameter>
+</method>
<method name="getType"
return="java.lang.String"
abstract="true"
@@ -39649,6 +39692,48 @@
<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
</exception>
</method>
+<method name="openPipeHelper"
+ return="android.os.ParcelFileDescriptor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="mimeType" type="java.lang.String">
+</parameter>
+<parameter name="opts" type="android.os.Bundle">
+</parameter>
+<parameter name="args" type="T">
+</parameter>
+<parameter name="func" type="android.content.ContentProvider.PipeDataWriter<T>">
+</parameter>
+<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
+</exception>
+</method>
+<method name="openTypedAssetFile"
+ return="android.content.res.AssetFileDescriptor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="mimeTypeFilter" type="java.lang.String">
+</parameter>
+<parameter name="opts" type="android.os.Bundle">
+</parameter>
+<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
+</exception>
+</method>
<method name="query"
return="android.database.Cursor"
abstract="true"
@@ -39740,6 +39825,35 @@
</parameter>
</method>
</class>
+<interface name="ContentProvider.PipeDataWriter"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="writeDataToPipe"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="output" type="android.os.ParcelFileDescriptor">
+</parameter>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="mimeType" type="java.lang.String">
+</parameter>
+<parameter name="opts" type="android.os.Bundle">
+</parameter>
+<parameter name="args" type="T">
+</parameter>
+</method>
+</interface>
<class name="ContentProviderClient"
extends="java.lang.Object"
abstract="false"
@@ -39812,6 +39926,23 @@
visibility="public"
>
</method>
+<method name="getStreamTypes"
+ return="java.lang.String[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="url" type="android.net.Uri">
+</parameter>
+<parameter name="mimeTypeFilter" type="java.lang.String">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
<method name="getType"
return="java.lang.String"
abstract="false"
@@ -39882,6 +40013,27 @@
<exception name="RemoteException" type="android.os.RemoteException">
</exception>
</method>
+<method name="openTypedAssetFileDescriptor"
+ return="android.content.res.AssetFileDescriptor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="mimeType" type="java.lang.String">
+</parameter>
+<parameter name="opts" type="android.os.Bundle">
+</parameter>
+<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
+</exception>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
<method name="query"
return="android.database.Cursor"
abstract="false"
@@ -40652,6 +40804,21 @@
<parameter name="authority" type="java.lang.String">
</parameter>
</method>
+<method name="getStreamTypes"
+ return="java.lang.String[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="url" type="android.net.Uri">
+</parameter>
+<parameter name="mimeTypeFilter" type="java.lang.String">
+</parameter>
+</method>
<method name="getSyncAdapterTypes"
return="android.content.SyncAdapterType[]"
abstract="false"
@@ -40849,6 +41016,25 @@
<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
</exception>
</method>
+<method name="openTypedAssetFileDescriptor"
+ return="android.content.res.AssetFileDescriptor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="mimeType" type="java.lang.String">
+</parameter>
+<parameter name="opts" type="android.os.Bundle">
+</parameter>
+<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
+</exception>
+</method>
<method name="query"
return="android.database.Cursor"
abstract="false"
@@ -47262,6 +47448,17 @@
visibility="public"
>
</field>
+<field name="ACTION_PASTE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.intent.action.PASTE""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="ACTION_PICK"
type="java.lang.String"
transient="false"
@@ -68809,16 +69006,6 @@
<parameter name="bitmap" type="android.graphics.Bitmap">
</parameter>
</constructor>
-<constructor name="Canvas"
- type="android.graphics.Canvas"
- static="false"
- final="false"
- deprecated="deprecated"
- visibility="public"
->
-<parameter name="gl" type="javax.microedition.khronos.opengles.GL">
-</parameter>
-</constructor>
<method name="clipPath"
return="boolean"
abstract="false"
@@ -69735,17 +69922,6 @@
<parameter name="paint" type="android.graphics.Paint">
</parameter>
</method>
-<method name="freeGlCaches"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="true"
- final="false"
- deprecated="deprecated"
- visibility="public"
->
-</method>
<method name="getClipBounds"
return="boolean"
abstract="false"
@@ -69800,7 +69976,7 @@
static="false"
final="false"
deprecated="deprecated"
- visibility="public"
+ visibility="protected"
>
</method>
<method name="getHeight"
@@ -70177,21 +70353,6 @@
<parameter name="matrix" type="android.graphics.Matrix">
</parameter>
</method>
-<method name="setViewport"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="deprecated"
- visibility="public"
->
-<parameter name="width" type="int">
-</parameter>
-<parameter name="height" type="int">
-</parameter>
-</method>
<method name="skew"
return="void"
abstract="false"
@@ -102917,19 +103078,6 @@
<parameter name="value" type="java.lang.String">
</parameter>
</method>
-<method name="setShowNotification"
- return="android.net.DownloadManager.Request"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="flags" type="int">
-</parameter>
-</method>
<method name="setTitle"
return="android.net.DownloadManager.Request"
abstract="false"
@@ -102976,17 +103124,6 @@
visibility="public"
>
</field>
-<field name="NOTIFICATION_WHEN_RUNNING"
- type="int"
- transient="false"
- volatile="false"
- value="1"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
</class>
<class name="LocalServerSocket"
extends="java.lang.Object"
@@ -133993,6 +134130,19 @@
<exception name="IOException" type="java.io.IOException">
</exception>
</method>
+<method name="createPipe"
+ return="android.os.ParcelFileDescriptor[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
<method name="describeContents"
return="int"
abstract="false"
@@ -180176,6 +180326,483 @@
</parameter>
</method>
</class>
+<class name="JsonReader"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="java.io.Closeable">
+</implements>
+<constructor name="JsonReader"
+ type="android.util.JsonReader"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="in" type="java.io.Reader">
+</parameter>
+</constructor>
+<method name="beginArray"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="beginObject"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="close"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="endArray"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="endObject"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="hasNext"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextBoolean"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextDouble"
+ return="double"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextInt"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextLong"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextName"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextNull"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextString"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="peek"
+ return="android.util.JsonToken"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="setLenient"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="lenient" type="boolean">
+</parameter>
+</method>
+<method name="skipValue"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="syntaxError"
+ return="java.io.IOException"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="message" type="java.lang.String">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+</class>
+<class name="JsonToken"
+ extends="java.lang.Enum"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="valueOf"
+ return="android.util.JsonToken"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="name" type="java.lang.String">
+</parameter>
+</method>
+<method name="values"
+ return="android.util.JsonToken[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
+<class name="JsonWriter"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="java.io.Closeable">
+</implements>
+<constructor name="JsonWriter"
+ type="android.util.JsonWriter"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="out" type="java.io.Writer">
+</parameter>
+</constructor>
+<method name="beginArray"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="beginObject"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="close"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="endArray"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="endObject"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="flush"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="name"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="name" type="java.lang.String">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nullValue"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="setIndent"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="indent" type="java.lang.String">
+</parameter>
+</method>
+<method name="value"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="java.lang.String">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="value"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="boolean">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="value"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="double">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="value"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="long">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+</class>
<class name="Log"
extends="java.lang.Object"
abstract="false"
@@ -212598,6 +213225,28 @@
visibility="public"
>
</method>
+<method name="getVisibleTitleHeight"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getVisibleTitleHeight"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="getZoomControls"
return="android.view.View"
abstract="false"
diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c
index 1fde437..02c9cbc 100644
--- a/cmds/dumpstate/dumpstate.c
+++ b/cmds/dumpstate/dumpstate.c
@@ -167,8 +167,11 @@
}
static void usage() {
- fprintf(stderr, "usage: dumpstate [-d] [-o file] [-s] [-z]\n"
+ fprintf(stderr, "usage: dumpstate [-b file] [-d] [-e file] [-o file] [-s] "
+ "[-z]\n"
+ " -b: play sound file instead of vibrate, at beginning of job\n"
" -d: append date to filename (requires -o)\n"
+ " -e: play sound file instead of vibrate, at end of job\n"
" -o: write to file (instead of stdout)\n"
" -s: write output to control socket (for init)\n"
" -z: gzip output (requires -o)\n");
@@ -178,6 +181,8 @@
int do_add_date = 0;
int do_compress = 0;
char* use_outfile = 0;
+ char* begin_sound = 0;
+ char* end_sound = 0;
int use_socket = 0;
LOGI("begin\n");
@@ -194,9 +199,11 @@
dump_traces_path = dump_vm_traces();
int c;
- while ((c = getopt(argc, argv, "dho:svz")) != -1) {
+ while ((c = getopt(argc, argv, "b:de:ho:svz")) != -1) {
switch (c) {
+ case 'b': begin_sound = optarg; break;
case 'd': do_add_date = 1; break;
+ case 'e': end_sound = optarg; break;
case 'o': use_outfile = optarg; break;
case 's': use_socket = 1; break;
case 'v': break; // compatibility no-op
@@ -244,16 +251,18 @@
gzip_pid = redirect_to_file(stdout, tmp_path, do_compress);
}
- /* bzzzzzz */
- if (vibrator) {
+ if (begin_sound) {
+ play_sound(begin_sound);
+ } else if (vibrator) {
fputs("150", vibrator);
fflush(vibrator);
}
dumpstate();
- /* bzzz bzzz bzzz */
- if (vibrator) {
+ if (end_sound) {
+ play_sound(end_sound);
+ } else if (vibrator) {
int i;
for (i = 0; i < 3; i++) {
fputs("75\n", vibrator);
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 682eafd..83b1d11 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -44,4 +44,7 @@
/* Displays a blocked processes in-kernel wait channel */
void show_wchan(int pid, const char *name);
+/* Play a sound via Stagefright */
+void play_sound(const char* path);
+
#endif /* _DUMPSTATE_H_ */
diff --git a/cmds/dumpstate/utils.c b/cmds/dumpstate/utils.c
index c7a78cc..f92acbbb 100644
--- a/cmds/dumpstate/utils.c
+++ b/cmds/dumpstate/utils.c
@@ -429,3 +429,7 @@
rename(anr_traces_path, traces_path);
return dump_traces_path;
}
+
+void play_sound(const char* path) {
+ run_command(NULL, 5, "/system/bin/stagefright", "-o", "-a", path, NULL);
+}
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index b0adaec..fd3a0d0 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -45,26 +45,26 @@
/**
* This class provides access to a centralized registry of the user's
- * online accounts. With this service, users only need to enter their
- * credentials (username and password) once for any account, granting
- * applications access to online resources with "one-click" approval.
+ * online accounts. The user enters credentials (username and password) once
+ * per account, granting applications access to online resources with
+ * "one-click" approval.
*
* <p>Different online services have different ways of handling accounts and
* authentication, so the account manager uses pluggable <em>authenticator</em>
- * modules for different <em>account types</em>. The authenticators (which
- * may be written by third parties) handle the actual details of validating
- * account credentials and storing account information. For example, Google,
- * Facebook, and Microsoft Exchange each have their own authenticator.
+ * modules for different <em>account types</em>. Authenticators (which may be
+ * written by third parties) handle the actual details of validating account
+ * credentials and storing account information. For example, Google, Facebook,
+ * and Microsoft Exchange each have their own authenticator.
*
* <p>Many servers support some notion of an <em>authentication token</em>,
* which can be used to authenticate a request to the server without sending
* the user's actual password. (Auth tokens are normally created with a
* separate request which does include the user's credentials.) AccountManager
- * can generate these auth tokens for applications, so the application doesn't
- * need to handle passwords directly. Auth tokens are normally reusable, and
- * cached by AccountManager, but must be refreshed periodically. It's the
- * responsibility of applications to <em>invalidate</em> auth tokens when they
- * stop working so the AccountManager knows it needs to regenerate them.
+ * can generate auth tokens for applications, so the application doesn't need to
+ * handle passwords directly. Auth tokens are normally reusable and cached by
+ * AccountManager, but must be refreshed periodically. It's the responsibility
+ * of applications to <em>invalidate</em> auth tokens when they stop working so
+ * the AccountManager knows it needs to regenerate them.
*
* <p>Applications accessing a server normally go through these steps:
*
@@ -84,14 +84,19 @@
* {@link #addAccount} may be called to prompt the user to create an
* account of the appropriate type.
*
+ * <li><b>Important:</b> If the application is using a previously remembered
+ * account selection, it must make sure the account is still in the list
+ * of accounts returned by {@link #getAccountsByType}. Requesting an auth token
+ * for an account no longer on the device results in an undefined failure.
+ *
* <li>Request an auth token for the selected account(s) using one of the
* {@link #getAuthToken} methods or related helpers. Refer to the description
* of each method for exact usage and error handling details.
*
* <li>Make the request using the auth token. The form of the auth token,
* the format of the request, and the protocol used are all specific to the
- * service you are accessing. The application makes the request itself, using
- * whatever network and protocol libraries are useful.
+ * service you are accessing. The application may use whatever network and
+ * protocol libraries are useful.
*
* <li><b>Important:</b> If the request fails with an authentication error,
* it could be that a cached auth token is stale and no longer honored by
@@ -103,7 +108,7 @@
* appropriate actions taken.
* </ul>
*
- * <p>Some AccountManager methods may require interaction with the user to
+ * <p>Some AccountManager methods may need to interact with the user to
* prompt for credentials, present options, or ask the user to add an account.
* The caller may choose whether to allow AccountManager to directly launch the
* necessary user interface and wait for the user, or to return an Intent which
@@ -113,18 +118,17 @@
* the current foreground {@link Activity} context.
*
* <p>Many AccountManager methods take {@link AccountManagerCallback} and
- * {@link Handler} as parameters. These methods return immediately but
+ * {@link Handler} as parameters. These methods return immediately and
* run asynchronously. If a callback is provided then
* {@link AccountManagerCallback#run} will be invoked on the Handler's
* thread when the request completes, successfully or not.
- * An {@link AccountManagerFuture} is returned by these requests and also
- * supplied to the callback (if any). The result is retrieved by calling
- * {@link AccountManagerFuture#getResult()} which waits for the operation
- * to complete (if necessary) and either returns the result or throws an
- * exception if an error occurred during the operation.
- * To make the request synchronously, call
+ * The result is retrieved by calling {@link AccountManagerFuture#getResult()}
+ * on the {@link AccountManagerFuture} returned by the method (and also passed
+ * to the callback). This method waits for the operation to complete (if
+ * necessary) and either returns the result or throws an exception if an error
+ * occurred during the operation. To make the request synchronously, call
* {@link AccountManagerFuture#getResult()} immediately on receiving the
- * future from the method. No callback need be supplied.
+ * future from the method; no callback need be supplied.
*
* <p>Requests which may block, including
* {@link AccountManagerFuture#getResult()}, must never be called on
@@ -143,32 +147,32 @@
public static final int ERROR_CODE_BAD_REQUEST = 8;
/**
- * The Bundle key used for the {@link String} account name in results
+ * Bundle key used for the {@link String} account name in results
* from methods which return information about a particular account.
*/
public static final String KEY_ACCOUNT_NAME = "authAccount";
/**
- * The Bundle key used for the {@link String} account type in results
+ * Bundle key used for the {@link String} account type in results
* from methods which return information about a particular account.
*/
public static final String KEY_ACCOUNT_TYPE = "accountType";
/**
- * The Bundle key used for the auth token value in results
+ * Bundle key used for the auth token value in results
* from {@link #getAuthToken} and friends.
*/
public static final String KEY_AUTHTOKEN = "authtoken";
/**
- * The Bundle key used for an {@link Intent} in results from methods that
+ * Bundle key used for an {@link Intent} in results from methods that
* may require the caller to interact with the user. The Intent can
* be used to start the corresponding user interface activity.
*/
public static final String KEY_INTENT = "intent";
/**
- * The Bundle key used to supply the password directly in options to
+ * Bundle key used to supply the password directly in options to
* {@link #confirmCredentials}, rather than prompting the user with
* the standard password prompt.
*/
@@ -476,7 +480,7 @@
* @param account The {@link Account} to add
* @param password The password to associate with the account, null for none
* @param userdata String values to use for the account's userdata, null for none
- * @return Whether the account was successfully added. False if the account
+ * @return True if the account was successfully added, false if the account
* already exists, the account is null, or another error occurs.
*/
public boolean addAccountExplicitly(Account account, String password, Bundle userdata) {
@@ -733,15 +737,14 @@
* sense to ask the user directly for a password.
*
* <p>If a previously generated auth token is cached for this account and
- * type, then it will be returned. Otherwise, if we have a saved password
- * the server accepts, it will be used to generate a new auth token.
- * Otherwise, the user will be asked for a password, which will be sent to
- * the server to generate a new auth token.
+ * type, then it is returned. Otherwise, if a saved password is
+ * available, it is sent to the server to generate a new auth token.
+ * Otherwise, the user is prompted to enter a password.
*
- * <p>The value of the auth token type depends on the authenticator.
- * Some services use different tokens to access different functionality --
- * for example, Google uses different auth tokens to access Gmail and
- * Google Calendar for the same account.
+ * <p>Some authenticators have auth token <em>types</em>, whose value
+ * is authenticator-dependent. Some services use different token types to
+ * access different functionality -- for example, Google uses different auth
+ * tokens to access Gmail and Google Calendar for the same account.
*
* <p>This method may be called from any thread, but the returned
* {@link AccountManagerFuture} must not be used on the main thread.
@@ -778,6 +781,9 @@
* <li> {@link IOException} if the authenticator experienced an I/O problem
* creating a new auth token, usually because of network trouble
* </ul>
+ * If the account is no longer present on the device, the return value is
+ * authenticator-dependent. The caller should verify the validity of the
+ * account before requesting an auth token.
*/
public AccountManagerFuture<Bundle> getAuthToken(
final Account account, final String authTokenType, final Bundle options,
@@ -800,29 +806,27 @@
* user should not be immediately interrupted with a password prompt.
*
* <p>If a previously generated auth token is cached for this account and
- * type, then it will be returned. Otherwise, if we have saved credentials
- * the server accepts, it will be used to generate a new auth token.
- * Otherwise, an Intent will be returned which, when started, will prompt
- * the user for a password. If the notifyAuthFailure parameter is set,
- * the same Intent will be associated with a status bar notification,
+ * type, then it is returned. Otherwise, if a saved password is
+ * available, it is sent to the server to generate a new auth token.
+ * Otherwise, an {@link Intent} is returned which, when started, will
+ * prompt the user for a password. If the notifyAuthFailure parameter is
+ * set, a status bar notification is also created with the same Intent,
* alerting the user that they need to enter a password at some point.
*
- * <p>If the intent is left in a notification, you will need to wait until
- * the user gets around to entering a password before trying again,
- * which could be hours or days or never. When it does happen, the
- * account manager will broadcast the {@link #LOGIN_ACCOUNTS_CHANGED_ACTION}
- * {@link Intent}, which applications can use to trigger another attempt
- * to fetch an auth token.
+ * <p>In that case, you may need to wait until the user responds, which
+ * could take hours or days or forever. When the user does respond and
+ * supply a new password, the account manager will broadcast the
+ * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent, which applications can
+ * use to try again.
*
- * <p>If notifications are not enabled, it is the application's
- * responsibility to launch the returned intent at some point to let
- * the user enter credentials. In either case, the result from this
- * call will not wait for user action.
+ * <p>If notifyAuthFailure is not set, it is the application's
+ * responsibility to launch the returned Intent at some point.
+ * Either way, the result from this call will not wait for user action.
*
- * <p>The value of the auth token type depends on the authenticator.
- * Some services use different tokens to access different functionality --
- * for example, Google uses different auth tokens to access Gmail and
- * Google Calendar for the same account.
+ * <p>Some authenticators have auth token <em>types</em>, whose value
+ * is authenticator-dependent. Some services use different token types to
+ * access different functionality -- for example, Google uses different auth
+ * tokens to access Gmail and Google Calendar for the same account.
*
* <p>This method may be called from any thread, but the returned
* {@link AccountManagerFuture} must not be used on the main thread.
@@ -851,7 +855,7 @@
* must enter credentials, the returned Bundle contains only
* {@link #KEY_INTENT} with the {@link Intent} needed to launch a prompt.
*
- * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+ * If an error occurred, {@link AccountManagerFuture#getResult()} throws:
* <ul>
* <li> {@link AuthenticatorException} if the authenticator failed to respond
* <li> {@link OperationCanceledException} if the operation is canceled for
@@ -859,6 +863,9 @@
* <li> {@link IOException} if the authenticator experienced an I/O problem
* creating a new auth token, usually because of network trouble
* </ul>
+ * If the account is no longer present on the device, the return value is
+ * authenticator-dependent. The caller should verify the validity of the
+ * account before requesting an auth token.
*/
public AccountManagerFuture<Bundle> getAuthToken(
final Account account, final String authTokenType, final boolean notifyAuthFailure,
@@ -910,9 +917,8 @@
*
* If no activity was specified, the returned Bundle contains only
* {@link #KEY_INTENT} with the {@link Intent} needed to launch the
- * actual account creation process.
- *
- * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+ * actual account creation process. If an error occurred,
+ * {@link AccountManagerFuture#getResult()} throws:
* <ul>
* <li> {@link AuthenticatorException} if no authenticator was registered for
* this account type or the authenticator failed to respond
@@ -979,9 +985,8 @@
*
* If no activity or password was specified, the returned Bundle contains
* only {@link #KEY_INTENT} with the {@link Intent} needed to launch the
- * password prompt.
- *
- * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+ * password prompt. If an error occurred,
+ * {@link AccountManagerFuture#getResult()} throws:
* <ul>
* <li> {@link AuthenticatorException} if the authenticator failed to respond
* <li> {@link OperationCanceledException} if the operation was canceled for
@@ -1040,9 +1045,8 @@
*
* If no activity was specified, the returned Bundle contains only
* {@link #KEY_INTENT} with the {@link Intent} needed to launch the
- * password prompt.
- *
- * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+ * password prompt. If an error occurred,
+ * {@link AccountManagerFuture#getResult()} throws:
* <ul>
* <li> {@link AuthenticatorException} if the authenticator failed to respond
* <li> {@link OperationCanceledException} if the operation was canceled for
@@ -1091,8 +1095,8 @@
* which is empty if properties were edited successfully, or
* if no activity was specified, contains only {@link #KEY_INTENT}
* needed to launch the authenticator's settings dialog.
- *
- * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+ * If an error occurred, {@link AccountManagerFuture#getResult()}
+ * throws:
* <ul>
* <li> {@link AuthenticatorException} if no authenticator was registered for
* this account type or the authenticator failed to respond
@@ -1617,7 +1621,7 @@
* <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
* </ul>
*
- * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+ * If an error occurred, {@link AccountManagerFuture#getResult()} throws:
* <ul>
* <li> {@link AuthenticatorException} if no authenticator was registered for
* this account type or the authenticator failed to respond
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index c55c07f..d49adc2 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -969,10 +969,6 @@
mTitleReady = true;
onTitleChanged(getTitle(), getTitleColor());
}
- if (mWindow != null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
- // Invalidate the action bar menu so that it can initialize properly.
- mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
- }
mCalled = true;
}
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index 5371fa5..d685cf3 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -34,6 +34,12 @@
* You do not instantiate this class directly; instead, retrieve it through
* {@link android.content.Context#getSystemService}.
*
+ * <p>
+ * The ClipboardManager API itself is very simple: it consists of methods
+ * to atomically get and set the current primary clipboard data. That data
+ * is expressed as a {@link ClippedData} object, which defines the protocol
+ * for data exchange between applications.
+ *
* @see android.content.Context#getSystemService
*/
public class ClipboardManager extends android.text.ClipboardManager {
@@ -152,7 +158,7 @@
public CharSequence getText() {
ClippedData clip = getPrimaryClip();
if (clip != null && clip.getItemCount() > 0) {
- return clip.getItem(0).getText();
+ return clip.getItem(0).coerceToText(mContext);
}
return null;
}
@@ -167,11 +173,11 @@
}
/**
- * Returns true if the clipboard has a primary clip containing text; false otherwise.
+ * @deprecated Use {@link #hasPrimaryClip()} instead.
*/
public boolean hasText() {
try {
- return getService().hasClipboardText();
+ return getService().hasPrimaryClip();
} catch (RemoteException e) {
return false;
}
diff --git a/core/java/android/content/ClippedData.java b/core/java/android/content/ClippedData.java
index ebb194f..c3f0237 100644
--- a/core/java/android/content/ClippedData.java
+++ b/core/java/android/content/ClippedData.java
@@ -16,12 +16,18 @@
package android.content;
+import android.content.res.AssetFileDescriptor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import android.util.Log;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
import java.util.ArrayList;
/**
@@ -31,19 +37,88 @@
* each of which can hold one or more representations of an item of data.
* For display to the user, it also has a label and iconic representation.</p>
*
- * <p>The types than an individial item can currently contain are:</p>
+ * <p>Each Item instance can be one of three main classes of data: a simple
+ * CharSequence of text, a single Intent object, or a Uri. See {@link Item}
+ * for more details.
*
- * <ul>
- * <li> Text: a basic string of text. This is actually a CharSequence,
- * so it can be formatted text supported by corresponding Android built-in
- * style spans. (Custom application spans are not supported and will be
- * stripped when transporting through the clipboard.)
- * <li> Intent: an arbitrary Intent object. A typical use is the shortcut
- * to create when pasting a clipped item on to the home screen.
- * <li> Uri: a URI reference. Currently this should only be a content: URI.
- * This representation allows an application to share complex or large clips,
- * by providing a URI to a content provider holding the data.
- * </ul>
+ * <a name="ImplementingPaste"></a>
+ * <h3>Implementing Paste or Drop</h3>
+ *
+ * <p>To implement a paste or drop of a ClippedData object into an application,
+ * the application must correctly interpret the data for its use. If the {@link Item}
+ * it contains is simple text or an Intent, there is little to be done: text
+ * can only be interpreted as text, and an Intent will typically be used for
+ * creating shortcuts (such as placing icons on the home screen) or other
+ * actions.
+ *
+ * <p>If all you want is the textual representation of the clipped data, you
+ * can use the convenience method {@link Item#coerceToText Item.coerceToText}.
+ *
+ * <p>More complicated exchanges will be done through URIs, in particular
+ * "content:" URIs. A content URI allows the recipient of a ClippedData item
+ * to interact closely with the ContentProvider holding the data in order to
+ * negotiate the transfer of that data.
+ *
+ * <p>For example, here is the paste function of a simple NotePad application.
+ * When retrieving the data from the clipboard, it can do either two things:
+ * if the clipboard contains a URI reference to an existing note, it copies
+ * the entire structure of the note into a new note; otherwise, it simply
+ * coerces the clip into text and uses that as the new note's contents.
+ *
+ * {@sample development/samples/NotePad/src/com/example/android/notepad/NoteEditor.java
+ * paste}
+ *
+ * <p>In many cases an application can paste various types of streams of data. For
+ * example, an e-mail application may want to allow the user to paste an image
+ * or other binary data as an attachment. This is accomplished through the
+ * ContentResolver {@link ContentResolver#getStreamTypes(Uri, String)} and
+ * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, android.os.Bundle)}
+ * methods. These allow a client to discover the type(s) of data that a particular
+ * content URI can make available as a stream and retrieve the stream of data.
+ *
+ * <p>For example, the implementation of {@link Item#coerceToText Item.coerceToText}
+ * itself uses this to try to retrieve a URI clip as a stream of text:
+ *
+ * {@sample frameworks/base/core/java/android/content/ClippedData.java coerceToText}
+ *
+ * <a name="ImplementingCopy"></a>
+ * <h3>Implementing Copy or Drag</h3>
+ *
+ * <p>To be the source of a clip, the application must construct a ClippedData
+ * object that any recipient can interpret best for their context. If the clip
+ * is to contain a simple text, Intent, or URI, this is easy: an {@link Item}
+ * containing the appropriate data type can be constructed and used.
+ *
+ * <p>More complicated data types require the implementation of support in
+ * a ContentProvider for describing and generating the data for the recipient.
+ * A common scenario is one where an application places on the clipboard the
+ * content: URI of an object that the user has copied, with the data at that
+ * URI consisting of a complicated structure that only other applications with
+ * direct knowledge of the structure can use.
+ *
+ * <p>For applications that do not have intrinsic knowledge of the data structure,
+ * the content provider holding it can make the data available as an arbitrary
+ * number of types of data streams. This is done by implementing the
+ * ContentProvider {@link ContentProvider#getStreamTypes(Uri, String)} and
+ * {@link ContentProvider#openTypedAssetFile(Uri, String, android.os.Bundle)}
+ * methods.
+ *
+ * <p>Going back to our simple NotePad application, this is the implementation
+ * it may have to convert a single note URI (consisting of a title and the note
+ * text) into a stream of plain text data.
+ *
+ * {@sample development/samples/NotePad/src/com/example/android/notepad/NotePadProvider.java
+ * stream}
+ *
+ * <p>The copy operation in our NotePad application is now just a simple matter
+ * of making a clip containing the URI of the note being copied:
+ *
+ * {@sample development/samples/NotePad/src/com/example/android/notepad/NotesList.java
+ * copy}
+ *
+ * <p>Note if a paste operation needs this clip as text (for example to paste
+ * into an editor), then {@link Item#coerceToText(Context)} will ask the content
+ * provider for the clip URI as text and successfully paste the entire note.
*/
public class ClippedData implements Parcelable {
CharSequence mLabel;
@@ -51,40 +126,166 @@
final ArrayList<Item> mItems = new ArrayList<Item>();
+ /**
+ * Description of a single item in a ClippedData.
+ *
+ * <p>The types than an individual item can currently contain are:</p>
+ *
+ * <ul>
+ * <li> Text: a basic string of text. This is actually a CharSequence,
+ * so it can be formatted text supported by corresponding Android built-in
+ * style spans. (Custom application spans are not supported and will be
+ * stripped when transporting through the clipboard.)
+ * <li> Intent: an arbitrary Intent object. A typical use is the shortcut
+ * to create when pasting a clipped item on to the home screen.
+ * <li> Uri: a URI reference. This may be any URI (such as an http: URI
+ * representing a bookmark), however it is often a content: URI. Using
+ * content provider references as clips like this allows an application to
+ * share complex or large clips through the standard content provider
+ * facilities.
+ * </ul>
+ */
public static class Item {
CharSequence mText;
Intent mIntent;
Uri mUri;
+ /**
+ * Create an Item consisting of a single block of (possibly styled) text.
+ */
public Item(CharSequence text) {
mText = text;
}
+ /**
+ * Create an Item consisting of an arbitrary Intent.
+ */
public Item(Intent intent) {
mIntent = intent;
}
+ /**
+ * Create an Item consisting of an arbitrary URI.
+ */
public Item(Uri uri) {
mUri = uri;
}
+ /**
+ * Create a complex Item, containing multiple representations of
+ * text, intent, and/or URI.
+ */
public Item(CharSequence text, Intent intent, Uri uri) {
mText = text;
mIntent = intent;
mUri = uri;
}
+ /**
+ * Retrieve the raw text contained in this Item.
+ */
public CharSequence getText() {
return mText;
}
+ /**
+ * Retrieve the raw Intent contained in this Item.
+ */
public Intent getIntent() {
return mIntent;
}
+ /**
+ * Retrieve the raw URI contained in this Item.
+ */
public Uri getUri() {
return mUri;
}
+
+ /**
+ * Turn this item into text, regardless of the type of data it
+ * actually contains.
+ *
+ * <p>The algorithm for deciding what text to return is:
+ * <ul>
+ * <li> If {@link #getText} is non-null, return that.
+ * <li> If {@link #getUri} is non-null, try to retrieve its data
+ * as a text stream from its content provider. If this succeeds, copy
+ * the text into a String and return it. If it is not a content: URI or
+ * the content provider does not supply a text representation, return
+ * the raw URI as a string.
+ * <li> If {@link #getIntent} is non-null, convert that to an intent:
+ * URI and returnit.
+ * <li> Otherwise, return an empty string.
+ * </ul>
+ *
+ * @param context The caller's Context, from which its ContentResolver
+ * and other things can be retrieved.
+ * @return Returns the item's textual representation.
+ */
+//BEGIN_INCLUDE(coerceToText)
+ public CharSequence coerceToText(Context context) {
+ // If this Item has an explicit textual value, simply return that.
+ if (mText != null) {
+ return mText;
+ }
+
+ // If this Item has a URI value, try using that.
+ if (mUri != null) {
+
+ // First see if the URI can be opened as a plain text stream
+ // (of any sub-type). If so, this is the best textual
+ // representation for it.
+ FileInputStream stream = null;
+ try {
+ // Ask for a stream of the desired type.
+ AssetFileDescriptor descr = context.getContentResolver()
+ .openTypedAssetFileDescriptor(mUri, "text/*", null);
+ stream = descr.createInputStream();
+ InputStreamReader reader = new InputStreamReader(stream, "UTF-8");
+
+ // Got it... copy the stream into a local string and return it.
+ StringBuilder builder = new StringBuilder(128);
+ char[] buffer = new char[8192];
+ int len;
+ while ((len=reader.read(buffer)) > 0) {
+ builder.append(buffer, 0, len);
+ }
+ return builder.toString();
+
+ } catch (FileNotFoundException e) {
+ // Unable to open content URI as text... not really an
+ // error, just something to ignore.
+
+ } catch (IOException e) {
+ // Something bad has happened.
+ Log.w("ClippedData", "Failure loading text", e);
+ return e.toString();
+
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ // If we couldn't open the URI as a stream, then the URI itself
+ // probably serves fairly well as a textual representation.
+ return mUri.toString();
+ }
+
+ // Finally, if all we have is an Intent, then we can just turn that
+ // into text. Not the most user-friendly thing, but it's something.
+ if (mIntent != null) {
+ return mIntent.toUri(Intent.URI_INTENT_SCHEME);
+ }
+
+ // Shouldn't get here, but just in case...
+ return "";
+ }
+//END_INCLUDE(coerceToText)
}
/**
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index a3252ed..1163add 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -28,6 +28,7 @@
import android.database.IContentObserver;
import android.database.SQLException;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
@@ -36,6 +37,7 @@
import java.io.File;
import java.io.FileNotFoundException;
+import java.io.IOException;
import java.util.ArrayList;
/**
@@ -251,6 +253,18 @@
return ContentProvider.this.call(method, request, args);
}
+ @Override
+ public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
+ return ContentProvider.this.getStreamTypes(uri, mimeTypeFilter);
+ }
+
+ @Override
+ public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeType, Bundle opts)
+ throws FileNotFoundException {
+ enforceReadPermission(uri);
+ return ContentProvider.this.openTypedAssetFile(uri, mimeType, opts);
+ }
+
private void enforceReadPermission(Uri uri) {
final int uid = Binder.getCallingUid();
if (uid == mMyUid) {
@@ -752,6 +766,164 @@
}
/**
+ * Helper to compare two MIME types, where one may be a pattern.
+ * @param concreteType A fully-specified MIME type.
+ * @param desiredType A desired MIME type that may be a pattern such as *\/*.
+ * @return Returns true if the two MIME types match.
+ */
+ public static boolean compareMimeTypes(String concreteType, String desiredType) {
+ final int typeLength = desiredType.length();
+ if (typeLength == 3 && desiredType.equals("*/*")) {
+ return true;
+ }
+
+ final int slashpos = desiredType.indexOf('/');
+ if (slashpos > 0) {
+ if (typeLength == slashpos+2 && desiredType.charAt(slashpos+1) == '*') {
+ if (desiredType.regionMatches(0, concreteType, 0, slashpos+1)) {
+ return true;
+ }
+ } else if (desiredType.equals(concreteType)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Called by a client to determine the types of data streams that this
+ * content provider supports for the given URI. The default implementation
+ * returns null, meaning no types. If your content provider stores data
+ * of a particular type, return that MIME type if it matches the given
+ * mimeTypeFilter. If it can perform type conversions, return an array
+ * of all supported MIME types that match mimeTypeFilter.
+ *
+ * @param uri The data in the content provider being queried.
+ * @param mimeTypeFilter The type of data the client desires. May be
+ * a pattern, such as *\/* to retrieve all possible data types.
+ * @returns Returns null if there are no possible data streams for the
+ * given mimeTypeFilter. Otherwise returns an array of all available
+ * concrete MIME types.
+ *
+ * @see #getType(Uri)
+ * @see #openTypedAssetFile(Uri, String, Bundle)
+ * @see #compareMimeTypes(String, String)
+ */
+ public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
+ return null;
+ }
+
+ /**
+ * Called by a client to open a read-only stream containing data of a
+ * particular MIME type. This is like {@link #openAssetFile(Uri, String)},
+ * except the file can only be read-only and the content provider may
+ * perform data conversions to generate data of the desired type.
+ *
+ * <p>The default implementation compares the given mimeType against the
+ * result of {@link #getType(Uri)} and, if the match, simple calls
+ * {@link #openAssetFile(Uri, String)}.
+ *
+ * <p>See {@link ClippedData} for examples of the use and implementation
+ * of this method.
+ *
+ * @param uri The data in the content provider being queried.
+ * @param mimeTypeFilter The type of data the client desires. May be
+ * a pattern, such as *\/*, if the caller does not have specific type
+ * requirements; in this case the content provider will pick its best
+ * type matching the pattern.
+ * @param opts Additional options from the client. The definitions of
+ * these are specific to the content provider being called.
+ *
+ * @return Returns a new AssetFileDescriptor from which the client can
+ * read data of the desired type.
+ *
+ * @throws FileNotFoundException Throws FileNotFoundException if there is
+ * no file associated with the given URI or the mode is invalid.
+ * @throws SecurityException Throws SecurityException if the caller does
+ * not have permission to access the data.
+ * @throws IllegalArgumentException Throws IllegalArgumentException if the
+ * content provider does not support the requested MIME type.
+ *
+ * @see #getStreamTypes(Uri, String)
+ * @see #openAssetFile(Uri, String)
+ * @see #compareMimeTypes(String, String)
+ */
+ public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
+ throws FileNotFoundException {
+ String baseType = getType(uri);
+ if (baseType != null && compareMimeTypes(baseType, mimeTypeFilter)) {
+ return openAssetFile(uri, "r");
+ }
+ throw new FileNotFoundException("Can't open " + uri + " as type " + mimeTypeFilter);
+ }
+
+ /**
+ * Interface to write a stream of data to a pipe. Use with
+ * {@link ContentProvider#openPipeHelper}.
+ */
+ public interface PipeDataWriter<T> {
+ /**
+ * Called from a background thread to stream data out to a pipe.
+ * Note that the pipe is blocking, so this thread can block on
+ * writes for an arbitrary amount of time if the client is slow
+ * at reading.
+ *
+ * @param output The pipe where data should be written. This will be
+ * closed for you upon returning from this function.
+ * @param uri The URI whose data is to be written.
+ * @param mimeType The desired type of data to be written.
+ * @param opts Options supplied by caller.
+ * @param args Your own custom arguments.
+ */
+ public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType,
+ Bundle opts, T args);
+ }
+
+ /**
+ * A helper function for implementing {@link #openTypedAssetFile}, for
+ * creating a data pipe and background thread allowing you to stream
+ * generated data back to the client. This function returns a new
+ * ParcelFileDescriptor that should be returned to the caller (the caller
+ * is responsible for closing it).
+ *
+ * @param uri The URI whose data is to be written.
+ * @param mimeType The desired type of data to be written.
+ * @param opts Options supplied by caller.
+ * @param args Your own custom arguments.
+ * @param func Interface implementing the function that will actually
+ * stream the data.
+ * @return Returns a new ParcelFileDescriptor holding the read side of
+ * the pipe. This should be returned to the caller for reading; the caller
+ * is responsible for closing it when done.
+ */
+ public <T> ParcelFileDescriptor openPipeHelper(final Uri uri, final String mimeType,
+ final Bundle opts, final T args, final PipeDataWriter<T> func)
+ throws FileNotFoundException {
+ try {
+ final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
+
+ AsyncTask<Object, Object, Object> task = new AsyncTask<Object, Object, Object>() {
+ @Override
+ protected Object doInBackground(Object... params) {
+ func.writeDataToPipe(fds[1], uri, mimeType, opts, args);
+ try {
+ fds[1].close();
+ } catch (IOException e) {
+ Log.w(TAG, "Failure closing pipe", e);
+ }
+ return null;
+ }
+ };
+ task.execute((Object[])null);
+
+ return fds[0];
+ } catch (IOException e) {
+ throw new FileNotFoundException("failure making pipe");
+ }
+ }
+
+ /**
* Returns true if this instance is a temporary content provider.
* @return true if this instance is a temporary content provider
*/
@@ -777,6 +949,11 @@
* @param info Registered information about this content provider
*/
public void attachInfo(Context context, ProviderInfo info) {
+ /*
+ * We may be using AsyncTask from binder threads. Make it init here
+ * so its static handler is on the main thread.
+ */
+ AsyncTask.init();
/*
* Only allow it to be set once, so after the content service gives
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 0858ea5..0540109 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -18,6 +18,7 @@
import android.database.Cursor;
import android.net.Uri;
+import android.os.Bundle;
import android.os.RemoteException;
import android.os.ParcelFileDescriptor;
import android.content.res.AssetFileDescriptor;
@@ -43,53 +44,77 @@
mContentResolver = contentResolver;
}
- /** see {@link ContentProvider#query} */
+ /** See {@link ContentProvider#query ContentProvider.query} */
public Cursor query(Uri url, String[] projection, String selection,
String[] selectionArgs, String sortOrder) throws RemoteException {
return mContentProvider.query(url, projection, selection, selectionArgs, sortOrder);
}
- /** see {@link ContentProvider#getType} */
+ /** See {@link ContentProvider#getType ContentProvider.getType} */
public String getType(Uri url) throws RemoteException {
return mContentProvider.getType(url);
}
- /** see {@link ContentProvider#insert} */
+ /** See {@link ContentProvider#getStreamTypes ContentProvider.getStreamTypes} */
+ public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException {
+ return mContentProvider.getStreamTypes(url, mimeTypeFilter);
+ }
+
+ /** See {@link ContentProvider#insert ContentProvider.insert} */
public Uri insert(Uri url, ContentValues initialValues)
throws RemoteException {
return mContentProvider.insert(url, initialValues);
}
- /** see {@link ContentProvider#bulkInsert} */
+ /** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */
public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException {
return mContentProvider.bulkInsert(url, initialValues);
}
- /** see {@link ContentProvider#delete} */
+ /** See {@link ContentProvider#delete ContentProvider.delete} */
public int delete(Uri url, String selection, String[] selectionArgs)
throws RemoteException {
return mContentProvider.delete(url, selection, selectionArgs);
}
- /** see {@link ContentProvider#update} */
+ /** See {@link ContentProvider#update ContentProvider.update} */
public int update(Uri url, ContentValues values, String selection,
String[] selectionArgs) throws RemoteException {
return mContentProvider.update(url, values, selection, selectionArgs);
}
- /** see {@link ContentProvider#openFile} */
+ /**
+ * See {@link ContentProvider#openFile ContentProvider.openFile}. Note that
+ * this <em>does not</em>
+ * take care of non-content: URIs such as file:. It is strongly recommended
+ * you use the {@link ContentResolver#openFileDescriptor
+ * ContentResolver.openFileDescriptor} API instead.
+ */
public ParcelFileDescriptor openFile(Uri url, String mode)
throws RemoteException, FileNotFoundException {
return mContentProvider.openFile(url, mode);
}
- /** see {@link ContentProvider#openAssetFile} */
+ /**
+ * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}.
+ * Note that this <em>does not</em>
+ * take care of non-content: URIs such as file:. It is strongly recommended
+ * you use the {@link ContentResolver#openAssetFileDescriptor
+ * ContentResolver.openAssetFileDescriptor} API instead.
+ */
public AssetFileDescriptor openAssetFile(Uri url, String mode)
throws RemoteException, FileNotFoundException {
return mContentProvider.openAssetFile(url, mode);
}
- /** see {@link ContentProvider#applyBatch} */
+ /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
+ public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri,
+ String mimeType, Bundle opts)
+ throws RemoteException, FileNotFoundException {
+ return mContentProvider.openTypedAssetFile(uri, mimeType, opts);
+ }
+
+ /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException {
return mContentProvider.applyBatch(operations);
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index fdb3d20..d1ab8d5 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -257,6 +257,38 @@
reply.writeBundle(responseBundle);
return true;
}
+
+ case GET_STREAM_TYPES_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ String mimeTypeFilter = data.readString();
+ String[] types = getStreamTypes(url, mimeTypeFilter);
+ reply.writeNoException();
+ reply.writeStringArray(types);
+
+ return true;
+ }
+
+ case OPEN_TYPED_ASSET_FILE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ String mimeType = data.readString();
+ Bundle opts = data.readBundle();
+
+ AssetFileDescriptor fd;
+ fd = openTypedAssetFile(url, mimeType, opts);
+ reply.writeNoException();
+ if (fd != null) {
+ reply.writeInt(1);
+ fd.writeToParcel(reply,
+ Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ } else {
+ reply.writeInt(0);
+ }
+ return true;
+ }
}
} catch (Exception e) {
DatabaseUtils.writeExceptionToParcel(reply, e);
@@ -568,5 +600,50 @@
return bundle;
}
+ public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ url.writeToParcel(data, 0);
+ data.writeString(mimeTypeFilter);
+
+ mRemote.transact(IContentProvider.GET_STREAM_TYPES_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ String[] out = reply.createStringArray();
+
+ data.recycle();
+ reply.recycle();
+
+ return out;
+ }
+
+ public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
+ throws RemoteException, FileNotFoundException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ url.writeToParcel(data, 0);
+ data.writeString(mimeType);
+ data.writeBundle(opts);
+
+ mRemote.transact(IContentProvider.OPEN_TYPED_ASSET_FILE_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply);
+ int has = reply.readInt();
+ AssetFileDescriptor fd = has != 0
+ ? AssetFileDescriptor.CREATOR.createFromParcel(reply) : null;
+
+ data.recycle();
+ reply.recycle();
+
+ return fd;
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 2ea0df96..22feb9a 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -186,8 +186,7 @@
* using the content:// scheme.
* @return A MIME type for the content, or null if the URL is invalid or the type is unknown
*/
- public final String getType(Uri url)
- {
+ public final String getType(Uri url) {
IContentProvider provider = acquireProvider(url);
if (provider == null) {
return null;
@@ -204,6 +203,39 @@
}
/**
+ * Query for the possible MIME types for the representations the given
+ * content URL can be returned when opened as as stream with
+ * {@link #openTypedAssetFileDescriptor}. Note that the types here are
+ * not necessarily a superset of the type returned by {@link #getType} --
+ * many content providers can not return a raw stream for the structured
+ * data that they contain.
+ *
+ * @param url A Uri identifying content (either a list or specific type),
+ * using the content:// scheme.
+ * @param mimeTypeFilter The desired MIME type. This may be a pattern,
+ * such as *\/*, to query for all available MIME types that match the
+ * pattern.
+ * @return Returns an array of MIME type strings for all availablle
+ * data streams that match the given mimeTypeFilter. If there are none,
+ * null is returned.
+ */
+ public String[] getStreamTypes(Uri url, String mimeTypeFilter) {
+ IContentProvider provider = acquireProvider(url);
+ if (provider == null) {
+ return null;
+ }
+ try {
+ return provider.getStreamTypes(url, mimeTypeFilter);
+ } catch (RemoteException e) {
+ return null;
+ } catch (java.lang.Exception e) {
+ return null;
+ } finally {
+ releaseProvider(provider);
+ }
+ }
+
+ /**
* <p>
* Query the given URI, returning a {@link Cursor} over the result set.
* </p>
@@ -349,7 +381,7 @@
}
/**
- * Open a raw file descriptor to access data under a "content:" URI. This
+ * Open a raw file descriptor to access data under a URI. This
* is like {@link #openAssetFileDescriptor(Uri, String)}, but uses the
* underlying {@link ContentProvider#openFile}
* ContentProvider.openFile()} method, so will <em>not</em> work with
@@ -399,7 +431,7 @@
}
/**
- * Open a raw file descriptor to access data under a "content:" URI. This
+ * Open a raw file descriptor to access data under a URI. This
* interacts with the underlying {@link ContentProvider#openAssetFile}
* method of the provider associated with the given URI, to retrieve any file stored there.
*
@@ -433,6 +465,11 @@
* </li>
* </ul>
*
+ * <p>Note that if this function is called for read-only input (mode is "r")
+ * on a content: URI, it will instead call {@link #openTypedAssetFileDescriptor}
+ * for you with a MIME type of "*\/*". This allows such callers to benefit
+ * from any built-in data conversion that a provider implements.
+ *
* @param uri The desired URI to open.
* @param mode The file mode to use, as per {@link ContentProvider#openAssetFile
* ContentProvider.openAssetFile}.
@@ -459,29 +496,97 @@
new File(uri.getPath()), modeToMode(uri, mode));
return new AssetFileDescriptor(pfd, 0, -1);
} else {
- IContentProvider provider = acquireProvider(uri);
- if (provider == null) {
- throw new FileNotFoundException("No content provider: " + uri);
- }
- try {
- AssetFileDescriptor fd = provider.openAssetFile(uri, mode);
- if(fd == null) {
- releaseProvider(provider);
- return null;
+ if ("r".equals(mode)) {
+ return openTypedAssetFileDescriptor(uri, "*/*", null);
+ } else {
+ IContentProvider provider = acquireProvider(uri);
+ if (provider == null) {
+ throw new FileNotFoundException("No content provider: " + uri);
}
- ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
- fd.getParcelFileDescriptor(), provider);
- return new AssetFileDescriptor(pfd, fd.getStartOffset(),
- fd.getDeclaredLength());
- } catch (RemoteException e) {
+ try {
+ AssetFileDescriptor fd = provider.openAssetFile(uri, mode);
+ if(fd == null) {
+ releaseProvider(provider);
+ return null;
+ }
+ ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
+ fd.getParcelFileDescriptor(), provider);
+
+ // Success! Don't release the provider when exiting, let
+ // ParcelFileDescriptorInner do that when it is closed.
+ provider = null;
+
+ return new AssetFileDescriptor(pfd, fd.getStartOffset(),
+ fd.getDeclaredLength());
+ } catch (RemoteException e) {
+ throw new FileNotFoundException("Dead content provider: " + uri);
+ } catch (FileNotFoundException e) {
+ throw e;
+ } finally {
+ if (provider != null) {
+ releaseProvider(provider);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Open a raw file descriptor to access (potentially type transformed)
+ * data from a "content:" URI. This interacts with the underlying
+ * {@link ContentProvider#openTypedAssetFile} method of the provider
+ * associated with the given URI, to retrieve retrieve any appropriate
+ * data stream for the data stored there.
+ *
+ * <p>Unlike {@link #openAssetFileDescriptor}, this function only works
+ * with "content:" URIs, because content providers are the only facility
+ * with an associated MIME type to ensure that the returned data stream
+ * is of the desired type.
+ *
+ * <p>All text/* streams are encoded in UTF-8.
+ *
+ * @param uri The desired URI to open.
+ * @param mimeType The desired MIME type of the returned data. This can
+ * be a pattern such as *\/*, which will allow the content provider to
+ * select a type, though there is no way for you to determine what type
+ * it is returning.
+ * @param opts Additional provider-dependent options.
+ * @return Returns a new ParcelFileDescriptor from which you can read the
+ * data stream from the provider. Note that this may be a pipe, meaning
+ * you can't seek in it. The only seek you should do is if the
+ * AssetFileDescriptor contains an offset, to move to that offset before
+ * reading. You own this descriptor and are responsible for closing it when done.
+ * @throws FileNotFoundException Throws FileNotFoundException of no
+ * data of the desired type exists under the URI.
+ */
+ public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri,
+ String mimeType, Bundle opts) throws FileNotFoundException {
+ IContentProvider provider = acquireProvider(uri);
+ if (provider == null) {
+ throw new FileNotFoundException("No content provider: " + uri);
+ }
+ try {
+ AssetFileDescriptor fd = provider.openTypedAssetFile(uri, mimeType, opts);
+ if (fd == null) {
releaseProvider(provider);
- throw new FileNotFoundException("Dead content provider: " + uri);
- } catch (FileNotFoundException e) {
+ return null;
+ }
+ ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
+ fd.getParcelFileDescriptor(), provider);
+
+ // Success! Don't release the provider when exiting, let
+ // ParcelFileDescriptorInner do that when it is closed.
+ provider = null;
+
+ return new AssetFileDescriptor(pfd, fd.getStartOffset(),
+ fd.getDeclaredLength());
+ } catch (RemoteException e) {
+ throw new FileNotFoundException("Dead content provider: " + uri);
+ } catch (FileNotFoundException e) {
+ throw e;
+ } finally {
+ if (provider != null) {
releaseProvider(provider);
- throw e;
- } catch (RuntimeException e) {
- releaseProvider(provider);
- throw e;
}
}
}
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index 67e7581..8f122ce 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -59,6 +59,7 @@
throws RemoteException, FileNotFoundException;
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException;
+
/**
* @hide -- until interface has proven itself
*
@@ -71,6 +72,11 @@
*/
public Bundle call(String method, String request, Bundle args) throws RemoteException;
+ // Data interchange.
+ public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException;
+ public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
+ throws RemoteException, FileNotFoundException;
+
/* IPC constants */
static final String descriptor = "android.content.IContentProvider";
@@ -84,4 +90,6 @@
static final int OPEN_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 14;
static final int APPLY_BATCH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 19;
static final int CALL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 20;
+ static final int GET_STREAM_TYPES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 21;
+ static final int OPEN_TYPED_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 22;
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 2acc4a0..58174fb 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -985,6 +985,15 @@
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_INSERT = "android.intent.action.INSERT";
/**
+ * Activity Action: Create a new item in the given container, initializing it
+ * from the current contents of the clipboard.
+ * <p>Input: {@link #getData} is URI of the directory (vnd.android.cursor.dir/*)
+ * in which to place the data.
+ * <p>Output: URI of the new data that was created.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_PASTE = "android.intent.action.PASTE";
+ /**
* Activity Action: Delete the given data from its container.
* <p>Input: {@link #getData} is URI of data to be deleted.
* <p>Output: nothing.
diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java
index a37e4e8..ccb8605 100644
--- a/core/java/android/content/res/AssetFileDescriptor.java
+++ b/core/java/android/content/res/AssetFileDescriptor.java
@@ -129,8 +129,12 @@
/**
* Checks whether this file descriptor is for a memory file.
*/
- private boolean isMemoryFile() throws IOException {
- return MemoryFile.isMemoryFile(mFd.getFileDescriptor());
+ private boolean isMemoryFile() {
+ try {
+ return MemoryFile.isMemoryFile(mFd.getFileDescriptor());
+ } catch (IOException e) {
+ return false;
+ }
}
/**
diff --git a/core/java/android/net/DownloadManager.java b/core/java/android/net/DownloadManager.java
index 1d88c18..e69c324 100644
--- a/core/java/android/net/DownloadManager.java
+++ b/core/java/android/net/DownloadManager.java
@@ -241,12 +241,6 @@
*/
public static class Request {
/**
- * Bit flag for {@link #setShowNotification} indicating a notification should be created
- * while the download is running.
- */
- public static final int NOTIFICATION_WHEN_RUNNING = 1;
-
- /**
* Bit flag for {@link #setAllowedNetworkTypes} corresponding to
* {@link ConnectivityManager#TYPE_MOBILE}.
*/
@@ -269,7 +263,7 @@
private Map<String, String> mRequestHeaders = new HashMap<String, String>();
private String mTitle;
private String mDescription;
- private int mNotificationFlags = 0;
+ private boolean mShowNotification = true;
private String mMediaType;
private boolean mRoamingAllowed = true;
private int mAllowedNetworkTypes = ~0; // default to all network types allowed
@@ -344,15 +338,20 @@
}
/**
- * Control system notifications posted by the download manager for this download. If
- * enabled, the download manager posts notifications about downloads through the system
- * {@link android.app.NotificationManager}. By default, no notification is shown.
+ * Control whether a system notification is posted by the download manager while this
+ * download is running. If enabled, the download manager posts notifications about downloads
+ * through the system {@link android.app.NotificationManager}. By default, a notification is
+ * shown.
*
- * @param flags any combination of the NOTIFICATION_* bit flags
+ * If set to false, this requires the permission
+ * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION.
+ *
+ * @param show whether the download manager should show a notification for this download.
* @return this object
+ * @hide
*/
- public Request setShowNotification(int flags) {
- mNotificationFlags = flags;
+ public Request setShowRunningNotification(boolean show) {
+ mShowNotification = show;
return this;
}
@@ -404,11 +403,9 @@
putIfNonNull(values, Downloads.COLUMN_DESCRIPTION, mDescription);
putIfNonNull(values, Downloads.COLUMN_MIME_TYPE, mMediaType);
- int visibility = Downloads.VISIBILITY_HIDDEN;
- if ((mNotificationFlags & NOTIFICATION_WHEN_RUNNING) != 0) {
- visibility = Downloads.VISIBILITY_VISIBLE;
- }
- values.put(Downloads.COLUMN_VISIBILITY, visibility);
+ values.put(Downloads.COLUMN_VISIBILITY,
+ mShowNotification ? Downloads.VISIBILITY_VISIBLE
+ : Downloads.VISIBILITY_HIDDEN);
values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes);
values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed);
diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java
index 832ce84..aadacab 100644
--- a/core/java/android/os/AsyncTask.java
+++ b/core/java/android/os/AsyncTask.java
@@ -175,6 +175,11 @@
FINISHED,
}
+ /** @hide Used to force static handler to be created. */
+ public static void init() {
+ sHandler.getLooper();
+ }
+
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*/
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index a17b7fe..72e21de 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -85,6 +85,8 @@
public static native int getPermissions(String file, int[] outPermissions);
+ public static native int setUMask(int mask);
+
/** returns the FAT file system volume ID for the volume mounted
* at the given mount point, or -1 for failure
* @param mount point for FAT volume
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 9d213b3..d853f13 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -134,6 +134,25 @@
private static native FileDescriptor getFileDescriptorFromSocket(Socket socket);
/**
+ * Create two ParcelFileDescriptors structured as a data pipe. The first
+ * ParcelFileDescriptor in the returned array is the read side; the second
+ * is the write side.
+ */
+ public static ParcelFileDescriptor[] createPipe() throws IOException {
+ FileDescriptor[] fds = new FileDescriptor[2];
+ int res = createPipeNative(fds);
+ if (res == 0) {
+ ParcelFileDescriptor[] pfds = new ParcelFileDescriptor[2];
+ pfds[0] = new ParcelFileDescriptor(fds[0]);
+ pfds[1] = new ParcelFileDescriptor(fds[1]);
+ return pfds;
+ }
+ throw new IOException("Unable to create pipe: errno=" + -res);
+ }
+
+ private static native int createPipeNative(FileDescriptor[] outFds);
+
+ /**
* Retrieve the actual FileDescriptor associated with this object.
*
* @return Returns the FileDescriptor associated with this object.
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index 1b37107..c9b5512 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -624,13 +624,19 @@
"android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS";
/**
- * The permission to downloads files to the cache partition that won't be automatically
+ * The permission to download files to the cache partition that won't be automatically
* purged when space is needed.
*/
public static final String PERMISSION_CACHE_NON_PURGEABLE =
"android.permission.DOWNLOAD_CACHE_NON_PURGEABLE";
/**
+ * The permission to download files without any system notification being shown.
+ */
+ public static final String PERMISSION_NO_NOTIFICATION =
+ "android.permission.DOWNLOAD_WITHOUT_NOTIFICATION";
+
+ /**
* The content:// URI for the data table in the provider
*/
public static final Uri CONTENT_URI =
diff --git a/core/java/android/util/JsonReader.java b/core/java/android/util/JsonReader.java
new file mode 100644
index 0000000..3345bfa
--- /dev/null
+++ b/core/java/android/util/JsonReader.java
@@ -0,0 +1,1058 @@
+/*
+ * Copyright (C) 2010 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.util;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Closeable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Reads a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
+ * encoded value as a stream of tokens. This stream includes both literal
+ * values (strings, numbers, booleans, and nulls) as well as the begin and
+ * end delimiters of objects and arrays. The tokens are traversed in
+ * depth-first order, the same order that they appear in the JSON document.
+ * Within JSON objects, name/value pairs are represented by a single token.
+ *
+ * <h3>Parsing JSON</h3>
+ * To create a recursive descent parser your own JSON streams, first create an
+ * entry point method that creates a {@code JsonReader}.
+ *
+ * <p>Next, create handler methods for each structure in your JSON text. You'll
+ * need a method for each object type and for each array type.
+ * <ul>
+ * <li>Within <strong>array handling</strong> methods, first call {@link
+ * #beginArray} to consume the array's opening bracket. Then create a
+ * while loop that accumulates values, terminating when {@link #hasNext}
+ * is false. Finally, read the array's closing bracket by calling {@link
+ * #endArray}.
+ * <li>Within <strong>object handling</strong> methods, first call {@link
+ * #beginObject} to consume the object's opening brace. Then create a
+ * while loop that assigns values to local variables based on their name.
+ * This loop should terminate when {@link #hasNext} is false. Finally,
+ * read the object's closing brace by calling {@link #endObject}.
+ * </ul>
+ * <p>When a nested object or array is encountered, delegate to the
+ * corresponding handler method.
+ *
+ * <p>When an unknown name is encountered, strict parsers should fail with an
+ * exception. Lenient parsers should call {@link #skipValue()} to recursively
+ * skip the value's nested tokens, which may otherwise conflict.
+ *
+ * <p>If a value may be null, you should first check using {@link #peek()}.
+ * Null literals can be consumed using either {@link #nextNull()} or {@link
+ * #skipValue()}.
+ *
+ * <h3>Example</h3>
+ * Suppose we'd like to parse a stream of messages such as the following: <pre> {@code
+ * [
+ * {
+ * "id": 912345678901,
+ * "text": "How do I read JSON on Android?",
+ * "geo": null,
+ * "user": {
+ * "name": "android_newb",
+ * "followers_count": 41
+ * }
+ * },
+ * {
+ * "id": 912345678902,
+ * "text": "@android_newb just use android.util.JsonReader!",
+ * "geo": [50.454722, -104.606667],
+ * "user": {
+ * "name": "jesse",
+ * "followers_count": 2
+ * }
+ * }
+ * ]}</pre>
+ * This code implements the parser for the above structure: <pre> {@code
+ *
+ * public List<Message> readJsonStream(InputStream in) throws IOException {
+ * JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
+ * return readMessagesArray(reader);
+ * }
+ *
+ * public List<Message> readMessagesArray(JsonReader reader) throws IOException {
+ * List<Message> messages = new ArrayList<Message>();
+ *
+ * reader.beginArray();
+ * while (reader.hasNext()) {
+ * messages.add(readMessage(reader));
+ * }
+ * reader.endArray();
+ * return messages;
+ * }
+ *
+ * public Message readMessage(JsonReader reader) throws IOException {
+ * long id = -1;
+ * String text = null;
+ * User user = null;
+ * List<Double> geo = null;
+ *
+ * reader.beginObject();
+ * while (reader.hasNext()) {
+ * String name = reader.nextName();
+ * if (name.equals("id")) {
+ * id = reader.nextLong();
+ * } else if (name.equals("text")) {
+ * text = reader.nextString();
+ * } else if (name.equals("geo") && reader.peek() != JsonToken.NULL) {
+ * geo = readDoublesArray(reader);
+ * } else if (name.equals("user")) {
+ * user = readUser(reader);
+ * } else {
+ * reader.skipValue();
+ * }
+ * }
+ * reader.endObject();
+ * return new Message(id, text, user, geo);
+ * }
+ *
+ * public List<Double> readDoublesArray(JsonReader reader) throws IOException {
+ * List<Double> doubles = new ArrayList<Double>();
+ *
+ * reader.beginArray();
+ * while (reader.hasNext()) {
+ * doubles.add(reader.nextDouble());
+ * }
+ * reader.endArray();
+ * return doubles;
+ * }
+ *
+ * public User readUser(JsonReader reader) throws IOException {
+ * String username = null;
+ * int followersCount = -1;
+ *
+ * reader.beginObject();
+ * while (reader.hasNext()) {
+ * String name = reader.nextName();
+ * if (name.equals("name")) {
+ * username = reader.nextString();
+ * } else if (name.equals("followers_count")) {
+ * followersCount = reader.nextInt();
+ * } else {
+ * reader.skipValue();
+ * }
+ * }
+ * reader.endObject();
+ * return new User(username, followersCount);
+ * }}</pre>
+ *
+ * <h3>Number Handling</h3>
+ * This reader permits numeric values to be read as strings and string values to
+ * be read as numbers. For example, both elements of the JSON array {@code
+ * [1, "1"]} may be read using either {@link #nextInt} or {@link #nextString}.
+ * This behavior is intended to prevent lossy numeric conversions: double is
+ * JavaScript's only numeric type and very large values like {@code
+ * 9007199254740993} cannot be represented exactly on that platform. To minimize
+ * precision loss, extremely large values should be written and read as strings
+ * in JSON.
+ *
+ * <p>Each {@code JsonReader} may be used to read a single JSON stream. Instances
+ * of this class are not thread safe.
+ */
+public final class JsonReader implements Closeable {
+
+ /** The input JSON. */
+ private final Reader in;
+
+ /** True to accept non-spec compliant JSON */
+ private boolean lenient = false;
+
+ /**
+ * Use a manual buffer to easily read and unread upcoming characters, and
+ * also so we can create strings without an intermediate StringBuilder.
+ */
+ private final char[] buffer = new char[1024];
+ private int pos = 0;
+ private int limit = 0;
+
+ private final List<JsonScope> stack = new ArrayList<JsonScope>();
+ {
+ push(JsonScope.EMPTY_DOCUMENT);
+ }
+
+ /**
+ * True if we've already read the next token. If we have, the string value
+ * for that token will be assigned to {@code value} if such a string value
+ * exists. And the token type will be assigned to {@code token} if the token
+ * type is known. The token type may be null for literals, since we derive
+ * that lazily.
+ */
+ private boolean hasToken;
+
+ /**
+ * The type of the next token to be returned by {@link #peek} and {@link
+ * #advance}, or {@code null} if it is unknown and must first be derived
+ * from {@code value}. This value is undefined if {@code hasToken} is false.
+ */
+ private JsonToken token;
+
+ /** The text of the next name. */
+ private String name;
+
+ /** The text of the next literal value. */
+ private String value;
+
+ /** True if we're currently handling a skipValue() call. */
+ private boolean skipping = false;
+
+ /**
+ * Creates a new instance that reads a JSON-encoded stream from {@code in}.
+ */
+ public JsonReader(Reader in) {
+ if (in == null) {
+ throw new NullPointerException("in == null");
+ }
+ this.in = in;
+ }
+
+ /**
+ * Configure this parser to be be liberal in what it accepts. By default,
+ * this parser is strict and only accepts JSON as specified by <a
+ * href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the
+ * parser to lenient causes it to ignore the following syntax errors:
+ *
+ * <ul>
+ * <li>End of line comments starting with {@code //} or {@code #} and
+ * ending with a newline character.
+ * <li>C-style comments starting with {@code /*} and ending with
+ * {@code *}{@code /}. Such comments may not be nested.
+ * <li>Names that are unquoted or {@code 'single quoted'}.
+ * <li>Strings that are unquoted or {@code 'single quoted'}.
+ * <li>Array elements separated by {@code ;} instead of {@code ,}.
+ * <li>Unnecessary array separators. These are interpreted as if null
+ * was the omitted value.
+ * <li>Names and values separated by {@code =} or {@code =>} instead of
+ * {@code :}.
+ * <li>Name/value pairs separated by {@code ;} instead of {@code ,}.
+ * </ul>
+ */
+ public void setLenient(boolean lenient) {
+ this.lenient = lenient;
+ }
+
+ /**
+ * Consumes the next token from the JSON stream and asserts that it is the
+ * beginning of a new array.
+ */
+ public void beginArray() throws IOException {
+ expect(JsonToken.BEGIN_ARRAY);
+ }
+
+ /**
+ * Consumes the next token from the JSON stream and asserts that it is the
+ * end of the current array.
+ */
+ public void endArray() throws IOException {
+ expect(JsonToken.END_ARRAY);
+ }
+
+ /**
+ * Consumes the next token from the JSON stream and asserts that it is the
+ * beginning of a new object.
+ */
+ public void beginObject() throws IOException {
+ expect(JsonToken.BEGIN_OBJECT);
+ }
+
+ /**
+ * Consumes the next token from the JSON stream and asserts that it is the
+ * end of the current array.
+ */
+ public void endObject() throws IOException {
+ expect(JsonToken.END_OBJECT);
+ }
+
+ /**
+ * Consumes {@code expected}.
+ */
+ private void expect(JsonToken expected) throws IOException {
+ quickPeek();
+ if (token != expected) {
+ throw new IllegalStateException("Expected " + expected + " but was " + peek());
+ }
+ advance();
+ }
+
+ /**
+ * Returns true if the current array or object has another element.
+ */
+ public boolean hasNext() throws IOException {
+ quickPeek();
+ return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY;
+ }
+
+ /**
+ * Returns the type of the next token without consuming it.
+ */
+ public JsonToken peek() throws IOException {
+ quickPeek();
+
+ if (token == null) {
+ decodeLiteral();
+ }
+
+ return token;
+ }
+
+ /**
+ * Ensures that a token is ready. After this call either {@code token} or
+ * {@code value} will be non-null. To ensure {@code token} has a definitive
+ * value, use {@link #peek()}
+ */
+ private JsonToken quickPeek() throws IOException {
+ if (hasToken) {
+ return token;
+ }
+
+ switch (peekStack()) {
+ case EMPTY_DOCUMENT:
+ replaceTop(JsonScope.NONEMPTY_DOCUMENT);
+ JsonToken firstToken = nextValue();
+ if (token != JsonToken.BEGIN_ARRAY && token != JsonToken.BEGIN_OBJECT) {
+ throw new IOException(
+ "Expected JSON document to start with '[' or '{' but was " + token);
+ }
+ return firstToken;
+ case EMPTY_ARRAY:
+ return nextInArray(true);
+ case NONEMPTY_ARRAY:
+ return nextInArray(false);
+ case EMPTY_OBJECT:
+ return nextInObject(true);
+ case DANGLING_NAME:
+ return objectValue();
+ case NONEMPTY_OBJECT:
+ return nextInObject(false);
+ case NONEMPTY_DOCUMENT:
+ hasToken = true;
+ return token = JsonToken.END_DOCUMENT;
+ case CLOSED:
+ throw new IllegalStateException("JsonReader is closed");
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Advances the cursor in the JSON stream to the next token.
+ */
+ private JsonToken advance() throws IOException {
+ quickPeek();
+
+ JsonToken result = token;
+ hasToken = false;
+ token = null;
+ value = null;
+ name = null;
+ return result;
+ }
+
+ /**
+ * Returns the next token, a {@link JsonToken#NAME property name}, and
+ * consumes it.
+ *
+ * @throws IOException if the next token in the stream is not a property
+ * name.
+ */
+ public String nextName() throws IOException {
+ quickPeek();
+ if (token != JsonToken.NAME) {
+ throw new IllegalStateException("Expected a name but was " + peek());
+ }
+ String result = name;
+ advance();
+ return result;
+ }
+
+ /**
+ * Returns the {@link JsonToken#STRING string} value of the next token,
+ * consuming it. If the next token is a number, this method will return its
+ * string form.
+ *
+ * @throws IllegalStateException if the next token is not a string or if
+ * this reader is closed.
+ */
+ public String nextString() throws IOException {
+ peek();
+ if (value == null || (token != JsonToken.STRING && token != JsonToken.NUMBER)) {
+ throw new IllegalStateException("Expected a string but was " + peek());
+ }
+
+ String result = value;
+ advance();
+ return result;
+ }
+
+ /**
+ * Returns the {@link JsonToken#BOOLEAN boolean} value of the next token,
+ * consuming it.
+ *
+ * @throws IllegalStateException if the next token is not a boolean or if
+ * this reader is closed.
+ */
+ public boolean nextBoolean() throws IOException {
+ quickPeek();
+ if (value == null || token == JsonToken.STRING) {
+ throw new IllegalStateException("Expected a boolean but was " + peek());
+ }
+
+ boolean result;
+ if (value.equalsIgnoreCase("true")) {
+ result = true;
+ } else if (value.equalsIgnoreCase("false")) {
+ result = false;
+ } else {
+ throw new IllegalStateException("Not a boolean: " + value);
+ }
+
+ advance();
+ return result;
+ }
+
+ /**
+ * Consumes the next token from the JSON stream and asserts that it is a
+ * literal null.
+ *
+ * @throws IllegalStateException if the next token is not null or if this
+ * reader is closed.
+ */
+ public void nextNull() throws IOException {
+ quickPeek();
+ if (value == null || token == JsonToken.STRING) {
+ throw new IllegalStateException("Expected null but was " + peek());
+ }
+
+ if (!value.equalsIgnoreCase("null")) {
+ throw new IllegalStateException("Not a null: " + value);
+ }
+
+ advance();
+ }
+
+ /**
+ * Returns the {@link JsonToken#NUMBER double} value of the next token,
+ * consuming it. If the next token is a string, this method will attempt to
+ * parse it as a double.
+ *
+ * @throws IllegalStateException if the next token is not a literal value.
+ * @throws NumberFormatException if the next literal value cannot be parsed
+ * as a double, or is non-finite.
+ */
+ public double nextDouble() throws IOException {
+ quickPeek();
+ if (value == null) {
+ throw new IllegalStateException("Expected a double but was " + peek());
+ }
+
+ double result = Double.parseDouble(value);
+
+ if ((result >= 1.0d && value.startsWith("0"))
+ || Double.isNaN(result)
+ || Double.isInfinite(result)) {
+ throw new NumberFormatException(
+ "JSON forbids octal prefixes, NaN and infinities: " + value);
+ }
+
+ advance();
+ return result;
+ }
+
+ /**
+ * Returns the {@link JsonToken#NUMBER long} value of the next token,
+ * consuming it. If the next token is a string, this method will attempt to
+ * parse it as a long. If the next token's numeric value cannot be exactly
+ * represented by a Java {@code long}, this method throws.
+ *
+ * @throws IllegalStateException if the next token is not a literal value.
+ * @throws NumberFormatException if the next literal value cannot be parsed
+ * as a number, or exactly represented as a long.
+ */
+ public long nextLong() throws IOException {
+ quickPeek();
+ if (value == null) {
+ throw new IllegalStateException("Expected a long but was " + peek());
+ }
+
+ long result;
+ try {
+ result = Long.parseLong(value);
+ } catch (NumberFormatException ignored) {
+ double asDouble = Double.parseDouble(value); // don't catch this NumberFormatException
+ result = (long) asDouble;
+ if ((double) result != asDouble) {
+ throw new NumberFormatException(value);
+ }
+ }
+
+ if (result >= 1L && value.startsWith("0")) {
+ throw new NumberFormatException("JSON forbids octal prefixes: " + value);
+ }
+
+ advance();
+ return result;
+ }
+
+ /**
+ * Returns the {@link JsonToken#NUMBER int} value of the next token,
+ * consuming it. If the next token is a string, this method will attempt to
+ * parse it as an int. If the next token's numeric value cannot be exactly
+ * represented by a Java {@code int}, this method throws.
+ *
+ * @throws IllegalStateException if the next token is not a literal value.
+ * @throws NumberFormatException if the next literal value cannot be parsed
+ * as a number, or exactly represented as an int.
+ */
+ public int nextInt() throws IOException {
+ quickPeek();
+ if (value == null) {
+ throw new IllegalStateException("Expected an int but was " + peek());
+ }
+
+ int result;
+ try {
+ result = Integer.parseInt(value);
+ } catch (NumberFormatException ignored) {
+ double asDouble = Double.parseDouble(value); // don't catch this NumberFormatException
+ result = (int) asDouble;
+ if ((double) result != asDouble) {
+ throw new NumberFormatException(value);
+ }
+ }
+
+ if (result >= 1L && value.startsWith("0")) {
+ throw new NumberFormatException("JSON forbids octal prefixes: " + value);
+ }
+
+ advance();
+ return result;
+ }
+
+ /**
+ * Closes this JSON reader and the underlying {@link Reader}.
+ */
+ public void close() throws IOException {
+ hasToken = false;
+ value = null;
+ token = null;
+ stack.clear();
+ stack.add(JsonScope.CLOSED);
+ in.close();
+ }
+
+ /**
+ * Skips the next value recursively. If it is an object or array, all nested
+ * elements are skipped. This method is intended for use when the JSON token
+ * stream contains unrecognized or unhandled values.
+ */
+ public void skipValue() throws IOException {
+ skipping = true;
+ try {
+ int count = 0;
+ do {
+ JsonToken token = advance();
+ if (token == JsonToken.BEGIN_ARRAY || token == JsonToken.BEGIN_OBJECT) {
+ count++;
+ } else if (token == JsonToken.END_ARRAY || token == JsonToken.END_OBJECT) {
+ count--;
+ }
+ } while (count != 0);
+ } finally {
+ skipping = false;
+ }
+ }
+
+ private JsonScope peekStack() {
+ return stack.get(stack.size() - 1);
+ }
+
+ private JsonScope pop() {
+ return stack.remove(stack.size() - 1);
+ }
+
+ private void push(JsonScope newTop) {
+ stack.add(newTop);
+ }
+
+ /**
+ * Replace the value on the top of the stack with the given value.
+ */
+ private void replaceTop(JsonScope newTop) {
+ stack.set(stack.size() - 1, newTop);
+ }
+
+ private JsonToken nextInArray(boolean firstElement) throws IOException {
+ if (firstElement) {
+ replaceTop(JsonScope.NONEMPTY_ARRAY);
+ } else {
+ /* Look for a comma before each element after the first element. */
+ switch (nextNonWhitespace()) {
+ case ']':
+ pop();
+ hasToken = true;
+ return token = JsonToken.END_ARRAY;
+ case ';':
+ checkLenient(); // fall-through
+ case ',':
+ break;
+ default:
+ throw syntaxError("Unterminated array");
+ }
+ }
+
+ switch (nextNonWhitespace()) {
+ case ']':
+ if (firstElement) {
+ pop();
+ hasToken = true;
+ return token = JsonToken.END_ARRAY;
+ }
+ // fall-through to handle ",]"
+ case ';':
+ case ',':
+ /* In lenient mode, a 0-length literal means 'null' */
+ checkLenient();
+ pos--;
+ hasToken = true;
+ value = "null";
+ return token = JsonToken.NULL;
+ default:
+ pos--;
+ return nextValue();
+ }
+ }
+
+ private JsonToken nextInObject(boolean firstElement) throws IOException {
+ /*
+ * Read delimiters. Either a comma/semicolon separating this and the
+ * previous name-value pair, or a close brace to denote the end of the
+ * object.
+ */
+ if (firstElement) {
+ /* Peek to see if this is the empty object. */
+ switch (nextNonWhitespace()) {
+ case '}':
+ pop();
+ hasToken = true;
+ return token = JsonToken.END_OBJECT;
+ default:
+ pos--;
+ }
+ } else {
+ switch (nextNonWhitespace()) {
+ case '}':
+ pop();
+ hasToken = true;
+ return token = JsonToken.END_OBJECT;
+ case ';':
+ case ',':
+ break;
+ default:
+ throw syntaxError("Unterminated object");
+ }
+ }
+
+ /* Read the name. */
+ int quote = nextNonWhitespace();
+ switch (quote) {
+ case '\'':
+ checkLenient(); // fall-through
+ case '"':
+ name = nextString((char) quote);
+ break;
+ default:
+ checkLenient();
+ pos--;
+ name = nextLiteral();
+ if (name.isEmpty()) {
+ throw syntaxError("Expected name");
+ }
+ }
+
+ replaceTop(JsonScope.DANGLING_NAME);
+ hasToken = true;
+ return token = JsonToken.NAME;
+ }
+
+ private JsonToken objectValue() throws IOException {
+ /*
+ * Read the name/value separator. Usually a colon ':'. In lenient mode
+ * we also accept an equals sign '=', or an arrow "=>".
+ */
+ switch (nextNonWhitespace()) {
+ case ':':
+ break;
+ case '=':
+ checkLenient();
+ if ((pos < limit || fillBuffer(1)) && buffer[pos] == '>') {
+ pos++;
+ }
+ break;
+ default:
+ throw syntaxError("Expected ':'");
+ }
+
+ replaceTop(JsonScope.NONEMPTY_OBJECT);
+ return nextValue();
+ }
+
+ private JsonToken nextValue() throws IOException {
+ int c = nextNonWhitespace();
+ switch (c) {
+ case '{':
+ push(JsonScope.EMPTY_OBJECT);
+ hasToken = true;
+ return token = JsonToken.BEGIN_OBJECT;
+
+ case '[':
+ push(JsonScope.EMPTY_ARRAY);
+ hasToken = true;
+ return token = JsonToken.BEGIN_ARRAY;
+
+ case '\'':
+ checkLenient(); // fall-through
+ case '"':
+ value = nextString((char) c);
+ hasToken = true;
+ return token = JsonToken.STRING;
+
+ default:
+ pos--;
+ return readLiteral();
+ }
+ }
+
+ /**
+ * Returns true once {@code limit - pos >= minimum}. If the data is
+ * exhausted before that many characters are available, this returns
+ * false.
+ */
+ private boolean fillBuffer(int minimum) throws IOException {
+ if (limit != pos) {
+ limit -= pos;
+ System.arraycopy(buffer, pos, buffer, 0, limit);
+ } else {
+ limit = 0;
+ }
+
+ pos = 0;
+ int total;
+ while ((total = in.read(buffer, limit, buffer.length - limit)) != -1) {
+ limit += total;
+ if (limit >= minimum) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private int nextNonWhitespace() throws IOException {
+ while (pos < limit || fillBuffer(1)) {
+ int c = buffer[pos++];
+ switch (c) {
+ case '\t':
+ case ' ':
+ case '\n':
+ case '\r':
+ continue;
+
+ case '/':
+ if (pos == limit && !fillBuffer(1)) {
+ return c;
+ }
+
+ checkLenient();
+ char peek = buffer[pos];
+ switch (peek) {
+ case '*':
+ // skip a /* c-style comment */
+ pos++;
+ if (!skipTo("*/")) {
+ throw syntaxError("Unterminated comment");
+ }
+ pos += 2;
+ continue;
+
+ case '/':
+ // skip a // end-of-line comment
+ pos++;
+ skipToEndOfLine();
+ continue;
+
+ default:
+ return c;
+ }
+
+ case '#':
+ /*
+ * Skip a # hash end-of-line comment. The JSON RFC doesn't
+ * specify this behaviour, but it's required to parse
+ * existing documents. See http://b/2571423.
+ */
+ checkLenient();
+ skipToEndOfLine();
+ continue;
+
+ default:
+ return c;
+ }
+ }
+
+ throw syntaxError("End of input");
+ }
+
+ private void checkLenient() throws IOException {
+ if (!lenient) {
+ throw syntaxError("Use JsonReader.setLenient(true) to accept malformed JSON");
+ }
+ }
+
+ /**
+ * Advances the position until after the next newline character. If the line
+ * is terminated by "\r\n", the '\n' must be consumed as whitespace by the
+ * caller.
+ */
+ private void skipToEndOfLine() throws IOException {
+ while (pos < limit || fillBuffer(1)) {
+ char c = buffer[pos++];
+ if (c == '\r' || c == '\n') {
+ break;
+ }
+ }
+ }
+
+ private boolean skipTo(String toFind) throws IOException {
+ outer:
+ for (; pos + toFind.length() < limit || fillBuffer(toFind.length()); pos++) {
+ for (int c = 0; c < toFind.length(); c++) {
+ if (buffer[pos + c] != toFind.charAt(c)) {
+ continue outer;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the string up to but not including {@code quote}, unescaping any
+ * character escape sequences encountered along the way. The opening quote
+ * should have already been read. This consumes the closing quote, but does
+ * not include it in the returned string.
+ *
+ * @param quote either ' or ".
+ * @throws NumberFormatException if any unicode escape sequences are
+ * malformed.
+ */
+ private String nextString(char quote) throws IOException {
+ StringBuilder builder = null;
+ do {
+ /* the index of the first character not yet appended to the builder. */
+ int start = pos;
+ while (pos < limit) {
+ int c = buffer[pos++];
+
+ if (c == quote) {
+ if (skipping) {
+ return "skipped!";
+ } else if (builder == null) {
+ return new String(buffer, start, pos - start - 1);
+ } else {
+ builder.append(buffer, start, pos - start - 1);
+ return builder.toString();
+ }
+
+ } else if (c == '\\') {
+ if (builder == null) {
+ builder = new StringBuilder();
+ }
+ builder.append(buffer, start, pos - start - 1);
+ builder.append(readEscapeCharacter());
+ start = pos;
+ }
+ }
+
+ if (builder == null) {
+ builder = new StringBuilder();
+ }
+ builder.append(buffer, start, pos - start);
+ } while (fillBuffer(1));
+
+ throw syntaxError("Unterminated string");
+ }
+
+ /**
+ * Returns the string up to but not including any delimiter characters. This
+ * does not consume the delimiter character.
+ */
+ private String nextLiteral() throws IOException {
+ StringBuilder builder = null;
+ do {
+ /* the index of the first character not yet appended to the builder. */
+ int start = pos;
+ while (pos < limit) {
+ int c = buffer[pos++];
+ switch (c) {
+ case '/':
+ case '\\':
+ case ';':
+ case '#':
+ case '=':
+ checkLenient(); // fall-through
+
+ case '{':
+ case '}':
+ case '[':
+ case ']':
+ case ':':
+ case ',':
+ case ' ':
+ case '\t':
+ case '\f':
+ case '\r':
+ case '\n':
+ pos--;
+ if (skipping) {
+ return "skipped!";
+ } else if (builder == null) {
+ return new String(buffer, start, pos - start);
+ } else {
+ builder.append(buffer, start, pos - start);
+ return builder.toString();
+ }
+ }
+ }
+
+ if (builder == null) {
+ builder = new StringBuilder();
+ }
+ builder.append(buffer, start, pos - start);
+ } while (fillBuffer(1));
+
+ return builder.toString();
+ }
+
+ @Override public String toString() {
+ return getClass().getSimpleName() + " near " + getSnippet();
+ }
+
+ /**
+ * Unescapes the character identified by the character or characters that
+ * immediately follow a backslash. The backslash '\' should have already
+ * been read. This supports both unicode escapes "u000A" and two-character
+ * escapes "\n".
+ *
+ * @throws NumberFormatException if any unicode escape sequences are
+ * malformed.
+ */
+ private char readEscapeCharacter() throws IOException {
+ if (pos == limit && !fillBuffer(1)) {
+ throw syntaxError("Unterminated escape sequence");
+ }
+
+ char escaped = buffer[pos++];
+ switch (escaped) {
+ case 'u':
+ if (pos + 4 > limit && !fillBuffer(4)) {
+ throw syntaxError("Unterminated escape sequence");
+ }
+ String hex = new String(buffer, pos, 4);
+ pos += 4;
+ return (char) Integer.parseInt(hex, 16);
+
+ case 't':
+ return '\t';
+
+ case 'b':
+ return '\b';
+
+ case 'n':
+ return '\n';
+
+ case 'r':
+ return '\r';
+
+ case 'f':
+ return '\f';
+
+ case '\'':
+ case '"':
+ case '\\':
+ default:
+ return escaped;
+ }
+ }
+
+ /**
+ * Reads a null, boolean, numeric or unquoted string literal value.
+ */
+ private JsonToken readLiteral() throws IOException {
+ String literal = nextLiteral();
+ if (literal.isEmpty()) {
+ throw syntaxError("Expected literal value");
+ }
+ value = literal;
+ hasToken = true;
+ return token = null; // use decodeLiteral() to get the token type
+ }
+
+ /**
+ * Assigns {@code nextToken} based on the value of {@code nextValue}.
+ */
+ private void decodeLiteral() throws IOException {
+ if (value.equalsIgnoreCase("null")) {
+ token = JsonToken.NULL;
+ } else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
+ token = JsonToken.BOOLEAN;
+ } else {
+ try {
+ Double.parseDouble(value); // this work could potentially be cached
+ token = JsonToken.NUMBER;
+ } catch (NumberFormatException ignored) {
+ // this must be an unquoted string
+ checkLenient();
+ token = JsonToken.STRING;
+ }
+ }
+ }
+
+ /**
+ * Throws a new IO exception with the given message and a context snippet
+ * with this reader's content.
+ */
+ public IOException syntaxError(String message) throws IOException {
+ throw new JsonSyntaxException(message + " near " + getSnippet());
+ }
+
+ private CharSequence getSnippet() {
+ StringBuilder snippet = new StringBuilder();
+ int beforePos = Math.min(pos, 20);
+ snippet.append(buffer, pos - beforePos, beforePos);
+ int afterPos = Math.min(limit - pos, 20);
+ snippet.append(buffer, pos, afterPos);
+ return snippet;
+ }
+
+ private static class JsonSyntaxException extends IOException {
+ private JsonSyntaxException(String s) {
+ super(s);
+ }
+ }
+}
diff --git a/core/java/android/util/JsonScope.java b/core/java/android/util/JsonScope.java
new file mode 100644
index 0000000..ca534e9
--- /dev/null
+++ b/core/java/android/util/JsonScope.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2010 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.util;
+
+/**
+ * Lexical scoping elements within a JSON reader or writer.
+ */
+enum JsonScope {
+
+ /**
+ * An array with no elements requires no separators or newlines before
+ * it is closed.
+ */
+ EMPTY_ARRAY,
+
+ /**
+ * A array with at least one value requires a comma and newline before
+ * the next element.
+ */
+ NONEMPTY_ARRAY,
+
+ /**
+ * An object with no name/value pairs requires no separators or newlines
+ * before it is closed.
+ */
+ EMPTY_OBJECT,
+
+ /**
+ * An object whose most recent element is a key. The next element must
+ * be a value.
+ */
+ DANGLING_NAME,
+
+ /**
+ * An object with at least one name/value pair requires a comma and
+ * newline before the next element.
+ */
+ NONEMPTY_OBJECT,
+
+ /**
+ * No object or array has been started.
+ */
+ EMPTY_DOCUMENT,
+
+ /**
+ * A document with at an array or object.
+ */
+ NONEMPTY_DOCUMENT,
+
+ /**
+ * A document that's been closed and cannot be accessed.
+ */
+ CLOSED,
+}
diff --git a/core/java/android/util/JsonToken.java b/core/java/android/util/JsonToken.java
new file mode 100644
index 0000000..45bc6ca
--- /dev/null
+++ b/core/java/android/util/JsonToken.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2010 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.util;
+
+/**
+ * A structure, name or value type in a JSON-encoded string.
+ */
+public enum JsonToken {
+
+ /**
+ * The opening of a JSON array. Written using {@link JsonWriter#beginObject}
+ * and read using {@link JsonReader#beginObject}.
+ */
+ BEGIN_ARRAY,
+
+ /**
+ * The closing of a JSON array. Written using {@link JsonWriter#endArray}
+ * and read using {@link JsonReader#endArray}.
+ */
+ END_ARRAY,
+
+ /**
+ * The opening of a JSON object. Written using {@link JsonWriter#beginObject}
+ * and read using {@link JsonReader#beginObject}.
+ */
+ BEGIN_OBJECT,
+
+ /**
+ * The closing of a JSON object. Written using {@link JsonWriter#endObject}
+ * and read using {@link JsonReader#endObject}.
+ */
+ END_OBJECT,
+
+ /**
+ * A JSON property name. Within objects, tokens alternate between names and
+ * their values. Written using {@link JsonWriter#name} and read using {@link
+ * JsonReader#nextName}
+ */
+ NAME,
+
+ /**
+ * A JSON string.
+ */
+ STRING,
+
+ /**
+ * A JSON number represented in this API by a Java {@code double}, {@code
+ * long}, or {@code int}.
+ */
+ NUMBER,
+
+ /**
+ * A JSON {@code true} or {@code false}.
+ */
+ BOOLEAN,
+
+ /**
+ * A JSON {@code null}.
+ */
+ NULL,
+
+ /**
+ * The end of the JSON stream. This sentinel value is returned by {@link
+ * JsonReader#peek()} to signal that the JSON-encoded value has no more
+ * tokens.
+ */
+ END_DOCUMENT
+}
diff --git a/core/java/android/util/JsonWriter.java b/core/java/android/util/JsonWriter.java
new file mode 100644
index 0000000..fecc1c8
--- /dev/null
+++ b/core/java/android/util/JsonWriter.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2010 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.util;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Writes a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
+ * encoded value to a stream, one token at a time. The stream includes both
+ * literal values (strings, numbers, booleans and nulls) as well as the begin
+ * and end delimiters of objects and arrays.
+ *
+ * <h3>Encoding JSON</h3>
+ * To encode your data as JSON, create a new {@code JsonWriter}. Each JSON
+ * document must contain one top-level array or object. Call methods on the
+ * writer as you walk the structure's contents, nesting arrays and objects as
+ * necessary:
+ * <ul>
+ * <li>To write <strong>arrays</strong>, first call {@link #beginArray()}.
+ * Write each of the array's elements with the appropriate {@link #value}
+ * methods or by nesting other arrays and objects. Finally close the array
+ * using {@link #endArray()}.
+ * <li>To write <strong>objects</strong>, first call {@link #beginObject()}.
+ * Write each of the object's properties by alternating calls to
+ * {@link #name} with the property's value. Write property values with the
+ * appropriate {@link #value} method or by nesting other objects or arrays.
+ * Finally close the object using {@link #endObject()}.
+ * </ul>
+ *
+ * <h3>Example</h3>
+ * Suppose we'd like to encode a stream of messages such as the following: <pre> {@code
+ * [
+ * {
+ * "id": 912345678901,
+ * "text": "How do I write JSON on Android?",
+ * "geo": null,
+ * "user": {
+ * "name": "android_newb",
+ * "followers_count": 41
+ * }
+ * },
+ * {
+ * "id": 912345678902,
+ * "text": "@android_newb just use android.util.JsonWriter!",
+ * "geo": [50.454722, -104.606667],
+ * "user": {
+ * "name": "jesse",
+ * "followers_count": 2
+ * }
+ * }
+ * ]}</pre>
+ * This code encodes the above structure: <pre> {@code
+ * public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException {
+ * JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
+ * writer.setIndentSpaces(4);
+ * writeMessagesArray(writer, messages);
+ * writer.close();
+ * }
+ *
+ * public void writeMessagesArray(JsonWriter writer, List<Message> messages) throws IOException {
+ * writer.beginArray();
+ * for (Message message : messages) {
+ * writeMessage(writer, message);
+ * }
+ * writer.endArray();
+ * }
+ *
+ * public void writeMessage(JsonWriter writer, Message message) throws IOException {
+ * writer.beginObject();
+ * writer.name("id").value(message.getId());
+ * writer.name("text").value(message.getText());
+ * if (message.getGeo() != null) {
+ * writer.name("geo");
+ * writeDoublesArray(writer, message.getGeo());
+ * } else {
+ * writer.name("geo").nullValue();
+ * }
+ * writer.name("user");
+ * writeUser(writer, message.getUser());
+ * writer.endObject();
+ * }
+ *
+ * public void writeUser(JsonWriter writer, User user) throws IOException {
+ * writer.beginObject();
+ * writer.name("name").value(user.getName());
+ * writer.name("followers_count").value(user.getFollowersCount());
+ * writer.endObject();
+ * }
+ *
+ * public void writeDoublesArray(JsonWriter writer, List<Double> doubles) throws IOException {
+ * writer.beginArray();
+ * for (Double value : doubles) {
+ * writer.value(value);
+ * }
+ * writer.endArray();
+ * }}</pre>
+ *
+ * <p>Each {@code JsonWriter} may be used to write a single JSON stream.
+ * Instances of this class are not thread safe. Calls that would result in a
+ * malformed JSON string will fail with an {@link IllegalStateException}.
+ */
+public final class JsonWriter implements Closeable {
+
+ /** The output data, containing at most one top-level array or object. */
+ private final Writer out;
+
+ private final List<JsonScope> stack = new ArrayList<JsonScope>();
+ {
+ stack.add(JsonScope.EMPTY_DOCUMENT);
+ }
+
+ /**
+ * A string containing a full set of spaces for a single level of
+ * indentation, or null for no pretty printing.
+ */
+ private String indent;
+
+ /**
+ * The name/value separator; either ":" or ": ".
+ */
+ private String separator = ":";
+
+ /**
+ * Creates a new instance that writes a JSON-encoded stream to {@code out}.
+ * For best performance, ensure {@link Writer} is buffered; wrapping in
+ * {@link java.io.BufferedWriter BufferedWriter} if necessary.
+ */
+ public JsonWriter(Writer out) {
+ if (out == null) {
+ throw new NullPointerException("out == null");
+ }
+ this.out = out;
+ }
+
+ /**
+ * Sets the indentation string to be repeated for each level of indentation
+ * in the encoded document. If {@code indent.isEmpty()} the encoded document
+ * will be compact. Otherwise the encoded document will be more
+ * human-readable.
+ *
+ * @param indent a string containing only whitespace.
+ */
+ public void setIndent(String indent) {
+ if (indent.isEmpty()) {
+ this.indent = null;
+ this.separator = ":";
+ } else {
+ this.indent = indent;
+ this.separator = ": ";
+ }
+ }
+
+ /**
+ * Begins encoding a new array. Each call to this method must be paired with
+ * a call to {@link #endArray}.
+ *
+ * @return this writer.
+ */
+ public JsonWriter beginArray() throws IOException {
+ return open(JsonScope.EMPTY_ARRAY, "[");
+ }
+
+ /**
+ * Ends encoding the current array.
+ *
+ * @return this writer.
+ */
+ public JsonWriter endArray() throws IOException {
+ return close(JsonScope.EMPTY_ARRAY, JsonScope.NONEMPTY_ARRAY, "]");
+ }
+
+ /**
+ * Begins encoding a new object. Each call to this method must be paired
+ * with a call to {@link #endObject}.
+ *
+ * @return this writer.
+ */
+ public JsonWriter beginObject() throws IOException {
+ return open(JsonScope.EMPTY_OBJECT, "{");
+ }
+
+ /**
+ * Ends encoding the current object.
+ *
+ * @return this writer.
+ */
+ public JsonWriter endObject() throws IOException {
+ return close(JsonScope.EMPTY_OBJECT, JsonScope.NONEMPTY_OBJECT, "}");
+ }
+
+ /**
+ * Enters a new scope by appending any necessary whitespace and the given
+ * bracket.
+ */
+ private JsonWriter open(JsonScope empty, String openBracket) throws IOException {
+ beforeValue(true);
+ stack.add(empty);
+ out.write(openBracket);
+ return this;
+ }
+
+ /**
+ * Closes the current scope by appending any necessary whitespace and the
+ * given bracket.
+ */
+ private JsonWriter close(JsonScope empty, JsonScope nonempty, String closeBracket)
+ throws IOException {
+ JsonScope context = peek();
+ if (context != nonempty && context != empty) {
+ throw new IllegalStateException("Nesting problem: " + stack);
+ }
+
+ stack.remove(stack.size() - 1);
+ if (context == nonempty) {
+ newline();
+ }
+ out.write(closeBracket);
+ return this;
+ }
+
+ /**
+ * Returns the value on the top of the stack.
+ */
+ private JsonScope peek() {
+ return stack.get(stack.size() - 1);
+ }
+
+ /**
+ * Replace the value on the top of the stack with the given value.
+ */
+ private void replaceTop(JsonScope topOfStack) {
+ stack.set(stack.size() - 1, topOfStack);
+ }
+
+ /**
+ * Encodes the property name.
+ *
+ * @param name the name of the forthcoming value. May not be null.
+ * @return this writer.
+ */
+ public JsonWriter name(String name) throws IOException {
+ if (name == null) {
+ throw new NullPointerException("name == null");
+ }
+ beforeName();
+ string(name);
+ return this;
+ }
+
+ /**
+ * Encodes {@code value}.
+ *
+ * @param value the literal string value, or null to encode a null literal.
+ * @return this writer.
+ */
+ public JsonWriter value(String value) throws IOException {
+ if (value == null) {
+ return nullValue();
+ }
+ beforeValue(false);
+ string(value);
+ return this;
+ }
+
+ /**
+ * Encodes {@code null}.
+ *
+ * @return this writer.
+ */
+ public JsonWriter nullValue() throws IOException {
+ beforeValue(false);
+ out.write("null");
+ return this;
+ }
+
+ /**
+ * Encodes {@code value}.
+ *
+ * @return this writer.
+ */
+ public JsonWriter value(boolean value) throws IOException {
+ beforeValue(false);
+ out.write(value ? "true" : "false");
+ return this;
+ }
+
+ /**
+ * Encodes {@code value}.
+ *
+ * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
+ * {@link Double#isInfinite() infinities}.
+ * @return this writer.
+ */
+ public JsonWriter value(double value) throws IOException {
+ if (Double.isNaN(value) || Double.isInfinite(value)) {
+ throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
+ }
+ beforeValue(false);
+ out.append(Double.toString(value));
+ return this;
+ }
+
+ /**
+ * Encodes {@code value}.
+ *
+ * @return this writer.
+ */
+ public JsonWriter value(long value) throws IOException {
+ beforeValue(false);
+ out.write(Long.toString(value));
+ return this;
+ }
+
+ /**
+ * Ensures all buffered data is written to the underlying {@link Writer}
+ * and flushes that writer.
+ */
+ public void flush() throws IOException {
+ out.flush();
+ }
+
+ /**
+ * Flushes and closes this writer and the underlying {@link Writer}.
+ *
+ * @throws IOException if the JSON document is incomplete.
+ */
+ public void close() throws IOException {
+ out.close();
+
+ if (peek() != JsonScope.NONEMPTY_DOCUMENT) {
+ throw new IOException("Incomplete document");
+ }
+ }
+
+ private void string(String value) throws IOException {
+ out.write("\"");
+ for (int i = 0, length = value.length(); i < length; i++) {
+ char c = value.charAt(i);
+
+ /*
+ * From RFC 4627, "All Unicode characters may be placed within the
+ * quotation marks except for the characters that must be escaped:
+ * quotation mark, reverse solidus, and the control characters
+ * (U+0000 through U+001F)."
+ */
+ switch (c) {
+ case '"':
+ case '\\':
+ case '/':
+ out.write('\\');
+ out.write(c);
+ break;
+
+ case '\t':
+ out.write("\\t");
+ break;
+
+ case '\b':
+ out.write("\\b");
+ break;
+
+ case '\n':
+ out.write("\\n");
+ break;
+
+ case '\r':
+ out.write("\\r");
+ break;
+
+ case '\f':
+ out.write("\\f");
+ break;
+
+ default:
+ if (c <= 0x1F) {
+ out.write(String.format("\\u%04x", (int) c));
+ } else {
+ out.write(c);
+ }
+ break;
+ }
+
+ }
+ out.write("\"");
+ }
+
+ private void newline() throws IOException {
+ if (indent == null) {
+ return;
+ }
+
+ out.write("\n");
+ for (int i = 1; i < stack.size(); i++) {
+ out.write(indent);
+ }
+ }
+
+ /**
+ * Inserts any necessary separators and whitespace before a name. Also
+ * adjusts the stack to expect the name's value.
+ */
+ private void beforeName() throws IOException {
+ JsonScope context = peek();
+ if (context == JsonScope.NONEMPTY_OBJECT) { // first in object
+ out.write(',');
+ } else if (context != JsonScope.EMPTY_OBJECT) { // not in an object!
+ throw new IllegalStateException("Nesting problem: " + stack);
+ }
+ newline();
+ replaceTop(JsonScope.DANGLING_NAME);
+ }
+
+ /**
+ * Inserts any necessary separators and whitespace before a literal value,
+ * inline array, or inline object. Also adjusts the stack to expect either a
+ * closing bracket or another element.
+ *
+ * @param root true if the value is a new array or object, the two values
+ * permitted as top-level elements.
+ */
+ private void beforeValue(boolean root) throws IOException {
+ switch (peek()) {
+ case EMPTY_DOCUMENT: // first in document
+ if (!root) {
+ throw new IllegalStateException(
+ "JSON must start with an array or an object.");
+ }
+ replaceTop(JsonScope.NONEMPTY_DOCUMENT);
+ break;
+
+ case EMPTY_ARRAY: // first in array
+ replaceTop(JsonScope.NONEMPTY_ARRAY);
+ newline();
+ break;
+
+ case NONEMPTY_ARRAY: // another in array
+ out.append(',');
+ newline();
+ break;
+
+ case DANGLING_NAME: // value for name
+ out.append(separator);
+ replaceTop(JsonScope.NONEMPTY_OBJECT);
+ break;
+
+ case NONEMPTY_DOCUMENT:
+ throw new IllegalStateException(
+ "JSON must have only one top-level value.");
+
+ default:
+ throw new IllegalStateException("Nesting problem: " + stack);
+ }
+ }
+}
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 8e1338d..96bd884 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -40,7 +40,6 @@
/**
* An implementation of Canvas on top of OpenGL ES 2.0.
*/
-@SuppressWarnings({"deprecation"})
class GLES20Canvas extends Canvas {
@SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
private final GL mGl;
@@ -56,6 +55,17 @@
private final Rect mClipBounds = new Rect();
private DrawFilter mFilter;
+
+ ///////////////////////////////////////////////////////////////////////////
+ // JNI
+ ///////////////////////////////////////////////////////////////////////////
+
+ private static native boolean nIsAvailable();
+ private static boolean sIsAvailable = nIsAvailable();
+
+ static boolean isAvailable() {
+ return sIsAvailable;
+ }
///////////////////////////////////////////////////////////////////////////
// Constructors
@@ -91,11 +101,6 @@
}
@Override
- public GL getGL() {
- throw new UnsupportedOperationException();
- }
-
- @Override
public void setBitmap(Bitmap bitmap) {
throw new UnsupportedOperationException();
}
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 090a743..60d495f 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -28,10 +28,6 @@
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
import javax.microedition.khronos.opengles.GL;
-import javax.microedition.khronos.opengles.GL11;
-
-import static javax.microedition.khronos.opengles.GL10.GL_COLOR_BUFFER_BIT;
-import static javax.microedition.khronos.opengles.GL10.GL_SCISSOR_TEST;
/**
* Interface for rendering a ViewRoot using hardware acceleration.
@@ -110,10 +106,8 @@
*/
static HardwareRenderer createGlRenderer(int glVersion, boolean translucent) {
switch (glVersion) {
- case 1:
- return new Gl10Renderer(translucent);
case 2:
- return new Gl20Renderer(translucent);
+ return Gl20Renderer.create(translucent);
}
throw new IllegalArgumentException("Unknown GL version: " + glVersion);
}
@@ -520,43 +514,13 @@
@Override
void onPreDraw() {
mGlCanvas.onPreDraw();
- }
- }
-
- /**
- * Hardware renderer using OpenGL ES 1.0.
- */
- @SuppressWarnings({"deprecation"})
- static class Gl10Renderer extends GlRenderer {
- Gl10Renderer(boolean translucent) {
- super(1, translucent);
}
- @Override
- Canvas createCanvas() {
- return new Canvas(mGl);
- }
-
- @Override
- void destroy() {
- if (isEnabled()) {
- nativeAbandonGlCaches();
+ static HardwareRenderer create(boolean translucent) {
+ if (GLES20Canvas.isAvailable()) {
+ return new Gl20Renderer(translucent);
}
-
- super.destroy();
- }
-
- @Override
- void onPreDraw() {
- GL11 gl = (GL11) mGl;
- gl.glDisable(GL_SCISSOR_TEST);
- gl.glClearColor(0, 0, 0, 0);
- gl.glClear(GL_COLOR_BUFFER_BIT);
- gl.glEnable(GL_SCISSOR_TEST);
+ return null;
}
}
-
- // Inform Skia to just abandon its texture cache IDs doesn't call glDeleteTextures
- // Used only by the native Skia OpenGL ES 1.x implementation
- private static native void nativeAbandonGlCaches();
}
diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java
index 25df1f4..fed55dc 100755
--- a/core/java/android/view/WindowOrientationListener.java
+++ b/core/java/android/view/WindowOrientationListener.java
@@ -68,7 +68,7 @@
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if (mSensor != null) {
// Create listener only if sensors do exist
- mSensorEventListener = new SensorEventListenerImpl();
+ mSensorEventListener = new SensorEventListenerImpl(this);
}
}
@@ -109,8 +109,35 @@
}
return -1;
}
-
- class SensorEventListenerImpl implements SensorEventListener {
+
+ /**
+ * This class filters the raw accelerometer data and tries to detect actual changes in
+ * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters,
+ * but here's the outline:
+ *
+ * - Convert the acceleromter vector from cartesian to spherical coordinates. Since we're
+ * dealing with rotation of the device, this is the sensible coordinate system to work in. The
+ * zenith direction is the Z-axis, i.e. the direction the screen is facing. The radial distance
+ * is referred to as the magnitude below. The elevation angle is referred to as the "tilt"
+ * below. The azimuth angle is referred to as the "orientation" below (and the azimuth axis is
+ * the Y-axis). See http://en.wikipedia.org/wiki/Spherical_coordinate_system for reference.
+ *
+ * - Low-pass filter the tilt and orientation angles to avoid "twitchy" behavior.
+ *
+ * - When the orientation angle reaches a certain threshold, transition to the corresponding
+ * orientation. These thresholds have some hysteresis built-in to avoid oscillation.
+ *
+ * - Use the magnitude to judge the accuracy of the data. Under ideal conditions, the magnitude
+ * should equal to that of gravity. When it differs significantly, we know the device is under
+ * external acceleration and we can't trust the data.
+ *
+ * - Use the tilt angle to judge the accuracy of orientation data. When the tilt angle is high
+ * in magnitude, we distrust the orientation data, because when the device is nearly flat, small
+ * physical movements produce large changes in orientation angle.
+ *
+ * Details are explained below.
+ */
+ static class SensorEventListenerImpl implements SensorEventListener {
// We work with all angles in degrees in this class.
private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI);
@@ -125,54 +152,50 @@
private static final int ROTATION_90 = 1;
private static final int ROTATION_270 = 2;
- // Current orientation state
- private int mRotation = ROTATION_0;
-
// Mapping our internal aliases into actual Surface rotation values
- private final int[] SURFACE_ROTATIONS = new int[] {Surface.ROTATION_0, Surface.ROTATION_90,
- Surface.ROTATION_270};
+ private static final int[] SURFACE_ROTATIONS = new int[] {
+ Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270};
// Threshold ranges of orientation angle to transition into other orientation states.
// The first list is for transitions from ROTATION_0, the next for ROTATION_90, etc.
// ROTATE_TO defines the orientation each threshold range transitions to, and must be kept
// in sync with this.
- // The thresholds are nearly regular -- we generally transition about the halfway point
- // between two states with a swing of 30 degrees for hysteresis. For ROTATION_180,
- // however, we enforce stricter thresholds, pushing the thresholds 15 degrees closer to 180.
- private final int[][][] THRESHOLDS = new int[][][] {
+ // We generally transition about the halfway point between two states with a swing of 30
+ // degrees for hysteresis.
+ private static final int[][][] THRESHOLDS = new int[][][] {
{{60, 180}, {180, 300}},
+ {{0, 30}, {195, 315}, {315, 360}},
{{0, 45}, {45, 165}, {330, 360}},
- {{0, 30}, {195, 315}, {315, 360}}
};
// See THRESHOLDS
- private final int[][] ROTATE_TO = new int[][] {
- {ROTATION_270, ROTATION_90},
+ private static final int[][] ROTATE_TO = new int[][] {
+ {ROTATION_90, ROTATION_270},
{ROTATION_0, ROTATION_270, ROTATION_0},
- {ROTATION_0, ROTATION_90, ROTATION_0}
+ {ROTATION_0, ROTATION_90, ROTATION_0},
};
- // Maximum absolute tilt angle at which to consider orientation changes. Beyond this (i.e.
- // when screen is facing the sky or ground), we refuse to make any orientation changes.
- private static final int MAX_TILT = 65;
+ // Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e.
+ // when screen is facing the sky or ground), we completely ignore orientation data.
+ private static final int MAX_TILT = 75;
// Additional limits on tilt angle to transition to each new orientation. We ignore all
- // vectors with tilt beyond MAX_TILT, but we can set stricter limits on transition to a
+ // data with tilt beyond MAX_TILT, but we can set stricter limits on transitions to a
// particular orientation here.
- private final int[] MAX_TRANSITION_TILT = new int[] {MAX_TILT, MAX_TILT, MAX_TILT};
+ private static final int[] MAX_TRANSITION_TILT = new int[] {MAX_TILT, 65, 65};
// Between this tilt angle and MAX_TILT, we'll allow orientation changes, but we'll filter
// with a higher time constant, making us less sensitive to change. This primarily helps
// prevent momentary orientation changes when placing a device on a table from the side (or
// picking one up).
- private static final int PARTIAL_TILT = 45;
+ private static final int PARTIAL_TILT = 50;
// Maximum allowable deviation of the magnitude of the sensor vector from that of gravity,
// in m/s^2. Beyond this, we assume the phone is under external forces and we can't trust
// the sensor data. However, under constantly vibrating conditions (think car mount), we
// still want to pick up changes, so rather than ignore the data, we filter it with a very
// high time constant.
- private static final int MAX_DEVIATION_FROM_GRAVITY = 1;
+ private static final float MAX_DEVIATION_FROM_GRAVITY = 1.5f;
// Actual sampling period corresponding to SensorManager.SENSOR_DELAY_NORMAL. There's no
// way to get this information from SensorManager.
@@ -185,28 +208,46 @@
// background.
// When device is near-vertical (screen approximately facing the horizon)
- private static final int DEFAULT_TIME_CONSTANT_MS = 200;
+ private static final int DEFAULT_TIME_CONSTANT_MS = 50;
// When device is partially tilted towards the sky or ground
- private static final int TILTED_TIME_CONSTANT_MS = 600;
+ private static final int TILTED_TIME_CONSTANT_MS = 300;
// When device is under external acceleration, i.e. not just gravity. We heavily distrust
// such readings.
- private static final int ACCELERATING_TIME_CONSTANT_MS = 5000;
+ private static final int ACCELERATING_TIME_CONSTANT_MS = 2000;
private static final float DEFAULT_LOWPASS_ALPHA =
- (float) SAMPLING_PERIOD_MS / (DEFAULT_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS);
+ computeLowpassAlpha(DEFAULT_TIME_CONSTANT_MS);
private static final float TILTED_LOWPASS_ALPHA =
- (float) SAMPLING_PERIOD_MS / (TILTED_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS);
+ computeLowpassAlpha(TILTED_TIME_CONSTANT_MS);
private static final float ACCELERATING_LOWPASS_ALPHA =
- (float) SAMPLING_PERIOD_MS / (ACCELERATING_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS);
+ computeLowpassAlpha(ACCELERATING_TIME_CONSTANT_MS);
- // The low-pass filtered accelerometer data
- private float[] mFilteredVector = new float[] {0, 0, 0};
+ private WindowOrientationListener mOrientationListener;
+ private int mRotation = ROTATION_0; // Current orientation state
+ private float mTiltAngle = 0; // low-pass filtered
+ private float mOrientationAngle = 0; // low-pass filtered
+
+ /*
+ * Each "distrust" counter represents our current level of distrust in the data based on
+ * a certain signal. For each data point that is deemed unreliable based on that signal,
+ * the counter increases; otherwise, the counter decreases. Exact rules vary.
+ */
+ private int mAccelerationDistrust = 0; // based on magnitude != gravity
+ private int mTiltDistrust = 0; // based on tilt close to +/- 90 degrees
+
+ public SensorEventListenerImpl(WindowOrientationListener orientationListener) {
+ mOrientationListener = orientationListener;
+ }
+
+ private static float computeLowpassAlpha(int timeConstantMs) {
+ return (float) SAMPLING_PERIOD_MS / (timeConstantMs + SAMPLING_PERIOD_MS);
+ }
int getCurrentRotation() {
return SURFACE_ROTATIONS[mRotation];
}
- private void calculateNewRotation(int orientation, int tiltAngle) {
+ private void calculateNewRotation(float orientation, float tiltAngle) {
if (localLOGV) Log.i(TAG, orientation + ", " + tiltAngle + ", " + mRotation);
int thresholdRanges[][] = THRESHOLDS[mRotation];
int row = -1;
@@ -226,7 +267,7 @@
if (localLOGV) Log.i(TAG, " new rotation = " + rotation);
mRotation = rotation;
- onOrientationChanged(SURFACE_ROTATIONS[rotation]);
+ mOrientationListener.onOrientationChanged(getCurrentRotation());
}
private float lowpassFilter(float newValue, float oldValue, float alpha) {
@@ -238,11 +279,11 @@
}
/**
- * Absolute angle between upVector and the x-y plane (the plane of the screen), in [0, 90].
- * 90 degrees = screen facing the sky or ground.
+ * Angle between upVector and the x-y plane (the plane of the screen), in [-90, 90].
+ * +/- 90 degrees = screen facing the sky or ground.
*/
private float tiltAngle(float z, float magnitude) {
- return Math.abs((float) Math.asin(z / magnitude) * RADIANS_TO_DEGREES);
+ return (float) Math.asin(z / magnitude) * RADIANS_TO_DEGREES;
}
public void onSensorChanged(SensorEvent event) {
@@ -253,34 +294,124 @@
float z = event.values[_DATA_Z];
float magnitude = vectorMagnitude(x, y, z);
float deviation = Math.abs(magnitude - SensorManager.STANDARD_GRAVITY);
- float tiltAngle = tiltAngle(z, magnitude);
- float alpha = DEFAULT_LOWPASS_ALPHA;
- if (tiltAngle > MAX_TILT) {
- return;
- } else if (deviation > MAX_DEVIATION_FROM_GRAVITY) {
+ handleAccelerationDistrust(deviation);
+
+ // only filter tilt when we're accelerating
+ float alpha = 1;
+ if (mAccelerationDistrust > 0) {
alpha = ACCELERATING_LOWPASS_ALPHA;
- } else if (tiltAngle > PARTIAL_TILT) {
+ }
+ float newTiltAngle = tiltAngle(z, magnitude);
+ mTiltAngle = lowpassFilter(newTiltAngle, mTiltAngle, alpha);
+
+ float absoluteTilt = Math.abs(mTiltAngle);
+ if (checkFullyTilted(absoluteTilt)) {
+ return; // when fully tilted, ignore orientation entirely
+ }
+
+ float newOrientationAngle = computeNewOrientation(x, y);
+ filterOrientation(absoluteTilt, newOrientationAngle);
+ calculateNewRotation(mOrientationAngle, absoluteTilt);
+ }
+
+ /**
+ * When accelerating, increment distrust; otherwise, decrement distrust. The idea is that
+ * if a single jolt happens among otherwise good data, we should keep trusting the good
+ * data. On the other hand, if a series of many bad readings comes in (as if the phone is
+ * being rapidly shaken), we should wait until things "settle down", i.e. we get a string
+ * of good readings.
+ *
+ * @param deviation absolute difference between the current magnitude and gravity
+ */
+ private void handleAccelerationDistrust(float deviation) {
+ if (deviation > MAX_DEVIATION_FROM_GRAVITY) {
+ if (mAccelerationDistrust < 5) {
+ mAccelerationDistrust++;
+ }
+ } else if (mAccelerationDistrust > 0) {
+ mAccelerationDistrust--;
+ }
+ }
+
+ /**
+ * Check if the phone is tilted towards the sky or ground and handle that appropriately.
+ * When fully tilted, we automatically push the tilt up to a fixed value; otherwise we
+ * decrement it. The idea is to distrust the first few readings after the phone gets
+ * un-tilted, no matter what, i.e. preventing an accidental transition when the phone is
+ * picked up from a table.
+ *
+ * We also reset the orientation angle to the center of the current screen orientation.
+ * Since there is no real orientation of the phone, we want to ignore the most recent sensor
+ * data and reset it to this value to avoid a premature transition when the phone starts to
+ * get un-tilted.
+ *
+ * @param absoluteTilt the absolute value of the current tilt angle
+ * @return true if the phone is fully tilted
+ */
+ private boolean checkFullyTilted(float absoluteTilt) {
+ boolean fullyTilted = absoluteTilt > MAX_TILT;
+ if (fullyTilted) {
+ if (mRotation == ROTATION_0) {
+ mOrientationAngle = 0;
+ } else if (mRotation == ROTATION_90) {
+ mOrientationAngle = 90;
+ } else { // ROTATION_270
+ mOrientationAngle = 270;
+ }
+
+ if (mTiltDistrust < 3) {
+ mTiltDistrust = 3;
+ }
+ } else if (mTiltDistrust > 0) {
+ mTiltDistrust--;
+ }
+ return fullyTilted;
+ }
+
+ /**
+ * Angle between the x-y projection of upVector and the +y-axis, increasing
+ * clockwise.
+ * 0 degrees = speaker end towards the sky
+ * 90 degrees = right edge of device towards the sky
+ */
+ private float computeNewOrientation(float x, float y) {
+ float orientationAngle = (float) -Math.atan2(-x, y) * RADIANS_TO_DEGREES;
+ // atan2 returns [-180, 180]; normalize to [0, 360]
+ if (orientationAngle < 0) {
+ orientationAngle += 360;
+ }
+ return orientationAngle;
+ }
+
+ /**
+ * Compute a new filtered orientation angle.
+ */
+ private void filterOrientation(float absoluteTilt, float orientationAngle) {
+ float alpha = DEFAULT_LOWPASS_ALPHA;
+ if (mTiltDistrust > 0 || mAccelerationDistrust > 1) {
+ // when fully tilted, or under more than a transient acceleration, distrust heavily
+ alpha = ACCELERATING_LOWPASS_ALPHA;
+ } else if (absoluteTilt > PARTIAL_TILT || mAccelerationDistrust == 1) {
+ // when tilted partway, or under transient acceleration, distrust lightly
alpha = TILTED_LOWPASS_ALPHA;
}
- x = mFilteredVector[0] = lowpassFilter(x, mFilteredVector[0], alpha);
- y = mFilteredVector[1] = lowpassFilter(y, mFilteredVector[1], alpha);
- z = mFilteredVector[2] = lowpassFilter(z, mFilteredVector[2], alpha);
- magnitude = vectorMagnitude(x, y, z);
- tiltAngle = tiltAngle(z, magnitude);
-
- // Angle between the x-y projection of upVector and the +y-axis, increasing
- // counter-clockwise.
- // 0 degrees = speaker end towards the sky
- // 90 degrees = left edge of device towards the sky
- float orientationAngle = (float) Math.atan2(-x, y) * RADIANS_TO_DEGREES;
- int orientation = Math.round(orientationAngle);
- // atan2 returns (-180, 180]; normalize to [0, 360)
- if (orientation < 0) {
- orientation += 360;
+ // since we're lowpass filtering a value with periodic boundary conditions, we need to
+ // adjust the new value to filter in the right direction...
+ float deltaOrientation = orientationAngle - mOrientationAngle;
+ if (deltaOrientation > 180) {
+ orientationAngle -= 360;
+ } else if (deltaOrientation < -180) {
+ orientationAngle += 360;
}
- calculateNewRotation(orientation, Math.round(tiltAngle));
+ mOrientationAngle = lowpassFilter(orientationAngle, mOrientationAngle, alpha);
+ // ...and then adjust back to ensure we're in the range [0, 360]
+ if (mOrientationAngle > 360) {
+ mOrientationAngle -= 360;
+ } else if (mOrientationAngle < 0) {
+ mOrientationAngle += 360;
+ }
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 43a3e04..d1b0902 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -21,8 +21,8 @@
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.Intent;
import android.content.DialogInterface.OnCancelListener;
+import android.content.Intent;
import android.database.DataSetObserver;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -43,7 +43,6 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.speech.tts.TextToSpeech;
import android.text.Selection;
@@ -74,13 +73,15 @@
import android.widget.AbsoluteLayout;
import android.widget.Adapter;
import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.CheckedTextView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.Scroller;
import android.widget.Toast;
-import android.widget.AdapterView.OnItemClickListener;
+
+import junit.framework.Assert;
import java.io.File;
import java.io.FileInputStream;
@@ -95,8 +96,6 @@
import java.util.Map;
import java.util.Set;
-import junit.framework.Assert;
-
/**
* <p>A View that displays web pages. This class is the basis upon which you
* can roll your own web browser or simply display some online content within your Activity.
@@ -291,7 +290,7 @@
* property to {@code device-dpi}. This stops Android from performing scaling in your web page and
* allows you to make the necessary adjustments for each density via CSS and JavaScript.</p>
*
- *
+ *
*/
@Widget
public class WebView extends AbsoluteLayout
@@ -1057,8 +1056,10 @@
/*
* Return the amount of the titlebarview (if any) that is visible
+ *
+ * @hide
*/
- int getVisibleTitleHeight() {
+ public int getVisibleTitleHeight() {
return Math.max(getTitleHeight() - mScrollY, 0);
}
@@ -2695,6 +2696,16 @@
mWebViewCore.sendMessage(EventHub.DOC_HAS_IMAGES, response);
}
+ /**
+ * Request the scroller to abort any ongoing animation
+ *
+ * @hide
+ */
+ public void stopScroll() {
+ mScroller.forceFinished(true);
+ mLastVelocity = 0;
+ }
+
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
@@ -3751,6 +3762,19 @@
private boolean mGotCenterDown = false;
@Override
+ public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+ // send complex characters to webkit for use by JS and plugins
+ if (keyCode == KeyEvent.KEYCODE_UNKNOWN && event.getCharacters() != null) {
+ // pass the key to DOM
+ mWebViewCore.sendMessage(EventHub.KEY_DOWN, event);
+ mWebViewCore.sendMessage(EventHub.KEY_UP, event);
+ // return true as DOM handles the key
+ return true;
+ }
+ return false;
+ }
+
+ @Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (DebugFlags.WEB_VIEW) {
Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis()
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 21af570..3c28c94 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -1627,9 +1627,16 @@
+ evt);
}
int keyCode = evt.getKeyCode();
- if (!nativeKey(keyCode, evt.getUnicodeChar(),
- evt.getRepeatCount(), evt.isShiftPressed(), evt.isAltPressed(),
- evt.isSymPressed(),
+ int unicodeChar = evt.getUnicodeChar();
+
+ if (keyCode == KeyEvent.KEYCODE_UNKNOWN && evt.getCharacters() != null
+ && evt.getCharacters().length() > 0) {
+ // we should only receive individual complex characters
+ unicodeChar = evt.getCharacters().codePointAt(0);
+ }
+
+ if (!nativeKey(keyCode, unicodeChar, evt.getRepeatCount(), evt.isShiftPressed(),
+ evt.isAltPressed(), evt.isSymPressed(),
isDown) && keyCode != KeyEvent.KEYCODE_ENTER) {
if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
&& keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 24f14dc..f3f68f9 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -21,7 +21,9 @@
import org.xmlpull.v1.XmlPullParserException;
+import android.content.ClippedData;
import android.content.Context;
+import android.content.ClipboardManager;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -34,6 +36,7 @@
import android.graphics.RectF;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -42,7 +45,6 @@
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.text.BoringLayout;
-import android.text.ClipboardManager;
import android.text.DynamicLayout;
import android.text.Editable;
import android.text.GetChars;
@@ -7061,7 +7063,7 @@
getSelectionStart() >= 0 &&
getSelectionEnd() >= 0 &&
((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
- hasText());
+ hasPrimaryClip());
}
/**
@@ -7270,7 +7272,7 @@
int min = Math.max(0, Math.min(selStart, selEnd));
int max = Math.max(0, Math.max(selStart, selEnd));
- ClipboardManager clip = (ClipboardManager)getContext()
+ ClipboardManager clipboard = (ClipboardManager)getContext()
.getSystemService(Context.CLIPBOARD_SERVICE);
switch (id) {
@@ -7278,8 +7280,18 @@
MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
URLSpan[] urls = ((Spanned) mText).getSpans(min, max, URLSpan.class);
- if (urls.length == 1) {
- clip.setText(urls[0].getURL());
+ if (urls.length >= 1) {
+ ClippedData clip = null;
+ for (int i=0; i<urls.length; i++) {
+ Uri uri = Uri.parse(urls[0].getURL());
+ ClippedData.Item item = new ClippedData.Item(uri);
+ if (clip == null) {
+ clip = new ClippedData(null, null, item);
+ } else {
+ clip.addItem(item);
+ }
+ }
+ clipboard.setPrimaryClip(clip);
}
return true;
@@ -7490,8 +7502,8 @@
return true;
}
- ClipboardManager clip = (ClipboardManager) getContext().
- getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipboardManager clipboard = (ClipboardManager) getContext().
+ getSystemService(Context.CLIPBOARD_SERVICE);
int min = 0;
int max = mText.length();
@@ -7506,23 +7518,36 @@
switch (item.getItemId()) {
case ID_PASTE:
- CharSequence paste = clip.getText();
-
- if (paste != null) {
- Selection.setSelection((Spannable) mText, max);
- ((Editable) mText).replace(min, max, paste);
+ ClippedData clip = clipboard.getPrimaryClip();
+ if (clip != null) {
+ boolean didfirst = false;
+ for (int i=0; i<clip.getItemCount(); i++) {
+ CharSequence paste = clip.getItem(i).coerceToText(getContext());
+ if (paste != null) {
+ if (!didfirst) {
+ Selection.setSelection((Spannable) mText, max);
+ ((Editable) mText).replace(min, max, paste);
+ } else {
+ ((Editable) mText).insert(getSelectionEnd(), "\n");
+ ((Editable) mText).insert(getSelectionEnd(), paste);
+ }
+ }
+ }
finishSelectionActionMode();
}
+
return true;
case ID_CUT:
- clip.setText(mTransformed.subSequence(min, max));
+ clipboard.setPrimaryClip(new ClippedData(null, null,
+ new ClippedData.Item(mTransformed.subSequence(min, max))));
((Editable) mText).delete(min, max);
finishSelectionActionMode();
return true;
case ID_COPY:
- clip.setText(mTransformed.subSequence(min, max));
+ clipboard.setPrimaryClip(new ClippedData(null, null,
+ new ClippedData.Item(mTransformed.subSequence(min, max))));
finishSelectionActionMode();
return true;
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index f8a77a1..9c84e0e 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -22,6 +22,7 @@
import android.graphics.drawable.Drawable;
import android.net.LocalServerSocket;
import android.os.Debug;
+import android.os.FileUtils;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Config;
@@ -506,6 +507,9 @@
closeServerSocket();
+ // set umask to 0077 so new files and directories will default to owner-only permissions.
+ FileUtils.setUMask(FileUtils.S_IRWXG | FileUtils.S_IRWXO);
+
/*
* Pass the remaining arguments to SystemServer.
* "--nice-name=system_server com.android.server.SystemServer"
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 77c77f9..d1a5ae14 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -19,6 +19,10 @@
LOCAL_CFLAGS += -DCUSTOM_RUNTIME_HEAP_MAX=$(USE_CUSTOM_RUNTIME_HEAP_MAX)
endif
+ifeq ($(USE_OPENGL_RENDERER),true)
+ LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER
+endif
+
LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
LOCAL_SRC_FILES:= \
@@ -47,7 +51,6 @@
android_view_InputChannel.cpp \
android_view_InputQueue.cpp \
android_view_KeyEvent.cpp \
- android_view_HardwareRenderer.cpp \
android_view_GLES20Canvas.cpp \
android_view_MotionEvent.cpp \
android_text_AndroidCharacter.cpp \
@@ -170,7 +173,6 @@
libbinder \
libnetutils \
libui \
- libhwui \
libgui \
libsurfaceflinger_client \
libcamera_client \
@@ -193,6 +195,10 @@
libwpa_client \
libjpeg
+ifeq ($(USE_OPENGL_RENDERER),true)
+ LOCAL_SHARED_LIBRARIES += libhwui
+endif
+
ifeq ($(BOARD_HAVE_BLUETOOTH),true)
LOCAL_C_INCLUDES += \
external/dbus \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 1e6d219..8b09e5f 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -115,7 +115,6 @@
extern int register_com_android_internal_graphics_NativeUtils(JNIEnv *env);
extern int register_android_view_Display(JNIEnv* env);
extern int register_android_view_GLES20Canvas(JNIEnv* env);
-extern int register_android_view_HardwareRenderer(JNIEnv* env);
extern int register_android_view_Surface(JNIEnv* env);
extern int register_android_view_ViewRoot(JNIEnv* env);
extern int register_android_database_CursorWindow(JNIEnv* env);
@@ -1244,7 +1243,6 @@
REG_JNI(register_android_graphics_PixelFormat),
REG_JNI(register_android_graphics_Graphics),
REG_JNI(register_android_view_GLES20Canvas),
- REG_JNI(register_android_view_HardwareRenderer),
REG_JNI(register_android_view_Surface),
REG_JNI(register_android_view_ViewRoot),
REG_JNI(register_com_google_android_gles_jni_EGLImpl),
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index 558f5ff..bf150a9 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -20,7 +20,6 @@
#include "SkCanvas.h"
#include "SkDevice.h"
-#include "SkGLCanvas.h"
#include "SkGraphics.h"
#include "SkImageRef_GlobalPool.h"
#include "SkPorterDuff.h"
@@ -67,13 +66,8 @@
return bitmap ? new SkCanvas(*bitmap) : new SkCanvas;
}
- static SkCanvas* initGL(JNIEnv* env, jobject) {
- return new SkGLCanvas;
- }
-
static void freeCaches(JNIEnv* env, jobject) {
// these are called in no particular order
- SkGLCanvas::DeleteAllTextures();
SkImageRef_GlobalPool::SetRAMUsed(0);
SkGraphics::SetFontCacheUsed(0);
}
@@ -110,11 +104,6 @@
return canvas->getDevice()->accessBitmap(false).height();
}
- static void setViewport(JNIEnv* env, jobject, SkCanvas* canvas,
- int width, int height) {
- canvas->setViewport(width, height);
- }
-
static void setBitmap(JNIEnv* env, jobject, SkCanvas* canvas,
SkBitmap* bitmap) {
canvas->setBitmapDevice(*bitmap);
@@ -880,12 +869,10 @@
static JNINativeMethod gCanvasMethods[] = {
{"finalizer", "(I)V", (void*) SkCanvasGlue::finalizer},
{"initRaster","(I)I", (void*) SkCanvasGlue::initRaster},
- {"initGL","()I", (void*) SkCanvasGlue::initGL},
{"isOpaque","()Z", (void*) SkCanvasGlue::isOpaque},
{"getWidth","()I", (void*) SkCanvasGlue::getWidth},
{"getHeight","()I", (void*) SkCanvasGlue::getHeight},
{"native_setBitmap","(II)V", (void*) SkCanvasGlue::setBitmap},
- {"nativeSetViewport", "(III)V", (void*) SkCanvasGlue::setViewport},
{"save","()I", (void*) SkCanvasGlue::saveAll},
{"save","(I)I", (void*) SkCanvasGlue::save},
{"native_saveLayer","(ILandroid/graphics/RectF;II)I",
diff --git a/core/jni/android/graphics/ColorFilter.cpp b/core/jni/android/graphics/ColorFilter.cpp
index 848234f..f3be8b0 100644
--- a/core/jni/android/graphics/ColorFilter.cpp
+++ b/core/jni/android/graphics/ColorFilter.cpp
@@ -36,40 +36,25 @@
obj->safeUnref();
}
- static SkColorFilter* CreatePorterDuffFilter(JNIEnv* env, jobject, jint srcColor,
- SkPorterDuff::Mode mode) {
- return SkColorFilter::CreateModeFilter(srcColor, SkPorterDuff::ToXfermodeMode(mode));
- }
-
static SkiaColorFilter* glCreatePorterDuffFilter(JNIEnv* env, jobject, jint srcColor,
SkPorterDuff::Mode mode) {
+#ifdef USE_OPENGL_RENDERER
return new SkiaBlendFilter(srcColor, SkPorterDuff::ToXfermodeMode(mode));
- }
-
- static SkColorFilter* CreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) {
- return SkColorFilter::CreateLightingFilter(mul, add);
- }
-
- static SkiaColorFilter* glCreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) {
- return new SkiaLightingFilter(mul, add);
- }
-
- static SkColorFilter* CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
- AutoJavaFloatArray autoArray(env, jarray, 20);
- const float* src = autoArray.ptr();
-
-#ifdef SK_SCALAR_IS_FIXED
- SkFixed array[20];
- for (int i = 0; i < 20; i++) {
- array[i] = SkFloatToScalar(src[i]);
- }
- return new SkColorMatrixFilter(array);
#else
- return new SkColorMatrixFilter(src);
+ return NULL;
+#endif
+ }
+
+ static SkiaColorFilter* glCreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) {
+#ifdef USE_OPENGL_RENDERER
+ return new SkiaLightingFilter(mul, add);
+#else
+ return NULL;
#endif
}
static SkiaColorFilter* glCreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
+#ifdef USE_OPENGL_RENDERER
AutoJavaFloatArray autoArray(env, jarray, 20);
const float* src = autoArray.ptr();
@@ -86,6 +71,33 @@
colorVector[3] = src[19];
return new SkiaColorMatrixFilter(colorMatrix, colorVector);
+#else
+ return NULL;
+#endif
+ }
+
+ static SkColorFilter* CreatePorterDuffFilter(JNIEnv* env, jobject, jint srcColor,
+ SkPorterDuff::Mode mode) {
+ return SkColorFilter::CreateModeFilter(srcColor, SkPorterDuff::ToXfermodeMode(mode));
+ }
+
+ static SkColorFilter* CreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) {
+ return SkColorFilter::CreateLightingFilter(mul, add);
+ }
+
+ static SkColorFilter* CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
+ AutoJavaFloatArray autoArray(env, jarray, 20);
+ const float* src = autoArray.ptr();
+
+#ifdef SK_SCALAR_IS_FIXED
+ SkFixed array[20];
+ for (int i = 0; i < 20; i++) {
+ array[i] = SkFloatToScalar(src[i]);
+ }
+ return new SkColorMatrixFilter(array);
+#else
+ return new SkColorMatrixFilter(src);
+#endif
}
};
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
index 34b4ab5..2b98e89 100644
--- a/core/jni/android/graphics/Shader.cpp
+++ b/core/jni/android/graphics/Shader.cpp
@@ -71,7 +71,9 @@
else {
shader->setLocalMatrix(*matrix);
}
+#ifdef USE_OPENGL_RENDERER
skiaShader->setMatrix(const_cast<SkMatrix*>(matrix));
+#endif
}
}
@@ -90,10 +92,14 @@
static SkiaShader* BitmapShader_postConstructor(JNIEnv* env, jobject o, SkShader* shader,
SkBitmap* bitmap, int tileModeX, int tileModeY) {
+#ifdef USE_OPENGL_RENDERER
SkiaShader* skiaShader = new SkiaBitmapShader(bitmap, shader,
static_cast<SkShader::TileMode>(tileModeX), static_cast<SkShader::TileMode>(tileModeY),
NULL, (shader->getFlags() & SkShader::kOpaqueAlpha_Flag) == 0);
return skiaShader;
+#else
+ return NULL;
+#endif
}
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -134,7 +140,7 @@
static SkiaShader* LinearGradient_postCreate1(JNIEnv* env, jobject o, SkShader* shader,
float x0, float y0, float x1, float y1, jintArray colorArray,
jfloatArray posArray, int tileMode) {
-
+#ifdef USE_OPENGL_RENDERER
size_t count = env->GetArrayLength(colorArray);
const jint* colorValues = env->GetIntArrayElements(colorArray, NULL);
@@ -162,10 +168,14 @@
env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues), JNI_ABORT);
return skiaShader;
+#else
+ return NULL;
+#endif
}
static SkiaShader* LinearGradient_postCreate2(JNIEnv* env, jobject o, SkShader* shader,
float x0, float y0, float x1, float y1, int color0, int color1, int tileMode) {
+#ifdef USE_OPENGL_RENDERER
float* storedBounds = new float[4];
storedBounds[0] = x0; storedBounds[1] = y0;
storedBounds[2] = x1; storedBounds[3] = y1;
@@ -183,6 +193,9 @@
(shader->getFlags() & SkShader::kOpaqueAlpha_Flag) == 0);
return skiaShader;
+#else
+ return NULL;
+#endif
}
static SkShader* LinearGradient_create2(JNIEnv* env, jobject o,
@@ -315,6 +328,7 @@
static SkiaShader* ComposeShader_postCreate2(JNIEnv* env, jobject o, SkShader* shader,
SkiaShader* shaderA, SkiaShader* shaderB, SkPorterDuff::Mode porterDuffMode) {
+#ifdef USE_OPENGL_RENDERER
SkAutoUnref au(SkPorterDuff::CreateXfermode(porterDuffMode));
SkXfermode* mode = (SkXfermode*) au.get();
SkXfermode::Mode skiaMode;
@@ -322,15 +336,22 @@
skiaMode = SkXfermode::kSrcOver_Mode;
}
return new SkiaComposeShader(shaderA, shaderB, skiaMode, shader);
+#else
+ return NULL;
+#endif
}
static SkiaShader* ComposeShader_postCreate1(JNIEnv* env, jobject o, SkShader* shader,
SkiaShader* shaderA, SkiaShader* shaderB, SkXfermode* mode) {
+#ifdef USE_OPENGL_RENDERER
SkXfermode::Mode skiaMode;
if (!SkXfermode::IsMode(mode, &skiaMode)) {
skiaMode = SkXfermode::kSrcOver_Mode;
}
return new SkiaComposeShader(shaderA, shaderB, skiaMode, shader);
+#else
+ return NULL;
+#endif
}
///////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/jni/android_os_FileUtils.cpp b/core/jni/android_os_FileUtils.cpp
index 21cb919..d3faa2f 100644
--- a/core/jni/android_os_FileUtils.cpp
+++ b/core/jni/android_os_FileUtils.cpp
@@ -113,6 +113,11 @@
#endif
}
+jint android_os_FileUtils_setUMask(JNIEnv* env, jobject clazz, jint mask)
+{
+ return umask(mask);
+}
+
jint android_os_FileUtils_getFatVolumeId(JNIEnv* env, jobject clazz, jstring path)
{
#if HAVE_ANDROID_OS
@@ -170,6 +175,7 @@
static const JNINativeMethod methods[] = {
{"setPermissions", "(Ljava/lang/String;III)I", (void*)android_os_FileUtils_setPermissions},
{"getPermissions", "(Ljava/lang/String;[I)I", (void*)android_os_FileUtils_getPermissions},
+ {"setUMask", "(I)I", (void*)android_os_FileUtils_setUMask},
{"getFatVolumeId", "(Ljava/lang/String;)I", (void*)android_os_FileUtils_getFatVolumeId},
{"getFileStatus", "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z", (void*)android_os_FileUtils_getFileStatus},
};
diff --git a/core/jni/android_os_ParcelFileDescriptor.cpp b/core/jni/android_os_ParcelFileDescriptor.cpp
index 848a57a..eceef1c 100644
--- a/core/jni/android_os_ParcelFileDescriptor.cpp
+++ b/core/jni/android_os_ParcelFileDescriptor.cpp
@@ -66,6 +66,26 @@
return fileDescriptorClone;
}
+static int android_os_ParcelFileDescriptor_createPipeNative(JNIEnv* env,
+ jobject clazz, jobjectArray outFds)
+{
+ int fds[2];
+ if (pipe(fds) < 0) {
+ return -errno;
+ }
+
+ for (int i=0; i<2; i++) {
+ jobject fdObj = env->NewObject(gFileDescriptorOffsets.mClass,
+ gFileDescriptorOffsets.mConstructor);
+ if (fdObj != NULL) {
+ env->SetIntField(fdObj, gFileDescriptorOffsets.mDescriptor, fds[i]);
+ }
+ env->SetObjectArrayElement(outFds, i, fdObj);
+ }
+
+ return 0;
+}
+
static jint getFd(JNIEnv* env, jobject clazz)
{
jobject descriptor = env->GetObjectField(clazz, gParcelFileDescriptorOffsets.mFileDescriptor);
@@ -109,6 +129,8 @@
static const JNINativeMethod gParcelFileDescriptorMethods[] = {
{"getFileDescriptorFromSocket", "(Ljava/net/Socket;)Ljava/io/FileDescriptor;",
(void*)android_os_ParcelFileDescriptor_getFileDescriptorFromSocket},
+ {"createPipeNative", "([Ljava/io/FileDescriptor;)I",
+ (void*)android_os_ParcelFileDescriptor_createPipeNative},
{"getStatSize", "()J",
(void*)android_os_ParcelFileDescriptor_getStatSize},
{"seekTo", "(J)J",
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index 9fd2b86..bb1a9e3 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -41,6 +41,13 @@
using namespace uirenderer;
+/**
+ * Note: OpenGLRenderer JNI layer is generated and compiled only on supported
+ * devices. This means all the logic must be compiled only when the
+ * preprocessor variable USE_OPENGL_RENDERER is defined.
+ */
+#ifdef USE_OPENGL_RENDERER
+
// ----------------------------------------------------------------------------
// Java APIs
// ----------------------------------------------------------------------------
@@ -284,6 +291,20 @@
env->ReleaseStringChars(text, textArray);
}
+#endif // USE_OPENGL_RENDERER
+
+// ----------------------------------------------------------------------------
+// Common
+// ----------------------------------------------------------------------------
+
+static jboolean android_view_GLES20Canvas_isAvailable(JNIEnv* env, jobject clazz) {
+#ifdef USE_OPENGL_RENDERER
+ return JNI_TRUE;
+#else
+ return JNI_FALSE;
+#endif
+}
+
// ----------------------------------------------------------------------------
// JNI Glue
// ----------------------------------------------------------------------------
@@ -291,6 +312,9 @@
const char* const kClassPathName = "android/view/GLES20Canvas";
static JNINativeMethod gMethods[] = {
+ { "nIsAvailable", "()Z", (void*) android_view_GLES20Canvas_isAvailable },
+#ifdef USE_OPENGL_RENDERER
+
{ "nCreateRenderer", "()I", (void*) android_view_GLES20Canvas_createRenderer },
{ "nDestroyRenderer", "(I)V", (void*) android_view_GLES20Canvas_destroyRenderer },
{ "nSetViewport", "(III)V", (void*) android_view_GLES20Canvas_setViewport },
@@ -334,16 +358,22 @@
{ "nGetClipBounds", "(ILandroid/graphics/Rect;)Z",
(void*) android_view_GLES20Canvas_getClipBounds },
+#endif
};
-#define FIND_CLASS(var, className) \
- var = env->FindClass(className); \
- LOG_FATAL_IF(! var, "Unable to find class " className); \
- var = jclass(env->NewGlobalRef(var));
-
-#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
- var = env->GetMethodID(clazz, methodName, methodDescriptor); \
- LOG_FATAL_IF(! var, "Unable to find method " methodName);
+#ifdef USE_OPENGL_RENDERER
+ #define FIND_CLASS(var, className) \
+ var = env->FindClass(className); \
+ LOG_FATAL_IF(! var, "Unable to find class " className); \
+ var = jclass(env->NewGlobalRef(var));
+
+ #define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+ var = env->GetMethodID(clazz, methodName, methodDescriptor); \
+ LOG_FATAL_IF(! var, "Unable to find method " methodName);
+#else
+ #define FIND_CLASS(var, className)
+ #define GET_METHOD_ID(var, clazz, methodName, methodDescriptor)
+#endif
int register_android_view_GLES20Canvas(JNIEnv* env) {
FIND_CLASS(gRectClassInfo.clazz, "android/graphics/Rect");
diff --git a/core/jni/android_view_HardwareRenderer.cpp b/core/jni/android_view_HardwareRenderer.cpp
deleted file mode 100644
index 6d20c9d..0000000
--- a/core/jni/android_view_HardwareRenderer.cpp
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <utils/SkGLCanvas.h>
-
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include <android_runtime/AndroidRuntime.h>
-#include <utils/misc.h>
-
-// ----------------------------------------------------------------------------
-
-namespace android {
-
-static void android_view_HardwareRenderer_abandonGlCaches(JNIEnv* env, jobject) {
- SkGLCanvas::AbandonAllTextures();
-}
-
-// ----------------------------------------------------------------------------
-
-const char* const kClassPathName = "android/view/HardwareRenderer";
-
-static JNINativeMethod gMethods[] = {
- { "nativeAbandonGlCaches", "()V", (void*)android_view_HardwareRenderer_abandonGlCaches },
-};
-
-int register_android_view_HardwareRenderer(JNIEnv* env) {
- return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
-}
-
-};
diff --git a/core/tests/coretests/src/android/util/JsonReaderTest.java b/core/tests/coretests/src/android/util/JsonReaderTest.java
new file mode 100644
index 0000000..ced9310
--- /dev/null
+++ b/core/tests/coretests/src/android/util/JsonReaderTest.java
@@ -0,0 +1,683 @@
+/*
+ * Copyright (C) 2010 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.util;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+public final class JsonReaderTest extends TestCase {
+
+ public void testReadArray() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[true, true]"));
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ assertEquals(true, reader.nextBoolean());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testReadEmptyArray() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[]"));
+ reader.beginArray();
+ assertFalse(reader.hasNext());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testReadObject() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader(
+ "{\"a\": \"android\", \"b\": \"banana\"}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ assertEquals("android", reader.nextString());
+ assertEquals("b", reader.nextName());
+ assertEquals("banana", reader.nextString());
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testReadEmptyObject() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("{}"));
+ reader.beginObject();
+ assertFalse(reader.hasNext());
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testSkipObject() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader(
+ "{\"a\": { \"c\": [], \"d\": [true, true, {}] }, \"b\": \"banana\"}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ reader.skipValue();
+ assertEquals("b", reader.nextName());
+ reader.skipValue();
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testHelloWorld() throws IOException {
+ String json = "{\n" +
+ " \"hello\": true,\n" +
+ " \"foo\": [\"world\"]\n" +
+ "}";
+ JsonReader reader = new JsonReader(new StringReader(json));
+ reader.beginObject();
+ assertEquals("hello", reader.nextName());
+ assertEquals(true, reader.nextBoolean());
+ assertEquals("foo", reader.nextName());
+ reader.beginArray();
+ assertEquals("world", reader.nextString());
+ reader.endArray();
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testNulls() {
+ try {
+ new JsonReader(null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void testEmptyString() throws IOException {
+ try {
+ new JsonReader(new StringReader("")).beginArray();
+ } catch (IOException expected) {
+ }
+ try {
+ new JsonReader(new StringReader("")).beginObject();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testNoTopLevelObject() throws IOException {
+ try {
+ new JsonReader(new StringReader("true")).nextBoolean();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testCharacterUnescaping() throws IOException {
+ String json = "[\"a\","
+ + "\"a\\\"\","
+ + "\"\\\"\","
+ + "\":\","
+ + "\",\","
+ + "\"\\b\","
+ + "\"\\f\","
+ + "\"\\n\","
+ + "\"\\r\","
+ + "\"\\t\","
+ + "\" \","
+ + "\"\\\\\","
+ + "\"{\","
+ + "\"}\","
+ + "\"[\","
+ + "\"]\","
+ + "\"\\u0000\","
+ + "\"\\u0019\","
+ + "\"\\u20AC\""
+ + "]";
+ JsonReader reader = new JsonReader(new StringReader(json));
+ reader.beginArray();
+ assertEquals("a", reader.nextString());
+ assertEquals("a\"", reader.nextString());
+ assertEquals("\"", reader.nextString());
+ assertEquals(":", reader.nextString());
+ assertEquals(",", reader.nextString());
+ assertEquals("\b", reader.nextString());
+ assertEquals("\f", reader.nextString());
+ assertEquals("\n", reader.nextString());
+ assertEquals("\r", reader.nextString());
+ assertEquals("\t", reader.nextString());
+ assertEquals(" ", reader.nextString());
+ assertEquals("\\", reader.nextString());
+ assertEquals("{", reader.nextString());
+ assertEquals("}", reader.nextString());
+ assertEquals("[", reader.nextString());
+ assertEquals("]", reader.nextString());
+ assertEquals("\0", reader.nextString());
+ assertEquals("\u0019", reader.nextString());
+ assertEquals("\u20AC", reader.nextString());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testIntegersWithFractionalPartSpecified() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[1.0,1.0,1.0]"));
+ reader.beginArray();
+ assertEquals(1.0, reader.nextDouble());
+ assertEquals(1, reader.nextInt());
+ assertEquals(1L, reader.nextLong());
+ }
+
+ public void testDoubles() throws IOException {
+ String json = "[-0.0,"
+ + "1.0,"
+ + "1.7976931348623157E308,"
+ + "4.9E-324,"
+ + "0.0,"
+ + "-0.5,"
+ + "2.2250738585072014E-308,"
+ + "3.141592653589793,"
+ + "2.718281828459045]";
+ JsonReader reader = new JsonReader(new StringReader(json));
+ reader.beginArray();
+ assertEquals(-0.0, reader.nextDouble());
+ assertEquals(1.0, reader.nextDouble());
+ assertEquals(1.7976931348623157E308, reader.nextDouble());
+ assertEquals(4.9E-324, reader.nextDouble());
+ assertEquals(0.0, reader.nextDouble());
+ assertEquals(-0.5, reader.nextDouble());
+ assertEquals(2.2250738585072014E-308, reader.nextDouble());
+ assertEquals(3.141592653589793, reader.nextDouble());
+ assertEquals(2.718281828459045, reader.nextDouble());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testNonFiniteDoubles() throws IOException {
+ String json = "[NaN]";
+ JsonReader reader = new JsonReader(new StringReader(json));
+ reader.beginArray();
+ try {
+ reader.nextDouble();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ }
+
+ public void testLongs() throws IOException {
+ String json = "[0,0,0,"
+ + "1,1,1,"
+ + "-1,-1,-1,"
+ + "-9223372036854775808,"
+ + "9223372036854775807]";
+ JsonReader reader = new JsonReader(new StringReader(json));
+ reader.beginArray();
+ assertEquals(0L, reader.nextLong());
+ assertEquals(0, reader.nextInt());
+ assertEquals(0.0, reader.nextDouble());
+ assertEquals(1L, reader.nextLong());
+ assertEquals(1, reader.nextInt());
+ assertEquals(1.0, reader.nextDouble());
+ assertEquals(-1L, reader.nextLong());
+ assertEquals(-1, reader.nextInt());
+ assertEquals(-1.0, reader.nextDouble());
+ try {
+ reader.nextInt();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ assertEquals(Long.MIN_VALUE, reader.nextLong());
+ try {
+ reader.nextInt();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ assertEquals(Long.MAX_VALUE, reader.nextLong());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ /**
+ * This test fails because there's no double for 9223372036854775806, and
+ * our long parsing uses Double.parseDouble() for fractional values.
+ */
+ public void testHighPrecisionLong() throws IOException {
+ String json = "[9223372036854775806.000]";
+ JsonReader reader = new JsonReader(new StringReader(json));
+ reader.beginArray();
+ assertEquals(9223372036854775806L, reader.nextLong());
+ reader.endArray();
+ }
+
+ public void testNumberWithOctalPrefix() throws IOException {
+ String json = "[01]";
+ JsonReader reader = new JsonReader(new StringReader(json));
+ reader.beginArray();
+ try {
+ reader.nextInt();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ try {
+ reader.nextLong();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ try {
+ reader.nextDouble();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ assertEquals("01", reader.nextString());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testBooleans() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[true,false]"));
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ assertEquals(false, reader.nextBoolean());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testMixedCaseLiterals() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[True,TruE,False,FALSE,NULL,nulL]"));
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ assertEquals(true, reader.nextBoolean());
+ assertEquals(false, reader.nextBoolean());
+ assertEquals(false, reader.nextBoolean());
+ reader.nextNull();
+ reader.nextNull();
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testMissingValue() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("{\"a\":}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ try {
+ reader.nextString();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testPrematureEndOfInput() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("{\"a\":true,"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ assertEquals(true, reader.nextBoolean());
+ try {
+ reader.nextName();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testPrematurelyClosed() throws IOException {
+ try {
+ JsonReader reader = new JsonReader(new StringReader("{\"a\":[]}"));
+ reader.beginObject();
+ reader.close();
+ reader.nextName();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+
+ try {
+ JsonReader reader = new JsonReader(new StringReader("{\"a\":[]}"));
+ reader.close();
+ reader.beginObject();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+
+ try {
+ JsonReader reader = new JsonReader(new StringReader("{\"a\":true}"));
+ reader.beginObject();
+ reader.nextName();
+ reader.peek();
+ reader.close();
+ reader.nextBoolean();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testNextFailuresDoNotAdvance() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("{\"a\":true}"));
+ reader.beginObject();
+ try {
+ reader.nextString();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ assertEquals("a", reader.nextName());
+ try {
+ reader.nextName();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.beginArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.endArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.beginObject();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.endObject();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ assertEquals(true, reader.nextBoolean());
+ try {
+ reader.nextString();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.nextName();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.beginArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.endArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ reader.close();
+ }
+
+ public void testStringNullIsNotNull() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[\"null\"]"));
+ reader.beginArray();
+ try {
+ reader.nextNull();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testNullLiteralIsNotAString() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[null]"));
+ reader.beginArray();
+ try {
+ reader.nextString();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testStrictNameValueSeparator() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("{\"a\"=true}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ try {
+ reader.nextBoolean();
+ fail();
+ } catch (IOException expected) {
+ }
+
+ reader = new JsonReader(new StringReader("{\"a\"=>true}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ try {
+ reader.nextBoolean();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientNameValueSeparator() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("{\"a\"=true}"));
+ reader.setLenient(true);
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ assertEquals(true, reader.nextBoolean());
+
+ reader = new JsonReader(new StringReader("{\"a\"=>true}"));
+ reader.setLenient(true);
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ assertEquals(true, reader.nextBoolean());
+ }
+
+ public void testStrictComments() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[// comment \n true]"));
+ reader.beginArray();
+ try {
+ reader.nextBoolean();
+ fail();
+ } catch (IOException expected) {
+ }
+
+ reader = new JsonReader(new StringReader("[# comment \n true]"));
+ reader.beginArray();
+ try {
+ reader.nextBoolean();
+ fail();
+ } catch (IOException expected) {
+ }
+
+ reader = new JsonReader(new StringReader("[/* comment */ true]"));
+ reader.beginArray();
+ try {
+ reader.nextBoolean();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientComments() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[// comment \n true]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+
+ reader = new JsonReader(new StringReader("[# comment \n true]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+
+ reader = new JsonReader(new StringReader("[/* comment */ true]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ }
+
+ public void testStrictUnquotedNames() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("{a:true}"));
+ reader.beginObject();
+ try {
+ reader.nextName();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientUnquotedNames() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("{a:true}"));
+ reader.setLenient(true);
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ }
+
+ public void testStrictSingleQuotedNames() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("{'a':true}"));
+ reader.beginObject();
+ try {
+ reader.nextName();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientSingleQuotedNames() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("{'a':true}"));
+ reader.setLenient(true);
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ }
+
+ public void testStrictUnquotedStrings() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[a]"));
+ reader.beginArray();
+ try {
+ reader.nextString();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientUnquotedStrings() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[a]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals("a", reader.nextString());
+ }
+
+ public void testStrictSingleQuotedStrings() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("['a']"));
+ reader.beginArray();
+ try {
+ reader.nextString();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientSingleQuotedStrings() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("['a']"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals("a", reader.nextString());
+ }
+
+ public void testStrictSemicolonDelimitedArray() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[true;true]"));
+ reader.beginArray();
+ try {
+ reader.nextBoolean();
+ reader.nextBoolean();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientSemicolonDelimitedArray() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[true;true]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ assertEquals(true, reader.nextBoolean());
+ }
+
+ public void testStrictSemicolonDelimitedNameValuePair() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("{\"a\":true;\"b\":true}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ try {
+ reader.nextBoolean();
+ reader.nextName();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientSemicolonDelimitedNameValuePair() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("{\"a\":true;\"b\":true}"));
+ reader.setLenient(true);
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ assertEquals(true, reader.nextBoolean());
+ assertEquals("b", reader.nextName());
+ }
+
+ public void testStrictUnnecessaryArraySeparators() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[true,,true]"));
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ try {
+ reader.nextNull();
+ fail();
+ } catch (IOException expected) {
+ }
+
+ reader = new JsonReader(new StringReader("[,true]"));
+ reader.beginArray();
+ try {
+ reader.nextNull();
+ fail();
+ } catch (IOException expected) {
+ }
+
+ reader = new JsonReader(new StringReader("[true,]"));
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ try {
+ reader.nextNull();
+ fail();
+ } catch (IOException expected) {
+ }
+
+ reader = new JsonReader(new StringReader("[,]"));
+ reader.beginArray();
+ try {
+ reader.nextNull();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientUnnecessaryArraySeparators() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[true,,true]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ reader.nextNull();
+ assertEquals(true, reader.nextBoolean());
+ reader.endArray();
+
+ reader = new JsonReader(new StringReader("[,true]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ reader.nextNull();
+ assertEquals(true, reader.nextBoolean());
+ reader.endArray();
+
+ reader = new JsonReader(new StringReader("[true,]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ reader.nextNull();
+ reader.endArray();
+
+ reader = new JsonReader(new StringReader("[,]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ reader.nextNull();
+ reader.nextNull();
+ reader.endArray();
+ }
+}
diff --git a/core/tests/coretests/src/android/util/JsonWriterTest.java b/core/tests/coretests/src/android/util/JsonWriterTest.java
new file mode 100644
index 0000000..fa84023
--- /dev/null
+++ b/core/tests/coretests/src/android/util/JsonWriterTest.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2010 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.util;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+public final class JsonWriterTest extends TestCase {
+
+ public void testWrongTopLevelType() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ try {
+ jsonWriter.value("a");
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testTwoNames() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ jsonWriter.name("a");
+ try {
+ jsonWriter.name("a");
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testNameWithoutValue() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ jsonWriter.name("a");
+ try {
+ jsonWriter.endObject();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testValueWithoutName() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ try {
+ jsonWriter.value(true);
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testMultipleTopLevelValues() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray().endArray();
+ try {
+ jsonWriter.beginArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testBadNestingObject() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.beginObject();
+ try {
+ jsonWriter.endArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testBadNestingArray() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.beginArray();
+ try {
+ jsonWriter.endObject();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testNullName() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ try {
+ jsonWriter.name(null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void testNullStringValue() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ jsonWriter.name("a");
+ jsonWriter.value(null);
+ jsonWriter.endObject();
+ assertEquals("{\"a\":null}", stringWriter.toString());
+ }
+
+ public void testNonFiniteDoubles() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ try {
+ jsonWriter.value(Double.NaN);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ jsonWriter.value(Double.NEGATIVE_INFINITY);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ jsonWriter.value(Double.POSITIVE_INFINITY);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testDoubles() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.value(-0.0);
+ jsonWriter.value(1.0);
+ jsonWriter.value(Double.MAX_VALUE);
+ jsonWriter.value(Double.MIN_VALUE);
+ jsonWriter.value(0.0);
+ jsonWriter.value(-0.5);
+ jsonWriter.value(Double.MIN_NORMAL);
+ jsonWriter.value(Math.PI);
+ jsonWriter.value(Math.E);
+ jsonWriter.endArray();
+ jsonWriter.close();
+ assertEquals("[-0.0,"
+ + "1.0,"
+ + "1.7976931348623157E308,"
+ + "4.9E-324,"
+ + "0.0,"
+ + "-0.5,"
+ + "2.2250738585072014E-308,"
+ + "3.141592653589793,"
+ + "2.718281828459045]", stringWriter.toString());
+ }
+
+ public void testLongs() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.value(0);
+ jsonWriter.value(1);
+ jsonWriter.value(-1);
+ jsonWriter.value(Long.MIN_VALUE);
+ jsonWriter.value(Long.MAX_VALUE);
+ jsonWriter.endArray();
+ jsonWriter.close();
+ assertEquals("[0,"
+ + "1,"
+ + "-1,"
+ + "-9223372036854775808,"
+ + "9223372036854775807]", stringWriter.toString());
+ }
+
+ public void testBooleans() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.value(true);
+ jsonWriter.value(false);
+ jsonWriter.endArray();
+ assertEquals("[true,false]", stringWriter.toString());
+ }
+
+ public void testNulls() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.nullValue();
+ jsonWriter.endArray();
+ assertEquals("[null]", stringWriter.toString());
+ }
+
+ public void testStrings() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.value("a");
+ jsonWriter.value("a\"");
+ jsonWriter.value("\"");
+ jsonWriter.value(":");
+ jsonWriter.value(",");
+ jsonWriter.value("\b");
+ jsonWriter.value("\f");
+ jsonWriter.value("\n");
+ jsonWriter.value("\r");
+ jsonWriter.value("\t");
+ jsonWriter.value(" ");
+ jsonWriter.value("\\");
+ jsonWriter.value("{");
+ jsonWriter.value("}");
+ jsonWriter.value("[");
+ jsonWriter.value("]");
+ jsonWriter.value("\0");
+ jsonWriter.value("\u0019");
+ jsonWriter.endArray();
+ assertEquals("[\"a\","
+ + "\"a\\\"\","
+ + "\"\\\"\","
+ + "\":\","
+ + "\",\","
+ + "\"\\b\","
+ + "\"\\f\","
+ + "\"\\n\","
+ + "\"\\r\","
+ + "\"\\t\","
+ + "\" \","
+ + "\"\\\\\","
+ + "\"{\","
+ + "\"}\","
+ + "\"[\","
+ + "\"]\","
+ + "\"\\u0000\","
+ + "\"\\u0019\"]", stringWriter.toString());
+ }
+
+ public void testEmptyArray() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.endArray();
+ assertEquals("[]", stringWriter.toString());
+ }
+
+ public void testEmptyObject() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ jsonWriter.endObject();
+ assertEquals("{}", stringWriter.toString());
+ }
+
+ public void testObjectsInArrays() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.beginObject();
+ jsonWriter.name("a").value(5);
+ jsonWriter.name("b").value(false);
+ jsonWriter.endObject();
+ jsonWriter.beginObject();
+ jsonWriter.name("c").value(6);
+ jsonWriter.name("d").value(true);
+ jsonWriter.endObject();
+ jsonWriter.endArray();
+ assertEquals("[{\"a\":5,\"b\":false},"
+ + "{\"c\":6,\"d\":true}]", stringWriter.toString());
+ }
+
+ public void testArraysInObjects() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ jsonWriter.name("a");
+ jsonWriter.beginArray();
+ jsonWriter.value(5);
+ jsonWriter.value(false);
+ jsonWriter.endArray();
+ jsonWriter.name("b");
+ jsonWriter.beginArray();
+ jsonWriter.value(6);
+ jsonWriter.value(true);
+ jsonWriter.endArray();
+ jsonWriter.endObject();
+ assertEquals("{\"a\":[5,false],"
+ + "\"b\":[6,true]}", stringWriter.toString());
+ }
+
+ public void testDeepNestingArrays() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ for (int i = 0; i < 20; i++) {
+ jsonWriter.beginArray();
+ }
+ for (int i = 0; i < 20; i++) {
+ jsonWriter.endArray();
+ }
+ assertEquals("[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]", stringWriter.toString());
+ }
+
+ public void testDeepNestingObjects() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ for (int i = 0; i < 20; i++) {
+ jsonWriter.name("a");
+ jsonWriter.beginObject();
+ }
+ for (int i = 0; i < 20; i++) {
+ jsonWriter.endObject();
+ }
+ jsonWriter.endObject();
+ assertEquals("{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":"
+ + "{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{"
+ + "}}}}}}}}}}}}}}}}}}}}}", stringWriter.toString());
+ }
+
+ public void testRepeatedName() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ jsonWriter.name("a").value(true);
+ jsonWriter.name("a").value(false);
+ jsonWriter.endObject();
+ // JsonWriter doesn't attempt to detect duplicate names
+ assertEquals("{\"a\":true,\"a\":false}", stringWriter.toString());
+ }
+
+ public void testPrettyPrintObject() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.setIndent(" ");
+
+ jsonWriter.beginObject();
+ jsonWriter.name("a").value(true);
+ jsonWriter.name("b").value(false);
+ jsonWriter.name("c").value(5.0);
+ jsonWriter.name("e").nullValue();
+ jsonWriter.name("f").beginArray();
+ jsonWriter.value(6.0);
+ jsonWriter.value(7.0);
+ jsonWriter.endArray();
+ jsonWriter.name("g").beginObject();
+ jsonWriter.name("h").value(8.0);
+ jsonWriter.name("i").value(9.0);
+ jsonWriter.endObject();
+ jsonWriter.endObject();
+
+ String expected = "{\n"
+ + " \"a\": true,\n"
+ + " \"b\": false,\n"
+ + " \"c\": 5.0,\n"
+ + " \"e\": null,\n"
+ + " \"f\": [\n"
+ + " 6.0,\n"
+ + " 7.0\n"
+ + " ],\n"
+ + " \"g\": {\n"
+ + " \"h\": 8.0,\n"
+ + " \"i\": 9.0\n"
+ + " }\n"
+ + "}";
+ assertEquals(expected, stringWriter.toString());
+ }
+
+ public void testPrettyPrintArray() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.setIndent(" ");
+
+ jsonWriter.beginArray();
+ jsonWriter.value(true);
+ jsonWriter.value(false);
+ jsonWriter.value(5.0);
+ jsonWriter.nullValue();
+ jsonWriter.beginObject();
+ jsonWriter.name("a").value(6.0);
+ jsonWriter.name("b").value(7.0);
+ jsonWriter.endObject();
+ jsonWriter.beginArray();
+ jsonWriter.value(8.0);
+ jsonWriter.value(9.0);
+ jsonWriter.endArray();
+ jsonWriter.endArray();
+
+ String expected = "[\n"
+ + " true,\n"
+ + " false,\n"
+ + " 5.0,\n"
+ + " null,\n"
+ + " {\n"
+ + " \"a\": 6.0,\n"
+ + " \"b\": 7.0\n"
+ + " },\n"
+ + " [\n"
+ + " 8.0,\n"
+ + " 9.0\n"
+ + " ]\n"
+ + "]";
+ assertEquals(expected, stringWriter.toString());
+ }
+}
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 77a1930..36a8e57 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -42,7 +42,6 @@
for both to be null.
*/
private Bitmap mBitmap; // if not null, mGL must be null
- private GL mGL; // if not null, mBitmap must be null
// optional field set by the caller
private DrawFilter mDrawFilter;
@@ -106,31 +105,22 @@
mDensity = bitmap.mDensity;
}
- /*package*/ Canvas(int nativeCanvas) {
+ Canvas(int nativeCanvas) {
if (nativeCanvas == 0) {
throw new IllegalStateException();
}
mNativeCanvas = nativeCanvas;
mDensity = Bitmap.getDefaultDensity();
}
-
+
/**
- * Construct a canvas with the specified gl context. All drawing through
- * this canvas will be redirected to OpenGL. Note: some features may not
- * be supported in this mode (e.g. some GL implementations may not support
- * antialiasing or certain effects like ColorMatrix or certain Xfermodes).
- * However, no exception will be thrown in those cases.
+ * Returns null.
*
- * <p>The initial target density of the canvas is the same as the initial
- * density of bitmaps as per {@link Bitmap#getDensity() Bitmap.getDensity()}.
- *
- * @deprecated This constructor is not supported and should not be invoked.
+ * @deprecated This method is not supported and should not be invoked.
*/
@Deprecated
- public Canvas(GL gl) {
- mNativeCanvas = initGL();
- mGL = gl;
- mDensity = Bitmap.getDefaultDensity();
+ protected GL getGL() {
+ return null;
}
/**
@@ -143,32 +133,9 @@
* false otherwise.
*/
public boolean isHardwareAccelerated() {
- return mGL != null;
+ return false;
}
-
- /**
- * Return the GL object associated with this canvas, or null if it is not
- * backed by GL.
- *
- * @deprecated This method is not supported and should not be invoked.
- */
- @Deprecated
- public GL getGL() {
- return mGL;
- }
-
- /**
- * Call this to free up OpenGL resources that may be cached or allocated
- * on behalf of the Canvas. Any subsequent drawing with a GL-backed Canvas
- * will have to recreate those resources.
- *
- * @deprecated This method is not supported and should not be invoked.
- */
- @Deprecated
- public static void freeGlCaches() {
- freeCaches();
- }
-
+
/**
* Specify a bitmap for the canvas to draw into. As a side-effect, also
* updates the canvas's target density to match that of the bitmap.
@@ -182,7 +149,7 @@
if (!bitmap.isMutable()) {
throw new IllegalStateException();
}
- if (mGL != null) {
+ if (isHardwareAccelerated()) {
throw new RuntimeException("Can't set a bitmap device on a GL canvas");
}
throwIfRecycled(bitmap);
@@ -196,16 +163,12 @@
* Set the viewport dimensions if this canvas is GL based. If it is not,
* this method is ignored and no exception is thrown.
*
- * @param width The width of the viewport
- * @param height The height of the viewport
+ * @param width The width of the viewport
+ * @param height The height of the viewport
*
- * @deprecated This method is not supported and should not be invoked.
+ * @hide
*/
- @Deprecated
public void setViewport(int width, int height) {
- if (mGL != null) {
- nativeSetViewport(mNativeCanvas, width, height);
- }
}
/**
@@ -1591,26 +1554,26 @@
@Override
protected void finalize() throws Throwable {
- super.finalize();
- // If the constructor threw an exception before setting mNativeCanvas, the native finalizer
- // must not be invoked.
- if (mNativeCanvas != 0) {
- finalizer(mNativeCanvas);
+ try {
+ super.finalize();
+ } finally {
+ // If the constructor threw an exception before setting mNativeCanvas,
+ // the native finalizer must not be invoked.
+ if (mNativeCanvas != 0) {
+ finalizer(mNativeCanvas);
+ }
}
}
/**
- * Free up as much memory as possible from private caches (e.g. fonts,
- * images)
+ * Free up as much memory as possible from private caches (e.g. fonts, images)
*
- * @hide - for now
+ * @hide
*/
public static native void freeCaches();
private static native int initRaster(int nativeBitmapOrZero);
- private static native int initGL();
private static native void native_setBitmap(int nativeCanvas, int bitmap);
- private static native void nativeSetViewport(int nCanvas, int w, int h);
private static native int native_saveLayer(int nativeCanvas, RectF bounds,
int paint, int layerFlags);
private static native int native_saveLayer(int nativeCanvas, float l,
diff --git a/graphics/java/android/graphics/ComposeShader.java b/graphics/java/android/graphics/ComposeShader.java
index 9b57ea4..ac1f277 100644
--- a/graphics/java/android/graphics/ComposeShader.java
+++ b/graphics/java/android/graphics/ComposeShader.java
@@ -20,6 +20,9 @@
an {@link android.graphics.Xfermode} subclass.
*/
public class ComposeShader extends Shader {
+ private final Shader mShaderA;
+ private final Shader mShaderB;
+
/** Create a new compose shader, given shaders A, B, and a combining mode.
When the mode is applied, it will be given the result from shader A as its
"dst", and the result of from shader B as its "src".
@@ -29,6 +32,8 @@
is null, then SRC_OVER is assumed.
*/
public ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode) {
+ mShaderA = shaderA;
+ mShaderB = shaderB;
native_instance = nativeCreate1(shaderA.native_instance, shaderB.native_instance,
(mode != null) ? mode.native_instance : 0);
native_shader = nativePostCreate1(native_instance, shaderA.native_shader,
@@ -43,6 +48,8 @@
@param mode The PorterDuff mode that combines the colors from the two shaders.
*/
public ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode) {
+ mShaderA = shaderA;
+ mShaderB = shaderB;
native_instance = nativeCreate2(shaderA.native_instance, shaderB.native_instance,
mode.nativeInt);
native_shader = nativePostCreate2(native_instance, shaderA.native_shader,
diff --git a/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java b/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java
index f4cf15c..b469d2a 100644
--- a/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java
+++ b/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java
@@ -57,13 +57,11 @@
*/
public RoundRectShape(float[] outerRadii, RectF inset,
float[] innerRadii) {
- if (outerRadii.length < 8) {
- throw new ArrayIndexOutOfBoundsException(
- "outer radii must have >= 8 values");
+ if (outerRadii != null && outerRadii.length < 8) {
+ throw new ArrayIndexOutOfBoundsException("outer radii must have >= 8 values");
}
if (innerRadii != null && innerRadii.length < 8) {
- throw new ArrayIndexOutOfBoundsException(
- "inner radii must have >= 8 values");
+ throw new ArrayIndexOutOfBoundsException("inner radii must have >= 8 values");
}
mOuterRadii = outerRadii;
mInset = inset;
@@ -97,8 +95,7 @@
r.right - mInset.right, r.bottom - mInset.bottom);
if (mInnerRect.width() < w && mInnerRect.height() < h) {
if (mInnerRadii != null) {
- mPath.addRoundRect(mInnerRect, mInnerRadii,
- Path.Direction.CCW);
+ mPath.addRoundRect(mInnerRect, mInnerRadii, Path.Direction.CCW);
} else {
mPath.addRect(mInnerRect, Path.Direction.CCW);
}
@@ -109,8 +106,8 @@
@Override
public RoundRectShape clone() throws CloneNotSupportedException {
RoundRectShape shape = (RoundRectShape) super.clone();
- shape.mOuterRadii = mOuterRadii.clone();
- shape.mInnerRadii = mInnerRadii.clone();
+ shape.mOuterRadii = mOuterRadii != null ? mOuterRadii.clone() : null;
+ shape.mInnerRadii = mInnerRadii != null ? mInnerRadii.clone() : null;
shape.mInset = new RectF(mInset);
shape.mInnerRect = new RectF(mInnerRect);
shape.mPath = new Path(mPath);
diff --git a/include/utils/String8.h b/include/utils/String8.h
index 0b18fe3..4e41410 100644
--- a/include/utils/String8.h
+++ b/include/utils/String8.h
@@ -374,7 +374,7 @@
inline String8 String8::operator+(const String8& other) const
{
- String8 tmp;
+ String8 tmp(*this);
tmp += other;
return tmp;
}
@@ -387,7 +387,7 @@
inline String8 String8::operator+(const char* other) const
{
- String8 tmp;
+ String8 tmp(*this);
tmp += other;
return tmp;
}
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 0444964..1efe6b5 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -1,33 +1,41 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES:= \
- FontRenderer.cpp \
- GradientCache.cpp \
- LayerCache.cpp \
- Matrix.cpp \
- OpenGLRenderer.cpp \
- Patch.cpp \
- PatchCache.cpp \
- PathCache.cpp \
- Program.cpp \
- ProgramCache.cpp \
- SkiaColorFilter.cpp \
- SkiaShader.cpp \
- TextureCache.cpp
+# Only build libhwui when USE_OPENGL_RENDERER is
+# defined in the current device/board configuration
+ifeq ($(USE_OPENGL_RENDERER),true)
+ LOCAL_SRC_FILES:= \
+ FontRenderer.cpp \
+ GradientCache.cpp \
+ LayerCache.cpp \
+ Matrix.cpp \
+ OpenGLRenderer.cpp \
+ Patch.cpp \
+ PatchCache.cpp \
+ PathCache.cpp \
+ Program.cpp \
+ ProgramCache.cpp \
+ SkiaColorFilter.cpp \
+ SkiaShader.cpp \
+ TextureCache.cpp
+
+ LOCAL_C_INCLUDES += \
+ $(JNI_H_INCLUDE) \
+ $(LOCAL_PATH)/../../include/utils \
+ external/skia/include/core \
+ external/skia/include/effects \
+ external/skia/include/images \
+ external/skia/src/ports \
+ external/skia/include/utils
-LOCAL_C_INCLUDES += \
- $(JNI_H_INCLUDE) \
- $(LOCAL_PATH)/../../include/utils \
- external/skia/include/core \
- external/skia/include/effects \
- external/skia/include/images \
- external/skia/src/ports \
- external/skia/include/utils
+ LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER
+ LOCAL_MODULE_CLASS := SHARED_LIBRARIES
+ LOCAL_SHARED_LIBRARIES := libcutils libutils libGLESv2 libskia
+ LOCAL_MODULE := libhwui
+ LOCAL_MODULE_TAGS := optional
+ LOCAL_PRELINK_MODULE := false
+
+ include $(BUILD_SHARED_LIBRARY)
-LOCAL_MODULE_CLASS := SHARED_LIBRARIES
-LOCAL_SHARED_LIBRARIES := libcutils libutils libGLESv2 libskia
-LOCAL_MODULE := libhwui
-LOCAL_PRELINK_MODULE := false
-
-include $(BUILD_SHARED_LIBRARY)
+ include $(call all-makefiles-under,$(LOCAL_PATH))
+endif
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index feb45ec..8d00e85 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -62,6 +62,27 @@
}
}
+void Font::measureCachedGlyph(CachedGlyphInfo *glyph, int x, int y, Rect *bounds) {
+ int nPenX = x + glyph->mBitmapLeft;
+ int nPenY = y + glyph->mBitmapTop;
+
+ int width = (int) glyph->mBitmapWidth;
+ int height = (int) glyph->mBitmapHeight;
+
+ if(bounds->bottom > nPenY) {
+ bounds->bottom = nPenY;
+ }
+ if(bounds->left > nPenX) {
+ bounds->left = nPenX;
+ }
+ if(bounds->right < nPenX + width) {
+ bounds->right = nPenX + width;
+ }
+ if(bounds->top < nPenY + height) {
+ bounds->top = nPenY + height;
+ }
+}
+
void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y) {
int nPenX = x + glyph->mBitmapLeft;
int nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight;
@@ -88,22 +109,17 @@
uint32_t endX = glyph->mStartX + glyph->mBitmapWidth;
uint32_t endY = glyph->mStartY + glyph->mBitmapHeight;
- if(nPenX < 0 || nPenY < 0) {
- LOGE("Cannot render into a bitmap, some of the glyph is below zero");
- return;
- }
-
- if(nPenX + glyph->mBitmapWidth >= bitmapW || nPenY + glyph->mBitmapHeight >= bitmapH) {
- LOGE("Cannot render into a bitmap, dimentions too small");
- return;
- }
-
uint32_t cacheWidth = mState->getCacheWidth();
const uint8_t* cacheBuffer = mState->getTextTextureData();
- uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
+ uint32_t cacheX = 0, cacheY = 0;
+ int32_t bX = 0, bY = 0;
for (cacheX = glyph->mStartX, bX = nPenX; cacheX < endX; cacheX++, bX++) {
for (cacheY = glyph->mStartY, bY = nPenY; cacheY < endY; cacheY++, bY++) {
+ if(bX < 0 || bY < 0 || bX >= (int32_t)bitmapW || bY >= (int32_t)bitmapH) {
+ LOGE("Skipping invalid index");
+ continue;
+ }
uint8_t tempCol = cacheBuffer[cacheY * cacheWidth + cacheX];
bitmap[bY * bitmapW + bX] = tempCol;
}
@@ -127,7 +143,33 @@
}
void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
- int numGlyphs, int x, int y, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) {
+ int numGlyphs, int x, int y,
+ uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) {
+ if(bitmap != NULL && bitmapW > 0 && bitmapH > 0) {
+ renderUTF(paint, text, start, len, numGlyphs, x, y, BITMAP,
+ bitmap, bitmapW, bitmapH, NULL);
+ }
+ else {
+ renderUTF(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER,
+ NULL, 0, 0, NULL);
+ }
+
+}
+
+void Font::measureUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+ int numGlyphs, Rect *bounds) {
+ if(bounds == NULL) {
+ LOGE("No return rectangle provided to measure text");
+ return;
+ }
+ bounds->set(1e6, -1e6, -1e6, 1e6);
+ renderUTF(paint, text, start, len, numGlyphs, 0, 0, MEASURE, NULL, 0, 0, bounds);
+}
+
+void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+ int numGlyphs, int x, int y, RenderMode mode,
+ uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
+ Rect *bounds) {
if (numGlyphs == 0 || text == NULL || len == 0) {
return;
}
@@ -152,11 +194,16 @@
// If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
if (cachedGlyph->mIsValid) {
- if(bitmap != NULL) {
- drawCachedGlyph(cachedGlyph, penX, penY, bitmap, bitmapW, bitmapH);
- }
- else {
+ switch(mode) {
+ case FRAMEBUFFER:
drawCachedGlyph(cachedGlyph, penX, penY);
+ break;
+ case BITMAP:
+ drawCachedGlyph(cachedGlyph, penX, penY, bitmap, bitmapW, bitmapH);
+ break;
+ case MEASURE:
+ measureCachedGlyph(cachedGlyph, penX, penY, bounds);
+ break;
}
}
@@ -245,6 +292,9 @@
mCurrentQuadIndex = 0;
mTextureId = 0;
+ mTextMeshPtr = NULL;
+ mTextTexture = NULL;
+
mIndexBufferID = 0;
mCacheWidth = DEFAULT_TEXT_CACHE_WIDTH;
@@ -272,10 +322,12 @@
}
mCacheLines.clear();
- delete[] mTextMeshPtr;
- delete[] mTextTexture;
+ if (mInitialized) {
+ delete[] mTextMeshPtr;
+ delete[] mTextTexture;
+ }
- if(mTextureId) {
+ if (mTextureId) {
glDeleteTextures(1, &mTextureId);
}
@@ -569,6 +621,32 @@
precacheLatin(paint);
}
}
+FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text,
+ uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius) {
+
+ Rect bounds;
+ mCurrentFont->measureUTF(paint, text, startIndex, len, numGlyphs, &bounds);
+ uint32_t paddedWidth = (uint32_t)(bounds.right - bounds.left) + 2*radius;
+ uint32_t paddedHeight = (uint32_t)(bounds.top - bounds.bottom) + 2*radius;
+ uint8_t* dataBuffer = new uint8_t[paddedWidth * paddedHeight];
+ for(uint32_t i = 0; i < paddedWidth * paddedHeight; i ++) {
+ dataBuffer[i] = 0;
+ }
+ int penX = radius - bounds.left;
+ int penY = radius - bounds.bottom;
+
+ mCurrentFont->renderUTF(paint, text, startIndex, len, numGlyphs, penX, penY,
+ dataBuffer, paddedWidth, paddedHeight);
+ blurImage(dataBuffer, paddedWidth, paddedHeight, radius);
+
+ DropShadow image;
+ image.width = paddedWidth;
+ image.height = paddedHeight;
+ image.image = dataBuffer;
+ image.penX = penX;
+ image.penY = penY;
+ return image;
+}
void FontRenderer::renderText(SkPaint* paint, const Rect* clip, const char *text,
uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y) {
@@ -598,11 +676,11 @@
// and sigma varies with radius.
// Based on some experimental radius values and sigma's
// we approximately fit sigma = f(radius) as
- // sigma = radius * 0.4 + 0.6
+ // sigma = radius * 0.3 + 0.6
// The larger the radius gets, the more our gaussian blur
// will resemble a box blur since with large sigma
// the gaussian curve begins to lose its shape
- float sigma = 0.4f * (float)radius + 0.6f;
+ float sigma = 0.3f * (float)radius + 0.6f;
// Now compute the coefficints
// We will store some redundant values to save some math during
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index 4cd902e..6346ded 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -45,6 +45,7 @@
/**
* Renders the specified string of text.
+ * If bitmap is specified, it will be used as the render target
*/
void renderUTF(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
int numGlyphs, int x, int y,
@@ -57,6 +58,20 @@
protected:
friend class FontRenderer;
+ enum RenderMode {
+ FRAMEBUFFER,
+ BITMAP,
+ MEASURE,
+ };
+
+ void renderUTF(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+ int numGlyphs, int x, int y, RenderMode mode,
+ uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
+ Rect *bounds);
+
+ void measureUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+ int numGlyphs, Rect *bounds);
+
struct CachedGlyphInfo {
// Has the cache been invalidated?
bool mIsValid;
@@ -89,6 +104,7 @@
CachedGlyphInfo* cacheGlyph(SkPaint* paint, int32_t glyph);
void updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo *glyph);
+ void measureCachedGlyph(CachedGlyphInfo *glyph, int x, int y, Rect *bounds);
void drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y);
void drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y,
uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH);
@@ -112,6 +128,19 @@
void renderText(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex,
uint32_t len, int numGlyphs, int x, int y);
+ struct DropShadow {
+ uint32_t width;
+ uint32_t height;
+ uint8_t* image;
+ int32_t penX;
+ int32_t penY;
+ };
+
+ // After renderDropShadow returns, the called owns the memory in DropShadow.image
+ // and is responsible for releasing it when it's done with it
+ DropShadow renderDropShadow(SkPaint* paint, const char *text, uint32_t startIndex,
+ uint32_t len, int numGlyphs, uint32_t radius);
+
GLuint getTexture() {
checkInit();
return mTextureId;
diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp
index aeda416..59fa0a7 100644
--- a/libs/hwui/GradientCache.cpp
+++ b/libs/hwui/GradientCache.cpp
@@ -80,8 +80,7 @@
///////////////////////////////////////////////////////////////////////////////
Texture* GradientCache::get(SkShader* shader) {
- Texture* texture = mCache.get(shader);
- return texture;
+ return mCache.get(shader);
}
void GradientCache::remove(SkShader* shader) {
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index a72045b..5d30b1a 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -436,6 +436,9 @@
}
const Texture* texture = mTextureCache.get(bitmap);
+ if (!texture) return;
+ const AutoTexture autoCleanup(texture);
+
drawTextureRect(left, top, right, bottom, texture, paint);
}
@@ -449,6 +452,9 @@
}
const Texture* texture = mTextureCache.get(bitmap);
+ if (!texture) return;
+ const AutoTexture autoCleanup(texture);
+
drawTextureRect(r.left, r.top, r.right, r.bottom, texture, paint);
}
@@ -461,6 +467,8 @@
}
const Texture* texture = mTextureCache.get(bitmap);
+ if (!texture) return;
+ const AutoTexture autoCleanup(texture);
const float width = texture->width;
const float height = texture->height;
@@ -484,6 +492,8 @@
}
const Texture* texture = mTextureCache.get(bitmap);
+ if (!texture) return;
+ const AutoTexture autoCleanup(texture);
int alpha;
SkXfermode::Mode mode;
@@ -610,7 +620,9 @@
GLuint textureUnit = 0;
glActiveTexture(gTextureUnits[textureUnit]);
- PathTexture* texture = mPathCache.get(path, paint);
+ const PathTexture* texture = mPathCache.get(path, paint);
+ if (!texture) return;
+ const AutoTexture autoCleanup(texture);
int alpha;
SkXfermode::Mode mode;
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 67a5f92..4a01ffa 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -34,6 +34,10 @@
mCache(GenerationCache<PathCacheEntry, PathTexture*>::kUnlimitedCapacity),
mSize(0), mMaxSize(maxByteSize) {
mCache.setOnEntryRemovedListener(this);
+
+ GLint maxTextureSize;
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
+ mMaxTextureSize = maxTextureSize;
}
PathCache::~PathCache() {
@@ -88,16 +92,24 @@
texture = addTexture(entry, path, paint);
}
- // TODO: Do something to destroy the texture object if it's too big for the cache
return texture;
}
PathTexture* PathCache::addTexture(const PathCacheEntry& entry,
const SkPath *path, const SkPaint* paint) {
const SkRect& bounds = path->getBounds();
+
+ const float pathWidth = bounds.width();
+ const float pathHeight = bounds.height();
+
+ if (pathWidth > mMaxTextureSize || pathHeight > mMaxTextureSize) {
+ LOGW("Path too large to be rendered into a texture");
+ return NULL;
+ }
+
const float offset = entry.strokeWidth * 1.5f;
- const uint32_t width = uint32_t(bounds.width() + offset * 2.0 + 0.5);
- const uint32_t height = uint32_t(bounds.height() + offset * 2.0 + 0.5);
+ const uint32_t width = uint32_t(pathWidth + offset * 2.0 + 0.5);
+ const uint32_t height = uint32_t(pathHeight + offset * 2.0 + 0.5);
const uint32_t size = width * height;
// Don't even try to cache a bitmap that's bigger than the cache
@@ -129,6 +141,8 @@
if (size < mMaxSize) {
mSize += size;
mCache.put(entry, texture);
+ } else {
+ texture->cleanup = true;
}
return texture;
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
index 87a9141..d09789f 100644
--- a/libs/hwui/PathCache.h
+++ b/libs/hwui/PathCache.h
@@ -71,6 +71,9 @@
* Alpha texture used to represent a path.
*/
struct PathTexture: public Texture {
+ PathTexture(): Texture() {
+ }
+
/**
* Left coordinate of the path bounds.
*/
@@ -136,6 +139,7 @@
uint32_t mSize;
uint32_t mMaxSize;
+ GLuint mMaxTextureSize;
}; // class PathCache
}; // namespace uirenderer
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index fe4b54d..42c0621 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -75,11 +75,13 @@
SkiaBitmapShader::SkiaBitmapShader(SkBitmap* bitmap, SkShader* key, SkShader::TileMode tileX,
SkShader::TileMode tileY, SkMatrix* matrix, bool blend):
- SkiaShader(kBitmap, key, tileX, tileY, matrix, blend), mBitmap(bitmap) {
+ SkiaShader(kBitmap, key, tileX, tileY, matrix, blend), mBitmap(bitmap), mTexture(NULL) {
}
void SkiaBitmapShader::describe(ProgramDescription& description, const Extensions& extensions) {
const Texture* texture = mTextureCache->get(mBitmap);
+ if (!texture) return;
+ mTexture = texture;
const float width = texture->width;
const float height = texture->height;
@@ -98,7 +100,11 @@
const Snapshot& snapshot, GLuint* textureUnit) {
GLuint textureSlot = (*textureUnit)++;
glActiveTexture(gTextureUnitsMap[textureSlot]);
- const Texture* texture = mTextureCache->get(mBitmap);
+
+ const Texture* texture = mTexture;
+ mTexture = NULL;
+ if (!texture) return;
+ const AutoTexture autoCleanup(texture);
const float width = texture->width;
const float height = texture->height;
@@ -133,9 +139,9 @@
}
SkiaLinearGradientShader::~SkiaLinearGradientShader() {
- delete mBounds;
- delete mColors;
- delete mPositions;
+ delete[] mBounds;
+ delete[] mColors;
+ delete[] mPositions;
}
void SkiaLinearGradientShader::describe(ProgramDescription& description,
diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h
index 58f2870..d95e3b0 100644
--- a/libs/hwui/SkiaShader.h
+++ b/libs/hwui/SkiaShader.h
@@ -116,6 +116,7 @@
}
SkBitmap* mBitmap;
+ const Texture* mTexture;
}; // struct SkiaBitmapShader
/**
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index d37013d..90f548b 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -26,6 +26,10 @@
* Represents an OpenGL texture.
*/
struct Texture {
+ Texture() {
+ cleanup = false;
+ }
+
/**
* Name of the texture.
*/
@@ -46,8 +50,26 @@
* Height of the backing bitmap.
*/
uint32_t height;
+ /**
+ * Indicates whether this texture should be cleaned up after use.
+ */
+ bool cleanup;
}; // struct Texture
+class AutoTexture {
+public:
+ AutoTexture(const Texture* texture): mTexture(texture) { }
+ ~AutoTexture() {
+ if (mTexture && mTexture->cleanup) {
+ glDeleteTextures(1, &mTexture->id);
+ delete mTexture;
+ }
+ }
+
+private:
+ const Texture* mTexture;
+}; // class AutoTexture
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index 4975edb..3f9698d 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -31,6 +31,9 @@
mCache(GenerationCache<SkBitmap*, Texture*>::kUnlimitedCapacity),
mSize(0), mMaxSize(maxByteSize) {
mCache.setOnEntryRemovedListener(this);
+
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
+ LOGD("Maximum texture dimension is %d pixels", mMaxTextureSize);
}
TextureCache::~TextureCache() {
@@ -79,6 +82,11 @@
Texture* TextureCache::get(SkBitmap* bitmap) {
Texture* texture = mCache.get(bitmap);
if (!texture) {
+ if (bitmap->width() > mMaxTextureSize || bitmap->height() > mMaxTextureSize) {
+ LOGW("Bitmap too large to be uploaded into a texture");
+ return NULL;
+ }
+
const uint32_t size = bitmap->rowBytes() * bitmap->height();
// Don't even try to cache a bitmap that's bigger than the cache
if (size < mMaxSize) {
@@ -93,11 +101,13 @@
if (size < mMaxSize) {
mSize += size;
mCache.put(bitmap, texture);
+ } else {
+ texture->cleanup = true;
}
} else if (bitmap->getGenerationID() != texture->generation) {
generateTexture(bitmap, texture, true);
}
- // TODO: Do something to destroy the texture object if it's too big for the cache
+
return texture;
}
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
index bed1191..452716c 100644
--- a/libs/hwui/TextureCache.h
+++ b/libs/hwui/TextureCache.h
@@ -82,6 +82,7 @@
uint32_t mSize;
uint32_t mMaxSize;
+ GLint mMaxTextureSize;
}; // class TextureCache
}; // namespace uirenderer
diff --git a/libs/rs/rsScriptC_Lib.cpp b/libs/rs/rsScriptC_Lib.cpp
index dc30afc..ac32810 100644
--- a/libs/rs/rsScriptC_Lib.cpp
+++ b/libs/rs/rsScriptC_Lib.cpp
@@ -267,18 +267,31 @@
static void SC_debugF(const char *s, float f) {
LOGE("%s %f, 0x%08x", s, f, *((int *) (&f)));
}
-static void SC_debugFv2(const char *s, rsvF_2 fv) {
- float *f = (float *)&fv;
- LOGE("%s {%f, %f}", s, f[0], f[1]);
+static void SC_debugFv2(const char *s, float f1, float f2) {
+ LOGE("%s {%f, %f}", s, f1, f2);
}
-static void SC_debugFv3(const char *s, rsvF_4 fv) {
- float *f = (float *)&fv;
- LOGE("%s {%f, %f, %f}", s, f[0], f[1], f[2]);
+static void SC_debugFv3(const char *s, float f1, float f2, float f3) {
+ LOGE("%s {%f, %f, %f}", s, f1, f2, f3);
}
-static void SC_debugFv4(const char *s, rsvF_4 fv) {
- float *f = (float *)&fv;
- LOGE("%s {%f, %f, %f, %f}", s, f[0], f[1], f[2], f[3]);
+static void SC_debugFv4(const char *s, float f1, float f2, float f3, float f4) {
+ LOGE("%s {%f, %f, %f, %f}", s, f1, f2, f3, f4);
}
+static void SC_debugFM4v4(const char *s, const float *f) {
+ LOGE("%s {%f, %f, %f, %f", s, f[0], f[4], f[8], f[12]);
+ LOGE("%s %f, %f, %f, %f", s, f[1], f[5], f[9], f[13]);
+ LOGE("%s %f, %f, %f, %f", s, f[2], f[6], f[10], f[14]);
+ LOGE("%s %f, %f, %f, %f}", s, f[3], f[7], f[11], f[15]);
+}
+static void SC_debugFM3v3(const char *s, const float *f) {
+ LOGE("%s {%f, %f, %f", s, f[0], f[3], f[6]);
+ LOGE("%s %f, %f, %f", s, f[1], f[4], f[7]);
+ LOGE("%s %f, %f, %f}",s, f[2], f[5], f[8]);
+}
+static void SC_debugFM2v2(const char *s, const float *f) {
+ LOGE("%s {%f, %f", s, f[0], f[2]);
+ LOGE("%s %f, %f}",s, f[1], f[3]);
+}
+
static void SC_debugI32(const char *s, int32_t i) {
LOGE("%s %i 0x%x", s, i, i);
}
@@ -394,9 +407,12 @@
// Debug
{ "_Z7rsDebugPKcf", (void *)&SC_debugF },
- { "_Z7rsDebugPKcDv2_f", (void *)&SC_debugFv2 },
- { "_Z7rsDebugPKcDv3_f", (void *)&SC_debugFv3 },
- { "_Z7rsDebugPKcDv4_f", (void *)&SC_debugFv4 },
+ { "_Z7rsDebugPKcff", (void *)&SC_debugFv2 },
+ { "_Z7rsDebugPKcfff", (void *)&SC_debugFv3 },
+ { "_Z7rsDebugPKcffff", (void *)&SC_debugFv4 },
+ { "_Z7rsDebugPKcPK12rs_matrix4x4", (void *)&SC_debugFM4v4 },
+ { "_Z7rsDebugPKcPK12rs_matrix3x3", (void *)&SC_debugFM3v3 },
+ { "_Z7rsDebugPKcPK12rs_matrix2x2", (void *)&SC_debugFM2v2 },
{ "_Z7rsDebugPKci", (void *)&SC_debugI32 },
{ "_Z7rsDebugPKcj", (void *)&SC_debugU32 },
{ "_Z7rsDebugPKcPKv", (void *)&SC_debugP },
diff --git a/libs/rs/scriptc/rs_core.rsh b/libs/rs/scriptc/rs_core.rsh
index 93e009a..85f3b25 100644
--- a/libs/rs/scriptc/rs_core.rsh
+++ b/libs/rs/scriptc/rs_core.rsh
@@ -1,6 +1,15 @@
#ifndef __RS_CORE_RSH__
#define __RS_CORE_RSH__
+static void __attribute__((overloadable)) rsDebug(const char *s, float2 v) {
+ rsDebug(s, v.x, v.y);
+}
+static void __attribute__((overloadable)) rsDebug(const char *s, float3 v) {
+ rsDebug(s, v.x, v.y, v.z);
+}
+static void __attribute__((overloadable)) rsDebug(const char *s, float4 v) {
+ rsDebug(s, v.x, v.y, v.z, v.w);
+}
static uchar4 __attribute__((overloadable)) rsPackColorTo8888(float r, float g, float b)
{
diff --git a/libs/rs/scriptc/rs_math.rsh b/libs/rs/scriptc/rs_math.rsh
index 66171d8..45f6bf4 100644
--- a/libs/rs/scriptc/rs_math.rsh
+++ b/libs/rs/scriptc/rs_math.rsh
@@ -1,6 +1,31 @@
#ifndef __RS_MATH_RSH__
#define __RS_MATH_RSH__
+// Debugging, print to the LOG a description string and a value.
+extern void __attribute__((overloadable))
+ rsDebug(const char *, float);
+extern void __attribute__((overloadable))
+ rsDebug(const char *, float, float);
+extern void __attribute__((overloadable))
+ rsDebug(const char *, float, float, float);
+extern void __attribute__((overloadable))
+ rsDebug(const char *, float, float, float, float);
+extern void __attribute__((overloadable))
+ rsDebug(const char *, const rs_matrix4x4 *);
+extern void __attribute__((overloadable))
+ rsDebug(const char *, const rs_matrix3x3 *);
+extern void __attribute__((overloadable))
+ rsDebug(const char *, const rs_matrix2x2 *);
+extern void __attribute__((overloadable))
+ rsDebug(const char *, int);
+extern void __attribute__((overloadable))
+ rsDebug(const char *, uint);
+extern void __attribute__((overloadable))
+ rsDebug(const char *, const void *);
+#define RS_DEBUG(a) rsDebug(#a, a)
+#define RS_DEBUG_MARKER rsDebug(__FILE__, __LINE__)
+
+
#include "rs_cl.rsh"
#include "rs_core.rsh"
@@ -31,25 +56,6 @@
extern const void * __attribute__((overloadable))
rsGetElementAt(rs_allocation, uint32_t x, uint32_t y, uint32_t z);
-
-// Debugging, print to the LOG a description string and a value.
-extern void __attribute__((overloadable))
- rsDebug(const char *, float);
-extern void __attribute__((overloadable))
- rsDebug(const char *, float2);
-extern void __attribute__((overloadable))
- rsDebug(const char *, float3);
-extern void __attribute__((overloadable))
- rsDebug(const char *, float4);
-extern void __attribute__((overloadable))
- rsDebug(const char *, int);
-extern void __attribute__((overloadable))
- rsDebug(const char *, uint);
-extern void __attribute__((overloadable))
- rsDebug(const char *, const void *);
-#define RS_DEBUG(a) rsDebug(#a, a)
-#define RS_DEBUG_MARKER rsDebug(__FILE__, __LINE__)
-
// Return a random value between 0 (or min_value) and max_malue.
extern int __attribute__((overloadable))
rsRand(int max_value);
diff --git a/libs/utils/tests/Android.mk b/libs/utils/tests/Android.mk
index b9f206a..725de9c 100644
--- a/libs/utils/tests/Android.mk
+++ b/libs/utils/tests/Android.mk
@@ -7,7 +7,8 @@
# Build the unit tests.
test_src_files := \
ObbFile_test.cpp \
- PollLoop_test.cpp
+ PollLoop_test.cpp \
+ String8_test.cpp
shared_libraries := \
libz \
@@ -41,4 +42,4 @@
$(eval include $(BUILD_EXECUTABLE)) \
)
-endif
\ No newline at end of file
+endif
diff --git a/libs/utils/tests/String8_test.cpp b/libs/utils/tests/String8_test.cpp
new file mode 100644
index 0000000..c42c68d
--- /dev/null
+++ b/libs/utils/tests/String8_test.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "String8_test"
+#include <utils/Log.h>
+#include <utils/String8.h>
+
+#include <gtest/gtest.h>
+
+namespace android {
+
+class String8Test : public testing::Test {
+protected:
+ virtual void SetUp() {
+ }
+
+ virtual void TearDown() {
+ }
+};
+
+TEST_F(String8Test, Cstr) {
+ String8 tmp("Hello, world!");
+
+ EXPECT_STREQ(tmp.string(), "Hello, world!");
+}
+
+TEST_F(String8Test, OperatorPlus) {
+ String8 src1("Hello, ");
+
+ // Test adding String8 + const char*
+ const char* ccsrc2 = "world!";
+ String8 dst1 = src1 + ccsrc2;
+ EXPECT_STREQ(dst1.string(), "Hello, world!");
+ EXPECT_STREQ(src1.string(), "Hello, ");
+ EXPECT_STREQ(ccsrc2, "world!");
+
+ // Test adding String8 + String8
+ String8 ssrc2("world!");
+ String8 dst2 = src1 + ssrc2;
+ EXPECT_STREQ(dst2.string(), "Hello, world!");
+ EXPECT_STREQ(src1.string(), "Hello, ");
+ EXPECT_STREQ(ssrc2.string(), "world!");
+}
+
+TEST_F(String8Test, OperatorPlusEquals) {
+ String8 src1("My voice");
+
+ // Testing String8 += String8
+ String8 src2(" is my passport.");
+ src1 += src2;
+ EXPECT_STREQ(src1.string(), "My voice is my passport.");
+ EXPECT_STREQ(src2.string(), " is my passport.");
+
+ // Adding const char* to the previous string.
+ const char* src3 = " Verify me.";
+ src1 += src3;
+ EXPECT_STREQ(src1.string(), "My voice is my passport. Verify me.");
+ EXPECT_STREQ(src2.string(), " is my passport.");
+ EXPECT_STREQ(src3, " Verify me.");
+}
+
+}
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index 805f86b..5756e53 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -38,8 +38,8 @@
#include <surfaceflinger/ISurface.h>
#include <utils/Errors.h>
#include <sys/types.h>
-#include <unistd.h>
#include <ctype.h>
+#include <unistd.h>
#include "ARTPWriter.h"
@@ -906,7 +906,7 @@
}
status_t StagefrightRecorder::setupCameraSource() {
- if(!mCaptureTimeLapse) {
+ if (!mCaptureTimeLapse) {
// Dont clip for time lapse capture as encoder will have enough
// time to encode because of slow capture rate of time lapse.
clipVideoBitRate();
@@ -929,9 +929,10 @@
// Set the actual video recording frame size
CameraParameters params(mCamera->getParameters());
- // dont change the preview size for time lapse as mVideoWidth, mVideoHeight
- // may correspond to HD resolution not supported by video camera.
- if (!mCaptureTimeLapse) {
+ // dont change the preview size when using still camera for time lapse
+ // as mVideoWidth, mVideoHeight may correspond to HD resolution not
+ // supported by the video camera.
+ if (!(mCaptureTimeLapse && mUseStillCameraForTimeLapse)) {
params.setPreviewSize(mVideoWidth, mVideoHeight);
}
@@ -947,7 +948,7 @@
// Check on video frame size
int frameWidth = 0, frameHeight = 0;
newCameraParams.getPreviewSize(&frameWidth, &frameHeight);
- if (!mCaptureTimeLapse &&
+ if (!(mCaptureTimeLapse && mUseStillCameraForTimeLapse) &&
(frameWidth < 0 || frameWidth != mVideoWidth ||
frameHeight < 0 || frameHeight != mVideoHeight)) {
LOGE("Failed to set the video frame size to %dx%d",
diff --git a/media/libstagefright/AMRExtractor.cpp b/media/libstagefright/AMRExtractor.cpp
index 9fc1d27..70af2da 100644
--- a/media/libstagefright/AMRExtractor.cpp
+++ b/media/libstagefright/AMRExtractor.cpp
@@ -263,6 +263,7 @@
buffer->set_range(0, frameSize);
buffer->meta_data()->setInt64(kKeyTime, mCurrentTimeUs);
+ buffer->meta_data()->setInt32(kKeyIsSyncFrame, 1);
mOffset += frameSize;
mCurrentTimeUs += 20000; // Each frame is 20ms
diff --git a/media/libstagefright/MP3Extractor.cpp b/media/libstagefright/MP3Extractor.cpp
index 2248e23..4058fbc 100644
--- a/media/libstagefright/MP3Extractor.cpp
+++ b/media/libstagefright/MP3Extractor.cpp
@@ -683,6 +683,7 @@
buffer->set_range(0, frame_size);
buffer->meta_data()->setInt64(kKeyTime, mCurrentTimeUs);
+ buffer->meta_data()->setInt32(kKeyIsSyncFrame, 1);
mCurrentPos += frame_size;
mCurrentTimeUs += frame_size * 8000ll / bitrate;
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index 7cea629..6af3a7f 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -492,7 +492,6 @@
case FOURCC('m', 'o', 'o', 'f'):
case FOURCC('t', 'r', 'a', 'f'):
case FOURCC('m', 'f', 'r', 'a'):
- case FOURCC('s', 'k', 'i' ,'p'):
case FOURCC('u', 'd', 't', 'a'):
case FOURCC('i', 'l', 's', 't'):
{
@@ -1552,13 +1551,14 @@
off_t offset;
size_t size;
uint32_t dts;
+ bool isSyncSample;
bool newBuffer = false;
if (mBuffer == NULL) {
newBuffer = true;
status_t err =
mSampleTable->getMetaDataForSample(
- mCurrentSampleIndex, &offset, &size, &dts);
+ mCurrentSampleIndex, &offset, &size, &dts, &isSyncSample);
if (err != OK) {
return err;
@@ -1595,6 +1595,10 @@
kKeyTargetTime, targetSampleTimeUs);
}
+ if (isSyncSample) {
+ mBuffer->meta_data()->setInt32(kKeyIsSyncFrame, 1);
+ }
+
++mCurrentSampleIndex;
}
@@ -1697,6 +1701,10 @@
kKeyTargetTime, targetSampleTimeUs);
}
+ if (isSyncSample) {
+ mBuffer->meta_data()->setInt32(kKeyIsSyncFrame, 1);
+ }
+
++mCurrentSampleIndex;
*out = mBuffer;
diff --git a/media/libstagefright/OggExtractor.cpp b/media/libstagefright/OggExtractor.cpp
index 641a876..b699d8f 100644
--- a/media/libstagefright/OggExtractor.cpp
+++ b/media/libstagefright/OggExtractor.cpp
@@ -181,6 +181,8 @@
}
#endif
+ packet->meta_data()->setInt32(kKeyIsSyncFrame, 1);
+
*out = packet;
return OK;
diff --git a/media/libstagefright/SampleTable.cpp b/media/libstagefright/SampleTable.cpp
index 2e62f9f..27faf4f 100644
--- a/media/libstagefright/SampleTable.cpp
+++ b/media/libstagefright/SampleTable.cpp
@@ -55,6 +55,8 @@
mTimeToSample(NULL),
mSyncSampleOffset(-1),
mNumSyncSamples(0),
+ mSyncSamples(NULL),
+ mLastSyncSampleIndex(0),
mSampleToChunkEntries(NULL) {
mSampleIterator = new SampleIterator(this);
}
@@ -63,6 +65,9 @@
delete[] mSampleToChunkEntries;
mSampleToChunkEntries = NULL;
+ delete[] mSyncSamples;
+ mSyncSamples = NULL;
+
delete[] mTimeToSample;
mTimeToSample = NULL;
@@ -278,6 +283,18 @@
if (mNumSyncSamples < 2) {
LOGW("Table of sync samples is empty or has only a single entry!");
}
+
+ mSyncSamples = new uint32_t[mNumSyncSamples];
+ size_t size = mNumSyncSamples * sizeof(uint32_t);
+ if (mDataSource->readAt(mSyncSampleOffset + 8, mSyncSamples, size)
+ != (ssize_t)size) {
+ return ERROR_IO;
+ }
+
+ for (size_t i = 0; i < mNumSyncSamples; ++i) {
+ mSyncSamples[i] = ntohl(mSyncSamples[i]) - 1;
+ }
+
return OK;
}
@@ -394,14 +411,7 @@
uint32_t left = 0;
while (left < mNumSyncSamples) {
- uint32_t x;
- if (mDataSource->readAt(
- mSyncSampleOffset + 8 + left * 4, &x, 4) != 4) {
- return ERROR_IO;
- }
-
- x = ntohl(x);
- --x;
+ uint32_t x = mSyncSamples[left];
if (x >= start_sample_index) {
break;
@@ -421,14 +431,7 @@
--x;
if (left + 1 < mNumSyncSamples) {
- uint32_t y;
- if (mDataSource->readAt(
- mSyncSampleOffset + 8 + (left + 1) * 4, &y, 4) != 4) {
- return ERROR_IO;
- }
-
- y = ntohl(y);
- --y;
+ uint32_t y = mSyncSamples[left + 1];
// our sample lies between sync samples x and y.
@@ -486,13 +489,7 @@
return ERROR_OUT_OF_RANGE;
}
- if (mDataSource->readAt(
- mSyncSampleOffset + 8 + (left + 1) * 4, &x, 4) != 4) {
- return ERROR_IO;
- }
-
- x = ntohl(x);
- --x;
+ x = mSyncSamples[left + 1];
CHECK(x >= start_sample_index);
}
@@ -532,13 +529,7 @@
}
for (size_t i = 0; i < numSamplesToScan; ++i) {
- uint32_t x;
- if (mDataSource->readAt(
- mSyncSampleOffset + 8 + i * 4, &x, 4) != 4) {
- return ERROR_IO;
- }
- x = ntohl(x);
- --x;
+ uint32_t x = mSyncSamples[i];
// Now x is a sample index.
size_t sampleSize;
@@ -568,7 +559,8 @@
uint32_t sampleIndex,
off_t *offset,
size_t *size,
- uint32_t *decodingTime) {
+ uint32_t *decodingTime,
+ bool *isSyncSample) {
Mutex::Autolock autoLock(mLock);
status_t err;
@@ -588,6 +580,28 @@
*decodingTime = mSampleIterator->getSampleTime();
}
+ if (isSyncSample) {
+ *isSyncSample = false;
+ if (mSyncSampleOffset < 0) {
+ // Every sample is a sync sample.
+ *isSyncSample = true;
+ } else {
+ size_t i = (mLastSyncSampleIndex < mNumSyncSamples)
+ && (mSyncSamples[mLastSyncSampleIndex] <= sampleIndex)
+ ? mLastSyncSampleIndex : 0;
+
+ while (i < mNumSyncSamples && mSyncSamples[i] < sampleIndex) {
+ ++i;
+ }
+
+ if (i < mNumSyncSamples && mSyncSamples[i] == sampleIndex) {
+ *isSyncSample = true;
+ }
+
+ mLastSyncSampleIndex = i;
+ }
+ }
+
return OK;
}
diff --git a/media/libstagefright/WAVExtractor.cpp b/media/libstagefright/WAVExtractor.cpp
index 7c2b07e..39b1b96 100644
--- a/media/libstagefright/WAVExtractor.cpp
+++ b/media/libstagefright/WAVExtractor.cpp
@@ -358,6 +358,7 @@
1000000LL * (mCurrentPos - mOffset)
/ (mNumChannels * bytesPerSample) / mSampleRate);
+ buffer->meta_data()->setInt32(kKeyIsSyncFrame, 1);
*out = buffer;
diff --git a/media/libstagefright/include/SampleTable.h b/media/libstagefright/include/SampleTable.h
index a2b2c99..f830690 100644
--- a/media/libstagefright/include/SampleTable.h
+++ b/media/libstagefright/include/SampleTable.h
@@ -60,7 +60,8 @@
uint32_t sampleIndex,
off_t *offset,
size_t *size,
- uint32_t *decodingTime);
+ uint32_t *decodingTime,
+ bool *isSyncSample = NULL);
enum {
kFlagBefore,
@@ -105,6 +106,8 @@
off_t mSyncSampleOffset;
uint32_t mNumSyncSamples;
+ uint32_t *mSyncSamples;
+ size_t mLastSyncSampleIndex;
SampleIterator *mSampleIterator;
diff --git a/media/libstagefright/matroska/MatroskaExtractor.cpp b/media/libstagefright/matroska/MatroskaExtractor.cpp
index 3739be1..71f6587 100644
--- a/media/libstagefright/matroska/MatroskaExtractor.cpp
+++ b/media/libstagefright/matroska/MatroskaExtractor.cpp
@@ -296,6 +296,7 @@
MediaBuffer *buffer = new MediaBuffer(size + 2);
buffer->meta_data()->setInt64(kKeyTime, timeUs);
+ buffer->meta_data()->setInt32(kKeyIsSyncFrame, block->IsKey());
long res = block->Read(
mExtractor->mReader, (unsigned char *)buffer->data() + 2);
diff --git a/packages/SystemUI/res/layout/status_bar_latest_event.xml b/packages/SystemUI/res/layout/status_bar_latest_event.xml
new file mode 100644
index 0000000..88d9739
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_latest_event.xml
@@ -0,0 +1,24 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="65sp"
+ android:orientation="vertical"
+ >
+
+ <com.android.systemui.statusbar.LatestItemView android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="64sp"
+ android:background="@android:drawable/status_bar_item_background"
+ android:focusable="true"
+ android:clickable="true"
+ android:paddingRight="6sp"
+ >
+ </com.android.systemui.statusbar.LatestItemView>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1sp"
+ android:background="@android:drawable/divider_horizontal_bright"
+ />
+
+</LinearLayout>
+
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 2299852..73fa93c 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -2412,8 +2412,17 @@
}
} else {
mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
- if (mActionBar != null && mActionBar.getTitle() == null) {
- mActionBar.setWindowTitle(mTitle);
+ if (mActionBar != null) {
+ if (mActionBar.getTitle() == null) {
+ mActionBar.setWindowTitle(mTitle);
+ }
+ // Post the panel invalidate for later; avoid application onCreateOptionsMenu
+ // being called in the middle of onCreate or similar.
+ mDecor.post(new Runnable() {
+ public void run() {
+ invalidatePanelMenu(FEATURE_ACTION_BAR);
+ }
+ });
}
}
}
diff --git a/services/java/com/android/server/ViewServer.java b/services/java/com/android/server/ViewServer.java
index b369f71..7b5d18a 100644
--- a/services/java/com/android/server/ViewServer.java
+++ b/services/java/com/android/server/ViewServer.java
@@ -60,6 +60,8 @@
private static final String COMMAND_WINDOW_MANAGER_LIST = "LIST";
// Keeps a connection open and notifies when the list of windows changes
private static final String COMMAND_WINDOW_MANAGER_AUTOLIST = "AUTOLIST";
+ // Returns the focused window
+ private static final String COMMAND_WINDOW_MANAGER_GET_FOCUS = "GET_FOCUS";
private ServerSocket mServer;
private Thread mThread;
@@ -250,6 +252,8 @@
result = writeValue(mClient, VALUE_SERVER_VERSION);
} else if (COMMAND_WINDOW_MANAGER_LIST.equalsIgnoreCase(command)) {
result = mWindowManager.viewServerListWindows(mClient);
+ } else if (COMMAND_WINDOW_MANAGER_GET_FOCUS.equalsIgnoreCase(command)) {
+ result = mWindowManager.viewServerGetFocusedWindow(mClient);
} else if(COMMAND_WINDOW_MANAGER_AUTOLIST.equalsIgnoreCase(command)) {
result = windowManagerAutolistLoop();
} else {
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index c4de958..b1e8968 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -4720,6 +4720,51 @@
}
/**
+ * Returns the focused window in the following format:
+ * windowHashCodeInHexadecimal windowName
+ *
+ * @param client The remote client to send the listing to.
+ * @return False if an error occurred, true otherwise.
+ */
+ boolean viewServerGetFocusedWindow(Socket client) {
+ if (isSystemSecure()) {
+ return false;
+ }
+
+ boolean result = true;
+
+ WindowState focusedWindow = getFocusedWindow();
+
+ BufferedWriter out = null;
+
+ // Any uncaught exception will crash the system process
+ try {
+ OutputStream clientStream = client.getOutputStream();
+ out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);
+
+ if(focusedWindow != null) {
+ out.write(Integer.toHexString(System.identityHashCode(focusedWindow)));
+ out.write(' ');
+ out.append(focusedWindow.mAttrs.getTitle());
+ }
+ out.write('\n');
+ out.flush();
+ } catch (Exception e) {
+ result = false;
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ result = false;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
* Sends a command to a target window. The result of the command, if any, will be
* written in the output stream of the specified socket.
*
diff --git a/telephony/java/com/android/internal/telephony/MccTable.java b/telephony/java/com/android/internal/telephony/MccTable.java
index 5dd29af..9b0aa3c 100644
--- a/telephony/java/com/android/internal/telephony/MccTable.java
+++ b/telephony/java/com/android/internal/telephony/MccTable.java
@@ -416,7 +416,7 @@
table.add(new MccEntry(438,"tm",2)); //Turkmenistan
table.add(new MccEntry(440,"jp",2,"ja",14)); //Japan
table.add(new MccEntry(441,"jp",2,"ja",14)); //Japan
- table.add(new MccEntry(450,"kr",2)); //Korea (Republic of)
+ table.add(new MccEntry(450,"kr",2,"ko",13)); //Korea (Republic of)
table.add(new MccEntry(452,"vn",2)); //Viet Nam (Socialist Republic of)
table.add(new MccEntry(454,"hk",2)); //"Hong Kong, China"
table.add(new MccEntry(455,"mo",2)); //"Macao, China"
diff --git a/test-runner/src/android/test/mock/MockContentProvider.java b/test-runner/src/android/test/mock/MockContentProvider.java
index 3fd71c8..b63ff3d 100644
--- a/test-runner/src/android/test/mock/MockContentProvider.java
+++ b/test-runner/src/android/test/mock/MockContentProvider.java
@@ -127,6 +127,16 @@
throw new UnsupportedOperationException();
}
+ @SuppressWarnings("unused")
+ public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException {
+ return MockContentProvider.this.getStreamTypes(url, mimeTypeFilter);
+ }
+
+ @SuppressWarnings("unused")
+ public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
+ throws RemoteException, FileNotFoundException {
+ return MockContentProvider.this.openTypedAssetFile(url, mimeType, opts);
+ }
}
private final InversionIContentProvider mIContentProvider = new InversionIContentProvider();
@@ -222,6 +232,14 @@
throw new UnsupportedOperationException("unimplemented mock method call");
}
+ public String[] getStreamTypes(Uri url, String mimeTypeFilter) {
+ throw new UnsupportedOperationException("unimplemented mock method call");
+ }
+
+ public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts) {
+ throw new UnsupportedOperationException("unimplemented mock method call");
+ }
+
/**
* Returns IContentProvider which calls back same methods in this class.
* By overriding this class, we avoid the mechanism hidden behind ContentProvider
diff --git a/test-runner/src/android/test/mock/MockIContentProvider.java b/test-runner/src/android/test/mock/MockIContentProvider.java
index 0be5bea..183be41 100644
--- a/test-runner/src/android/test/mock/MockIContentProvider.java
+++ b/test-runner/src/android/test/mock/MockIContentProvider.java
@@ -32,6 +32,7 @@
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import java.io.FileNotFoundException;
import java.util.ArrayList;
/**
@@ -102,4 +103,13 @@
public IBinder asBinder() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+
+ public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
+ throws RemoteException, FileNotFoundException {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas.java b/tools/layoutlib/bridge/src/android/graphics/Canvas.java
index d5d315e..1e1aba9 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas.java
@@ -69,11 +69,6 @@
throw new UnsupportedOperationException("Can't create Canvas(int)");
}
- public Canvas(javax.microedition.khronos.opengles.GL gl) {
- mLogger = null;
- throw new UnsupportedOperationException("Can't create Canvas(javax.microedition.khronos.opengles.GL)");
- }
-
// custom constructors for our use.
public Canvas(int width, int height, ILayoutLog logger) {
mLogger = logger;
@@ -1174,15 +1169,6 @@
}
/* (non-Javadoc)
- * @see android.graphics.Canvas#getGL()
- */
- @Override
- public GL getGL() {
- // TODO Auto-generated method stub
- return super.getGL();
- }
-
- /* (non-Javadoc)
* @see android.graphics.Canvas#getMatrix()
*/
@Override
@@ -1257,15 +1243,6 @@
}
/* (non-Javadoc)
- * @see android.graphics.Canvas#setViewport(int, int)
- */
- @Override
- public void setViewport(int width, int height) {
- // TODO Auto-generated method stub
- super.setViewport(width, height);
- }
-
- /* (non-Javadoc)
* @see android.graphics.Canvas#skew(float, float)
*/
@Override