diff options
82 files changed, 5332 insertions, 708 deletions
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/libreverb_inte $(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 @@ -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 dumpstate() { } 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 main(int argc, char *argv[]) { 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 @@ int main(int argc, char *argv[]) { 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 @@ int main(int argc, char *argv[]) { 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 @@ void for_each_pid(void (*func)(int, const char *), const char *header); /* 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..f92acbb 100644 --- a/cmds/dumpstate/utils.c +++ b/cmds/dumpstate/utils.c @@ -429,3 +429,7 @@ const char *dump_vm_traces() { 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 @@ import com.google.android.collect.Maps; /** * 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 @@ import com.google.android.collect.Maps; * {@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 @@ import com.google.android.collect.Maps; * 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 @@ import com.google.android.collect.Maps; * 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 class AccountManager { 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 @@ public class AccountManager { * @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 @@ public class AccountManager { * 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 @@ public class AccountManager { * <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 @@ public class AccountManager { * 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 @@ public class AccountManager { * 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 @@ public class AccountManager { * <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 @@ public class AccountManager { * * 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 @@ public class AccountManager { * * 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 @@ public class AccountManager { * * 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 @@ public class AccountManager { * 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 @@ public class AccountManager { * <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 @@ public class Activity extends ContextThemeWrapper 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 @@ import java.util.ArrayList; * 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 class ClipboardManager extends android.text.ClipboardManager { 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 @@ public class ClipboardManager extends android.text.ClipboardManager { } /** - * 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 @@ import java.util.ArrayList; * 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> - * - * <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> + * <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. + * + * <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 @@ public class ClippedData implements Parcelable { 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.IBulkCursor; 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 android.util.Log; import java.io.File; import java.io.FileNotFoundException; +import java.io.IOException; import java.util.ArrayList; /** @@ -251,6 +253,18 @@ public abstract class ContentProvider implements ComponentCallbacks { 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 @@ public abstract class ContentProvider implements ComponentCallbacks { } /** + * 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 @@ public abstract class ContentProvider implements ComponentCallbacks { * @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 @@ package android.content; 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 @@ public class ContentProviderClient { 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 @@ abstract public class ContentProviderNative extends Binder implements IContentPr 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 @@ final class ContentProviderProxy implements IContentProvider 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 @@ public abstract class ContentResolver { * 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 @@ public abstract class ContentResolver { } /** + * 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 @@ public abstract class ContentResolver { } /** - * 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 @@ public abstract class ContentResolver { } /** - * 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 @@ public abstract class ContentResolver { * </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 @@ public abstract class ContentResolver { 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) { - releaseProvider(provider); - throw new FileNotFoundException("Dead content provider: " + uri); - } catch (FileNotFoundException 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 e; - } catch (RuntimeException 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; } } } 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 @@ public interface IContentProvider extends IInterface { throws RemoteException, FileNotFoundException; public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) throws RemoteException, OperationApplicationException; + /** * @hide -- until interface has proven itself * @@ -71,6 +72,11 @@ public interface IContentProvider extends IInterface { */ 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 @@ public interface IContentProvider extends IInterface { 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 @@ public class Intent implements Parcelable, Cloneable { @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 @@ public class AssetFileDescriptor implements Parcelable { /** * 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 class DownloadManager { */ 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 @@ public class DownloadManager { 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 @@ public class DownloadManager { } /** - * 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 @@ public class DownloadManager { 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 @@ public abstract class AsyncTask<Params, Progress, Result> { 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 class FileUtils 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 @@ public class ParcelFileDescriptor implements Parcelable { 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 @@ public final class Downloads { "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 @@ import javax.microedition.khronos.opengles.GL; /** * 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 @@ class GLES20Canvas extends Canvas { 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 @@ class GLES20Canvas extends Canvas { } @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.EGLContext; 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 @@ abstract class HardwareRenderer { */ 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 @@ abstract class HardwareRenderer { @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 @@ public abstract class WindowOrientationListener { 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 @@ public abstract class WindowOrientationListener { } 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 @@ public abstract class WindowOrientationListener { 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 @@ public abstract class WindowOrientationListener { // 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); + + 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 - // The low-pass filtered accelerometer data - private float[] mFilteredVector = new float[] {0, 0, 0}; + /* + * 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 @@ public abstract class WindowOrientationListener { 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 @@ public abstract class WindowOrientationListener { } /** - * 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 @@ public abstract class WindowOrientationListener { 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); + handleAccelerationDistrust(deviation); + + // only filter tilt when we're accelerating + float alpha = 1; + if (mAccelerationDistrust > 0) { + alpha = ACCELERATING_LOWPASS_ALPHA; + } + 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 (tiltAngle > MAX_TILT) { - return; - } else if (deviation > MAX_DEVIATION_FROM_GRAVITY) { + if (mTiltDistrust > 0 || mAccelerationDistrust > 1) { + // when fully tilted, or under more than a transient acceleration, distrust heavily alpha = ACCELERATING_LOWPASS_ALPHA; - } else if (tiltAngle > PARTIAL_TILT) { + } 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; + } + 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; } - calculateNewRotation(orientation, Math.round(tiltAngle)); } 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.app.AlertDialog; 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.net.http.SslCertificate; 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.webkit.WebViewCore.TouchHighlightData; 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.List; 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 @@ import junit.framework.Assert; * 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 @@ public class WebView extends AbsoluteLayout /* * 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 @@ public class WebView extends AbsoluteLayout 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 @@ public class WebView extends AbsoluteLayout 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 @@ final class WebViewCore { + 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 com.android.internal.widget.EditableInputConnection; 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.Rect; 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.Parcelable; 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener getSelectionStart() >= 0 && getSelectionEnd() >= 0 && ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)). - hasText()); + hasPrimaryClip()); } /** @@ -7270,7 +7272,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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.content.res.TypedArray; 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 @@ public class ZygoteInit { 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..d1a5ae1 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -19,6 +19,10 @@ ifneq ($(USE_CUSTOM_RUNTIME_HEAP_MAX),) 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 @@ LOCAL_SRC_FILES:= \ 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 @@ LOCAL_SHARED_LIBRARIES := \ libbinder \ libnetutils \ libui \ - libhwui \ libgui \ libsurfaceflinger_client \ libcamera_client \ @@ -193,6 +195,10 @@ LOCAL_SHARED_LIBRARIES := \ 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_android_graphics_PixelFormat(JNIEnv* env); 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 @@ static const RegJNIRec gRegJNI[] = { 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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)); +#else + return NULL; +#endif } - 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) { +#ifdef USE_OPENGL_RENDERER 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* glCreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) { +#ifdef USE_OPENGL_RENDERER AutoJavaFloatArray autoArray(env, jarray, 20); const float* src = autoArray.ptr(); @@ -86,6 +71,33 @@ public: 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 @@ static void Shader_setLocalMatrix(JNIEnv* env, jobject o, SkShader* shader, Skia else { shader->setLocalMatrix(*matrix); } +#ifdef USE_OPENGL_RENDERER skiaShader->setMatrix(const_cast<SkMatrix*>(matrix)); +#endif } } @@ -90,10 +92,14 @@ static SkShader* BitmapShader_constructor(JNIEnv* env, jobject o, const SkBitmap 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 SkShader* LinearGradient_create1(JNIEnv* env, jobject o, 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 @@ static SkiaShader* LinearGradient_postCreate1(JNIEnv* env, jobject o, SkShader* 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 @@ static SkiaShader* LinearGradient_postCreate2(JNIEnv* env, jobject o, SkShader* (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 SkShader* ComposeShader_create2(JNIEnv* env, jobject o, 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 @@ static SkiaShader* ComposeShader_postCreate2(JNIEnv* env, jobject o, SkShader* s 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 @@ jint android_os_FileUtils_getPermissions(JNIEnv* env, jobject clazz, #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 @@ jboolean android_os_FileUtils_getFileStatus(JNIEnv* env, jobject clazz, jstring 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 @@ static jobject android_os_ParcelFileDescriptor_getFileDescriptorFromSocket(JNIEn 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 jlong android_os_ParcelFileDescriptor_seekTo(JNIEnv* env, 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 @@ namespace android { 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 @@ static void android_view_GLES20Canvas_drawText(JNIEnv* env, jobject canvas, 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 @@ static void android_view_GLES20Canvas_drawText(JNIEnv* env, jobject canvas, 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 @@ static JNINativeMethod gMethods[] = { { "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 @@ public class Canvas { 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 @@ public class Canvas { 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 @@ public class Canvas { * false otherwise. */ public boolean isHardwareAccelerated() { - return mGL != null; - } - - /** - * 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(); + return false; } - + /** * 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 @@ public class Canvas { 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 @@ public class Canvas { * 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 @@ public class Canvas { @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 @@ package android.graphics; 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 @@ public class ComposeShader extends Shader { 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 @@ public class ComposeShader extends Shader { @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 class RoundRectShape extends RectShape { */ 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 @@ public class RoundRectShape extends RectShape { 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 @@ public class RoundRectShape extends RectShape { @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) 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) 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::invalidateTextureCache() { } } +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 @@ void Font::drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y, 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 @@ Font::CachedGlyphInfo* Font::getCachedUTFChar(SkPaint* paint, int32_t utfChar) { } 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 @@ void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t // 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 @@ FontRenderer::FontRenderer() { mCurrentQuadIndex = 0; mTextureId = 0; + mTextMeshPtr = NULL; + mTextTexture = NULL; + mIndexBufferID = 0; mCacheWidth = DEFAULT_TEXT_CACHE_WIDTH; @@ -272,10 +322,12 @@ FontRenderer::~FontRenderer() { } mCacheLines.clear(); - delete[] mTextMeshPtr; - delete[] mTextTexture; + if (mInitialized) { + delete[] mTextMeshPtr; + delete[] mTextTexture; + } - if(mTextureId) { + if (mTextureId) { glDeleteTextures(1, &mTextureId); } @@ -569,6 +621,32 @@ void FontRenderer::setFont(SkPaint* paint, uint32_t fontId, float fontSize) { 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 @@ void FontRenderer::computeGaussianWeights(float* weights, int32_t radius) { // 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 @@ public: /** * 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 @@ public: 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 @@ protected: 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 @@ public: 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 @@ void GradientCache::operator()(SkShader*& shader, Texture*& texture) { /////////////////////////////////////////////////////////////////////////////// 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 @@ void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, const S } const Texture* texture = mTextureCache.get(bitmap); + if (!texture) return; + const AutoTexture autoCleanup(texture); + drawTextureRect(left, top, right, bottom, texture, paint); } @@ -449,6 +452,9 @@ void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, const SkMatrix* matrix, const } 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 @@ void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, } 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 @@ void OpenGLRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, } const Texture* texture = mTextureCache.get(bitmap); + if (!texture) return; + const AutoTexture autoCleanup(texture); int alpha; SkXfermode::Mode mode; @@ -610,7 +620,9 @@ void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) { 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 @@ PathCache::PathCache(uint32_t maxByteSize): 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 @@ PathTexture* PathCache::get(SkPath* path, SkPaint* paint) { 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 @@ PathTexture* PathCache::addTexture(const PathCacheEntry& entry, 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 @@ struct PathCacheEntry { * Alpha texture used to represent a path. */ struct PathTexture: public Texture { + PathTexture(): Texture() { + } + /** * Left coordinate of the path bounds. */ @@ -136,6 +139,7 @@ private: 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 @@ void SkiaShader::bindTexture(GLuint texture, GLenum wrapS, GLenum wrapT, GLuint 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 @@ void SkiaBitmapShader::setupProgram(Program* program, const mat4& modelView, 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(float* bounds, uint32_t* colo } 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 @@ private: } 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 @@ namespace uirenderer { * Represents an OpenGL texture. */ struct Texture { + Texture() { + cleanup = false; + } + /** * Name of the texture. */ @@ -46,8 +50,26 @@ struct Texture { * 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 @@ TextureCache::TextureCache(uint32_t maxByteSize): 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 @@ void TextureCache::operator()(SkBitmap*& bitmap, Texture*& texture) { 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 @@ Texture* TextureCache::get(SkBitmap* bitmap) { 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 @@ private: 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 @@ const void * SC_getElementAtXYZ(RsAllocation va, uint32_t x, uint32_t y, uint32_ 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 @@ static ScriptCState::SymbolTable_t gSyms[] = { // 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)) 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 @@ ifneq ($(TARGET_SIMULATOR),true) # 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 @@ $(foreach file,$(test_src_files), \ $(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 @@ void StagefrightRecorder::clipVideoFrameWidth() { } 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 @@ status_t StagefrightRecorder::setupCameraSource() { // 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 @@ status_t StagefrightRecorder::setupCameraSource() { // 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 @@ status_t AMRSource::read( 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 @@ status_t MP3Source::read( 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 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { 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 @@ status_t MPEG4Source::read( 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 @@ status_t MPEG4Source::read( kKeyTargetTime, targetSampleTimeUs); } + if (isSyncSample) { + mBuffer->meta_data()->setInt32(kKeyIsSyncFrame, 1); + } + ++mCurrentSampleIndex; } @@ -1697,6 +1701,10 @@ status_t MPEG4Source::read( 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 @@ status_t OggSource::read( } #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 @@ SampleTable::SampleTable(const sp<DataSource> &source) mTimeToSample(NULL), mSyncSampleOffset(-1), mNumSyncSamples(0), + mSyncSamples(NULL), + mLastSyncSampleIndex(0), mSampleToChunkEntries(NULL) { mSampleIterator = new SampleIterator(this); } @@ -63,6 +65,9 @@ SampleTable::~SampleTable() { delete[] mSampleToChunkEntries; mSampleToChunkEntries = NULL; + delete[] mSyncSamples; + mSyncSamples = NULL; + delete[] mTimeToSample; mTimeToSample = NULL; @@ -278,6 +283,18 @@ status_t SampleTable::setSyncSampleParams(off_t data_offset, size_t data_size) { 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 @@ status_t SampleTable::findSyncSampleNear( 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 @@ status_t SampleTable::findSyncSampleNear( --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 @@ status_t SampleTable::findSyncSampleNear( 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 @@ status_t SampleTable::findThumbnailSample(uint32_t *sample_index) { } 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 @@ status_t SampleTable::getMetaDataForSample( 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 @@ status_t SampleTable::getMetaDataForSample( *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 @@ status_t WAVSource::read( 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 @@ public: uint32_t sampleIndex, off_t *offset, size_t *size, - uint32_t *decodingTime); + uint32_t *decodingTime, + bool *isSyncSample = NULL); enum { kFlagBefore, @@ -105,6 +106,8 @@ private: 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 @@ status_t MatroskaSource::read( 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } } 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 @@ class ViewServer implements Runnable { 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 @@ class ViewServer implements Runnable { 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 @@ public class WindowManagerService extends IWindowManager.Stub } /** + * 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 @@ public final class MccTable 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 @@ public class MockContentProvider extends ContentProvider { 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 @@ public class MockContentProvider extends ContentProvider { 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.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import java.io.FileNotFoundException; import java.util.ArrayList; /** @@ -102,4 +103,13 @@ public class MockIContentProvider implements IContentProvider { 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 @@ public class Canvas extends _Original_Canvas { 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 @@ public class Canvas extends _Original_Canvas { } /* (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 @@ public class Canvas extends _Original_Canvas { } /* (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 |
